学习笔记(8):C++实现图结构相关的算法

在听完左神 的算法课后,历时三天,用C++实现了图结构的相关算法,代码中尽可能包含C++11标准的新功能,尽可能使用STL模板库以达到熟悉C++精髓的目的。

代码分为三部分 :图的CODE,一个简单的并查集CODE,主调函数CODE示例;

/*****************Code(Graph.h)*******************/
#pragma once
//IO_lib
#include<iostream>
#include<stdio.h>
//Containers
#include<unordered_set>//哈希表
#include<unordered_map>
#include<list>//链表
#include<set>//有序表
#include<queue>//队列 /优先级队列(小根堆 大根堆)
#include<stack>//堆栈
//Other
#include<cstdint>//用INT16_MIN等宏
#include<memory> //未用到,可以用智能指针 就不需要在重写析构函数了
#include<algorithm>//用了sort
//一个幼稚的并查集 用于最小生成树K算法
#include"UnionFind_easy.h"

using namespace std;
class Graph {
public:
	class Edge;//declaration for Class Edge
	class Node {
	public:
		int value;
		int in = 0;
		int out = 0;
		list<Node*> nexts;//不需要新建 直接就是动态的  也可以理解为 初始化之后  就是一个空指针  按照指针增减数据
		list<Edge*> edges;
		Node(int v) :value(v) {}
	};
	class Edge
	{
	public:
		Node* from = nullptr;   //指针  指向的Node 归(unordered_map<int, Node*>*) nodes管理
		Node* to = nullptr;
		int weight = 0;
		Edge() {};
		Edge(Node* f, Node* t,int w):from(f),to(t),weight(w){}
	};
	//Node and Edge在编译阶段不能给出实例化  因此unordered_set/map需要用指针的形式
	unordered_map<int, Node*>* nodes;  
	unordered_set<Edge*>* edges;
	//Graph(unordered_map<int, Node*>& n, unordered_set<Edge*>& e) :nodes(n), edges(e) {}
	Graph() {
		nodes = new unordered_map<int, Node*>;
		edges = new unordered_set<Edge*>;
	}
	~Graph() {	
		for (auto p : *nodes) 
			delete p.second;
		for (auto p : *edges)
			delete p;
		
		delete nodes;
		delete edges;
	}
	//接口构造函数
	Graph(vector<vector<int> >& arr, bool turned=false);
	static inline void clc() { while (getchar() != '\n')continue; }
	void input_node(int value = INT16_MIN);
	void input_edge(int from = INT16_MIN, int to = INT16_MIN, int weight = INT16_MIN);
	void input_TurnedEdge(int from = INT16_MIN, int to = INT16_MIN, int weight = INT16_MIN);

	friend ostream& operator<<(ostream& os, Graph& g);
	friend ostream& operator<<(ostream& os, Edge* e);

	class CMP {
		bool up;//是否升序
	public:
		CMP(bool c=true) :up(c) {};
		bool operator()(Edge* p1, Edge* p2) {
			if(up) return p1->weight < p2->weight;
			else return p1->weight > p2->weight;
		}
	};
	void EdgeSort(vector<Edge*>& ordered_edges);
	/*algorithm for Graph*/
	void DepthFirst_Traveral(int begin);
	void BreadthFirst_Traveral(int begin);
	void Topu();

	class prior {//优先级队列 是大根堆  小于为true是正向断言  因此最小生成树 要用大于为ture
	public:
		bool operator()(Graph::Edge* p1, Graph::Edge* p2) {
			return p1->weight > p2->weight;
		}
	};
	void MiniScanTree_Prmi();
	void MiniScanTree_Kruskal();
	void MiniPath_Dijkstra(int begin);
};

/*****************Code(Graph.cpp)*******************/

