图的基础算法

it2024-02-20  65

图的表示方式一般有两种,链表和矩阵。图的相关算法比较简单,深度,广度优先遍历,深度可以用递归。广度需要用队列先将访问过的层级加入队列,直到这一层的节点访问结束,进入下个几点再遍历。

最短路径问题

最短路径给出的条件就是起点和终点,最短路径问题是解法多种多样,只复盘了Dijkstra算法这个。这个算法的核心有两个,一个是保存从起点出发到达每个顶点的路径数值,然后从最短的那个点出发。修改路径数值表中,可以由这个点联通,并且经由这个点的路径值比原来还小的话,就更新数值表。

示例,a点到b点是3,到c点是5,现在从b点开始遍历,发现b点到c点是1,这样经由b点到c点的数值,就比从a直接到c的数值小,然后 更新即可。这样知道找到终点,就可以计算出最短路径是多少。

如果需要知道每个点,就需要知道,最后一次修改到终点的位置时,上一个点是多少,这样倒推往前依次按照这个方法去寻找上个点,就能找出所有的路径点。具体不再复述。回顾是为了更好的刷题!

最小生成树问题

最小生成树kruskal和prim。

其中prim算法有点类似最短路径的算法,每次找出距离已经生成的树中的节点最小权值的点,并把路径值相加,最终就得出最小生成树的路径节点。

kruskal算法是每次先将边的信息按照大小排序,排序结束,已从挑出最短的边和两个顶点。判断两个点是否已经联通,如果没有联通,就将两个顶点合并,合并时采用并查集的概念。判断是否联通也是用并查集查找,是否有共同的父节点。

拓扑排序

拓扑排序的也比较简单,是指在有向图中必须优先完成入度为0的工程节点,将节点依次按照先后完成的排序。拓扑排序的算法规则,就是每次找出入度为0的节点,并将节点的相连的边全部删掉。再次寻找入度为0的边,直到找出所有的节点。

由于图可能是有环图,所以只需要找N次,每次找出一个节点,如果当前这次找不出就直接找下个,如果找完之后,所找到的节店个数比N小,就代表图是有环图。

话不多说贴代码

P3.h

#pragma once #include <iostream> #include<vector> #include <queue> using namespace std; class EdgeInf { public: int from; int to; int wight; EdgeInf() { } EdgeInf(int f, int t, int w) { from = f; to = t; wight = w; } bool operator<(const EdgeInf& e1) { return wight < e1.wight; } }; class P3 { public: P3(); ~P3(); void createGraphicsByVector(int nodeSize); void coutGraphics(); void addEV(int i, int j, int v); /*深度优先 */ void DFS(int i); void BFS(); void createGraphicsByList(int nodeSize); void addNodeList(int i, int j, int v); class Node { public: Node() { m_vIndex = 0; m_wight = 0; m_nextNode = NULL; } Node(int index, int wight) { m_vIndex = index; m_wight = wight; m_nextNode = NULL; } ~Node() { if (m_nextNode != NULL) { while (m_nextNode != NULL) { Node* deleteNode = m_nextNode; m_nextNode = m_nextNode->m_nextNode; delete(deleteNode); } } } int m_vIndex; int m_wight; Node* m_nextNode; }; void resetState(); void DFSList(int i); void BFSList(); int findPath(int start, int end); int prim(); int kruskal(); int getFartherIndex(int nodeIndex); int unionIndex(int i, int j); void orderSort(); int findZero(vector<vector<int>> *vectorCopy, vector<int>* deleteV); private: vector<vector<int>> *m_graph; vector<int> *m_visitedState; queue<int> m_queue; vector<Node> *m_nodeGraph; vector<int> m_unionVector; };

P3.cpp

