图的基本算法

本文详细介绍了图的概念,包括有向图、邻接表和邻接矩阵的表示方法。重点讲解了图的遍历,如宽度优先遍历(BFS)和深度优先遍历(DFS)的实现,以及拓扑排序在有向无环图(DAG)中的应用。此外,还涵盖了最小生成树的Kruskal和Prim算法以及迪杰斯特拉算法求解单源最短路径问题。

图就是边和点的集合
任何图都可以看做是有向图
图的两种表示方法:邻接表和邻接矩阵
最常用的表示方法还有用二维数组表示:每一行第一个表示边的权重,第二个表示from结点的值,第三个表示to结点的值

点的结构

class Node {
public:

	int value;//结点的值
	int in;//入度
	int out;//出度
	list<Node*>nexts;//邻居结点(只包括由本身指向的结点)
	list<Edge*>edges;//从本身指出的边
	Node(int value) :value(value) {
		in = 0;
		out = 0;
	}
};

边的结构

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;
	}

};

完整代码如下

#include<iostream>
#include<list>
#include<algorithm>
#include<unordered_map>
#include<unordered_set>
using namespace std;
class Edge;
class Node {
public:
	int value;
	int in;//入度
	int out;//出度
	list<Node*>nexts;//邻居结点(只包括由本身指向的结点)
	list<Edge*>edges;//从本身指出的边
	Node(int value) :value(value) {
		in = 0;
		out = 0;
	}
};
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 Graph {
public:
	unordered_map<int, Node*>nodes;//点的集合(通过key找到特定的结点,key就是结点上的值)
	unordered_set<Edge*>edges;//边的集合

};

void creatGraph(vector<vector<int>>graph,Graph& gra) {//由二维数组构造图
	for (int i = 0; i < graph.size(); i++) {
		int weight = graph[i][0];
		if (!gra.nodes.count(graph[i][1])) {//没有from结点
				Node* from = new Node(graph[i][1]);
				gra.nodes.emplace(graph[i][1], from);
		}
		if (!gra.nodes.count(graph[i][2])) {//没有to结点
			Node* to = new Node(graph[i][2]);
			gra.nodes.emplace(graph[i][2], to);
		}
		Node* from = gra.nodes[graph[i][1]];
		Node* to = gra.nodes[graph[i][2]];
		Edge* edge = new Edge(graph[i][0], from, to);
		from->out++;
		from->nexts.push_back(to);
		from->edges.push_back(edge);
		to->in++;
		gra.edges.emplace(edge);
	}
}

int main()
{
	Graph gra;
	vector<vector<int>>graph = { {1,1,3},{2,3,2},{3,2,1} };//N*3的二维数组,每一行[边的权重,from结点到值,to结点的值],所以N就代表N条边
	creatGraph(graph, gra);
	
	
	
	return 0;
}

遍历方式:

  1. 宽度优先遍历
    数据结构:队列和set
    用set记录访问过的结点,避免同一个结点多次访问
void bfs(Node* start) {
	if (start == nullptr) {
		return;
	}
	queue<Node*>q;
	unordered_set<Node*>set;
	q.push(start);
	set.emplace(start);
	while (!q.empty()) {
		Node* cur = q.front();//当前队首元素
		cout << cur->value << endl;
		q.pop();
		for (Node* next : cur->nexts) {
			if (set.find(next) == set.end()) {//如果next结点之前没有遍历过,就加入队列
				q.push(next);
				set.emplace(next);
			}
		}
	}
}
  1. 深度优先遍历

递归实现

void dfs(Node* start,unordered_set<Node*>& set) {
	cout << start->value << endl;
	set.emplace(start);
	for (Node* next : start->nexts) {
		if (set.find(next) == set.end()) {//找到第一个没有访问过的邻居结点
			dfs(next, set);
		}
	}
}

迭代

在任何时刻,栈里放的是当前走过的结点(从底向上)

unordered_set<Node*>set;
	stack<Node*>st;
	st.push(gra.nodes[1]);
	cout << gra.nodes[1]->value << endl;
	set.emplace(gra.nodes[1]);
	while (!st.empty()) {
		Node* cur = st.top();
		st.pop();
		for (Node* next : cur->nexts) {
			if (set.find(next) == set.end()) {
				cout << next->value << endl;
				st.push(cur);//因为回退还会回到cur结点
				st.push(next);
				set.emplace(next);
				break;//找到一个邻居结点就重新找下一个结点
			}
		}
	}

