BinaryHeap.cs 9.4 KB

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