左神课程笔记——第六节课:图


前言

图的难点在于图的表示方法很多,要在每种表示方法上实现各种算法。解决办法:将所有图转换成自己最熟悉的表示方法,然后在该方法上是实现图的各种算法。
题目给的图—>熟悉的图—>各种算法(提前实现)。


1.图的存储方式

(1)邻接表法:
在这里插入图片描述
(2)邻接矩阵法:

在这里插入图片描述
(3)图的数据结构

class Node;//两个类相互包含,需要提前声明一下

class Edge {
public:
	int weight;
	Node* from;//有向图
	Node* to;
	Edge(int weight, Node* from, Node* to) {
		this->weight = weight;
		this->from = from;
		this->to = to;
	}
};

class Node {
public:
	int val;
	int in;//入度
	int out;//出度
	vector<Node*> nexts;
	vector<Edge*> edges;//又当前点发散出去的边

	Node(int val) {
		this->val = val;
		in = 0;
		out = 0;
	}
};

class Graph {
public:
	unordered_map<int, Node*>nodes;//key:点的编号;value:实际的点
	unordered_set<Edge*>edges;
};

2.图的遍历

1.引入库

图可能优化,需要一个机制使得不会变成死循环
(1)图的宽度优先遍历

在这里插入图片描述
对于3,用一个set来记录已经进入过队列的点,有时候也可以用数组来替换set(常熟时间更快)

在这里插入图片描述
代码实现:

//宽度优先遍历
void bfs(Node* n) {
	if (n == nullptr) {
		return;
	}
	queue<Node*>q;
	unordered_set<Node*>st;
	q.push(n);
	st.insert(n);

	while (!q.empty()) {
		Node* cur = q.front();
		q.pop();
		cout << cur->val << endl;
		for (Node* next : cur->nexts) {
			if (st.find(next) == st.end()) {
				q.push(next);
				st.insert(next);
			}
		}
	}
}

(2)图的深度优先遍历
在这里插入图片描述
bfs时是在出队列时打印(处理),dfs是在入栈时打印(处理)
在这里插入图片描述
代码实现:

//深度优先遍历
void dfs(Node* n) {
	if (n == nullptr) {
		return;
	}
	stack<Node*>sk;
	unordered_set<Node*>st;
	sk.push(n);
	st.insert(n);
	cout << n->val << endl;

	while (!sk.empty()) {
		Node* cur = sk.top();
		sk.pop();
		for (Node* next : cur->nexts) {
			if (st.find(next) == st.end()) {
				sk.push(cur);
				sk.push(next);
				st.insert(next);
				cout << next->val << endl;
				break;
			}
		}
	}
}

3.拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环
先找入度为0的点A,将A和A的影响擦除,再找剩下入度为0的点……
在这里插入图片描述
代码实现:

//拓扑排序
vector<Node*> sortedTopology(Graph gra) {
	unordered_map<Node*, int>inMap;//记录点的剩余入度
	queue<Node*>zeroIn;//入度为0的点
	for (pair<int,Node*> n : gra.nodes) {
		inMap.insert({ n.second,n.second->in });
		if (n.second->in == 0) {
			zeroIn.push(n.second);
		}
	}

	vector<Node*>res;
	while (!zeroIn.empty()) {
		Node* cur = zeroIn.front();
		zeroIn.pop();
		res.push_back(cur);
		for (Node* next : cur->nexts) {
			inMap[next]--;
			if (inMap[next] == 0) {
				zeroIn.push(next);
			}
		}
	}

	return res;
}

4.生成最小生成树算法

生成最小生成树:保持联通,整体边的权值最小

在这里插入图片描述
(1)kruskal算法
适用范围:要求无向图
从边的角度来思考,每次寻找权值最小的边,看加上该边是否形成环,没有形成环就可以加到生成树中,否则不能加入
在这里插入图片描述
怎么判断是否形成环?
首先,假设所有的点独自在一个集合中,考虑新加入边是否形成环则看这条边的两个顶点是否在一个集合中,如果不在一个集合中则可以加入该边,之后将边的两个顶点所在的集合合并。集合的查询和合并—并查集
在这里插入图片描述
代码实现:

//(1)kruskal算法——并查集实现
class Cmp {
public:
	bool operator()(Edge* e1,Edge* e2){
		return e1->weight > e2->weight;
	}

};