拓扑序:有向无环图
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序。
应用:拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。例如,在日常工作中,可能会将项目拆分成A、B、C、D四个子部分来完成,但A依赖于B和D,C依赖于D。为了计算这个项目进行的顺序,可对这个关系集进行拓扑排序,得出一个线性的序列,则排在前面的任务就是需要先完成的任务。
注意:这里得到的排序并不是唯一的!就好像你早上穿衣服可以先穿上衣也可以先穿裤子,只要里面的衣服在外面的衣服之前穿就行。

实现一:利用入度,每次选择入度为0的点,然后消除该结点的影响,重复该操作,直到没有入度为0的结点。

list<Node*> DAG(Graph graph) {
	list<Node*>result;
	queue<Node*>q;//只要入度为0的点才会放入队列,因此队列的结点都是不依赖其他结点的
	unordered_map<Node*, int>inMap;//key为某个结点,value为结点的入度(加快查询速度)
	for (pair<const int,Node*> next : graph.nodes) {
		inMap.emplace(next.second, next.second->in);
		if (next.second->in == 0) {//初始将入度为0的点加入队列,如果没有入度为0的点,就没有拓扑排序
			q.push(next.second);
		}
	}

	while (!q.empty()) {
		Node* cur = q.front();
		result.push_back(cur);
		q.pop();
		for (Node* next : cur->nexts) {
			inMap[next]--;//邻居结点入度减一;
			if (inMap[next] == 0) {//入度为0的点加入队列
				q.push(next);
			}
		}
	}
	return result;
}

实现二:利用点次,点次大的结点在前

127 · 拓扑排序
先解释一下本题的图表示
{1,2,4#2,1,4#3,5#4,1,2#5,3}
用#来分割各个结点,这是内部已经写好了,不需要管怎么分割的
上例可以看出一共有5个结点,1,2,3,4,5
1结点的直接邻居有2,4
2结点的直接邻居有1,4
……
它这种其实就是邻接表法表示

分析:如果X结点能走过的结点个数>Y结点能走过的结点个数,那么拓扑排序X一定在Y前面
因此:先计算所有结点的点次,然后将结点按点次从大到小排,这个顺序就是拓扑序

priority_queue的队头元素:top()

/**
 * Definition for Directed graph.
 * struct DirectedGraphNode {
 *     int label;
 *     vector<DirectedGraphNode *> neighbors;
 *     DirectedGraphNode(int x) : label(x) {};
 * };
 */
class Record{//点次
public:
    DirectedGraphNode* node;
    long nodes;//node结点能走过几个结点
    Record(DirectedGraphNode*node,long nodes){
        this->node=node;
        this->nodes=nodes;
    }
};

class Solution {
public:
    /**
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */
    //当前来到cur点,返回cur可以走过的所有的点次
    //缓存:order,key:cur的点次算过了,value:该结点的点次
    Record* f(DirectedGraphNode* cur,unordered_map<DirectedGraphNode*,Record*>&order){
        if(order.count(cur)){//cur结点的点次算过了就直接返回
            return order[cur];
        }
        //计算cur的点次
        long nodes=0;
        for(DirectedGraphNode* next:cur->neighbors){
            nodes+=f(next,order)->nodes;
        }
        Record* ans=new Record(cur,nodes+1);//再加上本身就是该结点对应的点次
        order.emplace(cur,ans);//在缓存上填写cur的点次
        return ans;
    }
    static bool cmp(Record* &r1,Record* &r2){
        return r1->nodes > r2->nodes;//按点次从高到低排
    }
    vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*> graph) {
        // write your code here
        unordered_map<DirectedGraphNode*,Record*>order;
        for(DirectedGraphNode* cur:graph){
            f(cur,order);//计算每一个结点的点次,order里存的就是所有点的点次
        }
       
        vector<DirectedGraphNode*>result;//存放结果
        vector<Record*>recordAll;//所有结点的点次
        for(pair<const DirectedGraphNode*,Record*> cur:order){//拿出所有点的点次
            recordAll.push_back(cur.second);
        }
        sort(recordAll.begin(),recordAll.end(),cmp);//将点次按从大到小排序,即为拓扑序
        for(Record* r:recordAll){
            result.push_back(r->node);
        }

        return result;
    }
};

