图
图-邻接矩阵转邻接表+DFS递归非递归+BFS非递归
#include<iostream> #include<vector> #include<queue> #include<stack> using namespace std; const int num = 10; vector<int> visited(num); //DFS中保存已经被访问过的节点 //结点结构类型 struct ANode { int adjvex; //值 ANode* nextarc; //指向表头结点下一条邻接的边 int info; //该边的相关信息,对于带权图可存放权值 }; //图的邻接表类型 struct AGraph { vector<ANode*> adjlist; //所有表头组成的数组 int n, e; //结点个数,边条数 }; //------生成图的邻接表算法------ void createAdj(AGraph* &G, vector<vector<int> > A, int n) {//n个节点 //由数组A生成邻接表 ANode *p; G = new AGraph(); G->n = n; G->e = 0; for (int i = 0; i < n; i++) {//初始化邻接表头结点 ANode *anode = new ANode(); anode->nextarc = NULL; G->adjlist.push_back(anode); } for (int j = 0; j < n; j++) { for (int k = n - 1; k >= 0; k--) {//从一行的后往前 if (A[j][k] != 0) { p = new ANode(); p->adjvex = k; p->nextarc = G->adjlist[j]->nextarc;//插入到链表头 G->adjlist[j]->nextarc = p; G->e++; } } } } //------输出图的邻接表算法------ void dispAdj(AGraph *G) { ANode* p; for (int i = 0; i < G->n; i++) {//头结点,从0开始,n个节点 cout << i << "->";//头结点 p = G->adjlist[i]->nextarc;//边节点 while (p) { cout << p->adjvex << "->";//边指向的值 p = p->nextarc; } cout << "NULL" << endl; } } //深度优先遍历---递归 void DFS(AGraph *G, int v) { //vector<int> visited(G->n);//递归 所以这里错误 cout << v << " "; visited[v] = 1;//标记该值被访问过 ANode *p = G->adjlist[v]->nextarc; while (p) { if (!visited[p->adjvex]) DFS(G, p->adjvex); p = p->nextarc;//同层 } } //深度优先遍历---非递归 void DFStranverse(AGraph *G, int v) { ANode *curr; stack<int> s; cout << v << " "; visited[v] = 1; s.push(v);//不需要压入节点指针。因为每次都需要从头结点找下一个而非在pre所在链表找同层节点 while (!s.empty()) { curr = G->adjlist[s.top()]->nextarc;//找到头结点G->adjlist[pre->adjvex]继续访问,而非pre while (curr) { //找pre邻接节点中没有访问过的 if (!visited[curr->adjvex]) { cout << curr->adjvex << " "; visited[curr->adjvex] = 1; s.push(curr->adjvex); curr = G->adjlist[s.top()]->nextarc;//深度 } else { curr = curr->nextarc; } } s.pop();//同层都被访问过 } } //广度优先遍历---非递归 void BFS(AGraph* G, int v) { ANode *curr; queue<int> qu; vector<int> visited(G->n);//n个元素的flag用于标明该值是否被访问 int w; cout << v << " "; visited[v] = 1; qu.push(v); while (!qu.empty()) { w = qu.front(); qu.pop(); curr = G->adjlist[w]->nextarc;//这里不可使用front换掉w,因为front已经不同 while (curr) { if (!visited[curr->adjvex]) {//未被访问 cout << curr->adjvex << " "; visited[curr->adjvex] = 1; qu.push(curr->adjvex);//访问后进队,此while访问完同层后,才pop该值,再访问该值邻接的元素 } curr = curr->nextarc;//同层 } } } //测试用例 int main() { //有向图 /*int a[] = { 0,1,0,1,0 }; int b[] = { 0,0,1,1,0 }; int c[] = { 0,0,0,1,1 }; int d[] = { 0,0,0,0,0 }; int e[] = { 1,0,0,1,0 };*/ //无向图 int a[] = { 0,1,0,1,1 }; int b[] = { 1,0,1,1,0 }; int c[] = { 0,1,0,1,1 }; int d[] = { 1,1,1,0,1 }; int e[] = { 1,0,1,1,0 }; vector<vector<int> > graph; vector<int> A(a, a + 5);//初始化 vector<int> B(b, b + 5); vector<int> C(c, c + 5); vector<int> D(d, d + 5); vector<int> E(e, e + 5); graph.push_back(A); graph.push_back(B); graph.push_back(C); graph.push_back(D); graph.push_back(E); AGraph* G; int n = 5;//节点数量 createAdj(G, graph, n);//构造图的邻接表 cout << "输出图的邻接表:" << endl; dispAdj(G); cout << endl; cout << "DFS递归 从0深度优先遍历:" << endl; //DFS(G, 0); cout << endl; cout << "DFS非递归 从0深度优先遍历:" << endl; DFStranverse(G, 0); cout << endl; cout << "BFS非递归 从0广度优先遍历" << endl; BFS(G, 0); return 0; }
图-最短路径-dijkstra、bellmanford、spfa、floyd
/* 最短路径 https://blog.csdn.net/YF_Li123/article/details/74090301 dijkstra算法 https://blog.csdn.net/YF_Li123/article/details/74090409 dijkstra笔试题只需要修改更新d[k]处 http://www.voidcn.com/article/p-pwheyixu-te.html 优化 */ #include<iostream> #include<vector> #include<algorithm> #include<queue> using namespace std; /*一、dijkstra迪杰斯特拉算法 对比 最小生成树的prim算法 */ const int INF = 1000000000; /* 1、邻接矩阵 求s到图G中其他顶点的最短路径 d是起点s到图中每个顶点的 最短距离 pre是s到每一节点的前驱节点,用于后续输出s到某个节点的 最短路径 */ void Dijkstra(int n, int s, vector<vector<int> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre) { for (int i = 0; i < n; i++)//pre用于后续输出s到其他顶点的最短路径 pre[i] = i; //vector<bool> vis(n, false); //vector<int> d(n); fill(d.begin(), d.end(), INF);//s与其余节点距离无限大 d[s] = 0; for (int i = 0; i < n; i++)//操作节点个数n次 { int u = -1;//与当前节点最小距离的未访问节点 int mindistance = INF; for (int j = 0; j < n; j++) { if (vis[j]==false && d[j] < mindistance) { mindistance = d[j]; u = j; } } if (u == -1)//不连通 return ; vis[u] = true; //以中介点u更新d。d为s到图中每个顶点的最短距离 for (int k = 0; k < n; k++)//遍历u可到达的未访问节点 以u为中介可以让s到k的距离d[k]更优 则更新d[k]【对比最小生成树的prim算法 { if (!vis[k] && G[u][k] + d[u] < d[k]) { d[k] = G[u][k] + d[u]; pre[k] = u;//pre记录u->k 用于后续输出最短路径 } } } } /* 2、邻接表 */ struct Node { int v;//连接的节点值 int dis;//权值 Node(int x, int y) :v(x), dis(y) {} }; void Dijkstra1(int n, int s, vector<vector<Node> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre) { for (int i = 0; i < n; i++)//pre用于后续输出s到其他顶点的最短路径 pre[i] = i; //vector<bool> vis(n, false); //vector<int> d(n); fill(d.begin(), d.end(), INF);//s与其余节点距离无限大 d[s] = 0; for (int i = 0; i < n; i++)//操作节点个数n次 { int u = -1;//与当前节点最小距离的未访问节点 int mindistance = INF; for (int j = 0; j < n; j++) { if (vis[j] == false && d[j] < mindistance) { mindistance = d[j]; u = j; } } if (u == -1)//不连通 return ; vis[u] = true; //上面和Dijkstra相同 for (int k = 0; k < G[u].size(); k++)//更新u与其余可连接的未访问节点(共G[u].size()个)的距离。实际是用原距离更新INF { int tmp = G[u][k].v;//未访问节点的值 if (!vis[tmp] && G[u][k].dis + d[u] < d[tmp]) { d[tmp] = G[u][k].dis + d[u]; pre[tmp] = u; } } } } /* 优化最短路径迪杰斯特拉:堆排序的priority_queue存储最短距离、目标节点编号,即不用遍历所有节点找最短路径(第二个for 原On^2 优化OElogn (更新最短距离只需访问每条可到达的边OE,【查找下一个可使用最短目标节点Ologn(原方法需要枚举所有节点比较)】) */ void Dijkstra_good(int n, int s, vector<vector<int> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre) { fill(d.begin(), d.end(), INF); d[s] = 0; for (int i = 0; i < n; i++) pre[i] = i; priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;//最小堆 pq.push(make_pair(0, s)); while (!pq.empty()) { pair<int, int> toppair = pq.top(); pq.pop(); int mindistance = toppair.first;//当前节点能到达的最短距离 int tonode = toppair.second;//当前节点能到达的最短距离的目标节点编号 if (vis[tonode]) continue;//当前最小距离节点已经访问过 vis[tonode] = true; for (int i = 0; i < G[tonode].size(); i++)//以目标节点为中介 更新s到其余节点的最短距离 { if (!vis[i] && mindistance + G[tonode][i] < d[i]) { d[i] = mindistance + G[tonode][i]; pre[i] = tonode; pq.push(make_pair(d[i], i));//把s经过中介tonode到可到达节点i的最短距离、目标节点编号加入优先级队列(自己会排序 } } } } /* bellmanford有负权 外层1到n-1次循环,每次松弛所有边,最后再循环所有边判断是否有负边权 */ struct Edge { int u, v; int weight; Edge(int x, int y, int z) :u(x), v(y), weight(z) {} }; /*返回是否有最短路径,即有负边权则false*/ bool bellmanford(int n, int m, int s, vector<Edge> &E, vector<int> &d, vector<int> &pre) { fill(d.begin(), d.end(), INF); d[s] = 0; for (int i = 0; i < n; i++) pre[i] = i; for (int i = 1; i < n; i++) { for (int j = 0; j < m; j++) { if (d[E[j].v] > d[E[j].u] + E[j].weight)//s->v的距离 > s->u->v的距离,则更新 { d[E[j].v] = d[E[j].u] + E[j].weight; pre[E[j].v] = E[j].u; } } } for (int j = 0; j < m; j++)//最后判定是否有负边权。没收敛 { if (d[E[j].v] > d[E[j].u] + E[j].weight) { cout << "xxxxxxxxxxxxxx" << endl; return false; } } return true; } /* 3、SPFA 队列优化bellmanford 单源 负边权 */ bool SPFA(int n, int s, vector<vector<int> > G, vector<int> &d, vector<bool> &vis, vector<int> &pre) { fill(d.begin(), d.end(), INF); d[s] = 0; for (int i = 0; i < n; i++) pre[i] = i; vector<int> flag(n, 0);//记录每个节点入队次数,如果n次,说明有负环路 queue<int> q;//队列优化 q.push(s); while (!q.empty()) { int curr = q.front(); q.pop();//从队列删除 vis[curr] = false;//curr不在当前队列 for (int i = 0; i < n; i++)//循环n-1次 { if (G[curr][i] != INF && d[i] > d[curr] + G[curr][i]) { d[i] = d[curr] + G[curr][i]; if (!vis[i]) { vis[i] = true; flag[i]++; if (flag[i] >= n)//进队n次 有负环路 return false; q.push(i); } pre[i] = curr; } } } return true; } /*输出s到v的最短路径*/ void Printresult(int s, int v,vector<int> pre) { if (s == v) { cout << s << " "; return; } Printresult(s, pre[v], pre);//s到v的前一节点路径 cout << v << " "; } /*floyd多源最短路径*/ void floyd(int n, vector<vector<int> > &e, vector<vector<int> > &path) { for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (e[i][j] > e[i][k] + e[k][j]) { e[i][j] = e[i][k] + e[k][j]; path[i][j] = path[i][k]; } } int main() { //1、dijkstra算法 int n = 6; vector<vector<int>> G = { {0,1,INF,4,4,INF}, {INF,0,INF,2,INF,INF}, {INF,INF,0,INF,INF,1}, {INF,INF,2,0,3,INF}, {INF,INF,INF,INF,0,3}, {INF,INF,INF,INF,5,0} };//有向图 vector<vector<Node>> Adj = { {Node(1,1),Node(3,4),Node(4,4)}, {Node(3,2)}, {Node(5,1)}, {Node(2,2),Node(3,4)}, {Node(3,5)}, {Node(4,5)}}; vector<bool> vis(n,false); vector<int> d(n);//保存起始节点s到图中每个节点的距离 vector<int> pre(n); //Dijkstra(n, 0, G, vis, d, pre);//邻接矩阵 //Dijkstra1(n, 0, Adj, vis, d, pre);//邻接表 //Dijkstra_good(n, 0, G, vis, d, pre);//priority_queue堆排序优化原On^2到OElogn E是边n是节点 //2、bellmanford 可判断负边权 vector<Edge> E = { Edge(0,1,1),Edge(0,3,4),Edge(0,4,4),Edge(1,3,2),Edge(2,5,1), Edge(3,2,2),Edge(3,4,3),Edge(4,5,3),Edge(5,4,5)};//没有负环路 int b_n = 6; int b_m = 9; //bool nonegative = bellmanford(b_n, b_m, 0, E, d, pre); //vector<Edge> test = { Edge(0,1,5),Edge(1,2,3),Edge(2,0,-10)};//有负环路 //bool nonegative = bellmanford(3, 3, 0, test, d, pre); //3、SPFA 队列优化bellmanford vector<vector<int>> G1 = { {0,5,INF}, {INF,0,3}, {-10,0,INF}}; //bool nonegative=SPFA(3, 0, G1, d, vis, pre);//负环路 //bool nonegative = SPFA(n, 0, G, d, vis, pre);//无负环路 //cout << "有最短路径,无负环路:" << nonegative << endl; /*cout << "起始点0到其余节点的最短距离:" << endl; for (auto x : d) cout << x << " "; cout << endl; //输出从起点s到顶点v的最短路径 cout << "从起点s到顶点v的最短路径:" << endl;*/ //Printresult(0, 5, pre); //4、floyd多源 动态规划 vector<vector<int> > path(G.size(),vector<int>(G[0].size()));//初始化后用于输出具体路径的二维数组path for (int i = 0; i < G.size(); i++) for (int j = 0; j < G[0].size(); j++) path[i][j] = j; floyd(n, G, path);//会修改G存储多源最短距离 for (int i = 0; i < n; ++i)//每个顶点到其他顶点最短距离 { for (int j = 0; j < n; ++j) { if (G[i][j] != INF)//如果存在路径 { //判断负环是否存在只需检查是否存在d[i][i]是负数的顶点i cout << "dis[" << i << "][" << j << "] = " << G[i][j];//最短距离 //最短路径 cout << " 路径:"; cout << i << " "; int temp = path[i][j]; while (temp != j) { cout << temp << " "; //经过节点 temp = path[temp][j]; } cout << j << " " << endl; } else cout << i << " cant to " << j << endl; } } return 0; }
图-最小生成树prim+kruskal
/* https://blog.csdn.net/YF_Li123/article/details/75148998 https://blog.csdn.net/YF_Li123/article/details/75195549 */ #include<iostream> #include<vector> #include<algorithm> using namespace std; /*一、prim普里姆算法*/ const int INF = 1000000000;//除了d[s]=0,s和其余节点初始距离无限大 /* 1、邻接矩阵实现最小生成树 n节点个数,s初始节点,G图的邻接矩阵,vis已访问节点,d其余未访问节点与集合S的最短距离(其余未访问节点与当前节点u的最短距离 返回最小权重和 */ int Prim(int n, int s, vector<vector<int> > G) { vector<bool> vis(n, false); vector<int> d(n); fill(d.begin(), d.end(), INF);//s与其余节点距离无限大 d[s] = 0; int result = 0;//最小权重和 返回 for (int i = 0; i < n; i++)//操作节点个数n次 { int u = -1;//与当前节点最小距离的未访问节点 int mindistance = INF; for (int j = 0; j < n; j++) { if (vis[j]==false && d[j] < mindistance) { mindistance = d[j]; u = j; } } if (u == -1)//不连通 return -1; vis[u] = true; result += d[u]; for (int k = 0; k < n; k++)//更新u与其余可连接的未访问节点的距离。实际是用原距离更新INF { if (!vis[k] && G[u][k] != INF && G[u][k] < d[k]) d[k] = G[u][k]; } } return result; } /* 2、邻接表实现最小生成树 n节点个数,s初始节点,G图的邻接表,vis已访问节点,d其余未访问节点与集合S的最短距离(其余未访问节点与当前节点u的最短距离 返回最小权重和 */ struct Node { int v;//连接的节点值 int dis;//权值 Node(int x, int y) :v(x), dis(y) {} }; int Prim1(int n, int s, vector<vector<Node> > G) { vector<bool> vis(n, false); vector<int> d(n); fill(d.begin(), d.end(), INF);//s与其余节点距离无限大 d[s] = 0; int result = 0;//最小权重和 返回 for (int i = 0; i < n; i++)//操作节点个数n次 { int u = -1;//与当前节点最小距离的未访问节点 int mindistance = INF; for (int j = 0; j < n; j++) { if (vis[j] == false && d[j] < mindistance) { mindistance = d[j]; u = j; } } if (u == -1)//不连通 return -1; vis[u] = true; result += d[u]; //上面和prim相同 for (int k = 0; k < G[u].size(); k++)//更新u与其余可连接的未访问节点(共G[u].size()个)的距离。实际是用原距离更新INF { int tmp = G[u][k].v;//未访问节点的值 if (!vis[tmp] && G[u][k].dis != INF && G[u][k].dis < d[tmp]) d[tmp] = G[u][k].dis; } } return result; } /*二、kruskal克鲁斯卡尔算法*/ struct Edge { int u, v; int weight; Edge(int x, int y, int z) :u(x), v(y), weight(z) {} }; bool cmp(Edge a, Edge b) { return a.weight < b.weight; } /*并查集查找。寻找s的所在集合的根节点*/ int findfather(vector<int> &father, int s) { while (father[s] != s) s = father[s]; return s; } /* 并查集https://blog.csdn.net/CoderPai/article/details/69388597 并查集查找优化:对于每个节点,一旦向上走到了一次根节点,就把这个点到父亲的边改为直接连向根*/ int findfather_good(vector<int> &father, int s) { int tmp = s; while (s != father[s])//得到s的根节点 给s s = father[s]; while (tmp != father[tmp])//优化s的所有父节点直接指向根,下次找父节点就路径压缩 { int ttmp = tmp;//先记录当前节点值 tmp = father[tmp];//让tmp指向父节点,用于下一轮更新父节点直接指向根 father[ttmp] = s;//直接让当前节点的父节点指向根 } } /* n为图中节点数,m为边数,E为边集合 */ int kruskal(int n,int m,vector<Edge> &E) { int result = 0;//最小生成树 权重结果 int numofedge = 0;//已加入最小生成树的边数量 vector<int> father(n);//并查集。用于判断边的两个节点是否已连接 for (int i = 0; i < n; i++)//并查集初始化 father[i] = i; sort(E.begin(), E.end(), cmp);//升序排列所有边权重 for (int i = 0; i < m; i++)//循环边次数 { /*当前边E[i]的两个顶点是否连通*/ int fau = findfather(father, E[i].u);//并查集查找 u所在集合的根节点 int fav = findfather(father, E[i].v); if (fau != fav)//不连通,合并表示加入最小生成树,连通 { father[fau] = fav;//并查集合并 result += E[i].weight; numofedge++; if (numofedge == n - 1)//如果边numofedge=节点n-1则有最小生成树,提前退出 break; } } if (numofedge != n - 1)//不连通 return -1; else return result; } int main() { //最小生成树 2种算法实现 /*1、prim算法*/ int n = 6; /*邻接矩阵*/ vector<vector<int>> G = { {0,4,INF,INF,1,2}, {4,0,6,INF,INF,3}, {INF,6,0,6,INF,5}, {INF,INF,6,0,4,5}, {1,INF,INF,4,0,3}, {2,3,5,5,3,0} }; /*邻接表*/ vector<vector<Node>> Adj = { {Node(4,1),Node(5,2),Node(1,4)}, {Node(0,4),Node(5,3),Node(2,6)}, {Node(1,6),Node(3,6),Node(5,5)}, {Node(2,6),Node(4,4),Node(5,5)}, {Node(0,1),Node(5,3),Node(3,4)}, {Node(0,2),Node(1,3),Node(2,5),Node(3,5),Node(4,3)} }; /*for (auto x : Adj) { for (auto y : x) cout << y.v<<"-"<<y.dis << " "; cout << endl; }*/ int res1 = Prim(n, 0, G);//邻接矩阵版 cout << res1 << endl; int res2 = Prim1(n, 0, Adj);//邻接表版 cout << res2 << endl; //2、kruskal克鲁斯卡尔算法*/ vector<Edge> E = { Edge(0,1,4),Edge(1,2,1),Edge(2,3,6),Edge(3,4,5),Edge(0,4,1), Edge(0,5,2),Edge(1,5,3),Edge(2,5,5),Edge(3,5,4),Edge(4,5,3) }; int k_n = 6;//节点数 int k_m = 10;//边数 int result = kruskal(k_n, k_m, E); cout << result << endl; return 0; }