数据结构与算法(五)图

基础知识:

一.图的存储

1.十字链表

十字链表是邻接表和逆邻接表的结合,能够由firstinarc出发得到vi的整个邻接表,由firstoutarc出发得到vi的整个逆邻接表。构建的方法就是每读入一条边E,就找到这条边所在的两个顶点,设从v1到v2。那么这条边的headvex(表示头节点)设为1(v1的序号),headnextarc指向下一个以v1为头的边的(v1.firstoutarc),将这条边的tailvex设为2,tailnextarc指向下一个以v2为尾的边(v2.firstinarc),然后v1.firstoutarc指向E,v2的firstinarc指向E。这样就插入了一条边,并更新了v1的第一条出边和v2的第一条入边。

二.(单源)最短路径

1.Dijastra算法

典型的贪心算法,目的是计算单源最短路。

(1)声明一个数组dis[N]来保存源点A到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合S。初始时,dist[A]=0。若对于顶点A存在能直接到达B1的边(A,B1),则把dis[B1]设为w(A, B1),同理设置w(A,B2)...同时把A不能直接到达的顶点的路径长度设为无穷大。初始状态,集合S只有顶点A。

(2)从dis数组选择最小值,则该值就是源点A到该值对应的顶点的最短路径,并且把该点加入到T中。用新加入的顶点更新dist值,若dist[Bi]+w(Bi,Cj)<dist[Cj],则dist[Cj]=dist[Bi]+w(Bi,Cj)。

(3)重复(2),直到T中包含了图的所有顶点。

Dijastra算法不能处理负权值,也不能有环,时间复杂度为O(n^2),可以用最小堆优化,时间复杂度降为O((n+e)loge)。

2.Floyd算法

Floyd:引入两个矩阵,矩阵S中的元素a[i][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素b[i][j],表示顶点i到顶点j经过了b[i][j]记录的值所表示的顶点。假设图G中顶点个数为N,则需要对矩阵D和矩阵P进行N次更新。初始时,矩阵D中顶点a[i][j]的距离为顶点i到顶点j的权值;如果i和j不相邻,则a[i][j]=∞,矩阵P的值为顶点b[i][j]的j的值。 接下来开始,对矩阵D进行N次更新。第1次更新时,如果”a[i][j]的距离” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i与j之间经过第1个顶点的距离”),则更新a[i][j]为”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新时,如果”a[i][j]的距离” > “a[i][k-1]+a[k-1][j]”,则更新a[i][j]为”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之后,操作完成!

3.SPFA算法

SPFA:用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

详见:https://blog.csdn.net/qq_35644234/article/details/60875818

三.最小生成树

1.Prim算法

(1)所有顶点集合为V,初始状态U={u0},TE={}。

(2)在所有u∈U和v∈V-U中选择权值最小的边(u,v),将v加入U中。

(3)重复(2)直到U=V。

Prim算法时间复杂度O(n^2)适合稠密图。

2.Kruskal算法

(1)先将所有边按权值由小到大排序;

(2)从边集中选出第一条边(即权值最小的边),如果与树中现有的边不构成环,则将其加入树中;

(3)重复(2)直至树中有n-1条边。

Kruskal算法时间复杂度为O(eloge)适合稀疏图。

1.兔子与樱花

描述

很久很久之前,森林里住着一群兔子。有一天,兔子们希望去赏樱花,但当他们到了上野公园门口却忘记了带地图。现在兔子们想求助于你来帮他们找到公园里的最短路。

输入

输入分为三个部分。
第一个部分有P+1行(P<30),第一行为一个整数P,之后的P行表示上野公园的地点。
第二个部分有Q+1行(Q<50),第一行为一个整数Q,之后的Q行每行分别为两个字符串与一个整数,表示这两点有直线的道路,并显示二者之间的矩离(单位为米)。
第三个部分有R+1行(R<20),第一行为一个整数R,之后的R行每行为两个字符串,表示需要求的路线。

输出

输出有R行,分别表示每个路线最短的走法。其中两个点之间,用->(矩离)->相隔。

样例输入

6
Ginza
Sensouji
Shinjukugyoen
Uenokouen
Yoyogikouen
Meijishinguu
6
Ginza Sensouji 80
Shinjukugyoen Sensouji 40
Ginza Uenokouen 35
Uenokouen Shinjukugyoen 85
Sensouji Meijishinguu 60
Meijishinguu Yoyogikouen 35
2
Uenokouen Yoyogikouen
Meijishinguu Meijishinguu

样例输出