int findFather(vector<int>& fathers, int i) {
	if (fathers[i] != i) {
		fathers[i] = findFather(fathers, fathers[i]);
	}
	return fathers[i];
}

void unionFather(vector<int>fathers, int i, int j) {
	int fatherOfI = fathers[i];
	int fatherOfJ = fathers[j];
	if (fatherOfI != fatherOfJ) {
		fathers[fatherOfI] = fatherOfJ;
	}
}

unordered_set<Edge*> kruskalMST(Graph gra) {
	vector<int>fathers(gra.nodes.size());//fathers[i]:i编号节点的父节点的编号
	for (pair<int, Node*> n : gra.nodes) {
		fathers[n.first] = n.first;
	}
	priority_queue<Edge*, vector<Edge*>, Cmp>p;
	for (Edge* e : gra.edges) {
		p.push(e);
	}

	unordered_set<Edge*>res;
	while (!p.empty()) {
		Edge* e = p.top();
		p.pop();
		if (findFather(fathers, e->from->val) != findFather(fathers, e->to->val)) {
			res.insert(e);
			unionFather(fathers, e->from->val, e->to->val);
		}
	}

	return res;
}

(2)prim算法
适用范围:要求无向图
从点的角度来思考,从任意一个点A开始,A的边被解锁,选择其中权值最小的边(已经被选择,后续不在考虑),来到该边的另一个点B,B的边被解锁,从所有已解锁的边中选择权值最小的边(该边的两个点都没有被加进来),来到该边的另一个点F,……

在这里插入图片描述
代码实现:

//(2)prim算法
//可能边重复进队列,当不影响结果
unordered_set<Edge*> primMST(Graph gra) {
	priority_queue<Edge*, vector<Edge*>, Cmp>p;//存放已解锁的边
	unordered_set<Node*>st;//存放已拉入进来的点
	unordered_set<Edge*>res;
	for (pair<int, Node*> n : gra.nodes) {处理森林的问题
		if (st.find(n.second) != st.end()) {
			st.insert(n.second);
			for (Edge* e : n.second->edges) {
				p.push(e);
			}
			while (!p.empty()) {
				Edge* e = p.top();
				p.pop();
				if (st.find(e->to) != st.end()) {
					st.insert(e->to);
					res.insert(e);
					for (Edge* nextE : e->to->edges) {
						p.push(nextE);
					}
				}
			}
		}
	}
	return res;
}

5.单元最短路径算法—Dijkstra算法

适用范围:不存在累加权值为负的环,如果有这样的环,每次增益为负,不停转圈圈,到任何节点的距离都能任意小
规定一个出发点,从该出发点到其他点的最短距离

在这里插入图片描述
初始化一个节点到其他节点距离表,该表到自己的距离为0,到其他节点的距离都为正无穷,每一次在表中选择距离最小的点A,考察从A出发的边能否使得起始点到其他点的距离变小,如果存在这样的边,则更新距离表,使用完A之后,A到起始点的距离确定,不再修改;再选择次小的点……

在这里插入图片描述
代码实现:

//dijkstral
Node* getMinNode(unordered_map<Node*, int>distanceMap, unordered_set<Node*>selectedNodes) {
	Node* minNode = nullptr;
	for (pair<Node*, int>n : distanceMap) {
		if (selectedNodes.find(n.first) == selectedNodes.end()) {
			if (minNode != nullptr) {
				minNode = n.second < distanceMap[minNode] ? n.first : minNode;
			}
			else {
				minNode = n.first;
			}
		}
	}
	return minNode;
}
unordered_map<Node*, int>dijkstral(Node* head) {
	unordered_map<Node*, int>distanceMap;//其他节点到head的最小距离
	unordered_set<Node*>selectedNodes;//已经锁死的节点
	distanceMap.insert({ head, 0 });
	Node* minNode = getMinNode(distanceMap, selectedNodes);
	while (minNode != nullptr) {
		int minDistance = distanceMap[minNode];
		for (Edge* e : minNode->edges) {
			if (selectedNodes.find(e->to) == selectedNodes.end() && (distanceMap.find(e->to)==distanceMap.end()||distanceMap[e->to] > minDistance + e->weight)) {
				distanceMap[e->to] = minDistance + e->weight;
			}
		}
		selectedNodes.insert(minNode);
		minNode = getMinNode(distanceMap, selectedNodes);
	}

	return distanceMap;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值