介绍
图,是由一个顶点集和一个边集构成的数据结构。
怎么在程序里表示图?有两种方法,邻接矩阵和邻接表。
虽然用邻接矩阵表示非常简单,但它也有些不可忽视的缺点。比如
- 矩阵的大小不好提前预知,动态数组的话又不好实现。
- 如果图是稀疏的话,也造成空间浪费。
邻接表就要好很多。
实现
数据结构要怎么设计?先试着去写一下最基本的东西。
顶点。
template<typename Vt, typename Et>
class Vertex
{
public:
typedef Edge<Et>* EdgeType;
Vertex();
virtual ~Vertex();
public:
Vt vData_;
private:
std::vector<EdgeType> outputs;
std::vector<EdgeType> inputs;
};
边。
template<typename T>
class Edge
{
public:
Edge(Vertex<T> *t, const T& d) : target_(t), eData_(d);
virtual ~Edge();
public:
T eData_;
Vertex<T> *target_;
};
图。
template<typename Vt, typename Et = int>
class Graph
{
public:
typedef Vertex<Vt,Et>* VertexPtr;
typedef Edge<Et>* EdgePtr;
Graph();
virtual ~Graph();
public:
void newEdge(VertexPtr source, VertexPtr target, Et data);
VertexPtr newVertex();
private:
std::deque<std::shared_ptr<Vertex<Vt, Et> > > vertexList_;
std::deque<std::shared_ptr<Edge<Et> > > edgeList_;
};
图的遍历
BFS,思路和树的层序遍历是一样的。
void BFS(VertexPtr &start)
{
std::queue<VertexPtr> q;
std::map<VertexPtr, bool> visit;
for(auto i: vertexList_)
{
visit[i.get()] = false;
}
visit[start] = true;
q.push(start);
while(!q.empty())
{
VertexPtr node = q.front();
q.pop();
for(auto edge : node->outputs)
{
if(!visit[edge->target_])
{
visit[edge->target_] = true;
q.push(edge->target_);
}
}
}
}
DFS,递归。
void DFSHelper(VertexPtr &s, std::map<VertexPtr,bool> &visit)
{
if(!s) return;
for(auto edge : s->outputs)
{
if(!visit[edge->target_])
{
visit[edge->target_] = true;
DFSHelper(edge->target_, visit);
}
}
}
void DFS(VertexPtr &start)
{
std::map<VertexPtr,bool> visit;
for(auto i : vertexList_)
{
visit[i.get()] = false;
}
DFSHelper(start, visit);
}
最短路径算法
Dijkstra,他老人家这辈子始终在追求简洁算法之美,为了向他致敬,我也用精炼的语言总结一下:“ 最短路径具有最优子结构性质,自底向上通过子问题的最优解去构建原问题的最优解”
void DijkstraHelper(VertexPtr &s, std::map<VertexPtr,bool> &visit,
std::map<VertexPtr, int> &dist, std::map<VertexPtr, VertexPtr> &path)
{
if(!s) return;
while(1)
{
int min = INT_MAX;
VertexPtr u = nullptr;
// 找出目前距离最小的结点
// 这里我遍历了所有的结点,为了提高效率,可以改用优先队列,不过实现方法就需要仔细考虑了
for(auto v : vertexList_)
{
if(dist[v.get()] < min && !visit[v.get()])
{
min = dist[v.get()];
u = v.get();
}
}
if(!u) break;
visit[u] = true;
for(auto edge : u->outputs)
{
if(!visit[edge->target_])
{
int d = dist[u] + edge->eData_;
// 如果当前的距离小于原来的距离,则更新结点的路径,距离信息
if(d < dist[edge->target_])
{
dist[edge->target_] = d;
path[edge->target_] = u;
}
}
}
}
}
void Dijkstra(VertexPtr &start)
{
std::map<VertexPtr,bool> visit;
std::map<VertexPtr, int> dist;
std::map<VertexPtr, VertexPtr> path;
// 初始化这三个表
for(auto i : vertexList_)
{
visit[i.get()] = false;
dist[i.get()] = INT_MAX;
path[i.get()] = nullptr;
}
// 设置好起始结点的信息
visit[start] = false;
dist[start] = 0;
path[start] = nullptr;
// 执行算法
DijkstraHelper(start, visit, dist, path);
// 打印每个结点的,路径、距离信息
for(auto i : vertexList_)
{
if(i.get() == start)
{
std::cout<<i->vData_<<" "<<dist[i.get()]<<" nullptr"<<"\n";
continue;
}
std::cout<<i->vData_<<" "<<dist[i.get()]<<" "<<path[i.get()]->vData_<<"\n";
}
}
应用
生活中很多东西用到了图算法,比如地图寻路,路由转发表,等等