我为我的游戏写了一个简单的A *算法:
Navigation.hpp
#pragma once
#include <SFML\Graphics.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;
class Field;
//Comparer using by set to allow priority
struct FieldComparer
{
bool operator()(const Field *, const Field *) const;
};
using FieldSet = set<Field*, FieldComparer>;
using FieldContainer = vector<Field*>; ///////////////////////////faster than list ?!
//Contains info about field, buildings builded on it and type of terrain
class Field
{
private:
sf::Vector2i mapPosition{ 0, 0 };
unsigned hCost{ 0 }; //to goal
unsigned gCost{ 0 }; //to start
Field * parent;
bool isWalkable { true };
bool isPathPart { false };
public:
void SetParent(Field&);
Field * GetParent() const;
unsigned GetFCost() const; //sum of hCost and gCost
unsigned GetHCost() const;
unsigned GetGCost() const;
bool IsWalkable() const;
bool IsPathPart() const;
void SetHCost(unsigned);
void SetGCost(unsigned);
void SetWalkable(bool);
void SetAsPartOfPath(bool);
sf::Vector2i GetMapPosition() const;
//compares positions
bool operator == (const Field& other);
Field(sf::Vector2i mapPosition, bool isWalkable)
: mapPosition(mapPosition), isWalkable(isWalkable) {}
};
//Contains fields and describes them
class Map
{
private:
sf::Vector2u mapSize;
Field *** fields; //two dimensional array of fields gives the fastest access
public:
sf::Vector2u GetMapSize() const;
Field *** GetFields();
Map(sf::Vector2u);
Map() {}
};
//Searching patch after giving a specified map
class PathFinder
{
private:
//Calculate score between two fields
unsigned CalcScore(Field&, Field&) const;
//Get neighbours of field in specified map
FieldContainer GetNeighbours(Field&, Map&) const;
public:
//Find path that have the lowest cost, from a to b in map
FieldContainer FindPath(Map&, Field&, Field&);
//Reconstruct path using pointers to parent
FieldContainer ReconstructPath(Field*, Field*) const;
};
Navigation.cpp
#include "Navigation.hpp"
#pragma region Field
void Field::SetParent(Field & parent) { this->parent = &parent; }
Field * Field::GetParent() const { return parent; }
unsigned Field::GetFCost() const { return hCost + gCost; }
unsigned Field::GetHCost() const { return hCost; }
unsigned Field::GetGCost() const { return gCost; }
bool Field::IsWalkable() const { return isWalkable; }
bool Field::IsPathPart() const { return isPathPart; }
void Field::SetHCost(unsigned value) { hCost = value; }
void Field::SetGCost(unsigned value) { gCost = value; }
void Field::SetWalkable(bool isWalkable) { this->isWalkable = isWalkable; }
void Field::SetAsPartOfPath(bool isPathPart) { this->isPathPart = isPathPart; }
sf::Vector2i Field::GetMapPosition() const { return mapPosition; }
bool Field::operator == (const Field& other)
{
return this->mapPosition == other.GetMapPosition();
}
#pragma endregion Field
#pragma region Map
sf::Vector2u Map::GetMapSize() const { return mapSize; }
Field *** Map::GetFields() { return fields; }
Map::Map(sf::Vector2u mapSize) : mapSize(mapSize)
{
//initialize map
fields = new Field**[mapSize.x];
//initialize all fields
for (unsigned x = 0; x < mapSize.x; x++)
{
fields[x] = new Field*[mapSize.y];
for (unsigned y = 0; y < mapSize.y; y++)
{
fields[x][y] = new Field({static_cast<int>(x), static_cast<int>(y)},
{
(!(y == 3 && x >= 1) || (x == 5 && y < 4))
});
}
}
}
#pragma endregion Map
#pragma region PathFinder
bool FieldComparer::operator()(const Field * l, const Field * r) const
{
return l->GetFCost() < r->GetFCost() || //another field has smaller fcost
l->GetFCost() == r->GetFCost() && //or fcost equals, and checked field is nearer to goal than current field
l->GetHCost() < r->GetHCost();
}
unsigned PathFinder::CalcScore(Field & a, Field & b) const
{
sf::Vector2u dst
{
static_cast<unsigned>(abs(b.GetMapPosition().x - a.GetMapPosition().x)),
static_cast<unsigned>(abs(b.GetMapPosition().y - a.GetMapPosition().y))
};
return (dst.x > dst.y ? 14 * dst.y + 10 * (dst.x - dst.y) :
14 * dst.x + 10 * (dst.y - dst.x));
}
FieldContainer PathFinder::GetNeighbours(Field & f, Map & m) const
{
FieldContainer neighbours{};
//cout << "checking neighbours for field: { " << f.GetMapPosition().x << ", " << f.GetMapPosition().y << " }\n";
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
int xPos = f.GetMapPosition().x + x;
int yPos = f.GetMapPosition().y + y;
if (x == 0 && y == 0) //dont check the same field
continue;
//check that field is in the map
bool isInTheMap = (xPos >= 0 && yPos >= 0 && xPos < m.GetMapSize().x && yPos < m.GetMapSize().y);
if (isInTheMap)
{
neighbours.push_back(m.GetFields()[xPos][yPos]);
}
}
}
return neighbours;
}
FieldContainer PathFinder::FindPath(Map& map, Field& a, Field& b)
{
FieldSet open = {}; //not expanded fields
FieldSet closed = {}; //expanded fields
a.SetHCost(CalcScore(a, b)); //calculate h cost for start field, gcost equals 0
open.insert(&a); //add start field to open vector
while (!open.empty()) //while we have unexpanded fields in open set
{
Field * current = *open.begin(); //set current field
//if current checked field is our goal field
if (*current == b)
{
return
ReconstructPath(&a, current); //return reversed path
}
closed.insert(current); //end of checking current field, add it to closed vector...
open.erase(open.find(current)); //set solution
//get neighbours of current field
for (auto f : GetNeighbours(*current, map))
{
//continue if f is unavailable
if (closed.find(f) != closed.end() || !f->IsWalkable())
{
continue;
}
//calculate tentative g cost, based on current cost and direction changed
unsigned tentativeGCost = current->GetGCost() + (current->GetMapPosition().x != f->GetMapPosition().x && current->GetMapPosition().y != f->GetMapPosition().y ? 14 : 10);
bool fieldIsNotInOpenSet = open.find(f) == open.end();
if (tentativeGCost < f->GetGCost() || fieldIsNotInOpenSet)
{
f->SetGCost(tentativeGCost);
f->SetHCost(CalcScore(*f, b));
f->SetParent(*current);
if (fieldIsNotInOpenSet)
{
open.insert(f);
}
}
}
}
return {}; //no path anaviable
}
FieldContainer PathFinder::ReconstructPath(Field * a, Field * current) const
{
FieldContainer totalPath { current };
while (!(current == a))
{
totalPath.push_back(current);
current->SetAsPartOfPath(true);
current = current->GetParent();
}
std::reverse(totalPath.begin(), totalPath.end()); //reverse the path
return totalPath;
}
#pragma endregion PathFinder
...我想知道为什么当我使用std :: list而不是std :: vector时,执行路径查找算法的时间等于或大于std :: vector,因为只有FieldContainer的添加操作
我使用std :: chrono高分辨率计时器在循环中检查执行时间10次:
#include "MapDrawer.h"
#include <iostream>
#include "Navigation.hpp"
//clock
#include <chrono>
typedef std::chrono::high_resolution_clock ChronoClock;
using namespace sf;
bool pathFound = false;
FieldContainer path;
Map gameMap;
const sf::Vector2f mapSize { 300, 300 };
void Test()
{
for (int i = 0; i < 10; i++)
{
gameMap = { static_cast<sf::Vector2u>(mapSize) };
PathFinder pathFinder{};
auto t1 = ChronoClock::now();
path = pathFinder.FindPath(gameMap, *gameMap.GetFields()[0][0], *gameMap.GetFields()[(int)(mapSize.x - 1)][(int)(mapSize.y - 1)]);
auto t2 = ChronoClock::now();
std::cout << "Delta: " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " milliseconds\n";
}
}
void MapDrawer::draw(RenderTarget &target, RenderStates states) const
{
if (!pathFound)
{
Test();
pathFound = true;
}
////////////////
}
以ms为单位的结果(DEBUG)
map size container time (average)
50x50 list 13,8
50x50 vector 12,4
150x150 list 54,0
150x150 vector 41,9
300x300 list 109,9
300x300 vector 100,8
以ms为单位的结果(RELEASE)
map size container time (average)
500x500 list 9,3
500x500 vector 7,4
1500x1500 list 23,9
1500x1500 vector 23,7
所以看起来向量比列表更快,但为什么,因为列表在添加操作时应该更快?
顺便说一下。那算法快吗?如果不是,那么我可以改变什么来提高速度?
感谢。
答案 0 :(得分:3)
是的情况,其中列表将比矢量更快。
显而易见的是在集合的开头插入了很多项目:
template <class T>
void build(T &t, int max) {
for (int i = 0; i < max; i++)
t.insert(t.begin(), i);
}
快速测试表明,插入像这样的100000个整数需要vector
几百毫秒,list
需要5毫秒(尽管在这种情况下,一个双端队列是明显的选择 - 大约一半一微秒,它比传真或列表更快 。
要使列表有意义,您需要在已经相当大的列表中间插入某个位置 - 并且(重要部分)需要在许多插入时插入同一点,所以你不会花很多时间遍历列表来找到你的插入位置。例如,如果我们这样做:
template <class T>
void build2(T &t, int max) {
max = max / 3;
for (int i = 0; i < max; i++)
t.insert(t.begin(), i);
auto pos = t.begin();
for (int i = -max; i < 0; i++)
t.insert(t.begin(), i);
// Now `pos` refers to the middle of the collection, insert some there:
for (int i = 0; i < max; i++)
t.insert(pos, i);
}
(轻微一点:对于deque
或vector
,代码必须略有不同 - 因为任何后续插入都可能使pos
无效,所以它们的第三个循环看起来像:
for (int i = 0; i < max; i++)
t.insert(t.begin() + max, i);
所以,无论如何,我们终于举行了list
胜过vector
或deque
的比赛。对于这个任务我得到的时间(注意:这不是具有相同数量的对象,所以时间不能与前面的相比):
List: 500 us
Deque: 3500 us
Vector: 6500 us
当然,这不是关于这个主题的最终(或唯一)一个词 - 很大程度上还取决于你要处理的物品有多大,因为大件物品是(至少通常)复制速度相当慢。
有趣的是:vector
总是在最后扩展,但deque
可以在结束或开始时扩展,所以即使它不理想,它仍然是此任务明显比vector
快得多。
哦,还有一点:时间也可能与编译器/库有所不同。前一次是gcc。使用VC ++,列表大致相同,向量大约快两倍,而deque 多慢:
List: 526 us
Deque: 37478 us
Vector: 3657 us
(在与前面相同的系统上执行)。