//重载图的输出运算符
ostream& operator<<(ostream& os, Graph& g) {
	/*点集输出*/
	os << "Nodes:\n";
	for (auto p : (*g.nodes)) {
		os << "node( " << p.first << " )\tin: " << p.second->in << " \tout: " << p.second->out << "\n\tnexts:";
		for (auto q : p.second->nexts)
			os << '\t' << q->value;
		os << "\n\n";
	}
	/*边集输出*/
	os << "Edges:\n";
	int i = 1;
	for (auto p : (*g.edges))
		os << "edge( " << i++ << " )\tfrom   " << p->from->value << \
		" \tto " << p->to->value << "\tweight:\t" << p->weight << '\n';
	return os;
}
//边按权重排序(用Edge*向量容器装排序结果) 用于最小生成树K算法
void Graph::EdgeSort(vector<Edge*>& ordered_edges) {
	ordered_edges = vector<Edge*>(edges->begin(), edges->end());
	sort(ordered_edges.begin(), ordered_edges.end(), CMP(true));
	/*结果可视化*/
	//int i = 1;
	//puts(" ");
	//for (auto p : ordered_edges)
	//	cout << "ordered_edge( " << i++ << " )\tfrom   " << p->from->value << \
	//	" \tto " << p->to->value << "\tweight:\t" << p->weight << '\n';
	/*结果可视化*/
}
//图 节点输入  参数缺省时 键入
void Graph::input_node(int value) {
	if (value == INT16_MIN) {
		puts("input node_value:		[Quit(-1)]");
		while ((!scanf("%d", &value) || value < 0) && value != -1) {
			clc();
			puts("bad input,try again:");
		}
		clc();
	}
	if (nodes->find(value) != nodes->end()){
		//printf("Node: %d is existed!\n", value);
	}
	else {
		Node* pn = new Node(value);
		nodes->emplace(value, pn);
		//printf("Node: %d is added!\n", value);
	}
}
//图 有向边输入  参数缺省时 键入
void Graph::input_TurnedEdge(int from, int to, int weight) {
	if (weight == INT16_MIN) {
		{//from
			puts("input from node value:	[Quit(-1)]");
			while (!scanf("%d", &from) || from < -1) {
				clc();
				if (from == -1)return;
				puts("bad input,try again:");
			}
			clc();
		}
		{//to
			puts("input to node value:	[Quit(-1)]");
			while (!scanf("%d", &to) || to < -1) {
				clc();
				if (to == -1)return;
				puts("bad input,try again:");
			}
			clc();
		}
		{//weight
			puts("input weight:	[Quit(-1)]");
			while (!scanf("%d", &weight)) {
				clc();
				puts("bad input,try again:");
			}
			clc();
		}
	}
	if (nodes->find(from) == nodes->end())
		input_node(from);
	if (nodes->find(to) == nodes->end())
		input_node(to);
	Node* f = nodes->at(from);
	Node* t = nodes->at(to);
	Edge* pe = new Edge(f, t, weight);
	//因为hash map (edges)中存放的是指针,且指针在函数结束后销毁,
	//因此不能用指向相同类的指针去查询是否存在指向该类的指针,因为指向相同
	//类的两个指针不同,因此遍历指针,对比指向的数据
	bool exist = false;
	for (auto& p :(*edges)) {
		if (p->weight == weight && p->from == f && p->to == t) {
			exist = true;
			break;
		}
	}
	if (!exist) {// 如果pe存在 则不会插入
		edges->emplace(pe);
		f->edges.push_back(pe);
		f->nexts.push_back(t);
		f->out++;
		t->in++;
		//printf("Edge: from %d to %d is added! ( weight=%d )\n", from, to, weight);
	}
	//else printf("Edge: from %d to %d is existed! ( weight=%d )\n", from, to, weight);
}
//图 无向边输入  参数缺省时 键入
void Graph::input_edge(int from, int to, int weight) {
	if (weight == INT16_MIN) {
		{//from
			puts("input from node value:	[Quit(-1)]");
			while (!scanf("%d", &from) || from < -1) {
				clc();
				if (from == -1)return;
				puts("bad input,try again:");
			}
			clc();
		}
		{//to
			puts("input to node value:	[Quit(-1)]");
			while (!scanf("%d", &to) || to < -1) {
				clc();
				if (to == -1)return;
				puts("bad input,try again:");
			}
			clc();
		}
		{//weight
			puts("input weight:	[Quit(-1)]");
			while (!scanf("%d", &weight)) {
				clc();
				puts("bad input,try again:");
			}
			clc();
		}
	}
	if (nodes->find(from) == nodes->end())
		input_node(from);
	if (nodes->find(to) == nodes->end())
		input_node(to);
	Node* f = nodes->at(from);
	Node* t = nodes->at(to);
	Edge* pe = new Edge(f, t, weight);
	bool exist = false;
	for (auto& p:(*edges) ) {
		if (p->weight == weight && (p->from == f && p->to == t ||
			p->from == t && p->to == f)) {
			exist = true;
			break;
		}
	}
	if (!exist) {// 如果pe存在 则不会插入
		edges->emplace(pe);

		f->edges.push_back(pe);
		f->nexts.push_back(t);
		f->out++;
		f->in++;

		t->edges.push_back(pe);
		t->nexts.push_back(f);
		t->out++;
		t->in++;
		//printf("Edge: from %d to %d is added! ( weight=%d )\n", from, to, weight);
	}
	//else printf("Edge: from %d to %d is existed! ( weight=%d )\n", from, to, weight);
}
//图 二维数组/链表 to 点集/边集 的接口构造函数 
Graph::Graph(vector<vector<int> >& arr, bool turned) :Graph() {
	if (turned)
		for (auto p : arr)
			input_TurnedEdge(p[1], p[2], p[0]);
	else
		for (auto p : arr)
			input_edge(p[1], p[2], p[0]);
}
//图 广度优先遍历
void Graph::BreadthFirst_Traveral(int begin) {
	puts("\nBreadthFirst_Traveral:");
	auto f = nodes->at(begin);
	queue<Node*> q;
	unordered_set<Node*> s;
	q.push(f);//出发节点进队列
	s.insert(f);//出发节点进hashset 防止重复处理
	while (!q.empty()) {
		Node* n = q.front();
		q.pop();
		printf("node(%d)\t", n->value);
		for (auto next : n->nexts)
			if (s.find(next) == s.end()) {
				q.push(next);
				s.insert(next);
			}
	}
	puts(" ");
}
//图 深度优先遍历
void Graph::DepthFirst_Traveral(int begin) {
	puts("\nDepthFirst_Traveral:");
	auto f = nodes->at(begin);//检查存在
	stack<Node*> q;
	unordered_set<Node*> s;
	q.push(f);//出发节点进堆栈
	s.insert(f);//出发节点进hashset 防止重复处理
	printf("node(%d)\t", f->value);//第一次进栈时候打印
	while (!q.empty()) {
		Node* n = q.top();
		q.pop();
		for (auto next : n->nexts)
			if (s.find(next) == s.end()) {
				q.push(n);//如果当前节点有next 则把当前节点放回
				q.push(next);
				s.insert(next);
				printf("node(%d)\t", next->value);
				break;
			}
	}
	puts(" ");
}
//图 拓扑排序
void Graph::Topu() {
	puts("\nTopu sort:");
	Node* begin = nullptr;
	unordered_map<Node*, int> inmap;
	queue<Node*> zeroin;
	for (auto& p :(*nodes)) {
		inmap.emplace(p.second, p.second->in);//保存每个节点的入度
		if (p.second->in == 0)
			zeroin.push(p.second);
	}
	while (!zeroin.empty()) {
		Node* cur = zeroin.front();
		zeroin.pop();
		printf("node(%d)\t", cur->value);
		for (auto next : cur->nexts) {
			inmap[next]= inmap[next] - 1;
			if (!inmap[next])
				zeroin.push(next);
		}
	}
	puts("");
}
//重载<< 打印Edge*指向的边  (用来查看最小生成树的结果)
ostream& operator<<(ostream& os, Graph::Edge* e) {
	os << "edge from\t" << e->from->value << "\tto\t" << e->to->value << endl;
	return os;
}
//最小生成树 Prim
void Graph::MiniScanTree_Prmi() {
	puts("\nMiniScanTree_Prmi:");
	Node* B = (*nodes->begin()).second;
	unordered_set<Node*> s;
	priority_queue<Edge*, vector<Edge*>, prior> e;
	s.insert(B);
	for (auto p : B->edges)
		e.push(p);
	while (!e.empty()) {
		Edge* minE = e.top();//当前节点的最小权重边
		e.pop();
		if (s.find(minE->from) == s.end() || s.find(minE->to) == s.end()) {//查看当前边是否冗余 from to 至少有一个是第一次到达的节点
			Node* next = s.find(minE->from) == s.end() ? minE->from : minE->to;
			s.insert(next);
			cout << minE;
			for (auto p : next->edges)//解锁minE->to的边(不包含已经使用过的minE) 因为同一个节点只能用一次  									
				if (p != minE)				//因此 同一条边只能被添加两次 pop掉第一次 跳过第二次 就相当于永久删除了这个边
					e.push(p);
		}
	}
}
//最小生成树 Kruskal 
void Graph::MiniScanTree_Kruskal() {
	puts("\nMiniScanTree_Kruskal:");
	UnionFind_easy<Node*> U(nodes);//创建了并查集
	vector<Edge*> E;
	EdgeSort(E);//获取边的权重升序排列
	for (Edge* e : E) {//考察边
		Node* from = e->from;
		Node* to = e->to;
		if (!U.IsSameSet(from, to)) {
			cout << e;
			U.Union(from, to);
		}
	}
}
//最短路径 参数:起始node
void Graph::MiniPath_Dijkstra(int begin) {//any edge not in a nagetive value loop
	puts("\nMiniPath_Dijkstra:");
	if (nodes->find(begin) == nodes->end())return;
	Node* B = nodes->operator[](begin); //重载运算符是可以显示调用的
	unordered_map<Node*, int> distance;
	distance[B]= 0;
	for (auto& x :(*nodes))
		distance.emplace(x.second, INT16_MAX);// 这个地方需要用emplace方法,如果用[ ]会覆盖已经存在的pair,[]用来查询和修改更好
	//至此  除了出发节点之外的节点 distance都是INT16_MAX
	unordered_set<Node*> lock;
	queue<Node*> q;
	q.push(B);
	while (!q.empty()) {
		Node* cur = q.front();
		q.pop();
		if (lock.find(cur) == lock.end()) {//弹出的点没处理过
			for (auto next : cur->nexts)//把这个点后面的点 放进队列(可能重复  没关系)
				if (lock.find(next) == lock.end())
					q.push(next);
			int myD = distance[cur];
			for (auto edge : cur->edges) {
				Node* to = edge->from == cur ? edge->to : edge->from;//无向边 选出不是自己的那一边
				if (lock.find(to) == lock.end()) {//如果没上锁  (边是可能指向处理过的点的  有了这句  避免做无用功)
					int yourD = distance[to];
					if (myD + edge->weight < yourD) //找到去出发节点更近的路 就更新距离
						distance[to] = myD + edge->weight;			
				}
			}
		}
		lock.insert(cur);
	}
	for (auto& x :distance)
		cout << "node(" << x.first->value << ")\tdistance:\t" << x.second << endl;
}
/****UnionFind_Easy***/
#pragma once
#include<unordered_set>
#include<unordered_map>

