SchematicDrawing : Dijkstra algorithm uses minimum heap.

This commit is contained in:
Milan Hanajik 2020-06-17 23:17:50 +02:00
parent ddca028eb2
commit 5b5f0ac41e
6 changed files with 242 additions and 66 deletions

View File

@ -11,6 +11,7 @@ namespace SchematicDrawing
public static class Const
{
public const int GridSize = 10;
public const int SecondaryStart = 10000;
public const int Vacuum = 1000000;
}

View File

@ -1,23 +1,35 @@
using System;
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
namespace SchematicDrawing
{
public class GNode
{
/// Graph node properties
public IDrawingItem Item;
public int NodeId;
public readonly IList<GEdge> Edges;
/// Distance from a start node
public int Dist;
public bool Visited;
/// Minimal heap index
public int HeapIx;
/// <summary>
/// Constructor
/// </summary>
/// <param name="item">Item</param>
/// <param name="nodeId"Node ID></param>
public GNode(IDrawingItem item, int nodeId)
{
this.Item = item;
this.NodeId = nodeId;
this.Edges = new List<GEdge>();
Reset();
Item = item;
NodeId = nodeId;
Edges = new List<GEdge>();
Dist = int.MaxValue;
}
public void AddEdge(GEdge edge)
@ -25,15 +37,16 @@ namespace SchematicDrawing
Edges.Add(edge);
}
public void Reset()
public GNode Reset(int heapIx, int dist = int.MaxValue)
{
Visited = false;
Dist = int.MaxValue;
HeapIx = heapIx;
Dist = dist;
return this;
}
public override string ToString()
{
return string.Format("{0}/{1} {2} dist={3}", Item.Name, NodeId, Visited ? "V" : "", Dist);
return string.Format("{0}/{1} dist={2}", Item.Name, NodeId, Dist);
}
}
}

View File

@ -1,4 +1,8 @@
using System;
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace SchematicDrawing
@ -6,25 +10,47 @@ namespace SchematicDrawing
public class Graph
{
IList<GNode> nodes;
GNode startNode;
int secondaryStartNodesCount;
MinHeap minHeap;
GNode currentNode;
public Graph()
{
nodes = new List<GNode>();
startNode = null;
secondaryStartNodesCount = 0;
minHeap = new MinHeap();
}
public void Clear()
{
nodes.Clear();
startNode = null;
secondaryStartNodesCount = 0;
}
/// <summary>
/// Add a node to the list of graph nodes
/// </summary>
/// <param name="node">Graph node</param>
public void AddNode(GNode node)
public void AddNode(GNode node, bool isStartNode = false, bool isSecondaryStartNode = false)
{
nodes.Add(node);
if (isStartNode && startNode == null)
{
startNode = node;
nodes.Insert(0, node);
}
else if (isStartNode || isSecondaryStartNode)
{
secondaryStartNodesCount++;
nodes.Insert(startNode != null ? 1 : 0, node);
}
else
{
nodes.Add(node);
}
}
/// <summary>
@ -47,44 +73,30 @@ namespace SchematicDrawing
/// </summary>
/// <param name="startNode">Start node</param>
/// <param name="secondaryStartNodes">List of secondary start nodes</param>
public void RunDijkstraAlgo(GNode startNode, IList<GNode> secondaryStartNodes = null)
public void RunDijkstraAlgo()
{
if (nodes == null || startNode == null || !nodes.Contains(startNode)) return;
if (nodes == null || startNode == null) return;
/// Reset all nodes
foreach (var node in nodes) node.Reset();
minHeap.ResetAndBuild(nodes, secondaryStartNodesCount);
startNode.Dist = 0;
if (secondaryStartNodes != null) foreach (var ssn in secondaryStartNodes) ssn.Dist = 10000;
currentNode = startNode;
int minDistance = int.MaxValue;
currentNode = minHeap.ExtractMinNode();
do
{
/// Consider all unvisited neighbors of the current node
foreach (var e in currentNode.Edges)
{
int distViaCurrNode = (e.Dist == int.MaxValue) ? int.MaxValue : currentNode.Dist + e.Dist;
if (!e.EndNode.Visited && distViaCurrNode < e.EndNode.Dist)
if (distViaCurrNode < e.EndNode.Dist)
{
e.EndNode.Dist = distViaCurrNode;
minHeap.SiftUp(e.EndNode.HeapIx);
}
}
currentNode.Visited = true;
/// Find new current node from all unvisited nodes
currentNode = null;
minDistance = int.MaxValue;
foreach (var n in nodes)
{
if (!n.Visited && n.Dist < minDistance)
{
minDistance = n.Dist;
currentNode = n;
}
}
currentNode = minHeap.ExtractMinNode();
}
while (minDistance < int.MaxValue && currentNode != null);
while (currentNode.Dist < int.MaxValue && currentNode != null);
}
}
}

134
SchematicDrawing/MinHeap.cs Normal file
View File

