开放列表和封闭列表的检索排序消耗是A*寻路中一块主要的消耗,对于封闭列表,可以采用在节点中标记的形式,去除封闭列表的查询检索,从而缩减寻路中在封闭列表中的消耗;对于开放列表,个人感觉采用合理的数据结构外加高效的排序算法是优化开放列表的主要出发点。 网上有说可以将开放列表缩减到只有两个元素,从而大大的提高寻路效率,这个下了源码,但是还是没看明白其中的奥妙。对于另外一类是采用二叉堆的形式,这个我的小伙伴有实践过代码,将开放列表用二叉堆处理效率确实有所优化,效果也还不错。对于二叉堆每次的消耗应该也是在于每次的插入和弹出最小元素的过程中对数组中的移位和排序处理,而且是分插入和弹出两次,个人觉得这两次的处理如果可以缩减为一次,效率也会提高好多。同时堆排序时间复杂度(O(nlogn)),而插入排序的时间复杂度为O(n), 插入排序比堆排序快点,而且堆排序不稳定哦。所以在再次临优化A*寻路的时候就想到了采用插入排序来处理开放列表。
本人的做法是采用双向链表来保存开放列表,每次插入的时候进行一次插入排序,每次取最近的时候直接弹出链表头。
先贴代码吧
头文件
struct ListNode
{
AStar_Node* CurNode;
ListNode* PreNode;
ListNode* NextNode;
ListNode(AStar_Node* pNode)
{
CurNode = pNode;
PreNode = NULL;
NextNode = NULL;
}
~ListNode()
{
CurNode = NULL;
PreNode = NULL;
NextNode = NULL;
}
};
class CList_InsertionSort
{
public:
CList_InsertionSort();
~CList_InsertionSort();
public:
void Insert(AStar_Node* pNode);
AStar_Node* Pop();
void Clear();
int Count(){return m_nNodeCount;}
private:
void InsertionSort(ListNode* nextNode, ListNode* newNode);
private:
ListNode* m_pHead; // 头
int m_nNodeCount;// 节点个数
};
源码
CList_InsertionSort::CList_InsertionSort()
:m_pHead(NULL)
,m_nNodeCount(0)
{
}
CList_InsertionSort::~CList_InsertionSort()
{
}
void CList_InsertionSort::Insert( AStar_Node* pNode )
{
m_nNodeCount++;
ListNode* pListNode = new ListNode(pNode);
if (m_pHead == NULL)
{
m_pHead = pListNode;
}
else
{
InsertionSort(m_pHead, pListNode);
}
}
AStar_Node* CList_InsertionSort::Pop()
{
if (m_pHead != NULL)
{
m_nNodeCount--;
AStar_Node* minNode = m_pHead->CurNode;
// 删除第一个,以第二个为头
ListNode* nextNode = m_pHead->NextNode;
safe_delete(m_pHead);
m_pHead = nextNode;
if (m_pHead != NULL)
{
m_pHead->PreNode = NULL;
}
return minNode;
}
else
{
return NULL;
}
}
void CList_InsertionSort::Clear()
{
m_nNodeCount = 0;
ListNode* nextNode = NULL;
while (true)
{
if (m_pHead != NULL)
{
nextNode = m_pHead->NextNode;
safe_delete(m_pHead);
m_pHead = nextNode;
}
else
{
break;
}
}
}
void CList_InsertionSort::InsertionSort( ListNode* nextNode, ListNode* newNode )
{
// 如果小于等于,插在前
if (newNode->CurNode->GetNodeF() <= nextNode->CurNode->GetNodeF())
{
newNode->NextNode = nextNode;
ListNode* preNode = nextNode->PreNode;
if (preNode != NULL)
{
preNode->NextNode = newNode;
newNode->PreNode = preNode;
}
// PreNode==NULL表示该节点为链表头
else
{
m_pHead = newNode;
}
nextNode->PreNode = newNode;
}
else
{
ListNode* nNode = nextNode->NextNode;
if (nNode == NULL)
{
nextNode->NextNode = newNode;
newNode->PreNode = nextNode;
}
else
{
InsertionSort(nNode, newNode);
}
}
}
其实在做的时候还想如果可以在链表中插入的时候加入一个二分法,那效率又会快好多,但是链表又不便于索引,所以只能在便于插入和便于检索之间做个选择。如果有好的方法还请大家提出。有问题,有BUG也请见谅。希望能给大家带来一定的思路。
补充一下,如果你地图格子数比较多,建议还是不要用这个方法来处理开放列表,当然如果可以用上二分法,可以一试,不过大数据量还是二叉堆更给力。