实现三:深度法,深度大的结点排前面

分析:和点次法步骤一致,也是先计算所有结点的最大深度,深度大的结点排前面。

/**
 * Definition for Directed graph.
 * struct DirectedGraphNode {
 *     int label;
 *     vector<DirectedGraphNode *> neighbors;
 *     DirectedGraphNode(int x) : label(x) {};
 * };
 */
class MaxDepth{
public:
    DirectedGraphNode* node;
    long maxDepth;
    MaxDepth(DirectedGraphNode* node,long maxDepth){
        this->node=node;
        this->maxDepth=maxDepth;
    }
};

class Solution {
public:
    /**
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */
    static bool cmp(MaxDepth* md1,MaxDepth* md2){
        return md1->maxDepth>md2->maxDepth;
    }
    MaxDepth* f(DirectedGraphNode* cur,unordered_map<DirectedGraphNode*,MaxDepth*>& order){
        if(order.count(cur)){
            return order[cur];
        }
        //计算cur的最大深度
        long maxdep=0;
        for(DirectedGraphNode* next:cur->neighbors){
            maxdep=maxdep>f(next,order)->maxDepth?maxdep:f(next,order)->maxDepth;
        }
        MaxDepth* mdp=new MaxDepth(cur,maxdep+1);
        order.emplace(cur,mdp);
        return mdp;
    }
    vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*> graph) {
        // write your code here
        unordered_map<DirectedGraphNode*,MaxDepth*>order;
        for(DirectedGraphNode* cur:graph){
            f(cur,order);//计算每一个结点的最大深度
        }
        vector<DirectedGraphNode*>result;
        vector<MaxDepth*>maxAll;//存放所有点的最大深度
        for(pair<const DirectedGraphNode*,MaxDepth*> md:order){
            maxAll.push_back(md.second);
        }
        sort(maxAll.begin(),maxAll.end(),cmp);
        for(MaxDepth* cur:maxAll){
            result.push_back(cur->node);
        }
        return result;
    }
};

最小生成树:边的权重之和最小且无环的连通图。
最小生成树一般是针对无向图而言的。
方法1:Kruskal(适用于边小于点)
最初一个结点一个集合,然后按边的权重从小到大排序,先选权重小的边连通的两个结点,如果这两个结点不在一个集合,就把它们放在结果集中,同时合并两个结点所在的集合。最后判断集合总数是否为1,只有集合个数为1才存在最小生成树。
需要的数据结构:优先队列;选择最小的边
并查集:判断两个结点是否在同一个集合里
这里的图结构是最初的结构,并查集用的最初的结构

vector<Edge*>Kruskal() {
		vector<Edge*>result;
		priority_queue<Edge*,vector<Edge*>,MyCompare>pq;
		unordered_set<int>nodes;
		for (pair<const int, Node*> cur : this->nodes) {//统计所有的结点
			nodes.emplace(cur.second->val);
		}
		for (Edge* edge : this->edges) {//将所有的边入队
			pq.push(edge);
		}
		vector<int>value(nodes.begin(), nodes.end());
		UnionSet unionset(value);
		while (!pq.empty()) {
			Edge* cur = pq.top();
			pq.pop();
			Node* from = cur->from;
			Node* to = cur->to;
			if (!unionset.isSameSet(from->val, to->val)) {
				unionset.unionSet(from->val, to->val);
				result.push_back(cur);
			}
		}
		if (unionset.sizeMap.size() == 1) {//只有最终集合的个数为1个时才存在最小生成树
			return result;

		}
		else {
			return {};
		}


	}

方法2:prim(适用于边大于点)
随机选择一个结点,并将该结点连接的边加入队列,然后选择最小边然后从队列弹出并解锁该边指向的结点,并将该结点连接的边加入队列,再从队列中选择最小的边,如果该边指向的结点没有被解锁过就要这个点(如果该点被解锁了,要这个点就会产生环),同时将该结点连接的边加入队列,直到所有的点都被解锁。
需要的数据结构:优先队列:选择最小的边
set:判断结点是否被解锁

