Use a binary heap in a * pathfinding

Source: Internet
Author: User
Tags sorts

Next article: A * exploring gamedev.net

Use a binary heap in a * pathfinding
Patrick Lester (updated in December April 11, 2003)
Translator: panic March 28, 2005
Preface:
This article is "A * pathfinding for beginners. ", that is, Supplement to another article I translated," A * exploring the path ". In this article, the author once again shows his extraordinary ability to elaborate on complex topics, the easy-to-understand statements clearly explain confusing problems. If you still cannot comprehend the author's intention after reading this article, you can only blame my translation for being too lame. Please refer to the original document for further understanding.
The binary heap described here is actually a binary tree in the form of a heap. This special structure is used to deduce the ordering requirements of the * Algorithm for enabling the list, it is undoubtedly the best partner of.
Finally, I hope this article will help you.
Link: http://www.policyalmanac.org/games/binaryHeaps.htm
The text of the translation is as follows:
This article is a supplement to my topic article "A * pathfinding for beginners. Before reading this article, you should read that article or have a thorough understanding of.
A * the slowest part of the algorithm is to find the node or square with the lowest f value in the open list. Depending on the map size, you may have more than a dozen nodes, and hundreds or even thousands of nodes need to use a * search at a certain time. If you search for a large list repeatedly, it will seriously slow down the entire process. However, these times are greatly affected by the way you store the list.
Orderly and unordered enable list: A Simple Method
The simplest way is to store each node in sequence, and traverse the entire list every time you need to extract the least-consuming elements. This provides a fast insert speed, but the removal speed may be the slowest, because you need to check each element to determine which is the lowest f value.
Generally, you can keep your list in order to improve efficiency. This takes a little more preprocessing time, because every time you insert new elements, you must put them in the proper place. However, removing elements is fast. You only need to remove the first element. It must have the lowest f value.
There are many ways to keep your data in order (select sorting, Bubble sorting, fast sorting, and so on) and you can use the search engine you are most familiar with to find this article. However, we can come up with at least a few ideas. The simplest way is to compare the values of F and F to be inserted from the beginning of the list when you need to add new elements. Once an equal or higher F value is found, you can insert the new element to the front of the element in the list. Depending on your computer Yu Ya, using the linked list implemented by class or struct may be a good method.
This method can be improved by keeping the average value of all elements in the list. The average value is used to determine whether to process the elements from the beginning (as mentioned above) or from the end. In general, new elements whose values are lower than the average f value will be processed from the beginning, while those whose values are higher than the average f value start from the end. This method can save half of the time.
It is more complicated, but the faster way is to increase this idea to a new level and use quick sorting. It basically starts from comparing the fvalue of the new element and the element in the list. If the F value of the new element is low, you can compare it with the element at 1/4. If it is lower, you can compare it with the element at 1/8, continue to fold your list and compare until you find the appropriate location. This description is very simple. You may think of searching for more information on the Internet for fast sorting. This is faster than any method described here.
Binary heap
The binary heap is similar to the fast sorting just mentioned. It is often used by those who demand a * speed. Based on my experience, the binary heap increases the tracking speed by 2-3 times on average, and is more effective for maps containing a large number of nodes (that is, 100x100 nodes or more. Friendly reminder, however, the binary heap is difficult to handle. Unless you use a map containing a large number of nodes, the speed is crucial. Otherwise, it is not worth a headache.
The other sections in this article explain in depth the binary heap and its usage in the * algorithm. If you have any questions about my article, you can read more ideas at the end of the article.
Still interested? Okay. Let's continue...
In a sequence table, each element is stored in the appropriate position in the order from low to high or from high to low. This is useful, but not enough. In fact, we are not concerned about whether the number 127 is at a lower position than the number 128. We just want the element with the lowest f value to be placed at the top of the list for easy access. The rest of the List is messy. The rest of the list must be ordered only when we need the element with the lowest f value.
Basically, what we really need is a "heap". Specifically, it is a binary heap. A binary heap is a group of elements. The maximum or minimum element (depending on the needs) is at the top of the heap. Now that we are looking for the element with the smallest F value, we will place it on the top of the heap. This element has two subnodes, with each fvalue equal to or slightly higher than this element. Each subnode has two subnodes, and they have the same or slightly higher subnodes... And so on. Here is a possible heap:

Note that the element (10) with the lowest f value is at the top, and the element (20) with the lowest f value is one of its subnodes. However, there will be no doubt afterwards. In this particular binary heap, the third-lowest element is 24, which is two steps away from the heap top and is smaller than 30, however, 30 is a step away from the top of the heap on the left. In simple stack, it is not important where other elements are located in the stack. Each independent element only needs to be equal to or higher than its parent node, and compared with its two subnodes, lower or equal. These conditions are fully met here, so this is a valid binary heap.
Well, you may think, it's really interesting, but how can you put it into practice? Well, an interesting fact about the binary heap is that you can simply store it in a one-dimensional array.
In this array, the element at the top of the heap should be the first element of the array (subscript 1 rather than 0 ). The two subnodes are located at 2 and 3. The four subnodes of the two nodes should be 4-7.

In general, the two subnodes of any element can be obtained by multiplying the position of the current element by 2 (getting the first subnode) and multiplying 2 and 1 (getting the second subnode. In this case, for example, the two subnodes of the third element (the value is 20) in the heap can be found at the positions 2*3 = 6 and 2*3 + 1 = 7. The numbers at those two locations are not 30 and 24. You can understand them when you view the heap.
You do not need to know this, except that there are no faults in the heap, but that there is no value in knowing this. The seven elements completely fill each layer of a three-tier heap. However, this is not necessary. To make the heap effective, we only need to fill each row on the bottom layer. The bottom layer can be an element of any number, and new elements are added from left to right. This is the method described in this article, so you don't have to worry about it.
Add new elements to the heap
When we actually use the binary heap in the pathfinding algorithm, we still need to consider more, but now we just want to learn how to use the binary heap. I skipped this part to make it easier to understand basic things. I will give a complete formula for dealing with all of this in the later part of the article, but understanding these details is still very important.
To add elements to the heap, we put them at the end of the array. Then it is compared with its parent node at the current position/2, and the score is rounded. If the F value of the new element is lower, We will swap the two elements. Then we will compare this element with its new parent node, in the (current position)/2, the decimal part is rounded. If its F value is lower, we exchange it again. Repeat this process until the element is no longer lower than its parent node, or the element has reached the top and is in the array position 1.
Let's take a look at how to add an element with a value of 17 to an existing heap. There are now 7 elements in our heap, and the new elements will be added to the 8th position. This is what the heap looks like. The new element is underlined.
10 30 20 34 38 30 24 17
Next we will compare it with its parent node, which is located at location 8/2 or 4. Position 4: The F value of the current element is 34. Since 17 is lower than 34, we swap the positions of two elements. Now our stack looks like this:
10 30 20 17 38 30 24 34
Then we compare it with the new parent node. Because we are at location 4, we will compare it with the element at location 4/2 = 2. The F value of the element is 30. Because 17 is lower than 30, we switch again. Now the heap looks like this:
10 17 20 30 38 30 24 34
Next we will compare it with the new parent node. Now in the second position, we compare it with 2/2 = 1, that is, the heap top. This time, 17 is no lower than 10. We stopped and kept the heap as it is.
Delete element from heap
Deleting an element from a heap is a similar process, but it is almost the opposite. First, we delete the element at location 1, and it is empty. Then, we take the last element of the heap and move it to location 1. In the heap, this is the final condition. The previous last element is underlined.
34 17 20 30 38 30 24
Then we compare it with two subnodes, which are at the position (current position * 2) and (current position * 2 + 1) respectively ). If it is lower than the fvalue of the two subnodes, it will remain in the original position. Instead, it is exchanged with lower subnodes. Here, the two subnodes of the element are located at 1*2 = 2 and 1*2 + 1 = 3. Obviously, 34 is no lower than any subnode, So we exchange it with the lower subnode, that is, 17. The result looks like this:
17 34 20 30 38 30 24
Then we compare it with the new subnodes, which are at 2*2 = 4, and 2*2 + 1 = 5. It is no lower than any subnode, So we switch it to a lower subnode (30 at location 4 ). This is the case now:
17 30 20 34 38 30 24
Last time, we compared it with the new subnode. As shown in the preceding example, the sub-nodes are located at 4x2 = 8 and 4x2 + 1 = 9. But there are no elements in those locations, because the list is not that long. We have reached the bottom of the heap, so we stopped.
Why is the binary heap so fast?
Now that you know the basic heap insertion and deletion methods, you should understand why it is faster than other methods, such as insert sorting. Suppose you have an open list with 1000 nodes and a map with many nodes in one grid. This is not impossible (Remember, even a 100 × 100 map, there are also 10,000 nodes ). If you use insert sorting, from the start point to the proper position of the new element, You need to perform an average of 500 comparisons before inserting the new element.
Using a binary heap, you can insert new elements to the correct position as long as you compare them 1 to 3 times. You need to perform nine comparisons to remove an element from the Enable list and keep the heap in order. In a *, you usually only need to remove one element (the element with the lowest f value) at a time ), add 0 to 5 new nodes at any location (as described in the main article ). The total time spent is about 1% of the insertion sorting by the same number of nodes. The difference increases with the increase of your map (that is, more nodes. The smaller the map, the less advantageous it will be. This is why the less map and node you have, the lower the value of the binary heap.
By the way, using a binary heap does not mean that your pathfinding algorithm will be 100 times faster. The following are some difficult questions. Additionally, a * is not only used to enable list sorting. However, based on my experience, using a binary heap can increase the speed by 2-3 times in most cases, with a longer path and a higher speed.
Create an array of enabled lists
Now that we know about the binary heap, how can we use it? The first thing we need to do is to construct our one-dimensional array. To solve this problem, we need to determine its size first. In general, the list size does not exceed the total number of nodes on the map (in the worst case, we search for the entire map to find the path ). In a square two-dimensional map, as described in my main article, our nodes do not exceed the map width × map height. Then our one-dimensional array is the size. In this example, we call this array openlist (). The top element of the heap is stored in openlist (1), the second element is in openlist (2), and so on.
Use Pointer
Now we have an array of the correct size, which can be used for path searching. However, before further adding or deleting operations, let's look at the original heap.

Now, it is only a list of fvalues and has been properly arranged. However, we ignore an important element. Yes, we have a series of F values stored in the heap in sequence, but we don't have any clue about what they represent. Basically, we only know that 10 is the lowest f value in the heap. But that refers to the grid?
To solve this problem, we must change the element values in the array. Instead of storing the sorted F value, we replace it with the unique identifier for storing the associated map mesh. My method is to create a unique ID for each newly added heap element called squareschecked. Each time a new element is added to the enabled list, we add 1 to squareschecked and use it as the unique ID of the new element in the list. The first is #1, the second is #2, and so on.
Finally, we store the specific F value in a single one-dimensional array. I call it dcost (). As with the Enable list, we set its size to (MAP width x map height ). We also store the X and Y coordinates of nodes in a similar one-dimensional array, called openx () and openy (). It looks like the following figure:

Although this seems a bit complicated, it is the same as the heap mentioned above. Only more information is stored.
#5 elements, with the lowest fvalue of 10, are still at the top of the heap, in the first column of the one-dimensional array. But now we store its unique ID 5 in the heap, instead of its F value. In other words, openlist (1) = 5. This unique value is used to find the F value of the element and the X and Y coordinates of the map. The F value of this element is fcost (5) = 10, the X coordinate is openx (5) = 12, and the Y coordinate is openy (5) = 22.
The top element has two subnodes, the values are 2 and 6, and their F values are 30 and 20, respectively stored in the position of 2 and 3 in opnelist (), and so on. Basically, our heap is the same as before, but it only adds information about the position of elements on the map, the value of F, and so on.
Add new elements to the heap (Part 2)
Well, we actually use this technology in a * pathtracing enabling list sorting. The technology we use is roughly the same as previously described.
We add the first element to the Enable list, which is generally the Start Node. We are assigned a unique ID with a value of 1 and placed it at the position #1 of the Enable list. That is, openlist (1) = 1. We also track the number of elements in the enabled list. Now it is also 1. We save it in a variable named numberofopenlistitems.
When we add new elements to the Enable list, we calculate the G, H, and f values, as described in the main article. Then add them to the Enable list according to the method described above.
First, we assign a unique ID to the new element, that is, the purpose of the squareschecked variable. Every time we add a new node, we add 1 to the variable and then assign its value to the new node. Add 1 to numberofopenlistitems. Then add it to the bottom of the Enable list. All these can be translated:
Squareschecked = squareschecked + 1
Numberofopenlistitems = numberofopenlistitems + 1
Openlist (numberofopenlistitems) = squareschecked
Then we compare it with the parent node until it reaches the correct position. Here is the code for these operations:
M = numberofopenlistitems
While m <> 1; while item hasn't bubbled to the top (M = 1)
; Check if child is <= parent. If so, swap them.
If fcost (openlist (M) <= fcost (openlist (M/2) then
Temp = openlist (M/2)
Openlist (M/2) = openlist (m)
Openlist (m) = temp
M = m/2
Else
Exit; exit the while/Wend Loop
End if
Wend
Remove elements from the heap (Part 2)
No doubt, we can't just create a heap. When we don't need it, we also need to delete elements from the heap. In particular, in a * pathfinding, after we check and switch to the close list, we need to delete the element with the lowest f value from the top of the heap.
As mentioned above, you move the last element to the top of the heap, And then subtract 1 from the total number of elements in the heap. The pseudo code is as follows:
Openlist (1) = openlist (numberofopenlistitems)
Numberofopenlistitems = numberofopenlistitems-1
Then we need to compare the values of the two subnodes in sequence. If its F value is higher, we can exchange it with a child node with a lower F value. Then we compare it with the new subnode (to see if it is lower ). If its F value is higher than the two subnodes, we exchange it with a lower one. We repeat this process until we find the correct position, which may continue until the heap bottom, but it is not completely necessary. The pseudo code looks like this:
V = 1; repeat he following until the item sinks to its proper spot in the binary heap.
Repeat
U = V
If 2 * u + 1 <= numberofopenlistitems; if both children exist
; Select the lowest of the two children.
If fcost (openlist (u)> = fcost (openlist (2 * u) then v = 2 * U; see note below
If fcost (openlist (V)> = fcost (openlist (2 * u + 1) then v = 2 * u + 1; see note below
Else if 2 * u <= numberofopenlistitems; if only child #1 exists
; Check if the f cost is greater than the child
If fcost (openlist (u)> = fcost (openlist (2 * u) then v = 2 * u
End if
If u <> V then; If parent's F> one or both of its children, swap them
Temp = openlist (u)
Openlist (u) = openlist (V)
Openlist (v) = temp
Else
Exit; If item <= both children, exit repeat/forever Loop
End if
Forever; repeat forever
Note the values of U and V in bold (red) in the two lines of code. In the second line, you should use V instead of U, which is not very obvious. This ensures that you exchange it with lower subnodes. If you do something wrong, it will cause incomplete heaps and disrupt your search.
Sorts the elements in the enabled list.
As described in the main article, sometimes you will find that the elements in the existing enable list change. In this case, we do not need to retrieve this element and try again. As long as it starts from the current position, use its new (lower) F value to compare with its parent node. If its F value is low enough to replace its parent node, you can replace it (otherwise you will get a wrong heap and everything is done ). Generally, you use the same code as the section "add new elements in the heap" and perform additional operations as follows:
Unfortunately, because your data is in a large and unordered heap, You need to traverse the entire heap to find the elements in the list that are enabled first. You mainly need to find the grids of the exact coordinates obtained by openx (openlist () and openy (openlist (). After finding them, you can start from that point, make necessary comparisons and exchanges as usual.
Final Annotation
Well, I hope you will still be able to understand it. If you are not in a hurry and want to use a binary heap in your own pathfinding algorithm, this is my advice.
First, stack the binary forks. Focus on your A * pathfinding algorithm and use a simple Sorting Algorithm to ensure that the algorithm works normally without any bugs. In the beginning, you don't need it very quickly. You only need it to work.
Next, before you add a binary heap to the Code, try to write the binary heap as an independent function and add and delete elements in an appropriate way. Make sure that you can view the operations in every step of the process in the program you write, and print the results on the screen if you want. You have to include some exit code to end the entire process when necessary. If the binary heap write is incorrect, you will easily fall into an infinite loop.
Once you are sure that both programs are running correctly, back up them and start to combine them. Unless you are smarter than me, you will inevitably encounter problems in the first place. The output contains some error messages and/or the Pathfinder goes in all sorts of weird directions due to the bug. But eventually you will get everything done.
Further reading
As before, there are many other good articles on this topic on the Internet. Here are some entry points. The first is illustrated in the following figure. The second article explains how to use a simple array (if my instructions are troublesome) to implement a binary heap. The third part discusses the general usage of heap in pathfinding algorithms.
* Http://www.onthenet.com.au /~ Grahamis/int2008/week11/lect11.html
* Http://www.purists.org/pqueue/
* Http://theory.stanford.edu /~ Amui/gameprogramming/implementationnotes.html # S5
Finally, you may want to see my path search code, which can be found here. The pseudocode in the text is mutually taken care of and annotated in detail. It has two versions: C ++ and blitz. Blitz is a language that is easier to understand than most others. Programmers who do not use C ++ can easily understand the code of the basic version.
Okay, that's all. Welcome to your comments on this (accepted) complex topic. You can contact me as follows:
Now, as you can see, good luck.

Use a binary heap in a * pathfinding

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.