Uenokouen->(35)->Ginza->(80)->Sensouji->(60)->Meijishinguu->(35)->Yoyogikouen
Meijishinguu

解析:由于输入的点较少,故可以采用弗洛伊德算法,更新所有的dist[i][j]的最短路线。难点是如何记录路径,可以维护一个point数组,point[i][j]表示从i到j最短路径i的下一个点。

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<map>
#include<string>
#include<cstring>
#include<string.h>
#include<vector>
#define REP(i,a,b) for(int i=(a);i<=(b);++i)
#define PER(i,a,b) for(int i=(a);i>=(b);--i)
#define ll long long
#define mk make_pair
#define INF 1000000
using namespace std;
map<string, int>map1;
map<int, string>map2;
int dist[35][35];
int point[35][35];
int p, q, r, d;
string t1, t2;
void init()
{
    //dist[i][j]表示i到j的最短距离
    REP(i,0,34){
        REP(j,0,34){
            dist[i][j]=INF;
        }
        dist[i][i]=0;
    }
    //point[i][j]表示从i到j的最短路线从i出发的下一个结点
    REP(i,0,34)
        REP(j,0,34)
            point[i][j]=j;
    //将地点编号
    scanf("%d",&p);
    REP(i,0,p-1){
        cin>>t1;
        map1[t1]=i;
        map2[i]=t1;
    }
    //初始化距离
    scanf("%d",&q);
    REP(i,0,q-1){
        cin>>t1>>t2>>d;
        dist[map1[t1]][map1[t2]]=dist[map1[t2]][map1[t1]]=d;
    }
}
//求出2点间最短距离,弗洛伊德算法
void floyd()
{
    REP(k,0,p-1)
        REP(i,0,p-1)
            REP(j,0,p-1)
                if(dist[i][j]>dist[i][k]+dist[k][j]){
                    dist[i][j]=dist[i][k]+dist[k][j];
                    //维护从i到j的最短距离从i出发的下一个结点
                    point[i][j]=point[i][k];
                }
}
int main()
{
    init();
    floyd();
    cin>>r;
    while(r--){
        cin>>t1>>t2;
        //从起点出发的下一个结点
        int k=point[map1[t1]][map1[t2]];
        if(t1!=t2){
            cout<<t1<<"->("<<dist[map1[t1]][k]<<")->";
            while(k!=map1[t2]){
                cout<<map2[k]<<"->("<<dist[k][point[k][map1[t2]]]<<")->";
                //更新下一结点
                k=point[k][map1[t2]];
            }
        }
        cout<<t2<<endl;
    }
    return 0;
}

2.舰队、海域出击!

描述

作为一名海军提督,Pachi将指挥一支舰队向既定海域出击!
Pachi已经得到了海域的地图,地图上标识了一些既定目标和它们之间的一些单向航线。如果我们把既定目标看作点、航线看作边,那么海域就是一张有向图。不幸的是,Pachi是一个会迷路的提督QAQ,所以他在包含环(圈)的海域中必须小心谨慎,而在无环的海域中则可以大展身手。
受限于战时的消息传递方式,海域的地图只能以若干整数构成的数据的形式给出。作为舰队的通讯员,在出击之前,请你告诉提督海域中是否包含环。

例如下面这个海域就是无环的:

而下面这个海域则是有环的(C-E-G-D-C):

 

输入

每个测试点包含多组数据,每组数据代表一片海域,各组数据之间无关。
第一行是数据组数T。
每组数据的第一行两个整数N,M,表示海域中既定目标数、航线数。
接下来M行每行2个不相等的整数x,y,表示从既定目标x到y有一条单向航线(所有既定目标使用1~N的整数表示)。
描述中的图片仅供参考,其顶点标记方式与本题数据无关。

1<=N<=100000,1<=M<=500000,1<=T<=5
注意:输入的有向图不一定是连通的。

输出

输出包含T行。
对于每组数据,输出Yes表示海域有环,输出No表示无环。

样例输入

2
7 6
1 2
1 3
2 4
2 5
3 6
3 7
12 13
1 2
2 3
2 4
3 5
5 6
4 6
6 7
7 8
8 4
7 9
9 10
10 11
10 12

样例输出

No
Yes

提示

输入中的两张图就是描述中给出的示例图片。