vector<Edge*>prim() {
		vector<Edge*>result;
		priority_queue<Edge*, vector<Edge*>, MyCompare>pq;
		unordered_set<Node*>nodes;//保存解锁的结点
		for (pair<const int,Node* >cur: this->nodes) {
			Node* start = cur.second;//选择一个结点出发
			nodes.emplace(start);//解锁该结点
			for (Edge* edge : start->edges) {
				pq.push(edge);
			}
			while (!pq.empty()) {
				Edge* cur = pq.top();
				pq.pop();
				Node* toNode = cur->to;//该边指向的结点
				if (nodes.find(toNode) == nodes.end()) {//判断指向的这个结点是否被解锁,只有没被解锁才要这个结点
					nodes.emplace(toNode);//解锁该结点
					result.push_back(cur);//收集结果
					for (Edge* edge : toNode->edges) {//将该结点指出的边加入队列
						pq.push(edge);
					}
				}
			}
			//break;//如果整个图是由多棵树也即森林组成,每次选择的结点产生的结果是不同的
		}
		
		
		if (nodes.size() == this->nodes.size()) {//最后所有的结点都被解锁才存在最小生成树
			return result;
		}
		else {
			return {};
		}
	}

629 · 最小生成树
分析:
此题用方法1方便
准备一个优先队列,按cost从小到大排序
初始化并查集,每一个城市一个集合
每次选择队列队首元素,如果这两个城市不在同一个集合,就放入结果集,同时连接他们。

class UnionSet {
public:
	unordered_map<string, string>parents;//key的父亲是value
	unordered_map<string, int>sizeMap;//代表结点所在集合的元素个数
	UnionSet(set<string>& cities) {

		for (set<string>::iterator it = cities.begin(); it != cities.end(); it++) {
			string cur = *it;
			parents[cur] = cur;
			sizeMap[cur] = 1;
		}

	}
	string findFather(string& cur) {
		stack<string>paths;
		while (cur != parents[cur]) {
			paths.push(cur);
			cur = parents[cur];
		}
		while (!paths.empty()) {
			string i = paths.top();
			paths.pop();
			parents[i] = cur;
		}
		return cur;
	}
	bool isSameSet(string i, string j) {
		return findFather(i) == findFather(j);
	}
	void unionSet(string& i, string& j) {
		string iHead = findFather(i);
		string jHead = findFather(j);
		if (iHead != jHead) {
			if (sizeMap[iHead] >= sizeMap[jHead]) {
				parents[jHead] = iHead;
				sizeMap[iHead] += sizeMap[jHead];
				sizeMap.erase(jHead);
			}
			else {
				parents[iHead] = jHead;
				sizeMap[jHead] += sizeMap[iHead];
				sizeMap.erase(iHead);
			}
		}
	}
};

class Mycompare {
public:
	bool operator()(Connection& c1, Connection& c2) {//小顶堆(cost小的在上面)
		if(c1.cost>c2.cost){
            return true;
        }
        else if(c1.cost==c2.cost && c1.city1>c2.city1){
            return true;
        }
        else if(c1.cost==c2.cost && c1.city1==c2.city1 && c1.city2>c2.city2){
            return true;
        }
        else{
            return false;
        }
	}
};
class Solution {
public:
	/**
	 * @param connections given a list of connections include two cities and cost
	 * @return a list of connections from results
	 */
	vector<Connection> lowestCost(vector<Connection>& connections) {
		// Write your code here
		vector<Connection>result;
		priority_queue<Connection, vector<Connection>, Mycompare>pq;//按cost从小到大放入优先队列中
		set<string>cities;
		for (Connection c : connections) {
			cities.emplace(c.city1);
			cities.emplace(c.city2);
			pq.push(c);
		}
		UnionSet unionset(cities);
		while (!pq.empty()) {//如果队首的两个城市不在同一个集合,就将他们放入结果中,同时连接他们
			Connection cur = pq.top();
			pq.pop();
			string city1 = cur.city1;
			string city2 = cur.city2;
			if (!unionset.isSameSet(city1, city2)) {
				unionset.unionSet(city1, city2);
				result.push_back(cur);
			}
		}
        if(unionset.sizeMap.size()==1){//所有的城市都联通当且仅当所有的城市都在同一个集合
            return result;
        }
        else{
            return {};
        }
       
		
	}
};

迪杰斯特拉算法:单源最短路径(有向无负权重图)
迪杰斯特拉不允许环的累计和为负。
图结构依然采用的是开始的结构
初始形式的代码