@ -0,0 +1,134 @@
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
namespace SchematicDrawing
{
public class MinHeap
{
GNode[] heap; /// Minimum heap, a binary tree in form of an array
int heapSize; /// Heap size (not necessarily a size of the array heap[])
/// <summary>
/// Constructor
/// </summary>
public MinHeap()
{
heap = null;
heapSize = 0;
}
/// <summary>
/// Reset nodes and build the initial minimum heap tree.
///
/// First node on the list is the start node (mostly the main water tank),
/// followed by 'secondaryStartNodesCount' secondary start nodes (typically
/// tanks above scales), followed by all remaining nodes of the drawing.
/// In this way the heap is heapified from the beginning.
/// </summary>
/// <param name="nodes">Graph nodes</param>
/// <param name="secondaryStartNodesCount">Number of secondary start nodes</param>
public void ResetAndBuild(IList<GNode> nodes, int secondaryStartNodesCount)
{
if (nodes == null || nodes.Count == 0) return;
if (heapSize != nodes.Count)
{
heapSize = nodes.Count;
heap = new GNode[nodes.Count];
}
heap[0] = nodes[0].Reset(0, 0);
for (int i = 1; i <= secondaryStartNodesCount; i++)
{
heap[i] = nodes[i].Reset(i, Const.SecondaryStart);
}
for (int i = secondaryStartNodesCount + 1; i < nodes.Count; i++)
{
heap[i] = nodes[i].Reset(i, int.MaxValue);
}
}
/// <summary>
/// Returns the node with minimal distance at the top of the heap binary tree.
/// This node is removed from the heap, replaced by the last node and the heap is 'Heapified'.
/// </summary>
/// <returns>Node with minimal distance</returns>
public GNode ExtractMinNode()
{
GNode minNode = heap[0];
if (--heapSize > 0)
{
heap[0] = heap[heapSize]; /// Swap with the last one
heap[0].HeapIx = 0;
heap[heapSize] = minNode;
SiftDown(0); /// Heapify the heap, sift down the swapped (formerly the last) item
}
return minNode;
}
/// <summary>
/// Sift up the node in the minimum heap binary tree.
/// Function is called when distance of a node in the graph is updated.
/// As the distance is always decreased, the node sifts up in the tree.
/// </summary>
/// <param name="heapIx">Index of the node to sift up</param>
public void SiftUp(int heapIx)
{
if (heapIx == 0) return;
int parentIx = (heapIx - 1) / 2;
if (heap[heapIx].Dist < heap[parentIx].Dist)
{
SwapNodes(heapIx, parentIx);
SiftUp(parentIx);
}
}
/// <summary>
/// Sift down the node in the minimum heap binary tree.
/// Function is called when node at the top of the tree is extracted and replaced
/// by the last node. This last node is then sift down according to its distance.
/// </summary>
/// <param name="heapIx">Index of the node to sift down</param>
void SiftDown(int heapIx)
{
int leftIx = 2 * heapIx + 1;
if (leftIx < heapSize)
{
int minIx = heapIx;
if (heap[leftIx].Dist < heap[minIx].Dist)
{
minIx = leftIx;
}
int rightIx = 2 * heapIx + 2;
if (rightIx < heapSize && heap[rightIx].Dist < heap[minIx].Dist)
{
minIx = rightIx;
}
if (minIx != heapIx)
{
SwapNodes(heapIx, minIx);
SiftDown(minIx);
}
}
}
/// <summary>
/// Swap two nodes in the minimum heap binary tree.
/// </summary>
/// <param name="ix1">Index of the 1st node</param>
/// <param name="ix2">Index of the 2nd node</param>
void SwapNodes(int ix1, int ix2)
{
GNode swap = heap[ix1];
heap[ix1] = heap[ix2];
heap[ix1].HeapIx = ix1;
heap[ix2] = swap;
heap[ix2].HeapIx = ix2;
}
}
}

View File

@ -56,6 +56,7 @@
<Compile Include="IDrawingItemWithMeasuredVal.cs" />
<Compile Include="IRouteBasedDrawingItem.cs" />
<Compile Include="IDrawingItemWithSetpoint.cs" />
<Compile Include="MinHeap.cs" />
<Compile Include="Pipe.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">

View File

