文章目录
一、图的点+边存储结构
相对于邻接矩阵和邻接表的结构,图的点+边的存储结构能够存储更多信息,因此更容易获取每个点的入度和出度以及对应的边,方便实现最小生成树、最短路径以及拓扑排序等算法。
另外,邻接表和邻接矩阵等存储方式与点+边的存储方式可以相互转化,实现算法接口的复用,所以当传入的参数是邻接表或邻接矩阵时,只需要将它们转化为点+边的存储结构,就可以调用本章的所有算法接口了。
1.1. 整体代码
//声明边类,否则在点类中会无法使用
template<class V>
class Edge;
template<class V>
class Node
{
//点类中包含当前点的入度和出度,以及该点指向的其它点的数组
//还包括以该点为起点的边的数组
public:
V value;
//入度和出度
int in;
int out;
//点的邻居
vector<Node<V>*> nexts;
//从当前节点出发的边
vector<Edge<V>*> edges;
Node(V _value)
{
value = _value;
in = 0;
out = 0;
}
};
//边类
template<class V>
class Edge
{
//边类相对简单,包含权值,以及起点和终点
public:
//权重
int weight;
//边的起点和终点
Node<V>* from;
Node<V>* to;
Edge(int _weight, Node<V>*& _from, Node<V>*& _to)
{
weight = _weight;
from = _from;
to = _to;
}
};
//图类
template<class V>
class Graph
{
//图类当中,为每个值创建一个对应的点,并用map保存它们的映射关系
//同时用set记录每一条边,当然也可以用vector等其他容器
public:
Graph(){
}
//连接两个点,以及它们的权值
void AddEdge(V from, V to, int weight);
~Graph()
{
for (auto& e : nodes)
{
delete e.second;
}
for (auto& e : edges)
{
delete e;
}
}
map<V, Node<V>*> nodes;
set<Edge<V>*> edges;
};
插入操作只能表示有向图,如果要表示无向图,只需要将两个点双向连通即可。比如g.AddEdge("a", "b", 3); g.AddEdge("b", "a", 3);
1.2. 插入操作
void AddEdge(V from, V to, int weight)
{
//如果两个值对应的Node不存在,则新建,并将它们入nodes
if (nodes.find(from) == nodes.end())
{
nodes.insert(make_pair(from, new Node<V>(from)));
}
if (nodes.find(to) == nodes.end())
{
nodes.insert(make_pair(to, new Node<V>(to)));
}
//找到这两个值对应的Node
Node<V>*& fromNode = nodes[from];
Node<V>*& toNode = nodes[to];
//建立它们的边,并且对应的入度和出度++
Edge<V>* newEdge = new Edge<V>(weight, fromNode, toNode);
fromNode->nexts.push_back(toNode);
fromNode->out++;
toNode->in++;
//将创建的边放入以fromNode为起点的边的数组中
fromNode->edges.push_back(newEdge);
//图中保存这条边
edges.insert(newEdge);
}
1.3. BFS(广度优先遍历)
//广度优先遍历
void BFS(const V& src)
{
if (nodes.find(src) == nodes.end())
{
return;
}
Node<V>* start = nodes[src];
queue<Node<V>*>q;
//记录已经遍历过的节点
set<Node<V>*>set;
q.push(start);
set.insert(start);
while (!q.empty())
{
Node<V>*& cur = q.front();
q.pop();
cout << cur->value << " ";
//将该点所有的邻居都放到队列中
for (Node<V>*& next : cur->nexts)
{
if (set.find(next) == set.end())
{
set.insert(next);
q.push(next);
}
}
}
}
1.4. DFS(深度优先遍历)
//深度优先遍历
void DFS(const V& src)
{
if (nodes.find(src) == nodes.end())
{
return;
}
Node<V>* start = nodes[src];
stack<Node<V>*>stack;
set<Node<V>*>set;
stack.push(start);
set.insert(start);
cout << start->value << " ";
while (!stack.empty())
{
Node<V>*& cur = stack.top();
stack.pop();
//放入当前节点的邻居节点前,需要先放入当前节点
//这样当该邻居节点出栈访问完后,能够访问当前节点其他邻居节点
for (Node<V>*& next : cur->nexts)
{
if (set.find(next) == set.end())
{
stack.push(cur);
stack.push(next);
set.insert(next);
cout << next->value << " ";
//访问该邻居节点后,循环结束(其他邻居节点还没入栈),
//从栈中获取该邻居节点的邻居节点,达到深度遍历的效果
break;
}
}
}
}
二、拓扑排序
拓扑排序是一个有向无环图的所有顶点的线性序列。且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
2.1. 使用入度实现的拓扑排序
如果知道所有点的入度,只需要寻找入度为0的点(如果有多个入度为0的点则任取一个),然后该点的边删掉,然后边对应的入度边对应的入度-1,如此循环即可,比如这种情况:
-
结点1的入度为0,因此节点1为最开始的点
-
删掉1对应的两条边,此时节点2的入度为0,删掉2,重复操作
-
最终得到拓扑排序为1, 2, 3, 4, 5
//拓扑排序,返回拓扑排序后的列表
vector<Node<V>*>sortedTopology()
{
//记录节点和剩余的入度
map<Node<V>*, int>inMap;
//入度为0则放到队列zeroInQueue中
queue<Node<V>*>zeroInQueue;
//将当前图的所有节点放入inMap中
for (auto& e : nodes)
{
inMap[e.second] = e.second->in;
//入度为0的节点,则进入队列
if (e.second->in == 0)
{
zeroInQueue.push(e.second);
}
}
vector<Node<V>*> result;
while (!zeroInQueue.empty())
{
//将当前入度为0的节点放入result中,并且其邻居节点的入度--
//如果入度--后入度为0,则继续放入队列
Node<V>*& cur = zeroInQueue.front();
zeroInQueue.pop();
result.push_back(cur);
//找到当前节点所有邻居节点,他们的入度--
for (Node<V>*& next : cur->nexts)
{
//邻居节点的入度--
inMap[next]--;
if (inMap[next] == 0)
{
zeroInQueue.push(next);
}
}
}
return result;
}
2.2. 不使用入度的拓扑排序
如果不知道对应的入度也没关系,因为根据拓扑排序的性质,最右边的点(这里为5)一定是没有出度的,其深度为1,其他节点到该节点最大路径上节点数目就是它们的深度,比如4的深度为2(4->5),3的深度为3(3->4->5),2的深度为4(2->3->4->5),1的深度为5(1->2->3->4->5),只需要获取每个节点的深度,即可求出拓扑排序。
比如这道oj题目127 · 拓扑排序
这个图只有点,以及点的邻居节点。
struct DirectedGraphNode
{
int label;
vector<DirectedGraphNode *> neighbors;
DirectedGraphNode(int x) : label(x) {};
};
class Solution {
public:
class Record {
public:
//节点和节点的深度
DirectedGraphNode* node;
long deep;
Record(DirectedGraphNode* _node, long num)
{
node = _node;
deep = num;
}
};
//当前来到cur点,返回cur点的深度
Record* getDeep(DirectedGraphNode* cur, map<DirectedGraphNode*, Record*>& order)
{
//该节点在order中已经存在,则直接返回
if (order.find(cur) != order.end())
{
return order[cur];
}
long deep = 0;
for (DirectedGraphNode* next : cur->neighbors)
{
//当前节点的深度等于原来的深度和它所有邻居节点深度的最大值,再+1(算上自己)
deep = max(deep, getDeep(next, order)->deep);
}
Record* ans = new Record(cur, deep + 1);
order[cur] = ans;
return ans;
}
vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*> graph) {
//order记录所有节点以及它们的深度
map<DirectedGraphNode*, Record*>order;
for (DirectedGraphNode* cur : graph)
{
getDeep(cur, order);
}
vector<Record*>v;
for (auto e : order)
{
v.push_back(e.second);
}
//将节点按照降序排序,然后放入vector中返回
sort(v.begin(), v.end(), [](Record* o1, Record* o2) {
return o1->deep > o2->deep;
});
vector<DirectedGraphNode*>ans;
for (Record* r : v)
{
ans.push_back(r->node);
}
return ans;
}
};
三、最小生成树
连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树 就不在连通;反之,在其中引入任何一条新边,都会形成一条回路。若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:
- 只能使用图中的边来构造最小生成树,这几条边加起来的权值是最小的。
- 只能使用恰好n-1条边来连接图中的n个顶点。
- 选用的n-1条边不能构成回路。
构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。
贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。
3.1. Kruskal算法
任给一个有n个顶点的连通网络N={V,E},首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量,其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止。
核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。
总结起来就是每次都从剩下边里面选权值最小的边加入生成树,看是否构成环,不构成环就加入,构成环就不加入,直到选出N-1条边。
从实现角度来说,难点是如何判断加入的新边是否构成环,比较高效的方法是用并查集解决:
- 先判断加入边的两个顶点在不在一个集合,如果在一个集合,加入就会构成环。
- 加入的边,就把它的两个顶点合并。
并查集的代码参考C++并查集
template<class V>
struct cmp
{
bool operator()(Edge<V>* a, Edge<V>* b)
{
return a->weight > b->weight;
}
};
template<class V>
set<Edge<V>*>kruskalMST(const Graph<V>* g)
{
//将所有的点放入并查集
vector<Node<V>*> v;
for (auto e : g->nodes)
{
v.push_back(e.second);
}
UnionFindNodeSet<Node<V>*> unionFind(v);
//升序的优先级队列,每次获取权值最小的边
priority_queue<Edge<V>*, vector<Edge<V>*>, cmp<V>>q;
for (Edge<V>* edge : g->edges)
{
q.push(edge);
}
set<Edge<V>*>result;
//每次从优先级队列中获取权值最小的边,然后判断它们的起点和终点在不在一个集合
//若不在则选中这条边,并将起点和终点放入集合
//若在则说明如果选中则会形成环,此时忽略掉这条边
//重复上述操作,直到优先级队列为空
while (!q.empty())
{
Edge<V>* edge = q.top();
q.pop();
//权值最小的边不在同一集合则相连接
if (!unionFind.isSameSet(edge->from, edge->to))
{
result.insert(edge);
unionFind.Union(edge->from, edge->to);
}
}
return result;
}
3.2. Prime算法
Prime算法大致思路为,随便选一个点,然后选该点权值最小的那条边,再选这条边相连的节点的所有边中权值最小的边,不断重复。
这种方式不会构成环,因为每次选边,都是从两个集合里面分别选一个节点构成的边:已经相连顶点是一个集合,没有相连顶点是一个集合。因此只需要用set记录选过的节点即可。
template<class V>
struct cmp
{
bool operator()(Edge<V>* a, Edge<V>* b)
{
return a->weight > b->weight;
}
};
template<class V>
set<Edge<V>*>primMST(const Graph<V>* g)
{
//解锁的边放入小根堆
priority_queue<Edge<V>*, vector<Edge<V>*>, cmp<V>>q;
//使用set记录被解锁出来的点
set<Node<V>*>nodeSet;
//将选出来的边放入result
set<Edge<V>*>result;
//遍历图中所有的点,防止出现森林的情况
for (auto iter = g->nodes.begin(); iter != g->nodes.end(); ++iter)
{
Node<V>* node = iter->second;
//node是开始的点
if (nodeSet.find(node) == nodeSet.end())
{
nodeSet.insert(node);
//node所有相连的边放入集合
for (Edge<V>* edge : node->edges)
{
q.push(edge);
}
while (!q.empty())
{
//取出最小权值的边
Edge<V>* edge = q.top();
q.pop();
Node<V>*& toNode = edge->to;
//这个边已经被使用过,则什么都不做,回到while再取一个边
if (nodeSet.find(toNode) == nodeSet.end())
{
nodeSet.insert(toNode);
result.insert(edge);
//toNode连向的所有边放入集合
for (Edge<V>* nextEdge : toNode->edges)
{
q.push(nextEdge);
}
}
}
}
break;//break可以不加,防止出现森林的情况
}
return result;
}
四、最短路径—Dijkstra算法
最短路径问题:从在带权图的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。
关于Dijkstra算法的核心为:运用贪心思想,每次从 「未求出最短路径的点」中取出距离距离起点最小路径的点,以这个点为跳转点刷新「未求出最短路径的点」的距离。然后锁定该跳转点,因为该跳转点到起始位置的距离已经是最小值了。
Dijkstra算法详解 通俗易懂
4.1. 优化前的代码
template<class V>
Node<V>* getMinDistanceAndUnseletedNode(map<Node<V>*, int>& distanceMap, set<Node<V>*>& selectedNodes)
{
Node<V>* minNode = nullptr;
int minDistance = INT_MAX;
for (auto e : distanceMap)
{
if (e.second < minDistance && (selectedNodes.find(e.first) == selectedNodes.end()))
{
minNode = e.first;
minDistance = e.second;
}
}
return minNode;
}
template<class V>
map<Node<V>*, int>dijkstra(Node<V>*& from)
{
map<Node<V>*, int>distanceMap;
//起始位置到自己的距离为0
distanceMap[from] = 0;
//selectedNodes记录已经锁定的节点,这些节点到起始位置的距离已经是最小值
set<Node<V>*>selectedNodes;
//这个函数的作用是从distanceMap返回一个距离最小值的点,并且这个点不在selectedNodes中
Node<V>* minNode = getMinDistanceAndUnseletedNode(distanceMap, selectedNodes);
while (minNode != nullptr)
{
//原始点->minNode(跳转点) 最小距离为distance
//一开始这个距离为0
int distance = distanceMap[minNode];
//找到minNode所有的邻居,然后更新到这些邻居的距离
for (Edge<V>*& edge : minNode->edges)
{
Node<V>*& toNode = edge->to;
//这个邻居还没有被distanceMap记录
if (distanceMap.find(toNode) == distanceMap.end())
{
distanceMap[toNode] = distance + edge->weight;
}
//这个邻居已经被记录,更新它最小的距离
else
{
distanceMap[toNode] = min(distanceMap[toNode], distance + edge->weight);
}
}
//锁定这个跳转点
selectedNodes.insert(minNode);
//继续找一个跳转点,重复刚才的操作
minNode = getMinDistanceAndUnseletedNode(distanceMap, selectedNodes);;
}
return distanceMap;
}
getMinDistanceAndUnseletedNode
函数每次都需要遍历一个map找到距离最小的节点,同时还需要查找set中是否已经出现过该节点。其时间复杂度是很高的,此时可以使用加强堆进行优化,减少查找时间。
4.2. 使用加强堆优化Dijkstra算法
为了让加强堆适配该算法,需要对加强堆进行一些修改,我们在堆中用哈希表记录节点到起始位置的距离,如果该节点已经被锁定(距离已经是最小),我们就删除这个节点(并不是真的删除,把它下标映射位置改成-1即可,因为这个节点有可能会多次插入,我们只需要插入一次即可),同时拿到节点和对应的距离:
//这个类记录节点,以及节点到出发点的距离
//在获取堆顶数据的时候返回该节点
//因为返回值只能是一个值,
//所以要同时拿到节点以及节点对应的距离,只能以对象的形式返回
template<class T>
class NodeRecord {
public:
friend class Node<T>;
Node<T>* node;
int distance;
NodeRecord(Node<T>*_node,int _distance)
:node(_node)
,distance(_distance){}
};
template<class T>
class NodeHeap
{
public:
NodeHeap(int size)
{
heap.resize(size,nullptr);
}
~NodeHeap()
{}
NodeRecord<T>*& pop()
{
assert(!empty());
//获取节点以及其对应的距离,剩下的操作和加强堆的删除相同
NodeRecord<T>* nodeRecord = new NodeRecord<T>(heap[0], distanceMap[heap[0]]);
swap(0, heapSize - 1);
//将该节点映射下标改为-1
indexMap[heap[heapSize - 1]] = -1;
distanceMap.erase(heap[heapSize - 1]);
heap[heapSize - 1] = nullptr;
AdjustDwon(0,--heapSize);
return nodeRecord;
}
bool empty()
{
return heapSize==0;
}
//一个节点有没有进入过堆
bool isEntered(Node<T>*& node)
{
return indexMap.find(node) != indexMap.end();
}
//一个节点在不在堆上,已经被删除的点(映射的下标为-1)不在堆上
bool inHeap(Node<T>*& node)
{
return isEntered(node) && indexMap[node] != -1;
}
//有一个节点node,从出发点到node的距离为distance
//判断需不需要更新
void addOrUpdateOrIgnore(Node<T>*& node, int distance)
{
//该节点存在
if (inHeap(node))
{
distanceMap[node] = min(distanceMap[node], distance);
//由于距离变小,因此向上调整距离
AdjustUp(node,indexMap[node]);
}
//该节点为新增节点
if (!isEntered(node))
{
heap[heapSize] = node;
indexMap[node] = heapSize;
distanceMap[node] = distance;
AdjustUp(node,heapSize++);
}
//该节点已经被锁点,其值为-1,什么都不做
}
private:
//需要封装交换函数,因为heap和indexMap的值都需要交换
void swap(int i, int j)
{
Node<T>* o1 = heap[i];
Node<T>* o2 = heap[j];
heap[i] = o2;
heap[j] = o1;
indexMap[o2] = i;
indexMap[o1] = j;
}
//向上调整算法,每push一个数都要调用向上调整算法,保证插入后是一个大堆
void AdjustUp(Node<T>* node,int index)
{
int parent = (index - 1) / 2;
while (index > 0)
{
if (distanceMap[heap[index]]<distanceMap[heap[parent]])
{
swap(parent, index);
index = parent;
parent = (index - 1) / 2;
}
else
{
break;
}
}
}
//向下调整算法,每次调用pop都要进行向下调整算法重新构成大堆
void AdjustDwon(int parent,int size)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && (distanceMap[heap[child+1]] < distanceMap[heap[child]]))
{
child++;
}
if (distanceMap[heap[child]] < distanceMap[heap[parent]])
{
swap(parent, child);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
vector<Node<T>*> heap;
//反向索引表
unordered_map<Node<T>*, int>indexMap;
//记录当前节点与出发点的距离
unordered_map<Node<T>*, int>distanceMap;
int heapSize = 0;
};
优化后的代码为:
template<class V>
map<Node<V>*, int>dijkstra_better(Node<V>*& from,int size)
{
//创建加强堆并放入起始位置以及距离
NodeHeap<V> nodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(from, 0);
map<Node<V>*, int>result;
while (!nodeHeap.empty())
{
//从加强堆中获取堆顶的节点,也就是距离最小的节点
NodeRecord<V>*& record = nodeHeap.pop();
Node<V>* cur = record->node;
int distance = record->distance;
//record是new出来的对象,需要被释放
delete record;
//以该节点为跳转点,更新其他节点的距离,随后锁定这个节点
for (Edge<V>*& edge : cur->edges)
{
nodeHeap.addOrUpdateOrIgnore(edge->to, edge->weight + distance);
}
result[cur] = distance;
}
return result;
}
附录
HeapGreater.h
#include"Graph.h"
template<class T>
class NodeRecord {
public:
friend class Node<T>;
Node<T>* node;
int distance;
NodeRecord(Node<T>*&_node,int _distance)
:node(_node)
,distance(_distance){}
};
template<class T>
class NodeHeap
{
public:
NodeHeap(int size)
{
heap.resize(size,nullptr);
}
~NodeHeap()
{}
NodeRecord<T>*& pop()
{
assert(!empty());
NodeRecord<T>* nodeRecord = new NodeRecord<T>(heap[0], distanceMap[heap[0]]);
swap(0, heapSize - 1);
indexMap[heap[heapSize - 1]] = -1;
distanceMap.erase(heap[heapSize - 1]);
heap[heapSize - 1] = nullptr;
AdjustDwon(0,--heapSize);
return nodeRecord;
}
bool empty()
{
return heapSize==0;
}
//一个节点有没有进入过堆
bool isEntered(Node<T>*& node)
{
return indexMap.find(node) != indexMap.end();
}
//一个节点在不在堆上
bool inHeap(Node<T>*& node)
{
return isEntered(node) && indexMap[node] != -1;
}
//有一个节点node,从出发点到node的距离为distance
//判断需不需要更新
void addOrUpdateOrIgnore(Node<T>*& node, int distance)
{
//该节点存在
if (inHeap(node))
{
distanceMap[node] = min(distanceMap[node], distance);
//由于距离变小,因此向上调整距离
AdjustUp(node,indexMap[node]);
}
//该节点为新增节点
if (!isEntered(node))
{
heap[heapSize] = node;
indexMap[node] = heapSize;
distanceMap[node] = distance;
AdjustUp(node,heapSize++);
}
//该节点已经被忽略,其值为-1,什么都不做
}
private:
//需要封装交换函数,因为heap和indexMap的值都需要交换
void swap(int i, int j)
{
Node<T>* o1 = heap[i];
Node<T>* o2 = heap[j];
heap[i] = o2;
heap[j] = o1;
indexMap[o2] = i;
indexMap[o1] = j;
}
//向上调整算法,每push一个数都要调用向上调整算法,保证插入后是一个大堆
void AdjustUp(Node<T>* node,int index)
{
int parent = (index - 1) / 2;
while (index > 0)
{
if (distanceMap[heap[index]]<distanceMap[heap[parent]])
{
swap(parent, index);
index = parent;
parent = (index - 1) / 2;
}
else
{
break;
}
}
}
//向下调整算法,每次调用pop都要进行向下调整算法重新构成大堆
void AdjustDwon(int parent,int size)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && (distanceMap[heap[child+1]] < distanceMap[heap[child]]))
{
child++;
}
if (distanceMap[heap[child]] < distanceMap[heap[parent]])
{
swap(parent, child);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
vector<Node<T>*> heap;
//反向索引表
unordered_map<Node<T>*, int>indexMap;
//记录当前节点与出发点的距离
unordered_map<Node<T>*, int>distanceMap;
int heapSize = 0;
};
UnionFind.h
template<class V>
class FindNode
{
public:
FindNode(V v)
:value(v)
{
}
private:
V value;
};
//用链表实现的并查集
template<class V>
class UnionFindNodeSet
{
public:
UnionFindNodeSet(vector<V>values)
{
for (auto& value : values)
{
FindNode<V>* node = new FindNode<V>(value);
nodes.insert(make_pair(value, node));
parents.insert(make_pair(node, node));
sizeMap.insert(make_pair(node, 1));
}
}
//从cur一直往上找最顶上的父节点
//同时把路径上的点都与该父节点相连
//为了减少下次查找的路径
FindNode<V>* findFather(FindNode<V>* cur)
{
//记录路径查找的路径
stack<FindNode<V>*>path;
while (cur != parents[cur])
{
path.push(cur);
cur = parents[cur];
}
//找到头结点,将路径上的节点插到头结点下面
while (!path.empty())
{
FindNode<V>* ret = path.top();
path.pop();
parents.insert(make_pair(ret, cur));
}
return cur;
}
bool isSameSet(V a, V b)
{
if (!nodes.count(a) || !nodes.count(b))
{
return false;
}
return findFather(nodes[a])== findFather(nodes[b]);
}
void Union(V a, V b)
{
if (!nodes.count(a) || !nodes.count(b))
{
return;
}
FindNode<V>* aHead = findFather(nodes[a]);
FindNode<V>* bHead = findFather(nodes[b]);
if (aHead != bHead)
{
int aSetSize = sizeMap[aHead];
int bSetSize = sizeMap[bHead];
FindNode<V>* bigNode = aSetSize >= bSetSize ? aHead : bHead;
FindNode<V>* smallNode = bigNode == aHead ? bHead : aHead;
parents[smallNode] = bigNode;
sizeMap[bigNode] = aSetSize + bSetSize;
sizeMap.erase(smallNode);
}
}
//获取有几个集合,也就是根的数量
//在parents,其映射为自己
int GetSize()
{
int n = 0;
auto it = parents.begin();
while (it != parents.end())
{
if (it->first == it->second)
{
n++;
}
it++;
}
return n;
}
private:
//映射当前值和对应的节点
map<V, FindNode<V>*>nodes;
//映射node和其父节点
map<FindNode<V>*, FindNode<V>*>parents;
//记录当前节点对应集合中节点的个数
map<FindNode<V>*, int>sizeMap;
};
Graph.h
#include<vector>
#include<map>
#include<set>
#include<iostream>
#include<queue>
#include<stack>
#include<string>
#include<assert.h>
#include<unordered_map>
using namespace std;
template<class V>
class Edge;
template<class V>
class Node
{
public:
V value;
//入度和出度
int in;
int out;
//邻居
vector<Node<V>*> nexts;
//从当前节点出发的边
vector<Edge<V>*> edges;
Node(V _value)
{
value = _value;
in = 0;
out = 0;
}
};
template<class V>
class Edge
{
public:
//权重
int weight;
//边的起点和终点
Node<V>* from;
Node<V>* to;
Edge(int _weight, Node<V>*& _from, Node<V>*& _to)
{
weight = _weight;
from = _from;
to = _to;
}
};
template<class V>
class Graph
{
public:
Graph(){
}
void AddEdge(V from, V to, int weight)
{
//将起点和终点放入nodes
if (nodes.find(from) == nodes.end())
{
nodes.insert(make_pair(from, new Node<V>(from)));
}
if (nodes.find(to) == nodes.end())
{
nodes.insert(make_pair(to, new Node<V>(to)));
}
//找到这两个点对应的Node
Node<V>*& fromNode = nodes[from];
Node<V>*& toNode = nodes[to];
//建立它们的边,并且对应的入度和出度++
Edge<V>* newEdge = new Edge<V>(weight, fromNode, toNode);
fromNode->nexts.push_back(toNode);
fromNode->out++;
toNode->in++;
fromNode->edges.push_back(newEdge);
edges.insert(newEdge);
}
//广度优先遍历
void BFS(const V& src)
{
if (nodes.find(src) == nodes.end())
{
return;
}
Node<V>* start = nodes[src];
queue<Node<V>*>q;
set<Node<V>*>set;
q.push(start);
set.insert(start);
while (!q.empty())
{
Node<V>*& cur = q.front();
q.pop();
cout << cur->value << " ";
for (Node<V>*& next : cur->nexts)
{
if (set.find(next) == set.end())
{
set.insert(next);
q.push(next);
}
}
}
}
//深度优先遍历
void DFS(const V& src)
{
if (nodes.find(src) == nodes.end())
{
return;
}
Node<V>* start = nodes[src];
stack<Node<V>*>stack;
set<Node<V>*>set;
stack.push(start);
set.insert(start);
cout << start->value << " ";
while (!stack.empty())
{
Node<V>*& cur = stack.top();
stack.pop();
for (Node<V>*& next : cur->nexts)
{
if (set.find(next) == set.end())
{
stack.push(cur);
stack.push(next);
set.insert(next);
cout << next->value << " ";
break;
}
}
}
}
//拓扑排序,返回拓扑排序后的列表
vector<Node<V>*>sortedTopology()
{
//记录节点和剩余的入度
map<Node<V>*, int>inMap;
//剩余节点为0则放到队列q中
queue<Node<V>*>zeroInQueue;
for (auto& e : nodes)
{
//当入度为0时,则进入队列
inMap[e.second] = e.second->in;
if (e.second->in == 0)
{
zeroInQueue.push(e.second);
}
}
vector<Node<V>*> result;
while (!zeroInQueue.empty())
{
//将当前入度为0的节点放入result中,并且其邻居节点的入度--
//如果入度--后入度为0,则继续放入队列
Node<V>*& cur = zeroInQueue.front();
zeroInQueue.pop();
result.push_back(cur);
for (Node<V>*& next : cur->nexts)
{
//邻居节点的入度--
inMap[next]--;
if (inMap[next] == 0)
{
zeroInQueue.push(next);
}
}
}
return result;
}
~Graph()
{
for (auto& e : nodes)
{
delete e.second;
}
for (auto& e : edges)
{
delete e;
}
}
map<V, Node<V>*> nodes;
set<Edge<V>*> edges;
};
测试代码
#include"Graph.h"
#include"UnionFind.h"
#include"HeapGreater.h"
template<class V>
struct cmp
{
bool operator()(Edge<V>* a, Edge<V>* b)
{
return a->weight > b->weight;
}
};
template<class V>
set<Edge<V>*>kruskalMST(const Graph<V>* g)
{
//将所有的点放入并查集
vector<Node<V>*> v;
for (auto e : g->nodes)
{
v.push_back(e.second);
}
UnionFindNodeSet<Node<V>*> unionFind(v);
//升序的优先级队列,每次获取权值最小的边
priority_queue<Edge<V>*, vector<Edge<V>*>, cmp<V>>q;
for (Edge<V>* edge : g->edges)
{
q.push(edge);
}
set<Edge<V>*>result;
while (!q.empty())
{
Edge<V>* edge = q.top();
q.pop();
//权值最小的边不在同一集合则相连接
if (!unionFind.isSameSet(edge->from, edge->to))
{
result.insert(edge);
unionFind.Union(edge->from, edge->to);
}
}
return result;
}
template<class V>
set<Edge<V>*>primMST(const Graph<V>* g)
{
//解锁的边放入小根堆
priority_queue<Edge<V>*, vector<Edge<V>*>, cmp<V>>q;
//使用set记录被解锁出来的点
set<Node<V>*>nodeSet;
//将选出来的边放入result
set<Edge<V>*>result;
//遍历图中所有的点,防止出现森林的情况
for (auto iter = g->nodes.begin(); iter != g->nodes.end(); ++iter)
{
Node<V>* node = iter->second;
//node是开始的点
if (nodeSet.find(node) == nodeSet.end())
{
nodeSet.insert(node);
//解锁node所有相连的边
for (Edge<V>* edge : node->edges)
{
q.push(edge);
}
while (!q.empty())
{
//取出最小权值的边
Edge<V>* edge = q.top();
q.pop();
Node<V>*& toNode = edge->to;
//这个边已经被使用过,则什么都不做
if (nodeSet.find(toNode) == nodeSet.end())
{
nodeSet.insert(toNode);
result.insert(edge);
//解锁toNode连向的所有边
for (Edge<V>*& nextEdge : toNode->edges)
{
q.push(nextEdge);
}
}
}
}
break;//break可以不加,防止出现森林的情况
}
return result;
}
template<class V>
Node<V>* getMinDistanceAndUnseletedNode(map<Node<V>*, int>& distanceMap, set<Node<V>*>& selectedNodes)
{
Node<V>* minNode = nullptr;
int minDistance = INT_MAX;
for (auto e : distanceMap)
{
if (e.second < minDistance && (selectedNodes.find(e.first) == selectedNodes.end()))
{
minNode = e.first;
minDistance = e.second;
}
}
return minNode;
}
template<class V>
map<Node<V>*, int>dijkstra(Node<V>*& from)
{
map<Node<V>*, int>distanceMap;
//起始位置到自己的距离为0
distanceMap[from] = 0;
//selectedNodes记录已经锁定的节点,这些节点到起始位置的距离已经是最小值
set<Node<V>*>selectedNodes;
//这个函数的作用是从distanceMap返回一个距离最小值的点,并且这个点不在selectedNodes中
Node<V>* minNode = getMinDistanceAndUnseletedNode(distanceMap, selectedNodes);
while (minNode != nullptr)
{
//原始点->minNode(跳转点) 最小距离为distance
//一开始这个距离为0
int distance = distanceMap[minNode];
//找到minNode所有的邻居,然后更新到这些邻居的距离
for (Edge<V>*& edge : minNode->edges)
{
Node<V>*& toNode = edge->to;
//这个邻居还没有被distanceMap记录
if (distanceMap.find(toNode) == distanceMap.end())
{
distanceMap[toNode] = distance + edge->weight;
}
//这个邻居已经被记录,更新它最小的距离
else
{
distanceMap[toNode] = min(distanceMap[toNode], distance + edge->weight);
}
}
//锁定这个跳转点
selectedNodes.insert(minNode);
//继续找一个跳转点,重复刚才的操作
minNode = getMinDistanceAndUnseletedNode(distanceMap, selectedNodes);;
}
return distanceMap;
}
template<class V>
map<Node<V>*, int>dijkstra_better(Node<V>*& from,int size)
{
//创建加强堆并放入起始位置以及距离
NodeHeap<V> nodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(from, 0);
map<Node<V>*, int>result;
while (!nodeHeap.empty())
{
//从加强堆中获取堆顶的节点,也就是距离最小的节点
NodeRecord<V>*& record = nodeHeap.pop();
Node<V>* cur = record->node;
int distance = record->distance;
//record是new出来的对象,需要被释放
delete record;
//以该节点为跳转点,更新其他节点的距离,随后锁定这个节点
for (Edge<V>*& edge : cur->edges)
{
nodeHeap.addOrUpdateOrIgnore(edge->to, edge->weight + distance);
}
result[cur] = distance;
}
return result;
}
void test_dijkstra_better()
{
Graph<string> g;
g.AddEdge("a", "d", 2);
g.AddEdge("a", "c", 7);
g.AddEdge("a", "b", 1);
g.AddEdge("b", "c", 2);
g.AddEdge("b", "e", 6);
g.AddEdge("c", "d", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("d", "e", 10);
map<Node<string>*, int>m = dijkstra_better(g.nodes["a"],g.nodes.size());
for (auto e : m)
{
cout << e.first->value << ":" << e.second << endl;
}
}
void test_dijkstra()
{
Graph<string> g;
g.AddEdge("a", "d", 2);
g.AddEdge("a", "c", 7);
g.AddEdge("a", "b", 1);
g.AddEdge("b", "c", 2);
g.AddEdge("b", "e", 6);
g.AddEdge("c", "d", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("d", "e", 10);
map<Node<string>*, int>m = dijkstra(g.nodes["a"]);
for (auto e : m)
{
cout << e.first->value << ":" << e.second << endl;
}
}
void test_primMST()
{
//最小生成树为无向图
Graph<string> g;
g.AddEdge("a", "b", 3);
g.AddEdge("a", "c", 1);
g.AddEdge("b", "c", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("b", "e", 10);
g.AddEdge("e", "g", 1);
g.AddEdge("c", "f", 50);
g.AddEdge("f", "g", 3);
g.AddEdge("f", "h", 6);
g.AddEdge("h", "g", 9);
g.AddEdge("b", "a", 3);
g.AddEdge("c", "a", 1);
g.AddEdge("c", "b", 1);
g.AddEdge("e", "c", 2);
g.AddEdge("e", "b", 10);
g.AddEdge("g", "e", 1);
g.AddEdge("f", "c", 50);
g.AddEdge("g", "f", 3);
g.AddEdge("h", "f", 6);
g.AddEdge("g", "h", 9);
int sumWeight = 0;
set<Edge<string>*> set = primMST(&g);
for (auto e : set)
{
sumWeight += e->weight;
cout << e->from->value << " " << e->to->value << endl;
}
cout << "sumWeight=" << sumWeight << endl;
}
void test_kruskalMST()
{
//最小生成树为无向图
Graph<string> g;
g.AddEdge("a", "b", 3);
g.AddEdge("a", "c", 1);
g.AddEdge("b", "c", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("b", "e", 10);
g.AddEdge("e", "g", 1);
g.AddEdge("c", "f", 50);
g.AddEdge("f", "g", 3);
g.AddEdge("f", "h", 6);
g.AddEdge("h", "g", 9);
g.AddEdge("b", "a", 3);
g.AddEdge("c", "a", 1);
g.AddEdge("c", "b", 1);
g.AddEdge("e", "c", 2);
g.AddEdge("e", "b", 10);
g.AddEdge("g", "e", 1);
g.AddEdge("f", "c", 50);
g.AddEdge("g", "f", 3);
g.AddEdge("h", "f", 6);
g.AddEdge("g", "h", 9);
int sumWeight = 0;
set<Edge<string>*> set = kruskalMST(&g);
for (auto e : set)
{
sumWeight += e->weight;
cout << e->from->value << " " << e->to->value << endl;
}
cout << "sumWeight=" << sumWeight << endl;
}
void test_sortedTopology()
{
Graph<string> g;
g.AddEdge("a", "b", 3);
g.AddEdge("a", "c", 1);
g.AddEdge("b", "c", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("b", "e", 10);
g.AddEdge("e", "g", 1);
g.AddEdge("c", "f", 50);
g.AddEdge("f", "g", 3);
g.AddEdge("f", "h", 6);
g.AddEdge("h", "g", 9);
vector<Node<string>*>v = g.sortedTopology();
for (auto& e : v)
{
cout << e->value << " ";
}
}
void test_BFS_DFS()
{
Graph<string> g;
g.AddEdge("a", "b", 3);
g.AddEdge("a", "c", 1);
g.AddEdge("b", "c", 1);
g.AddEdge("c", "e", 2);
g.AddEdge("b", "e", 10);
g.AddEdge("e", "g", 1);
g.AddEdge("c", "f", 50);
g.AddEdge("f", "g", 3);
g.AddEdge("f", "h", 6);
g.AddEdge("h", "g", 9);
g.BFS("a");
cout << endl;
g.DFS("b");
cout << endl;
}
int main()
{
test_BFS_DFS();
cout << endl << endl;
test_sortedTopology();
cout << endl << endl;
test_kruskalMST();
cout << endl << endl;
test_primMST();
cout << endl << endl;
test_dijkstra();
cout << endl << endl;
test_dijkstra_better();
return 0;
}