Node* getMinDistanceAndUnselectedNode(unordered_map<Node*, int>& distanceMap, unordered_set<Node*>& selectedNodes) {//返回当前从from出发距离最近的结点,也就是distanceMap中int最小对应的结点
		Node* minNode = NULL;
		for (pair<Node*, int>p : distanceMap) {
			Node* node = p.first;
			if (!selectedNodes.count(node)) {//node不在selectedNode中
				if (minNode == NULL) {
					minNode = node;
				}
				else {
					minNode = distanceMap[minNode] <= distanceMap[node] ? minNode : node;//更更新最小距离的结点
				}

			}
		}
		return minNode;
	}
	//从from结点开始到各个结点的最短路径
	void dijiesitela(Node* from, unordered_map<Node*, int>& distanceMap) {//从frrom结点出发,距离放在distanceMap中
		distanceMap.emplace(from, 0);
		unordered_set<Node*>selectedNodes;//打过对号的结点(已选过的结点)
		Node* minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//从from出发距离最小且为打对号的结点
		while (minNode != NULL) {
			//此时minNode作为跳转点,到达与minNode连通且未打对号的结点的最小距离
			
			int distance = distanceMap[minNode];//获取最小距离
			for (Edge* edge : minNode->edges) {//与minNode连通的所有边都计算
				Node* toNode = edge->to;//从minNode经edge到达的结点
				if (!distanceMap.count(toNode)) {//初始时规定从from到各个点的距离是无穷大,所以如果distanceMap里没有toNode的记录,说明当前from到toNode的最小距离是无穷大
					distanceMap.emplace(toNode, distance + edge->weight);//minNode作为跳转点到toNode的距离
					

				}
				else {
					
					distanceMap[toNode] = min(distanceMap[toNode], distance + edge->weight);//如果经过minNode到达toNode的距离比之前算过的小就更新,不可以重复添加键值相同的键值对
					
				}
			}
			selectedNodes.emplace(minNode);//minNode已选过了,下次不能再选minNode了
			minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//找下一个从from出发距离最小且为打对号的结点
		}
	}

该算法的改进需要重写堆,这方面还没弄懂,以后再补充改进写法

814 · 无向图中的最短路径

/**
 * Definition for undirected graph.
 * struct UndirectedGraphNode {
 *     int label;
 *     vector<UndirectedGraphNode *> neighbors;
 *     UndirectedGraphNode(int x) : label(x) {};
 * };
 */

class Solution {
public:
    /**
     * @param graph: a list of Undirected graph node
     * @param A: nodeA
     * @param B: nodeB
     * @return:  the length of the shortest path
     */
	UndirectedGraphNode* getMinNode(unordered_map<UndirectedGraphNode*,int>& distanceMap,unordered_set<UndirectedGraphNode*>& selectedNode){
		UndirectedGraphNode* minNode=NULL;
		for(pair<UndirectedGraphNode*,int>p:distanceMap){
			UndirectedGraphNode* curNode=p.first;
			if(!selectedNode.count(curNode)){
				if(minNode==NULL){
					minNode=curNode;
				}else{
					minNode=distanceMap[minNode]<=distanceMap[curNode]?minNode:curNode;
				}
			}
		}
		return minNode;
	}
	void dijiesi(UndirectedGraphNode* from,unordered_map<UndirectedGraphNode*,int>& distanceMap){
		unordered_set<UndirectedGraphNode*>selectedNode;
		distanceMap.emplace(from,0);
		UndirectedGraphNode* minNode=getMinNode(distanceMap,selectedNode);
		while(minNode!=NULL){
			int distance=distanceMap[minNode];
			for(UndirectedGraphNode* neighbor:minNode->neighbors){
				if(!distanceMap.count(neighbor)){
					distanceMap[neighbor]=distance+1;
				}else{
					distanceMap[neighbor]=min(distanceMap[neighbor],distance+1);
				}
			}

			selectedNode.emplace(minNode);
			minNode=getMinNode(distanceMap,selectedNode);
		}
	}
    int shortestPath(vector<UndirectedGraphNode*> graph, UndirectedGraphNode* A, UndirectedGraphNode* B) {
        // Write your code here
	   unordered_map<UndirectedGraphNode*,int>distanceMap;
	   dijiesi(A,distanceMap);
	   return distanceMap[B];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值