解析:判断图中是否有环,利用拓扑排序,首先计算所有点的入度,访问入度为零的点,然后将所有与该点相连的点入度减1。递归访问其他入度为零的点,直到所有递归结束如果还有没被访问到的点,则存在环。

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<map>
#include<string>
#include<cstring>
#include<string.h>
#include<vector>
#define REP(i,a,b) for(int i=(a);i<=(b);++i)
#define PER(i,a,b) for(int i=(a);i>=(b);--i)
#define ll long long
#define mk make_pair
#define MAX 100005
using namespace std;
int visit[MAX];
int In[MAX];
vector<int> To[MAX];
int N,M;
void dfs(int x){
    visit[x]=1;
    REP(i,0,(int)To[x].size()-1){
        if(!visit[To[x][i]]){
            In[To[x][i]]--;
            if(!In[To[x][i]])dfs(To[x][i]);
        }
    }
}
int main(){
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d%d",&N,&M);
        REP(i,1,N){visit[i]=0;In[i]=0;To[i].clear();}
        REP(i,1,M){
            int x,y;scanf("%d%d",&x,&y);
            To[x].push_back(y);
            In[y]++;
        }
        REP(i,1,N){
            if(!visit[i]&&In[i]==0){
                dfs(i);
            }
        }
        bool flag=0;
        REP(i,1,N)if(!visit[i])flag=1;
        if(!flag)printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}

3.The Unique MST

描述

Given a connected undirected graph, tell if its minimum spanning tree is unique.

Definition 1 (Spanning Tree): Consider a connected, undirected graph G = (V, E). A spanning tree of G is a subgraph of G, say T = (V', E'), with the following properties:
1. V' = V.
2. T is connected and acyclic.