#include "P3.h" #include <algorithm> P3::P3() { } P3::~P3() { } void P3::createGraphicsByVector(int nodeSize) { m_graph = new vector<vector<int>>(nodeSize, vector<int>(nodeSize)); m_visitedState = new vector<int>(10, 0); } void P3::addEV(int i, int j, int v) { //添加有向图的顶点和边 if (m_graph->size() <= 0) { return; } (*m_graph)[i][j] = v; //(*m_graph)[j][i] = v; } void P3::coutGraphics() { } void P3::DFS(int i) { (*m_visitedState)[i] = 1; cout << "当前节点:" << i << endl; for (int k = 0; k != m_graph->size(); ++k) { if ((*m_graph)[i][k] != 0 && (*m_visitedState)[k] == 0) { DFS(k); } } } void P3::BFS() { for (int k = 0; k != m_graph->size(); ++k) { if ((*m_visitedState)[k] == 0) { cout << "当前节点:" << k << endl; (*m_visitedState)[k] = 1; m_queue.push(k); while (!m_queue.empty()) { k = m_queue.front(); m_queue.pop(); for (int j = 0; j != m_graph->size(); ++j) { if ((*m_visitedState)[j] == 0 && (*m_graph)[k][j] != 0) { cout << "当zssd前节点:" << j << endl; (*m_visitedState)[j] = 1; m_queue.push(j); } } } } } } void P3::createGraphicsByList(int nodeSize) { m_nodeGraph = new vector<Node>(nodeSize); for (int i = 0; i != nodeSize; ++i) { Node nd = Node(i, 0); nd.m_nextNode = NULL; (*m_nodeGraph)[i] = nd; } } void P3::addNodeList(int i, int j, int v) { Node *nd = new Node(j, v); Node* tail = (*m_nodeGraph)[i].m_nextNode; if (tail == NULL) { (*m_nodeGraph)[i].m_nextNode = nd; } else { while (tail->m_nextNode != NULL) { tail = tail->m_nextNode; } tail->m_nextNode = nd; } } void P3::resetState() { for (int i = 0; i != m_visitedState->size(); ++i) { (*m_visitedState)[i] = 0; } } void P3::DFSList(int i) { (*m_visitedState)[i] = 1; cout << "访问:" << i << endl; Node* p = (*m_nodeGraph)[i].m_nextNode; while (p != NULL) { int j = p->m_vIndex; if ((*m_visitedState)[j] == 0) { DFSList(j); } p = p->m_nextNode; } } void P3::BFSList() { for (int i = 0; i != m_nodeGraph->size(); ++i) { if ((*m_visitedState)[i] == 0) { cout << "访问:" << i << endl; (*m_visitedState)[i] = 1; } m_queue.push(i); while (!m_queue.empty()) { i = m_queue.front(); m_queue.pop(); if ((*m_visitedState)[i] == 0) { Node *p = (*m_nodeGraph)[i].m_nextNode; while (p != NULL) { (*m_visitedState)[i] = 1; cout << "访问:" << p->m_vIndex << endl; m_queue.push(p->m_vIndex); p = p->m_nextNode; } } } } } int P3::findPath(int start, int end) { //找出最短路径 vector<int> visited = vector<int>(10, 0); queue<int> path; vector<int> pathValue(10, 10000); vector<int> hadVisited; //初始化起始点到所有点的数值,根据连同情况初始化权值,默认是最大值,有相邻的点就取相邻的点的值,初始化。 for (int i = 0; i != m_graph->size(); ++i) { if ((*m_graph)[start][i] != 0) { pathValue[i] = (*m_graph)[start][i]; } else { pathValue[i] = 10000; } } int min = 100001; for (int i = 0; i < m_visitedState->size() - 1; ++i) { min = 100001; int u = 0; /* 找出当前路径值表中最近的那个点,也就是路径值最小的那个点。 */ for (int j = 0; j != m_visitedState->size(); ++j) { if (visited[j] == 0 && pathValue[j] < min) { u = j; min = pathValue[j]; path.push(u); } } visited[u] = 1; /*以这个点位起始点,修改一下路径表所有点的路径值,也就是更新经过这个点,可以到每个点的最小值 举例,如从第一个点0开始找,找出了三个1,2,3三个点,接下来,就要从里边挑出最近的点,以最近的点为起点,找出,到其他所有点的最小值。 路径值中的变化。找完之后,就可以将这个点标记为已经找过了,也就是从起始点经过此点的可以到的所有点的最近路径都已经找过了,全部更新了。 每一次循环就反复干这件事,直到所有的点被找完 */ for (int k = 0; k < m_graph->size(); ++k) { if (visited[k] == 0 && (*m_graph)[u][k] != 0 && (*m_graph)[u][k] + min < pathValue[k]) { pathValue[k] = (*m_graph)[u][k] + min; cout << "path from :"<<u << "to "<< k << endl; // 这里最后一次存储到k的是上个点u就是,到k的最短的点,如果全部存储到队列中,倒过来找一下就知道到终点的最短路径了 if (k == end) { for (int jj = 0; jj != path.size(); ++jj) { //cout << "path : " << k << endl; } } } } } while(!path.empty ()) { cout << "path : " << path.front() << endl; path.pop(); } return pathValue[end]; } int P3::prim() { /*最小生成树的算法,这是以顶点为起始,查找,顶点上的所有点到达每个节点的最小权值的那个连接线的值,也就是找出到下一个顶点的最小的权值边。 这个与最短路径像似,最短路径是起点到终点的最小权值和。 这个是已生成的树的上所有点,与下个点连同的最小权值。 每次新加进来一个点,就需要检测一遍原来的点有没有经过新的点可以更近的路径,有的话修改原来点的权值 prim是从点出发,每次找出当前生成的树中,可以到达下个点最短的边。 */ int max = 100000; vector<int> pathValue(10, 100000); vector<int> had(10, 0); int v = 0; for (int i = 0; i != m_graph->size(); ++i) { int p = (*m_graph)[0][i]; if (p == 0) { p = max; } pathValue[i] = p; } had[0] =1; for (int kkk = 1; kkk != m_graph->size(); ++kkk) { int temp = max; int k = -1; //找出当前距离树最近的节点的值,就是那个点最小。 for (int i = 1; i < m_graph->size(); i++) { if (had[i] == 0 && temp > pathValue[i]) { temp = pathValue[i]; k = i; } } if (k == -1) { return -1; } had[k] = 1;//找到后,将这个点置为已经加进来了,成了树的一个新节点。 v += temp; //通过新的这个树的点,在将剩余每个顶点距离树的权值更新到最小 for (int j = 1; j < m_graph->size(); j++) { if (had[j] == 0 && (*m_graph)[k][j] != 0 && (*m_graph)[k][j] < pathValue[j]) { pathValue[j] = (*m_graph)[k][j]; //cout << "last k =" << k << " j = " << j << endl; } } cout << "last k =" << k << endl; } return v; } bool cmp(EdgeInf i, EdgeInf j) { return i.wight < j.wight; } int P3::kruskal() { /* 这个就更简单了,先把所有边和顶点关系按照边的权值排序, 依次检测最短的边的顶点是否已经联通了, 已经联通就什么都不做 否则,将边的两个顶点放到同一个树中,并用归并集去保存。用归并集判定检测两个点是否联通 */ for (int i = 0; i != m_graph->size(); ++i) { m_unionVector.push_back(i); } vector<EdgeInf> v; for (int i=0;i!=m_graph->size ();++i) { for (int j=0;j!=m_graph->size ();++j) { int k = (*m_graph)[i][j]; if (k != 0) { EdgeInf node = EdgeInf(i,j,k); v.push_back(node); } } } sort(v.begin(), v.end()); int value = 0; int edgeCount = 0; for (int i=0;i!=v.size ();++i) { int from = v[i].from; int to = v[i].to; int w = v[i].wight; if (edgeCount == m_graph->size() - 1) { break; } //用归并集判定是否是同一个父节点,由此可判定两个点否已经连通,但这仅仅适用于无向图 //如果是有向图,可以用广度优先搜索判定是否连通,如果连通了就丢弃,这样就需要用一个新的图存储已经连接的信息了。 if (getFartherIndex(from) != getFartherIndex(to)) { unionIndex(from,to); value += w; cout<<"from :" << from << " to :" << to<<endl; edgeCount++; } } //sort(v.begin (),v.end (), cmp); return value; } int P3::getFartherIndex(int nodeIndex) { if (m_unionVector[nodeIndex] != nodeIndex) { /*这里处理是按照无向图的处理方式来的,所以与prim算法的结果不一致*/ /* 按照有向图的算法,优化一下,即,每次寻找父节点时,向上寻找,一定是自己是from,to是结束点*/ int from = nodeIndex; int to = m_unionVector[nodeIndex]; //如果存在一个form to的回路就继续往下找,如果没有,就找出所有的回路,用回路检测父节点。 //很明显,归并集的方法是不能用来搞有向图的。 return getFartherIndex(m_unionVector[nodeIndex]); } return nodeIndex; } int P3::unionIndex(int i,int j) { int index = getFartherIndex(j); m_unionVector[index] = getFartherIndex(i); return 0; } void P3::orderSort() { /*拓扑排序,每次从图中找出入度为0 的顶点, 选出这个顶点,并将这个顶点的相关的边删掉,输出这个顶点。 算法复杂度 */ vector<vector<int>> *copy = new vector<vector<int>>(); for (int i=0;i!=m_graph->size ();++i) { vector<int> v; for (int j=0;j!=(*m_graph)[i].size();++j) { v.push_back((*m_graph)[i][j]); } copy->push_back(v); } vector<int> order; vector<int> *deleteV= new vector<int>(10, 0); for (int i = 0; i != deleteV->size(); ++i) { int end = findZero(copy, deleteV); if (end != -1) { /*删除节点上的边和顶点*/ (*deleteV)[end] = 1; for (int k=0;k!= (*copy)[end].size();++k) { if ((*copy)[end][k] != 0) { (*copy)[end][k] = 0; } } cout<<"end = "<<end<<endl; order.push_back(end); } } } int P3::findZero(vector<vector<int>> *vectorCopy,vector<int>* deleteV) { /* 找入度为0的节点 */ int end= -1; for (int i = 0; i != vectorCopy->size(); ++i) { if ((*deleteV)[i] == 1) { continue; } end = i; for (int j = 0; j != vectorCopy->size(); ++j) { if ((*vectorCopy)[j][i] != 0) { end = -1; break; } } if (end != -1) { return i; } } return end; } int main(int argc, char* argv[]) { /* 示例用有向图来标识图 0->1,2,3 1->4,5,6 2->4,5,6; 3->4,5,6 4->7,8,9 5->7,8,9 6->7,8,9 使用矩阵存储图的信息 0,1,2,3,4,5,6,7,8,9 0 0 6,2, 4 1 0 9,3,7 2 0 4,6,2 3 0 1,5,8 4 0 2,3,5 5 0 9,5,2 6 0 2,3,1 7 0 8 0 9 0 */ /* 1 0 --------2 3 */ P3 p3; p3.createGraphicsByVector(10); p3.addEV(0, 1, 6); p3.addEV(0, 2, 2); p3.addEV(0, 3, 4); p3.addEV(1, 4, 9); p3.addEV(1, 5, 3); p3.addEV(1, 6, 7); p3.addEV(2, 4, 4); p3.addEV(2, 5, 6); p3.addEV(2, 6, 2); p3.addEV(3, 4, 1); p3.addEV(3, 5, 5); p3.addEV(3, 6, 8); p3.addEV(4, 7, 2); p3.addEV(4, 8, 3); p3.addEV(4, 9, 5); p3.addEV(5, 7, 9); p3.addEV(5, 8, 5); p3.addEV(5, 9, 2); p3.addEV(6, 7, 2); p3.addEV(6, 8, 3); p3.addEV(6, 9, 1); p3.resetState(); p3.DFS(0); p3.createGraphicsByList(10); p3.addNodeList(0, 1, 6); p3.addNodeList(0, 2, 2); p3.addNodeList(0, 3, 4); p3.addNodeList(1, 4, 9); p3.addNodeList(1, 5, 3); p3.addNodeList(1, 6, 7); p3.addNodeList(2, 4, 4); p3.addNodeList(2, 5, 6); p3.addNodeList(2, 6, 2); p3.addNodeList(3, 4, 1); p3.addNodeList(3, 5, 5); p3.addNodeList(3, 6, 8); p3.addNodeList(4, 7, 2); p3.addNodeList(4, 8, 3); p3.addNodeList(4, 9, 5); p3.addNodeList(5, 7, 9); p3.addNodeList(5, 8, 5); p3.addNodeList(5, 9, 2); p3.addNodeList(6, 7, 2); p3.addNodeList(6, 8, 3); p3.addNodeList(6, 9, 1); p3.resetState(); p3.BFSList(); int v = p3.findPath(0, 9); cout << v << endl; v= p3.prim(); cout << v << endl; //这里输出的是26 v = p3.kruskal(); //这里输出的是18 是不是很惊喜?为啥两个算法的最小生成树的权值不一样? // 这是因为矩阵存储的是按照有向图存储的,但kruskal里边我用的是按照无向图来用的。 //只要打开addEV()函数的注释,就可以转换为无向图,再次运行就就是一样的了 cout << v << endl; p3.orderSort(); system("pause"); return 0; }

 

最新回复(0)