C++图的点+边存储结构以及最小生成树、拓扑排序、最短路径算法实现


一、图的点+边存储结构

相对于邻接矩阵和邻接表的结构,图的点+边的存储结构能够存储更多信息,因此更容易获取每个点的入度和出度以及对应的边,方便实现最小生成树、最短路径以及拓扑排序等算法。

另外,邻接表和邻接矩阵等存储方式与点+边的存储方式可以相互转化,实现算法接口的复用,所以当传入的参数是邻接表或邻接矩阵时,只需要将它们转化为点+边的存储结构,就可以调用本章的所有算法接口了。

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. 结点1的入度为0,因此节点1为最开始的点

  2. 删掉1对应的两条边,此时节点2的入度为0,删掉2,重复操作
    在这里插入图片描述

  3. 最终得到拓扑排序为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条边。因此构造最小生成树的准则有三条:

  1. 只能使用图中的边来构造最小生成树,这几条边加起来的权值是最小的。
  2. 只能使用恰好n-1条边来连接图中的n个顶点。
  3. 选用的n-1条边不能构成回路。
    构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。
    贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。

3.1. Kruskal算法

任给一个有n个顶点的连通网络N={V,E},首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量,其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止。
核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。

总结起来就是每次都从剩下边里面选权值最小的边加入生成树,看是否构成环,不构成环就加入,构成环就不加入,直到选出N-1条边。
在这里插入图片描述

从实现角度来说,难点是如何判断加入的新边是否构成环,比较高效的方法是用并查集解决:

  1. 先判断加入边的两个顶点在不在一个集合,如果在一个集合,加入就会构成环。
  2. 加入的边,就把它的两个顶点合并。

并查集的代码参考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;
}


  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值