BinaryHeap.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. #pragma warning disable 162
  2. #pragma warning disable 429
  3. #define DECREASE_KEY
  4. namespace Pathfinding {
  5. /// <summary>
  6. /// Binary heap implementation.
  7. /// Binary heaps are really fast for ordering nodes in a way that
  8. /// makes it possible to get the node with the lowest F score.
  9. /// Also known as a priority queue.
  10. ///
  11. /// This has actually been rewritten as a 4-ary heap
  12. /// for performance, but it's the same principle.
  13. ///
  14. /// See: http://en.wikipedia.org/wiki/Binary_heap
  15. /// See: https://en.wikipedia.org/wiki/D-ary_heap
  16. /// </summary>
  17. public class BinaryHeap {
  18. /// <summary>Number of items in the tree</summary>
  19. public int numberOfItems;
  20. /// <summary>The tree will grow by at least this factor every time it is expanded</summary>
  21. public float growthFactor = 2;
  22. /// <summary>
  23. /// Number of children of each node in the tree.
  24. /// Different values have been tested and 4 has been empirically found to perform the best.
  25. /// See: https://en.wikipedia.org/wiki/D-ary_heap
  26. /// </summary>
  27. const int D = 4;
  28. /// <summary>
  29. /// Sort nodes by G score if there is a tie when comparing the F score.
  30. /// Disabling this will improve pathfinding performance with around 2.5%
  31. /// but may break ties between paths that have the same length in a less
  32. /// desirable manner (only relevant for grid graphs).
  33. /// </summary>
  34. const bool SortGScores = true;
  35. public const ushort NotInHeap = 0xFFFF;
  36. /// <summary>Internal backing array for the heap</summary>
  37. private Tuple[] heap;
  38. /// <summary>True if the heap does not contain any elements</summary>
  39. public bool isEmpty {
  40. get {
  41. return numberOfItems <= 0;
  42. }
  43. }
  44. /// <summary>Item in the heap</summary>
  45. private struct Tuple {
  46. public PathNode node;
  47. public uint F;
  48. public Tuple (uint f, PathNode node) {
  49. this.F = f;
  50. this.node = node;
  51. }
  52. }
  53. /// <summary>
  54. /// Rounds up v so that it has remainder 1 when divided by D.
  55. /// I.e it is of the form n*D + 1 where n is any non-negative integer.
  56. /// </summary>
  57. static int RoundUpToNextMultipleMod1 (int v) {
  58. // I have a feeling there is a nicer way to do this
  59. return v + (4 - ((v-1) % D)) % D;
  60. }
  61. /// <summary>Create a new heap with the specified initial capacity</summary>
  62. public BinaryHeap (int capacity) {
  63. // Make sure the size has remainder 1 when divided by D
  64. // This allows us to always guarantee that indices used in the Remove method
  65. // will never throw out of bounds exceptions
  66. capacity = RoundUpToNextMultipleMod1(capacity);
  67. heap = new Tuple[capacity];
  68. numberOfItems = 0;
  69. }
  70. /// <summary>Removes all elements from the heap</summary>
  71. public void Clear () {
  72. #if DECREASE_KEY
  73. // Clear all heap indices
  74. // This is important to avoid bugs
  75. for (int i = 0; i < numberOfItems; i++) {
  76. heap[i].node.heapIndex = NotInHeap;
  77. }
  78. #endif
  79. numberOfItems = 0;
  80. }
  81. internal PathNode GetNode (int i) {
  82. return heap[i].node;
  83. }
  84. internal void SetF (int i, uint f) {
  85. heap[i].F = f;
  86. }
  87. /// <summary>Expands to a larger backing array when the current one is too small</summary>
  88. void Expand () {
  89. // 65533 == 1 mod 4 and slightly smaller than 1<<16 = 65536
  90. int newSize = System.Math.Max(heap.Length+4, System.Math.Min(65533, (int)System.Math.Round(heap.Length*growthFactor)));
  91. // Make sure the size has remainder 1 when divided by D
  92. // This allows us to always guarantee that indices used in the Remove method
  93. // will never throw out of bounds exceptions
  94. newSize = RoundUpToNextMultipleMod1(newSize);
  95. // Check if the heap is really large
  96. // Also note that heaps larger than this are not supported
  97. // since PathNode.heapIndex is a ushort and can only store
  98. // values up to 65535 (NotInHeap = 65535 is reserved however)
  99. if (newSize > (1<<16) - 2) {
  100. throw new System.Exception("Binary Heap Size really large (>65534). A heap size this large is probably the cause of pathfinding running in an infinite loop. ");
  101. }
  102. var newHeap = new Tuple[newSize];
  103. heap.CopyTo(newHeap, 0);
  104. #if ASTARDEBUG
  105. UnityEngine.Debug.Log("Resizing binary heap to "+newSize);
  106. #endif
  107. heap = newHeap;
  108. }
  109. /// <summary>Adds a node to the heap</summary>
  110. public void Add (PathNode node) {
  111. if (node == null) throw new System.ArgumentNullException("node");
  112. #if DECREASE_KEY
  113. // Check if node is already in the heap
  114. if (node.heapIndex != NotInHeap) {
  115. DecreaseKey(heap[node.heapIndex], node.heapIndex);
  116. return;
  117. }
  118. #endif
  119. if (numberOfItems == heap.Length) {
  120. Expand();
  121. }
  122. DecreaseKey(new Tuple(0, node), (ushort)numberOfItems);
  123. numberOfItems++;
  124. }
  125. void DecreaseKey (Tuple node, ushort index) {
  126. // This is where 'obj' is in the binary heap logically speaking
  127. // (for performance reasons we don't actually store it there until
  128. // we know the final index, that's just a waste of CPU cycles)
  129. int bubbleIndex = index;
  130. // Update F value, it might have changed since the node was originally added to the heap
  131. uint nodeF = node.F = node.node.F;
  132. uint nodeG = node.node.G;
  133. while (bubbleIndex != 0) {
  134. // Parent node of the bubble node
  135. int parentIndex = (bubbleIndex-1) / D;
  136. if (nodeF < heap[parentIndex].F || (SortGScores && nodeF == heap[parentIndex].F && nodeG > heap[parentIndex].node.G)) {
  137. // Swap the bubble node and parent node
  138. // (we don't really need to store the bubble node until we know the final index though
  139. // so we do that after the loop instead)
  140. heap[bubbleIndex] = heap[parentIndex];
  141. #if DECREASE_KEY
  142. heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
  143. #endif
  144. bubbleIndex = parentIndex;
  145. } else {
  146. break;
  147. }
  148. }
  149. heap[bubbleIndex] = node;
  150. #if DECREASE_KEY
  151. node.node.heapIndex = (ushort)bubbleIndex;
  152. #endif
  153. }
  154. /// <summary>Returns the node with the lowest F score from the heap</summary>
  155. public PathNode Remove () {
  156. PathNode returnItem = heap[0].node;
  157. #if DECREASE_KEY
  158. returnItem.heapIndex = NotInHeap;
  159. #endif
  160. numberOfItems--;
  161. if (numberOfItems == 0) return returnItem;
  162. // Last item in the heap array
  163. var swapItem = heap[numberOfItems];
  164. var swapItemG = swapItem.node.G;
  165. int swapIndex = 0, parent;
  166. // Trickle upwards
  167. while (true) {
  168. parent = swapIndex;
  169. uint swapF = swapItem.F;
  170. int pd = parent * D + 1;
  171. // If this holds, then the indices used
  172. // below are guaranteed to not throw an index out of bounds
  173. // exception since we choose the size of the array in that way
  174. if (pd <= numberOfItems) {
  175. // Loading all F scores here instead of inside the if statements
  176. // reduces data dependencies and improves performance
  177. uint f0 = heap[pd+0].F;
  178. uint f1 = heap[pd+1].F;
  179. uint f2 = heap[pd+2].F;
  180. uint f3 = heap[pd+3].F;
  181. // The common case is that all children of a node are present
  182. // so the first comparison in each if statement below
  183. // will be extremely well predicted so it is essentially free
  184. // (I tried optimizing for the common case, but it didn't affect performance at all
  185. // at the expense of longer code, the CPU branch predictor is really good)
  186. if (pd+0 < numberOfItems && (f0 < swapF || (SortGScores && f0 == swapF && heap[pd+0].node.G < swapItemG))) {
  187. swapF = f0;
  188. swapIndex = pd+0;
  189. }
  190. if (pd+1 < numberOfItems && (f1 < swapF || (SortGScores && f1 == swapF && heap[pd+1].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
  191. swapF = f1;
  192. swapIndex = pd+1;
  193. }
  194. if (pd+2 < numberOfItems && (f2 < swapF || (SortGScores && f2 == swapF && heap[pd+2].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
  195. swapF = f2;
  196. swapIndex = pd+2;
  197. }
  198. if (pd+3 < numberOfItems && (f3 < swapF || (SortGScores && f3 == swapF && heap[pd+3].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
  199. swapIndex = pd+3;
  200. }
  201. }
  202. // One if the parent's children are smaller or equal, swap them
  203. // (actually we are just pretenting we swapped them, we hold the swapData
  204. // in local variable and only assign it once we know the final index)
  205. if (parent != swapIndex) {
  206. heap[parent] = heap[swapIndex];
  207. #if DECREASE_KEY
  208. heap[parent].node.heapIndex = (ushort)parent;
  209. #endif
  210. } else {
  211. break;
  212. }
  213. }
  214. // Assign element to the final position
  215. heap[swapIndex] = swapItem;
  216. #if DECREASE_KEY
  217. swapItem.node.heapIndex = (ushort)swapIndex;
  218. #endif
  219. // For debugging
  220. // Validate ();
  221. return returnItem;
  222. }
  223. void Validate () {
  224. for (int i = 1; i < numberOfItems; i++) {
  225. int parentIndex = (i-1)/D;
  226. if (heap[parentIndex].F > heap[i].F) {
  227. throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + heap[parentIndex].F + " > " + heap[i].F + " ) ");
  228. }
  229. #if DECREASE_KEY
  230. if (heap[i].node.heapIndex != i) {
  231. throw new System.Exception("Invalid heap index");
  232. }
  233. #endif
  234. }
  235. }
  236. /// <summary>
  237. /// Rebuilds the heap by trickeling down all items.
  238. /// Usually called after the hTarget on a path has been changed
  239. /// </summary>
  240. public void Rebuild () {
  241. #if ASTARDEBUG
  242. int changes = 0;
  243. #endif
  244. for (int i = 2; i < numberOfItems; i++) {
  245. int bubbleIndex = i;
  246. var node = heap[i];
  247. uint nodeF = node.F;
  248. while (bubbleIndex != 1) {
  249. int parentIndex = bubbleIndex / D;
  250. if (nodeF < heap[parentIndex].F) {
  251. heap[bubbleIndex] = heap[parentIndex];
  252. #if DECREASE_KEY
  253. heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
  254. #endif
  255. heap[parentIndex] = node;
  256. #if DECREASE_KEY
  257. heap[parentIndex].node.heapIndex = (ushort)parentIndex;
  258. #endif
  259. bubbleIndex = parentIndex;
  260. #if ASTARDEBUG
  261. changes++;
  262. #endif
  263. } else {
  264. break;
  265. }
  266. }
  267. }
  268. #if ASTARDEBUG
  269. UnityEngine.Debug.Log("+++ Rebuilt Heap - "+changes+" changes +++");
  270. #endif
  271. }
  272. }
  273. }