forked from wangchen/Programming-Game-AI-by-Example-src
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathGraphAlgorithms.h
772 lines (587 loc) · 23.5 KB
/
GraphAlgorithms.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
#ifndef GRAPHALGORITHMS_H
#define GRAPHALGORITHMS_H
#pragma warning (disable:4786)
//------------------------------------------------------------------------
//
// Name: GraphSearches.h
//
// Desc: classes to implement graph algorithms, including DFS, BFS,
// Dijkstra's, A*, Prims etc. (based upon the code created
// by Robert Sedgewick in his book "Algorithms in C++")
//
// Any graphs passed to these functions must conform to the
// same interface used by the SparseGraph
//
// Author: Mat Buckland (fup@ai-junkie.com)
//
//------------------------------------------------------------------------
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include "graph/SparseGraph.h"
#include "misc/PriorityQueue.h"
//----------------------------- Graph_SearchDFS -------------------------------
//
// class to implement a depth first search.
//-----------------------------------------------------------------------------
template<class graph_type>
class Graph_SearchDFS
{
private:
//to aid legibility
enum {visited, unvisited, no_parent_assigned};
//create a typedef for the edge and node types used by the graph
typedef typename graph_type::EdgeType Edge;
typedef typename graph_type::NodeType Node;
private:
//a reference to the graph to be searched
const graph_type& m_Graph;
//this records the indexes of all the nodes that are visited as the
//search progresses
std::vector<int> m_Visited;
//this holds the route taken to the target. Given a node index, the value
//at that index is the node's parent. ie if the path to the target is
//3-8-27, then m_Route[8] will hold 3 and m_Route[27] will hold 8.
std::vector<int> m_Route;
//As the search progresses, this will hold all the edges the algorithm has
//examined. THIS IS NOT NECESSARY FOR THE SEARCH, IT IS HERE PURELY
//TO PROVIDE THE USER WITH SOME VISUAL FEEDBACK
std::vector<const Edge*> m_SpanningTree;
//the source and target node indices
int m_iSource,
m_iTarget;
//true if a path to the target has been found
bool m_bFound;
//this method performs the DFS search
bool Search();
public:
Graph_SearchDFS(const graph_type& graph,
int source,
int target = -1 ):
m_Graph(graph),
m_iSource(source),
m_iTarget(target),
m_bFound(false),
m_Visited(m_Graph.NumNodes(), unvisited),
m_Route(m_Graph.NumNodes(), no_parent_assigned)
{
m_bFound = Search();
}
//returns a vector containing pointers to all the edges the search has examined
std::vector<const Edge*> GetSearchTree()const{return m_SpanningTree;}
//returns true if the target node has been located
bool Found()const{return m_bFound;}
//returns a vector of node indexes that comprise the shortest path
//from the source to the target
std::list<int> GetPathToTarget()const;
};
//-----------------------------------------------------------------------------
template <class graph_type>
bool Graph_SearchDFS<graph_type>::Search()
{
//create a std stack of edges
std::stack<const Edge*> stack;
//create a dummy edge and put on the stack
Edge Dummy(m_iSource, m_iSource, 0);
stack.push(&Dummy);
//while there are edges in the stack keep searching
while (!stack.empty())
{
//grab the next edge
const Edge* Next = stack.top();
//remove the edge from the stack
stack.pop();
//make a note of the parent of the node this edge points to
m_Route[Next->To()] = Next->From();
//put it on the tree. (making sure the dummy edge is not placed on the tree)
if (Next != &Dummy)
{
m_SpanningTree.push_back(Next);
}
//and mark it visited
m_Visited[Next->To()] = visited;
//if the target has been found the method can return success
if (Next->To() == m_iTarget)
{
return true;
}
//push the edges leading from the node this edge points to onto
//the stack (provided the edge does not point to a previously
//visited node)
graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, Next->To());
for (const Edge* pE=ConstEdgeItr.begin();
!ConstEdgeItr.end();
pE=ConstEdgeItr.next())
{
if (m_Visited[pE->To()] == unvisited)
{
stack.push(pE);
}
}
}
//no path to target
return false;
}
//-----------------------------------------------------------------------------
template <class graph_type>
std::list<int> Graph_SearchDFS<graph_type>::GetPathToTarget()const
{
std::list<int> path;
//just return an empty path if no path to target found or if
//no target has been specified
if (!m_bFound || m_iTarget<0) return path;
int nd = m_iTarget;
path.push_front(nd);
while (nd != m_iSource)
{
nd = m_Route[nd];
path.push_front(nd);
}
return path;
}
//----------------------------- Graph_SearchBFS -------------------------------
//
//-----------------------------------------------------------------------------
template<class graph_type>
class Graph_SearchBFS
{
private:
//to aid legibility
enum {visited, unvisited, no_parent_assigned};
//create a typedef for the edge type used by the graph
typedef typename graph_type::EdgeType Edge;
private:
//a reference to the graph to be searched
const graph_type& m_Graph;
//this records the indexes of all the nodes that are visited as the
//search progresses
std::vector<int> m_Visited;
//this holds the route taken to the target. Given a node index, the value
//at that index is the node's parent. ie if the path to the target is
//3-8-27, then m_Route[8] will hold 3 and m_Route[27] will hold 8.
std::vector<int> m_Route;
//the source and target node indices
int m_iSource,
m_iTarget;
//true if a path to the target has been found
bool m_bFound;
//As the search progresses, this will hold all the edges the algorithm has
//examined. THIS IS NOT NECESSARY FOR THE SEARCH, IT IS HERE PURELY
//TO PROVIDE THE USER WITH SOME VISUAL FEEDBACK
std::vector<const Edge*> m_SpanningTree;
//the BFS algorithm is very similar to the DFS except that it uses a
//FIFO queue instead of a stack.
bool Search();
public:
Graph_SearchBFS(const graph_type& graph,
int source,
int target = -1 ):m_Graph(graph),
m_iSource(source),
m_iTarget(target),
m_bFound(false),
m_Visited(m_Graph.NumNodes(), unvisited),
m_Route(m_Graph.NumNodes(), no_parent_assigned)
{
m_bFound = Search();
}
bool Found()const{return m_bFound;}
//returns a vector containing pointers to all the edges the search has examined
std::vector<const Edge*> GetSearchTree()const{return m_SpanningTree;}
//returns a vector of node indexes that comprise the shortest path
//from the source to the target
std::list<int> GetPathToTarget()const;
};
//-----------------------------------------------------------------------------
template <class graph_type>
bool Graph_SearchBFS<graph_type>::Search()
{
//create a std queue of edges
std::queue<const Edge*> Q;
const Edge Dummy(m_iSource, m_iSource, 0);
//create a dummy edge and put on the queue
Q.push(&Dummy);
//mark the source node as visited
m_Visited[m_iSource] = visited;
//while there are edges in the queue keep searching
while (!Q.empty())
{
//grab the next edge
const Edge* Next = Q.front();
Q.pop();
//mark the parent of this node
m_Route[Next->To()] = Next->From();
//put it on the tree. (making sure the dummy edge is not placed on the tree)
if (Next != &Dummy)
{
m_SpanningTree.push_back(Next);
}
//exit if the target has been found
if (Next->To() == m_iTarget)
{
return true;
}
//push the edges leading from the node at the end of this edge
//onto the queue
graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, Next->To());
for (const Edge* pE=ConstEdgeItr.begin();
!ConstEdgeItr.end();
pE=ConstEdgeItr.next())
{
//if the node hasn't already been visited we can push the
//edge onto the queue
if (m_Visited[pE->To()] == unvisited)
{
Q.push(pE);
//and mark it visited
m_Visited[pE->To()] = visited;
}
}
}
//no path to target
return false;
}
//-----------------------------------------------------------------------------
template <class graph_type>
std::list<int> Graph_SearchBFS<graph_type>::GetPathToTarget()const
{
std::list<int> path;
//just return an empty path if no path to target found or if
//no target has been specified
if (!m_bFound || m_iTarget<0) return path;
int nd = m_iTarget;
path.push_front(nd);
while (nd != m_iSource)
{
nd = m_Route[nd];
path.push_front(nd);
}
return path;
}
//----------------------- Graph_SearchDijkstra --------------------------------
//
// Given a graph, source and optional target this class solves for
// single source shortest paths (without a target being specified) or
// shortest path from source to target.
//
// The algorithm used is a priority queue implementation of Dijkstra's.
// note how similar this is to the algorithm used in Graph_MinSpanningTree.
// The main difference is in the calculation of the priority in the line:
//
// double NewCost = m_CostToThisNode[best] + pE->Cost;
//------------------------------------------------------------------------
template <class graph_type>
class Graph_SearchDijkstra
{
private:
//create a typedef for the edge type used by the graph
typedef typename graph_type::EdgeType Edge;
private:
const graph_type& m_Graph;
//this vector contains the edges that comprise the shortest path tree -
//a directed subtree of the graph that encapsulates the best paths from
//every node on the SPT to the source node.
std::vector<const Edge*> m_ShortestPathTree;
//this is indexed into by node index and holds the total cost of the best
//path found so far to the given node. For example, m_CostToThisNode[5]
//will hold the total cost of all the edges that comprise the best path
//to node 5, found so far in the search (if node 5 is present and has
//been visited)
std::vector<double> m_CostToThisNode;
//this is an indexed (by node) vector of 'parent' edges leading to nodes
//connected to the SPT but that have not been added to the SPT yet. This is
//a little like the stack or queue used in BST and DST searches.
std::vector<const Edge*> m_SearchFrontier;
int m_iSource;
int m_iTarget;
void Search();
public:
Graph_SearchDijkstra(const graph_type &graph,
int source,
int target = -1):m_Graph(graph),
m_ShortestPathTree(graph.NumNodes()),
m_SearchFrontier(graph.NumNodes()),
m_CostToThisNode(graph.NumNodes()),
m_iSource(source),
m_iTarget(target)
{
Search();
}
//returns the vector of edges that defines the SPT. If a target was given
//in the constructor then this will be an SPT comprising of all the nodes
//examined before the target was found, else it will contain all the nodes
//in the graph.
std::vector<const Edge*> GetSPT()const{return m_ShortestPathTree;}
//returns a vector of node indexes that comprise the shortest path
//from the source to the target. It calculates the path by working
//backwards through the SPT from the target node.
std::list<int> GetPathToTarget()const;
//returns the total cost to the target
double GetCostToTarget()const{return m_CostToThisNode[m_iTarget];}
//returns the total cost to the given node
double GetCostToNode(unsigned int nd)const{return m_CostToThisNode[nd];}
};
//-----------------------------------------------------------------------------
template <class graph_type>
void Graph_SearchDijkstra<graph_type>::Search()
{
//create an indexed priority queue that sorts smallest to largest
//(front to back).Note that the maximum number of elements the iPQ
//may contain is N. This is because no node can be represented on the
//queue more than once.
IndexedPriorityQLow<double> pq(m_CostToThisNode, m_Graph.NumNodes());
//put the source node on the queue
pq.insert(m_iSource);
//while the queue is not empty
while(!pq.empty())
{
//get lowest cost node from the queue. Don't forget, the return value
//is a *node index*, not the node itself. This node is the node not already
//on the SPT that is the closest to the source node
int NextClosestNode = pq.Pop();
//move this edge from the frontier to the shortest path tree
m_ShortestPathTree[NextClosestNode] = m_SearchFrontier[NextClosestNode];
//if the target has been found exit
if (NextClosestNode == m_iTarget) return;
//now to relax the edges.
graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, NextClosestNode);
//for each edge connected to the next closest node
for (const Edge* pE=ConstEdgeItr.begin();
!ConstEdgeItr.end();
pE=ConstEdgeItr.next())
{
//the total cost to the node this edge points to is the cost to the
//current node plus the cost of the edge connecting them.
double NewCost = m_CostToThisNode[NextClosestNode] + pE->Cost();
//if this edge has never been on the frontier make a note of the cost
//to get to the node it points to, then add the edge to the frontier
//and the destination node to the PQ.
if (m_SearchFrontier[pE->To()] == 0)
{
m_CostToThisNode[pE->To()] = NewCost;
pq.insert(pE->To());
m_SearchFrontier[pE->To()] = pE;
}
//else test to see if the cost to reach the destination node via the
//current node is cheaper than the cheapest cost found so far. If
//this path is cheaper, we assign the new cost to the destination
//node, update its entry in the PQ to reflect the change and add the
//edge to the frontier
else if ( (NewCost < m_CostToThisNode[pE->To()]) &&
(m_ShortestPathTree[pE->To()] == 0) )
{
m_CostToThisNode[pE->To()] = NewCost;
//because the cost is less than it was previously, the PQ must be
//re-sorted to account for this.
pq.ChangePriority(pE->To());
m_SearchFrontier[pE->To()] = pE;
}
}
}
}
//-----------------------------------------------------------------------------
template <class graph_type>
std::list<int> Graph_SearchDijkstra<graph_type>::GetPathToTarget()const
{
std::list<int> path;
//just return an empty path if no target or no path found
if (m_iTarget < 0) return path;
int nd = m_iTarget;
path.push_front(nd);
while ((nd != m_iSource) && (m_ShortestPathTree[nd] != 0))
{
nd = m_ShortestPathTree[nd]->From();
path.push_front(nd);
}
return path;
}
//------------------------------- Graph_SearchAStar --------------------------
//
// this searchs a graph using the distance between the target node and the
// currently considered node as a heuristic.
//
// This search is more commonly known as A* (pronounced Ay-Star)
//-----------------------------------------------------------------------------
template <class graph_type, class heuristic>
class Graph_SearchAStar
{
private:
//create a typedef for the edge type used by the graph
typedef typename graph_type::EdgeType Edge;
private:
const graph_type& m_Graph;
//indexed into my node. Contains the 'real' accumulative cost to that node
std::vector<double> m_GCosts;
//indexed into by node. Contains the cost from adding m_GCosts[n] to
//the heuristic cost from n to the target node. This is the vector the
//iPQ indexes into.
std::vector<double> m_FCosts;
std::vector<const Edge*> m_ShortestPathTree;
std::vector<const Edge*> m_SearchFrontier;
int m_iSource;
int m_iTarget;
//the A* search algorithm
void Search();
public:
Graph_SearchAStar(graph_type &graph,
int source,
int target):m_Graph(graph),
m_ShortestPathTree(graph.NumNodes()),
m_SearchFrontier(graph.NumNodes()),
m_GCosts(graph.NumNodes(), 0.0),
m_FCosts(graph.NumNodes(), 0.0),
m_iSource(source),
m_iTarget(target)
{
Search();
}
//returns the vector of edges that the algorithm has examined
std::vector<const Edge*> GetSPT()const{return m_ShortestPathTree;}
//returns a vector of node indexes that comprise the shortest path
//from the source to the target
std::list<int> GetPathToTarget()const;
//returns the total cost to the target
double GetCostToTarget()const{return m_GCosts[m_iTarget];}
};
//-----------------------------------------------------------------------------
template <class graph_type, class heuristic>
void Graph_SearchAStar<graph_type, heuristic>::Search()
{
//create an indexed priority queue of nodes. The nodes with the
//lowest overall F cost (G+H) are positioned at the front.
IndexedPriorityQLow<double> pq(m_FCosts, m_Graph.NumNodes());
//put the source node on the queue
pq.insert(m_iSource);
//while the queue is not empty
while(!pq.empty())
{
//get lowest cost node from the queue
int NextClosestNode = pq.Pop();
//move this node from the frontier to the spanning tree
m_ShortestPathTree[NextClosestNode] = m_SearchFrontier[NextClosestNode];
//if the target has been found exit
if (NextClosestNode == m_iTarget) return;
//now to test all the edges attached to this node
graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, NextClosestNode);
for (const Edge* pE=ConstEdgeItr.begin();
!ConstEdgeItr.end();
pE=ConstEdgeItr.next())
{
//calculate the heuristic cost from this node to the target (H)
double HCost = heuristic::Calculate(m_Graph, m_iTarget, pE->To());
//calculate the 'real' cost to this node from the source (G)
double GCost = m_GCosts[NextClosestNode] + pE->Cost();
//if the node has not been added to the frontier, add it and update
//the G and F costs
if (m_SearchFrontier[pE->To()] == NULL)
{
m_FCosts[pE->To()] = GCost + HCost;
m_GCosts[pE->To()] = GCost;
pq.insert(pE->To());
m_SearchFrontier[pE->To()] = pE;
}
//if this node is already on the frontier but the cost to get here
//is cheaper than has been found previously, update the node
//costs and frontier accordingly.
else if ((GCost < m_GCosts[pE->To()]) && (m_ShortestPathTree[pE->To()]==NULL))
{
m_FCosts[pE->To()] = GCost + HCost;
m_GCosts[pE->To()] = GCost;
pq.ChangePriority(pE->To());
m_SearchFrontier[pE->To()] = pE;
}
}
}
}
//-----------------------------------------------------------------------------
template <class graph_type, class heuristic>
std::list<int> Graph_SearchAStar<graph_type, heuristic>::GetPathToTarget()const
{
std::list<int> path;
//just return an empty path if no target or no path found
if (m_iTarget < 0) return path;
int nd = m_iTarget;
path.push_front(nd);
while ((nd != m_iSource) && (m_ShortestPathTree[nd] != 0))
{
nd = m_ShortestPathTree[nd]->From();
path.push_front(nd);
}
return path;
}
//---------------------- Graph_MinSpanningTree --------------------------------
//
// given a graph and a source node you can use this class to calculate
// the minimum spanning tree. If no source node is specified then the
// algorithm will calculate a spanning forest starting from node 1
//
// It uses a priority first queue implementation of Prims algorithm
//------------------------------------------------------------------------
template <class graph_type>
class Graph_MinSpanningTree
{
private:
//create a typedef for the edge type used by the graph
typedef typename graph_type::EdgeType Edge;
const graph_type& m_Graph;
std::vector<double> m_CostToThisNode;
std::vector<const Edge*> m_SpanningTree;
std::vector<const Edge*> m_Fringe;
void Search(const int source)
{
//create a priority queue
IndexedPriorityQLow<double> pq(m_CostToThisNode, m_Graph.NumNodes());
//put the source node on the queue
pq.insert(source);
//while the queue is not empty
while(!pq.empty())
{
//get lowest cost edge from the queue
int best = pq.Pop();
//move this edge from the fringe to the spanning tree
m_SpanningTree[best] = m_Fringe[best];
//now to test the edges attached to this node
graph_type::ConstEdgeIterator ConstEdgeItr(m_Graph, best);
for (const Edge* pE=ConstEdgeItr.beg(); !ConstEdgeItr.end(); pE=ConstEdgeItr.nxt())
{
double Priority = pE->Cost;
if (m_Fringe[pE->To()] == 0)
{
m_CostToThisNode[pE->To()] = Priority;
pq.insert(pE->To());
m_Fringe[pE->To()] = pE;
}
else if ((Priority<m_CostToThisNode[pE->To()]) && (m_SpanningTree[pE->To()]==0))
{
m_CostToThisNode[pE->To()] = Priority;
pq.ChangePriority(pE->To());
m_Fringe[pE->To()] = pE;
}
}
}
}
public:
Graph_MinSpanningTree(graph_type &G,
int source = -1):m_Graph(G),
m_SpanningTree(G.NumNodes()),
m_Fringe(G.NumNodes()),
m_CostToThisNode(G.NumNodes(), -1)
{
if (source < 0)
{
for (int nd=0; nd<G.NumNodes(); ++nd)
{
if (m_SpanningTree[nd] == 0)
{
Search(nd);
}
}
}
else
{
Search(source);
}
}
std::vector<const Edge*> GetSpanningTree()const{return m_SpanningTree;}
};
#endif