Definition 2 (Minimum Spanning Tree): Consider an edge-weighted, connected, undirected graph G = (V, E). The minimum spanning tree T = (V, E') of G is the spanning tree that has the smallest total cost. The total cost of T means the sum of the weights on all the edges in E'.

输入

The first line contains a single integer t (1 <= t <= 20), the number of test cases. Each case represents a graph. It begins with a line containing two integers n and m (1 <= n <= 100), the number of nodes and edges. Each of the following m lines contains a triple (xi, yi, wi), indicating that xi and yi are connected by an edge with weight = wi. For any two nodes, there is at most one edge connecting them.

输出

For each input, if the MST is unique, print the total cost of it, or otherwise print the string 'Not Unique!'.

样例输入

2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2

样例输出

3
Not Unique!

解析:首先需要熟悉最小生成树的两种算法,kruskal算法和prim算法。方法一:首先建立任意一个最小生成树,扫描这棵树的边,如果在未用到的边中有与此边相同权值的边,则强制使用这条边,再构造最小生成树,如果

4.丛林中的路

描述


 

热带岛屿Lagrishan的首领现在面临一个问题:几年前,一批外援资金被用于维护村落之间的道路,但日益繁茂的丛林无情的侵蚀着村民的道路,导致道路维修开销巨大,长老会不得不放弃部分道路的维护。上图左侧图显示的是正在使用道路的简图以及每条路每个月的维修费用(单位为aacms)。现在长老会需要提出一种方案,即需要保证村落之间都可以互相到达,又要将每个月的道路维修费用控制在最小。村子编号为从A到I。上图右侧显示的方案最小维修开销为216 aacms每月。

输入

输入包含1~100个数据集,最后一行为0.每个数据集第一行为村落数目n, 1 < n < 27,依次用字母表的前n个字母标记。接下来有n-1行,每行的第一个数据便是按字母顺序排列的村子编号(不包括最后一个村庄)。每个村庄后面的数据k代表该村庄通往编号在其之后的村庄的道路数目,如A 2 B 12 I 25,代表A村庄有2个编号在A之后的村庄和其相连。若k大于0,k后面会依次给出这k个村庄的编号以及各自到起始村庄的道路维修费用,如A 2 B 12 I 25,代表A和B之间道路维修费用为12, A和I之间道路维修费用为25(维修费用为不超过100的正整数).路的总数目不超过75条,每个村庄到其他村庄不会有超过15条路(包括编号在其之前和之后的)。

输出

每个数据集有一个输出:针对解决方案每个月维修道路的小费用。
提示:蛮力算法虽能找出解决方案,但将会超出时间限制。

样例输入

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0

样例输出

216
30

 

5.Window Pains

描述

Boudreaux likes to multitask, especially when it comes to using his computer. Never satisfied with just running one application at a time, he usually runs nine applications, each in its own window. Due to limited screen real estate, he overlaps these windows and brings whatever window he currently needs to work with to the foreground. If his screen were a 4 x 4 grid of squares, each of Boudreaux's windows would be represented by the following 2 x 2 windows: 

11..
11..
....
....
.22.
.22.
....
....
..33
..33
....
....
....
44..
44..
....
....
.55.
.55.
....
....
..66
..66
....
....
....
77..
77..
....
....
.88.
.88.
....
....
..99
..99

When Boudreaux brings a window to the foreground, all of its squares come to the top, overlapping any squares it shares with other windows. For example, if window 1and then window 2 were brought to the foreground, the resulting representation would be: 

122?
122?
????
????
If window 4 were then brought to the foreground: 
122?
442?
44??
????

. . . and so on . . . 
Unfortunately, Boudreaux's computer is very unreliable and crashes often. He could easily tell if a crash occurred by looking at the windows and seeing a graphical representation that should not occur if windows were being brought to the foreground correctly. And this is where you come in . . . 

输入

Input to this problem will consist of a (non-empty) series of up to 100 data sets. Each data set will be formatted according to the following description, and there will be no blank lines separating data sets. 

A single data set has 3 components: 

  1. Start line - A single line: 
    START
     
  2. Screen Shot - Four lines that represent the current graphical representation of the windows on Boudreaux's screen. Each position in this 4 x 4 matrix will represent the current piece of window showing in each square. To make input easier, the list of numbers on each line will be delimited by a single space. 
  3. End line - A single line: 
    END


After the last data set, there will be a single line: 
ENDOFINPUT

Note that each piece of visible window will appear only in screen areas where the window could appear when brought to the front. For instance, a 1 can only appear in the top left quadrant. 

输出

For each data set, there will be exactly one line of output. If there exists a sequence of bringing windows to the foreground that would result in the graphical representation of the windows on Boudreaux's screen, the output will be a single line with the statement: 

THESE WINDOWS ARE CLEAN

Otherwise, the output will be a single line with the statement: 
THESE WINDOWS ARE BROKEN
 

样例输入

START
1 2 3 3
4 5 6 6
7 8 9 9
7 8 9 9
END
START
1 1 3 3
4 1 3 3
7 7 9 9
7 7 9 9
END
ENDOFINPUT

样例输出

THESE WINDOWS ARE CLEAN
THESE WINDOWS ARE BROKEN

 

6.兔子与星空

描述

很久很久以前,森林里住着一群兔子。兔子们无聊的时候就喜欢研究星座。如图所示,天空中已经有了n颗星星,其中有些星星有边相连。兔子们希望删除掉一些边,然后使得保留下的边仍能是n颗星星连通。他们希望计算,保留的边的权值之和最小是多少?

 

 

输入

第一行只包含一个表示星星个数的数n,n不大于26,并且这n个星星是由大写字母表里的前n个字母表示。接下来的n-1行是由字母表的前n-1个字母开头。最后一个星星表示的字母不用输入。对于每一行,以每个星星表示的字母开头,然后后面跟着一个数字,表示有多少条边可以从这个星星到后面字母表中的星星。如果k是大于0,表示该行后面会表示k条边的k个数据。每条边的数据是由表示连接到另一端星星的字母和该边的权值组成。权值是正整数的并且小于100。该行的所有数据字段分隔单一空白。该星星网络将始终连接所有的星星。该星星网络将永远不会超过75条边。没有任何一个星星会有超过15条的边连接到其他星星(之前或之后的字母)。在下面的示例输入,数据是与上面的图相一致的。

输出

输出是一个整数,表示最小的权值和

样例输入

9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35

样例输出

216

提示

考虑看成最小生成树问题,注意输入表示。

 

7.拓扑排序

描述

给出一个图的结构,输出其拓扑排序序列,要求在同等条件下,编号小的顶点在前

输入

若干行整数,第一行有2个数,分别为顶点数v和弧数a,接下来有a行,每一行有2个数,分别是该条弧所关联的两个顶点编号

输出

若干个空格隔开的顶点构成的序列(用小写字母)

样例输入

6 8
1 2
1 3
1 4
3 2
3 5
4 5
6 4
6 5

样例输出

v1 v3 v2 v6 v4 v5

 

8.地震之后

描述

  2008年地震之后,坚强县受灾严重,该县通信线路也收到了致命的打击,县总部为了能够及时的向各村的发送消息,命令小强去解决一下这个问题。

  小强经过调查发现,为了能够快速的实现通信,当务之急是能够建立起一条从总部可以向各个村单向发送信息的通信系统。由于灾后情况紧急,不是每一个村之间都能够快速的建立起通信线路,只有有限的村比如村A和村B间可以建立单向的从A向B发送信息的通信线路,小强现在的问题就是要找出一个合适的方案,使从总部发送出的信息能够通到各村,而用的总的通信线路是最短的(毕竟情况危急,物质短缺)。

输入

输入包括多组的测试数据。
对于每一组测试数据:
1、第一行包括两个整数N(1<=N<=100),M(M<<=10000),N表示通信网络中村子的数量,M表示在这些村子中,可以有M条通信线路建起来。
2、接下来N行,每行两个整数xi,yi,代表着这个村子的X轴,Y轴的坐标(xi,yi)
3、接下来M行,每行两个整数c1(1<=c1<=N),c2(1<=c2<=N),代表着从村c1到村c2可以建一个单向线路。

注:两村之间的直线段距离(通信线路长度),即为两点间的欧式距离。
县总部所在的村假设都在编号为1的村。
输入以文件终止为结束。

输出

对于每一组的测试数据,输出完全的一行,值为最短的总长度,结果保留两位小数。
如果不能建立一个单向的临时网络,输出"NO".

样例输入

4 6
3 6
4 6
3 4
7 20
1 2
1 3
2 3
3 4
3 1
3 2
4 3
0 0
1 0
1 1
1 2
1 3
4 1
2 3

样例输出

19.49
NO

 

9.欧拉回路

描述

欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路

给定一个无向图,请判断该图是否存在欧拉回路

输入

输入数据包含若干测试用例
每个测试用例的第一行是两个正整数,分别表示图的节点数N(1 < N < 1000)和边数M
随后的M行对应M条边,每行有两个正整数,分别表示这条边上的两个节点的编号(节点编号从1到N)
当N为0时输入结束

输出

每个测试用例的输出占一行,若存在欧拉回路则输出1,否则输出0

样例输入

3 3
1 2
1 3
2 3
3 2
1 2
2 3
0

样例输出

1
0

 

10.道路

描述

N个以 1 ... N 标号的城市通过单向的道路相连:。每条道路包含两个参数:道路的长度和需要为该路付的通行费(以金币的数目来表示)

Bob and Alice 过去住在城市 1.在注意到Alice在他们过去喜欢玩的纸牌游戏中作弊后,Bob和她分手了,并且决定搬到城市N。他希望能够尽可能快的到那,但是他囊中羞涩。我们希望能够帮助Bob找到从1到N最短的路径,前提是他能够付的起通行费。

输入

第一行包含一个整数K, 0 <= K <= 10000, 代表Bob能够在他路上花费的最大的金币数。第二行包含整数N, 2 <= N <= 100, 指城市的数目。第三行包含整数R, 1 <= R <= 10000, 指路的数目. 
接下来的R行,每行具体指定几个整数S, D, L 和 T来说明关于道路的一些情况,这些整数之间通过空格间隔: 
S is 道路起始城市, 1 <= S <= N 
D is 道路终点城市, 1 <= D <= N 
L is 道路长度, 1 <= L <= 100 
T is 通行费 (以金币数量形式度量), 0 <= T <=100
注意不同的道路可能有相同的起点和终点。

输出

输入结果应该只包括一行,即从城市1到城市N所需要的最小的路径长度(花费不能超过K个金币)。如果这样的路径不存在,结果应该输出-1。

样例输入

5
6
7
1 2 2 3
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2

样例输出

11

 

11.社交网络

描述

随着社交平台的兴起,人们之间的沟通变得越来越密切。通过Facebook的分享功能,只要你是对方的好友,你就可以转发对方的状态,并且你的名字将出现在“转发链”上。经过若干次转发以后,很可能A分享了一条好友C的状态,而C的这条状态实际上是分享B的,但A与B可能并不是好友,即A通过C间接分享了B的状态。
给定你N个人之间的好友关系,好友关系一定是双向的。只要两个人是好友,他们就可以互相转发对方的状态,无论这条状态是他自己的,还是他转发了其他人的。现在请你统计,对于每两个人,他们是否有可能间接转发对方的状态。

输入

第一行1个整数N(1<=N<=300)。
接下来N行每行N个整数,表示一个N*N的01矩阵,若矩阵的第i行第j列是1,表示这两个人是好友,0则表示不是好友。
保证矩阵的主对角线上都是1,并且矩阵关于主对角线对称。

输出

一个N*N的01矩阵,若矩阵的第i行第j列是1,表示这两个人可能间接转发对方的状态,0则表示不可能。

样例输入

5
11000
11100
01100
00011
00011

样例输出

11100
11100
11100
00011
00011

提示

在输入数据中,1与2是好友,2与3是好友,4与5是好友。
因此1、2、3有可能互相转发状态;4、5有可能互相转发状态。这两组人之间则不可能。

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值