using namespace std;
template <class T> //
class UnionFind_easy
{
	unordered_map<T, unordered_set<T>*>* setmap;//每个节点Node*   对应其所在的  Node* 集合 

public:
	UnionFind_easy(){
		setmap = new unordered_map<T, unordered_set<T>*>;
	}
	UnionFind_easy(unordered_map<int,T>* m):UnionFind_easy<T>(){
		for (auto& x :(*m)) {
			unordered_set<T>* s = new unordered_set<T>;
			s->insert(x.second);
			setmap->emplace(x.second, s);
		}
	}
	~UnionFind_easy() {
		unordered_set<unordered_set<T>*> d;//防止重复删除(并查集  setmap的map值中 会有重复的集合)
		for (auto& x : (*setmap)) {
			if (d.find(x.second) == d.end()) {
				d.emplace(x.second);
				delete x.second;
			}
		}		
		delete setmap;	
	}
	bool IsSameSet(T n1, T n2) {
		return setmap->operator[](n1) == setmap->operator[](n2);
	}
	void Union(T n1, T n2) {
		auto m = *setmap;
		unordered_set<T>* s1 =m[n1];	//假设 S1 {n1 n5}  n1->S1  n5->S1
		unordered_set<T>* s2 = m[n2];  // S2 {n2 n4}	n2->S2  n4->S2
		//for (auto x = s2->begin(); x != s2->end(); x++)		
		//	s1->insert(*x);									//S1{n1 n2 n4 n5}		n1->S1  n5->S1					
		//setmap->erase(n2);									//n1->S1  n5->S1	n2->S1    but n5?
		/*修改*/
		for (auto& x :(*s2)) {//把 s2中的每个node* 都复制到s1 并在map中将其指向s1
			s1->insert(x);  
			setmap->operator[](x)= s1;
		}
		delete s2;
	}
};
/*********MAIN*********/
#include "map.h"
int main() {	
	//接口函数测试数据 weight  from  to  /最小生成树测试 no turned , reslut:1-3 2-3 2-5 3-6 4-6
	vector<vector<int> > data(10, vector<int>(3));
	data[0] = { 6,1,2 };
	data[1] = { 1,1,3 };
	data[2] = { 5,1,4 };
	data[3] = { 5,2,3 };
	data[4] = { 3,2,5 };
	data[5] = { 5,3,4 };
	data[6] = { 6,3,5 };
	data[7] = { 4,3,6 };
	data[8] = { 2,4,6 };
	data[9] = { 6,5,6 };

	//Topu数据 weight  from  to   , turned   result:4 2 3 1 5
	vector<vector<int> > topu(7, vector<int>(3));
	topu[0] = { 0,4,2 };
	topu[1] = { 0,1,5 };
	topu[2] = { 0,4,3 };
	topu[3] = { 0,2,3 };
	topu[4] = { 0,2,1 };
	topu[5] = { 0,2,5 };
	topu[6] = { 0,3,1 };

	//最短路径测试数据 no turned  result(begin=1):1 - 5{0 3 5 9 19}
	vector<vector<int> > path(7, vector<int>(3));
	path[0] = { 3,1,2 };
	path[1] = { 15,1,3 };
	path[2] = { 9,1,4 };
	path[3] = { 2,2,3 };
	path[4] = { 200,2,5 };
	path[5] = { 7,4,3 };
	path[6] = { 16,4,5 };
	path[6] = { 14,3,5 };

	//深度优先遍历  result(begin=1):1 2 5 7 6 3 4  ; 宽度优先遍历 result(begin=1): 1 2 3 4 5 6 7
	//turned	深度优先遍历  result(begin=1):1 2 5 7 3 6 4 
	vector<vector<int> > scan(8, vector<int>(3));
	scan[0] = { 0,1,2 };
	scan[1] = { 0,1,3 };
	scan[2] = { 0,1,4 };
	scan[3] = { 0,4,3 };
	scan[4] = { 0,2,5 };
	scan[5] = { 0,3,6 };
	scan[6] = { 0,5,7 };
	scan[7] = { 0,6,7 };

	Graph d(data, false);
	Graph p(path,false);
	Graph t(topu, true);
	Graph s(scan, false);
	d.MiniScanTree_Prmi();
	d.MiniScanTree_Kruskal();
	p.MiniPath_Dijkstra(1);
	t.Topu();
	s.DepthFirst_Traveral(1);
	s.BreadthFirst_Traveral(1);
	}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wql_njust

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

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

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

打赏作者

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

抵扣说明:

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

余额充值