计算机考研机试指南(八)——图论(畅通工程、还是畅通工程、最短路、more is better、Freckles、legal or not、确定比赛名次、产生冠军、最短路径问题)
机试指南 cha 5 图论
并查集
畅通工程
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <string.h> 6 7 using namespace std; 8 /* 9 并查集 10 畅通工程:所有顶点之间均可连通 11 time : 20+20+20 12 */ 13 14 int findroot(int v[],int a) 15 { 16 // 寻找根节点的过程中,如果不是根节点的直接孩子,则修改为其直接孩子 17 if (v[a] == -1) 18 return a; 19 else 20 { 21 int tmp = findroot(v,v[a]); 22 v[a] = tmp; 23 return tmp; 24 } 25 } 26 int main() 27 { 28 int m,n,x1,x2; 29 int i,l,r; 30 int v[1001] = {0}; // v[i] 代表第i个顶点对应的双亲节点 31 while (cin >> n >> m) 32 { 33 if (n == 0) 34 break; 35 for (int i = 1;i<=n;i++) 36 v[i] = -1; 37 for (i = 0 ;i < m;i++) 38 { 39 cin >> x1 >> x2; 40 // 根节点不同,即不在一个集合,则连通 41 l = findroot(v,x1); 42 r = findroot(v,x2); 43 if (l!=r) 44 v[r] = l; 45 46 47 } 48 // 输入完了以后,相连的结点会在同一个集合中,计算有几个并查集,并查集-1即为新增路径条数 49 int count = 0 ; 50 for (i = 1;i<=n;i++) 51 { 52 if (v[i] == -1) 53 count ++; 54 } 55 cout << count -1 << endl; 56 57 } 58 return 0; 59 }
最小生成树
还是畅通工程
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <string.h> 6 #include <algorithm> 7 8 using namespace std; 9 /* 10 最小生成树 11 time : 12 */ 13 int v[1010]; 14 int findroot(int a) 15 { 16 if (v[a] == -1) 17 return a; 18 else 19 { 20 int tmp = findroot(v[a]); 21 // 非根节点a直接和根相连 22 v[a] = tmp; 23 return tmp; 24 } 25 } 26 struct node 27 { 28 int a; 29 int b; 30 int w; 31 }; 32 bool cmp(const node &a,const node &b) 33 { 34 return a.w < b.w; // 从小到大 35 } 36 int main() 37 { 38 int n,a,b,w; 39 int i,l,r; 40 node node[5000],tmp; 41 int ans = 0; 42 while (cin >>n && n!=0) 43 { 44 ans = 0; 45 for (int i = 1;i<=n;i++) 46 v[i] = -1; 47 // 初始化node数组 48 for (i = 0;i< n*(n-1)/2 ;i++) 49 { 50 cin >> a >>b >> w; 51 node[i].a =a; 52 node[i].b =b; 53 node[i].w =w; 54 } 55 sort(node,node+n*(n-1)/2,cmp); // 不是n条边! 56 for (i = 0;i<n*(n-1)/2;i++) 57 { 58 tmp = node[i]; 59 l = findroot(tmp.a); 60 r = findroot(tmp.b); 61 if (l!=r) 62 { 63 v[r] = l; // 两个根节点集合合并 64 ans += tmp.w; 65 } 66 } 67 cout << ans << endl; 68 69 } 70 71 return 0; 72 }
最短路径
最短路
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <string.h> 6 #include <algorithm> 7 8 #define INFINITY 65515 9 using namespace std; 10 /* 11 最短路径 12 time : 13 */ 14 15 int main() 16 { 17 int i,j,n,m; 18 int p[101][101]; 19 while (cin >> n >> m && (n!=0 && m != 0)) 20 { 21 for (i = 1;i<=n;i++) 22 for (j=1;j<=n;j++) 23 { 24 if (i == j) 25 p[i][i] = 0; 26 else 27 p[i][j] = INFINITY; 28 } 29 for (i = 0 ;i < m;i++) 30 { 31 int a,b,c; 32 cin >> a>> b >> c; 33 p[a][b] = c; 34 p[b][a] = c; 35 } 36 for (int k = 1 ; k <= n; k++) 37 { 38 for ( i = 1;i<=n ; i++) 39 for (j = 1 ;j<=n ; j++) 40 if ( i!=k && j!= k && (p[i][k] + p[k][j] < p[i][j])) 41 { 42 p[i][j] = p[i][k] + p[k][j]; 43 } 44 } 45 cout << p[1][n] <<endl; 46 } 47 48 return 0; 49 }
more is better
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <string.h> 6 #include <algorithm> 7 8 using namespace std; 9 // 求并查集的过程中在每个集合根节点中记录该集合的人数 10 11 const int num = 100001; 12 int v[num]; // v[i] 为第i个节点的双亲节点 13 int sum[num];// sum[i]当且仅当v[i] == -1 时有效,数字代表该集合的人数 14 15 int findroot(int a) 16 { 17 if (v[a] == -1) 18 return a; 19 else 20 { 21 int tmp = findroot(v[a]); // 递归得到根节点 22 v[a] = tmp; 23 return tmp; 24 } 25 } 26 int main() 27 { 28 29 int n; 30 int a,b,i,r,l; 31 while ( cin>>n ) 32 { 33 for (i = 1;i<= num ; i++) // 这个地方不能设为2*n,因为如果n很大时,2*n可能会超过num 34 { 35 v[i] = -1; // 只知道N对好朋友,但不知道男孩的数目,最多为一对儿两个不同的男孩 36 sum[i] = 1; 37 } 38 for (i = 1 ;i <= n ;i++) 39 { 40 cin >> a >> b; 41 l = findroot(a); 42 r = findroot(b); 43 if (l!=r) 44 { 45 v[l] = r; 46 sum[r] += sum[l] ;// 注意!合并集合时要把两个集合的sum值合并 47 } 48 49 } 50 int max = 1; 51 for (i = 1;i<=num;i++) 52 { 53 if (v[i] == -1 && sum[i] > max) 54 { 55 max = sum[i]; 56 } 57 } 58 cout << max <<endl; 59 } 60 61 62 return 0; 63 }
Freckles
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <string.h> 6 #include <algorithm> 7 8 using namespace std; 9 const int num = 101; 10 int v[num]; // 点集合 11 struct edge 12 { 13 int v1,v2; // 每条边的两个端点均在v[num]中 14 float w; 15 }e[num*(num-1)/2]; // 边集合 16 17 int findroot(int a) 18 { 19 if (v[a] == -1) 20 return a; 21 else 22 { 23 int tmp = findroot(v[a]); // 递归得到根节点 24 v[a] = tmp; 25 return tmp; 26 } 27 } 28 bool cmp(const edge &a,const edge &b) 29 { 30 return a.w < b.w; 31 } 32 int main() 33 { 34 int n,i,j,l,r; 35 float dot[num][2],a,b; 36 float ans; 37 while (cin>>n) 38 { 39 for (i=1;i<=n;i++) 40 { 41 v[i] = -1; 42 cin >> dot[i][0] >> dot[i][1] ; 43 } 44 // 初始化边edge数组,n个点有n(n-1)/2条边,进行二层循环 45 float ans; 46 int size = 1; 47 for (i = 1 ;i <= n ;i++) 48 { 49 for (j = i+1 ; j <= n ; j++) 50 { 51 a = dot[i][0] - dot[j][0]; 52 b = dot[i][1] - dot[j][1]; 53 ans = sqrt(a*a + b*b); 54 e[size].v1 = i; 55 e[size].v2 = j; 56 e[size++].w = ans; 57 58 } 59 } 60 // sort(edge),合并并查集 61 sort(e+1,e+size,cmp); 62 // for (i = 1 ;i<4;i++) 63 // cout << e[i].w << endl; 64 ans = 0; 65 // 从小权值边开始选择,每次要判断这条边的两个端点是否在同一个集合中 66 for (i = 1;i<size;i++) 67 { 68 l = findroot(e[i].v1); 69 r = findroot(e[i].v2); 70 if (l!=r) 71 { 72 v[l] = r; 73 ans += e[i].w; 74 } 75 } 76 printf("%.2f",ans); 77 78 } 79 return 0; 80 }
拓扑排序
legal or not
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <vector> 5 #include <queue> 6 using namespace std; 7 struct edge 8 { 9 int nextnode; 10 }; 11 queue<int> q; 12 vector<edge> vex[100]; 13 int visited[100]; 14 15 void findrudu(int n) 16 { 17 // 找到入度为0的点(未在邻接表右侧链中出现的点),放到队列中 18 int i,a; 19 bool rudu[100]; 20 for (i = 0;i<100;i++) 21 rudu[i] = true; 22 // 一个再细微不过的知识点,都有可能成为决定成败的事情! 23 for (i = 0;i<n;i++) 24 { 25 for (vector<edge>::iterator it = vex[i].begin() ; it != vex[i].end() ; it++) 26 { 27 a = (*it).nextnode; 28 rudu[a] = false; 29 } 30 } 31 32 for (i = 0;i<n;i++) 33 { 34 // cout << rudu[i] <<' ' << visited[i] << endl; 35 if (rudu[i]&&visited[i]){ 36 q.push(i); 37 } 38 } 39 } 40 int main() 41 { 42 int n,m,i,a,b; 43 edge e; 44 vector<edge>::iterator it; 45 // i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点 46 while (cin >> n >> m) 47 { 48 if (m==0&&n==0) 49 break; 50 // 初始化vector 51 for (i = 0;i<n;i++) 52 { 53 vex[i].clear(); 54 visited[i] = true; 55 } 56 // 初始化队列 57 while (!q.empty()) 58 q.pop(); 59 60 for (i = 0;i<m;i++) 61 { 62 cin >> a >> b; // a->b为一条弧 63 e.nextnode = b; 64 vex[a].push_back(e); 65 } 66 findrudu(n); 67 while (!q.empty()) 68 { 69 a = q.front();// 从队列中取出一个入度为0的结点 70 q.pop(); 71 vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的弧 72 visited[a] = false; 73 findrudu(n); 74 } 75 // 如果队列为空时仍旧有顶点为true,则存在环 76 bool flag = true; 77 for (i = 0;i<n;i++ ) 78 if (visited[i]) 79 { 80 flag = false; 81 } 82 if (flag) 83 cout << "YES" << endl; 84 else 85 cout << "NO" << endl; 86 87 88 89 } 90 return 0 ; 91 }
太蠢的做法!结果超时了,完全可以第一次统计每个结点的入度,然后每次删除节点时对该入度数组进行– 操作,用bool真的太蠢了!而且这道题墨迹了一整个下午! 以后半小时内做不出来就研究答案!一道题目不要超过1个半小时!
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <vector> 5 #include <queue> 6 using namespace std; 7 8 queue<int> q; 9 vector<int> vex[100]; 10 11 12 int main() 13 { 14 int n,m,i,a,b; 15 vector<int>::iterator it; 16 int degree[100]; 17 // i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点 18 while (cin >> n >> m) 19 { 20 if (m==0&&n==0) 21 break; 22 // 初始化vector和degree数组 23 for (i = 0;i<n;i++) 24 { 25 vex[i].clear(); 26 degree[i] = 0; 27 } 28 // 初始化队列 29 while (!q.empty()) 30 q.pop(); 31 for (i = 0;i<m;i++) 32 { 33 cin >> a >> b; // a->b为一条弧 34 vex[a].push_back(b); 35 // 增加b的入度 36 degree[b] ++; 37 } 38 for (i = 0;i<n;i++) 39 { 40 if (degree[i] == 0) 41 q.push(i); 42 } 43 int c = 0; // 计数器,用来确定已经被删除的结点的个数,或者用bool数组,但bool数组还得遍历 44 while (!q.empty()) 45 { 46 a = q.front();// 从队列中取出队首 47 q.pop(); // 删除队首 48 c ++; 49 for (it = vex[a].begin();it != vex[a].end() ; it++) 50 { 51 // 将待删除节点的邻接点的入度--,如果该邻接点成为入度为0的点,入队 52 degree[*it] -- ; 53 if (degree[*it] == 0) 54 q.push(*it); 55 } 56 vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的邻接顶点 57 58 } 59 60 if (c == n) 61 cout << "YES" << endl; 62 else 63 cout << "NO" << endl; 64 65 66 67 } 68 return 0 ; 69 }
- 另一种遍历vector的方法
- for (int i = 0;i
确定比赛名次 30min
和上面那道题比起来,只需要每次pop()之前或之后输出结点数据。而且并不需要erase掉被选中结点的链表,因为我们看的是被push进queue的结点。
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <vector> 5 #include <queue> 6 using namespace std; 7 8 vector<int> vex[501]; 9 struct cmp{ 10 bool operator() (const int &a, const int& b ){ 11 return a > b; 12 } 13 }; 14 priority_queue<int, vector<int>, cmp > q; 15 int degree[501]; 16 // 如何保证号小的先被选中?优先队列! 17 int main() 18 { 19 int n,m,i,a,b; 20 while (cin>>n>>m) 21 { 22 // 初始化 23 for (i=1;i<=n;i++) 24 { 25 vex[i].clear(); 26 degree[i] = 0; 27 } 28 // queue 没有 clear()方法,故需要循环pop()数据 29 while (!q.empty()) 30 q.pop(); 31 32 for (i = 0;i<m;i++) 33 { 34 cin >> a >> b; 35 vex[a].push_back(b); 36 degree[b]++; 37 } 38 for (i=1;i<=n;i++) 39 if (degree[i] == 0) 40 q.push(i); 41 bool flag = true; 42 while (!q.empty()) 43 { 44 a = q.top();// 选取队尾元素 45 q.pop(); 46 if (flag) 47 { 48 flag = false; 49 cout << a; 50 }else 51 cout << ' ' << a; 52 for (i=0;i<vex[a].size();i++) 53 { 54 b = vex[a][i]; 55 if ((--degree[b])==0) 56 q.push(b); 57 } 58 } 59 cout << endl; 60 } 61 return 0 ; 62 }
产生冠军
没调过,已经调了一个小时了,附带上正确答案
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <vector> 5 #include <queue> 6 #include <map> 7 #include <string> 8 using namespace std; 9 10 vector<int> vex[10000]; 11 int degree[10000]; 12 queue<int> q; 13 int main() 14 { 15 int i,n,count,a; 16 string s1,s2; 17 map<string,int> m; // 映射 18 map<string,int>::iterator it; 19 while (cin>>n && n!=0) 20 { 21 count =0; 22 for (i=0;i<1000;i++) 23 { 24 vex[i].clear(); 25 degree[i] = 0; 26 } 27 for (i=0;i<n;i++) 28 { 29 // 难点:把名字和数组的i进行映射,不知道具体的队员个数 30 cin >>s1>>s2; 31 if (m.find(s1) == m.end()) 32 m.insert(pair<string,int>(s1,count++)); 33 if (m.count(s2)==0) 34 m.insert(pair<string,int>(s2,count++)); 35 vex[m[s1]].push_back(m[s2]); 36 degree[m[s2]]++; 37 // 如果插入的字符串已经出现过了则不会执行该条语句的啦!名字和索引的映射成功! 38 // for (it = m.begin() ; it!=m.end();it++) 39 // cout << (*it).first << " : " << (*it).second << endl; 40 } 41 // !!!!!! 42 m.clear(); 43 while (!q.empty()) 44 q.pop(); 45 46 for (i=0;i<m.size();i++) 47 if (degree[i] == 0) 48 q.push(i); 49 int cnt = 0; 50 while (!q.empty()) 51 { 52 a = q.front(); 53 q.pop(); 54 cnt ++; 55 for (i=0;i<vex[a].size();i++) 56 { 57 if ((--degree[vex[a][i]])==0) 58 q.push(vex[a][i]); 59 } 60 } 61 if (cnt == 1) 62 cout << "Yes" << endl; 63 else 64 cout << "No" << endl; 65 } 66 return 0 ; 67 }
正确答案:
1 #include <stdio.h> 2 #include <string.h> 3 #include <string> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <iostream> 8 #include <map> 9 using namespace std; 10 map<string,int> name; 11 vector<int> edge[10000]; 12 queue<int> Q; 13 int indegree[10000]; 14 int main(){ 15 int n,i,j; 16 while(~scanf("%d",&n)&&n){ 17 for(i=0;i<10000;i++) { edge[i].clear();indegree[i]=0;} 18 while(!Q.empty()) Q.pop(); 19 i=0; 20 name.clear(); 21 for(j=0;j<n;j++){ 22 string a,b; 23 cin>>a>>b; 24 if(name.find(a)==name.end()) { name[a]=i++;} 25 if(name.find(b)==name.end()) { name[b]=i++;} 26 edge[name[a]].push_back(name[b]); 27 indegree[name[b]]++; 28 } 29 int num=i; 30 int cnt=0; 31 for(i=0;i<num;i++) 32 if(indegree[i]==0) cnt++; 33 /* 34 int num=i; 35 for(i=0;i<n;i++){ 36 if(indegree[i]==0) Q.push(i); 37 } 38 int cnt=0; 39 while(!Q.empty()){ 40 int nowp=Q.front(); 41 Q.pop(); 42 cnt++; 43 for(i=0;i<edge[nowp].size();i++){ 44 indegree[edge[nowp][i]]--; 45 if(indegree[edge[nowp][i]]==0) Q.push(edge[nowp][i]); 46 } 47 } 48 */ 49 if(cnt==1) puts("Yes"); 50 else puts("No"); 51 } 52 return 0; 53 }
最短路径问题
1 #include <iostream> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <vector> 5 #include <queue> 6 #include <map> 7 #include <string> 8 #define INFINITY 65535 9 using namespace std; 10 11 struct edge 12 { 13 int nextnode; 14 int d;//距离 15 int p;//花费 16 }; 17 vector<edge> vex[1001]; // 邻接链表,vex[i]为一个向量,记录与vex[i]邻接的所有边的信息(邻接点以及权值) 18 bool S[1001]; // s集合为已经找到最短路径的顶点集合 19 int dis[1001]; // 距离向量 20 int cost[1001];//花费向量 21 int main() 22 { 23 int n,m,d,p,s,t,i,j,k,min,a,b; 24 edge e; 25 while (cin >> n >> m && n!=0 && m!=0) 26 { 27 // 1. 初始化邻接链表、距离向量和集合S,现在未知初始顶点和结束顶点是什么 28 for (i=1;i<=n;i++) 29 { 30 vex[i].clear(); 31 S[i] = false; 32 dis[i] = INFINITY; // -1代表不可达 33 cost[i] = 0; 34 } 35 // 2.输入数据 36 for (i=0;i<m;i++) 37 { 38 cin >> a>>b>>d>>p; 39 e.d = d;e.p = p; 40 e.nextnode = b; 41 vex[a].push_back(e); 42 e.nextnode = a; 43 vex[b].push_back(e); 44 // 无向图,加入两条边 45 } 46 cin >> s >>t ; 47 // 确定初始顶点后,初始化集合S和dis 48 S[s] = true;dis[s] = 0; // 初始顶点自己到自己为0 49 for (i=0;i<vex[s].size();i++) 50 { 51 int num = vex[s][i].nextnode; 52 dis[num] = vex[s][i].d; 53 cost[num] = vex[s][i].p; 54 } 55 // 循环找到最短路径 56 for (i=2;i<=n;i++) 57 { 58 // 找到当前最短路径和顶点 59 min = INFINITY; 60 for (j=1;j<=n;j++) 61 { 62 if (!S[j] && min > dis[j] ) 63 { 64 k = j; 65 min = dis[j]; 66 } 67 } 68 S[k] = true; 69 70 for (j = 0;j<vex[k].size();j++) 71 { 72 int node = vex[k][j].nextnode; 73 if (S[node]) 74 continue; 75 76 if (min+vex[k][j].d<dis[node] || min+vex[k][j].d == dis[node] && cost[node] > cost[k]+vex[k][j].p) 77 { 78 dis[node] = min + vex[k][j].d; 79 cost[node] = cost[k]+vex[k][j].p; 80 } 81 } 82 83 } 84 cout << dis[t]<<" "<< cost[t]<< endl; 85 86 } 87 88 return 0 ; 89 }
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步