@ -7,6 +7,7 @@ using System.Drawing;
using System.Windows.Forms;
using Dirichlet.Numerics;
using System.Numerics;
using System.Diagnostics;
namespace SchematicDrawing
{
@ -54,7 +55,8 @@ namespace SchematicDrawing
Utils.UpdateGraphEdges(route, it as IRouteBasedDrawingItem);
}
}
graph.RunDijkstraAlgo(startNode, secondaryStartNodes);
graph.RunDijkstraAlgo();
Redraw();
}
}
@ -231,8 +233,6 @@ namespace SchematicDrawing
IList<IDrawingItem> items;
Graph graph;
GNode startNode;
IList<GNode> secondaryStartNodes;
///
/// Rectangles of elements of drawn items
@ -287,8 +287,6 @@ namespace SchematicDrawing
pipesXL = new List<Pipe>();
graph = new Graph();
startNode = null;
secondaryStartNodes = new List<GNode>();
ItemElementRectangles = new List<ItemElementRect>();
@ -313,7 +311,7 @@ namespace SchematicDrawing
{
GNode gnode = new GNode(item, nid);
item.GNodes.Add(gnode);
graph.AddNode(gnode);
graph.AddNode(gnode, dshape.Shape == Shape.Tank, dshape.Shape == Shape.Scale);
}
foreach (var edge in dshape.Edges)
@ -324,16 +322,6 @@ namespace SchematicDrawing
(item as IRouteBasedDrawingItem).EdgesToSet.Add(gedge);
}
}
if (dshape.Shape == Shape.Tank && item.GNodes.Count > 0)
{
startNode = item.GNodes[0];
}
if (dshape.Shape == Shape.Scale && item.GNodes.Count > 0)
{
secondaryStartNodes.Add(item.GNodes[0]);
}
}
}
@ -344,8 +332,6 @@ namespace SchematicDrawing
items.Clear();
Name2Item.Clear();
graph.Clear();
startNode = null;
secondaryStartNodes.Clear();
Redraw();
}
public IList<IDrawingItem> GetItems()
@ -388,11 +374,6 @@ namespace SchematicDrawing
return tempPipe;
}
public void RunDijkstraAlgo()
{
graph.RunDijkstraAlgo(startNode, secondaryStartNodes);
}
void PrepareFontsPensBrushesEtc()
{
/// Font for drawing labels
@ -474,6 +455,10 @@ namespace SchematicDrawing
/// <param name="e">PaintEventArgs</param>
void SchematicDrawingCtrl_Paint(object sender, PaintEventArgs e)
{
/// Profiling
//stopwatch.Reset();
//stopwatch.Start();
e.Graphics.Clear(Color.White);
ItemElementRectangles.Clear();
@ -669,15 +654,37 @@ namespace SchematicDrawing
}
}
}
/// Profiling
//stopwatch.Stop();
//if (firstTime)
//{
// firstTime = false;
//}
//else if (totalTimes < long.MaxValue)
//{
// totalMs += stopwatch.ElapsedMilliseconds;
// totalTicks += stopwatch.ElapsedTicks;
// totalTimes++;
// Console.WriteLine("Average Paint duration is {0} ms = {1} ticks", totalMs / totalTimes, totalTicks / totalTimes);
//}
}
/// Profiling
//Stopwatch stopwatch = new Stopwatch();
//bool firstTime = true;
//long totalMs = 0;
//long totalTicks = 0;
//long totalTimes = 0;
/// <summary>
/// Draw a pipe
/// Draw a pipe.
/// </summary>
/// <param name="e">PaintEventArgs</param>
/// <param name="pipe">Edge string</param>
/// <param name="pipeSize">Edge size</param>
/// <param name="isWaterOpen">true = filled, false = empty</param>
/// <param name="pipe">Pipe object</param>
/// <param name="distance">Distance from source of water for coloring</param>
/// <param name="highlighted">true = highlighted (edit mode)</param>
void DrawPipe(PaintEventArgs e, Pipe pipe, int distance, bool highlighted = false)
{
int x1, y1, x2, y2;
@ -689,6 +696,13 @@ namespace SchematicDrawing
}
}
/// <summary>
/// Draw an item (body).
/// </summary>
/// <param name="e">PaintEventArgs</param>
/// <param name="item">IDrawingItem object</param>
/// <param name="dsh">DrawingShape</param>
/// <returns>Rectangle where item body is drawn</returns>
Rectangle DrawItem(PaintEventArgs e, IDrawingItem item, DrawingShape dsh)
{
bool routeBit = false;
@ -810,9 +824,10 @@ namespace SchematicDrawing
/// <param name="x">X-coordinate</param>
/// <param name="y">Y-coordinate</param>
/// <param name="elements">Bitfield of elements to be searched for</param>
/// <param name="itemNameOrEdge">Found component or edge or null</param>
/// <param name="nodeId">Fount node id or 0</param>
/// <returns>Element enum value (Element.None if nothing found)</returns>
/// <param name="item">Found item or null (in case of body, node, label, measured value or setpoint)</param>
/// <param name="pipe">Found pipe or null (in case of a pipe)</param>
/// <param name="nodeId">Found node id or 0 (in case of an item node)</param>
/// <returns>Element enum value (Element.None when nothing was found)</returns>
public Element FindElement(int x, int y, Element elements, out IDrawingItem item, out Pipe pipe, out int nodeId)
{
const int NodeTolerance = 5;
@ -848,7 +863,7 @@ namespace SchematicDrawing
}
/// <summary>
///
/// Searches for an item body, label, measured value or setpoint.
/// </summary>
/// <param name="x">X coordinate</param>
/// <param name="y">Y coordinate</param>