数据结构第六节(图(上))
图(上)
在前几节实现树结构,从这一节开始,开始进入图结构。
什么是图
图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。 顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系。
图的表示(邻接矩阵表示法)
有1~N个节点,很自然的想到,可以构建一个二维数组G[N-1][N-1],令G[i][j]=1或0,不为零时表示第i个节点有指向第j个节点的路径。在一个无向图中,必有G[i][j] = G[j][i],因为它是对称的。对于无向图为了节省空间可以只开一个三角形的二维数组,只保留下三角。
对于带权路径图,只需将G[i][j]设置为权值。
优点:
- 可以很快的判断一对顶点之间是否存在边。
- 可以很快找到一个节点的所有邻接点。
- 可以很方便的计算一个节点的度。
缺点:
- 在构建稀疏图(点有很多但边却很少)时,空间浪费很严重。
- 在构建稀疏图时数边时,时间浪费严重。
代码实现:
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#define WeightType int
#define MAXSIZE 100
#define DataType int
#define Vertex int
using namespace std;
//Use the adjacency matrix to represent the graph
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
WeightType graph[MAXSIZE][MAXSIZE];
DataType Data[MAXSIZE];
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
G->graph[i][j] = 0;
}
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
G->graph[e->begin][e->end] = e->weight;
//If it is an undirected graph, you need to add the following
G->graph[e->end][e->begin] = e->weight;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf_s("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf_s("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin, end, weight));
}
return G;
}
int main()
{
Graph G = BuildGraph();
for (int i = 1; i <= G->Nvertex; i++)
{
for (int j = 1; j <= G->Nvertex; j++)
{
if (G->graph[i][j] != 0) {
printf("%d->%d\n", i, j);
}
}
}
return 0;
}
图的表示(邻接表表示法)
有1~N个节点,构建一个一维指针数组G[N],G[i]作为第i个节点的头节点,将所有与其相邻的节点,串成一条链表。
优点:
- 可以很快找到一个节点的所有邻接点。
- 可以节省稀疏图的使用空间。
- 可以很方便的计算一个节点的出度。
缺点:
- 找一个节点的出度会很难。
- 判断一对顶点之间是否存在边很费时间。
代码实现:
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#define WeightType int
#define MAXSIZE 100
#define DataType int
#define Vertex int
using namespace std;
//Use the adjacency List to represent the graph
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {
Vertex Adjv;
WeightType Wight;
PtrToAdjVNode Next;
};
typedef struct VNode AdjList[MAXSIZE];
struct VNode
{
PtrToAdjVNode first;
DataType Data;
};
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
AdjList graph;
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i <= G->Nvertex; i++)
{
G->graph[i].first = NULL;
}
return G;
}
//insert edge
void InsertEdge(Graph G,Edge e) {
PtrToAdjVNode newnode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newnode->Wight = e->weight;
newnode->Adjv = e->end;
newnode->Next = G->graph[e->begin].first;
G->graph[e->begin].first = newnode;
//If it is an undirected graph, you need to add the following
newnode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newnode->Wight = e->weight;
newnode->Adjv = e->begin;
newnode->Next = G->graph[e->end].first;
G->graph[e->end].first = newnode;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf_s("%d %d", &Nvertex, &Nedge);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf_s("%d %d %d",&begin,&end,&weight);
InsertEdge(G, BuildEdge(begin, end, weight));
}
return G;
}
int main()
{
Graph G = BuildGraph();
for (int i = 1; i <= G->Nvertex; i++)
{
PtrToAdjVNode temp = G->graph[i].first;
while (temp)
{
printf("%d->%d\n", i, temp->Adjv);
temp = temp->Next;
}
}
return 0;
}
图的遍历
广度优先遍历(BFS)
与树的层序遍历相似,对树的层序遍历是一层一层的从左向右,从上到下入队又出队。同样的对图的广度优先遍历,首先从一个节点开始,将所有他指向的节点入队,出队时将出队节点所指向的节点入队。与树的遍历的不同的是,图中节点交错,一个节点不止有唯一一个节点指向他,故用建立一个bool数组,默认是未访问过(false),当访问过后该节点就将其设置为true,一旦某个节点已经被访问过将不再对其入队。
#define ElementType Vertex
typedef struct QNode* Queue;
struct QNode
{
ElementType Data[MAXSIZE];
int front;
int rear;
};
Queue makeempty() {
Queue Q = (Queue)malloc(sizeof(struct QNode));
Q->front = 0;
Q->rear = 0;
return Q;
}
void QueueAdd(ElementType value, Queue ptrq) {
if ((ptrq->rear + 1) % MAXSIZE == ptrq->front) {
printf("The quque has been full.");
return;
}
ptrq->rear = (++ptrq->rear) % MAXSIZE;
ptrq->Data[ptrq->rear] = value;
}
ElementType QueueDelete(Queue ptrq) {
if (ptrq->front == ptrq->rear) {
printf("The queue has been empty.");
return -1;
}
ptrq->front = (++ptrq->front) % MAXSIZE;
return ptrq->Data[ptrq->front];
}
bool isEmpty(Queue Q) {
if (Q->front == Q->rear) {
return true;
}
else {
return false;
}
}
//BFS
void BFS(Graph G, Vertex x, bool b[]) {
//如果已经访问过直接退出
if (b[x]) {
return;
}
b[x] = true;
Queue Q = makeempty();
QueueAdd(x,Q);
while (!isEmpty(Q))
{
Vertex v = QueueDelete(Q);
printf("%d\n", v);
for (int i = 0; i < G->Nvertex; i++)
{
if (G->graph[v][i] != 0 && b[i] != true) {
b[i] = true;
QueueAdd(i, Q);
}
}
}
}
深度优先遍历(DFS)
顾名思义,就是优先了往深了走,直到不能走了回退,看是否有其他的路可走,可以用一个递归的形式去描绘,与树的遍历的不同,图中节点交错,一个节点不止有唯一一个节点指向他,故用一个bool数组,默认是未访问过(false),当访问过后该节点就将其设置为true。
void DFS(Graph G,Vertex x,bool b[]) {
//如果已经访问过直接退出
if (b[x]) {
return;
}
printf("%d\n", x);
b[x] = true;
for (int i = 0; i < G->Nvertex; i++)
{
if (G->graph[x][i] != 0 && b[i] != true) {
DFS(G, i, b);
}
}
}
课后练习题(3个小题)
06-图1 列出连通集 (25point(s))
给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。
输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
输出格式:
按照"{ v1,v2....vk}"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。
输入样例:
8 6
0 7
0 1
2 0
4 1
2 4
3 5
输出样例:
{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
题解:
直接对所有节点DFS和BFS输出所有与其相连的点。(当然访问过的点就不输出了 )。
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include<stdbool.h>
#define WeightType int
#define MAXSIZE 1000
#define DataType int
#define Vertex int
#define ElementType Vertex
using namespace std;
int size;
typedef struct QNode* Queue;
struct QNode
{
ElementType Data[MAXSIZE];
int front;
int rear;
};
//Use the adjacency matrix to represent the graph
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
WeightType graph[MAXSIZE][MAXSIZE];
DataType Data[MAXSIZE];
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
G->graph[i][j] = 0;
}
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
G->graph[e->begin][e->end] = e->weight;
//If it is an undirected graph, you need to add the following
G->graph[e->end][e->begin] = e->weight;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
size = Nvertex;
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d", &begin, &end);
InsertEdge(G, BuildEdge(begin, end, 1));
}
return G;
}
Queue makeempty() {
Queue Q = (Queue)malloc(sizeof(struct QNode));
Q->front = 0;
Q->rear = 0;
return Q;
}
void QueueAdd(ElementType value, Queue ptrq) {
if ((ptrq->rear + 1) % MAXSIZE == ptrq->front) {
printf("The quque has been full.");
return;
}
ptrq->rear = (++ptrq->rear) % MAXSIZE;
ptrq->Data[ptrq->rear] = value;
}
ElementType QueueDelete(Queue ptrq) {
if (ptrq->front == ptrq->rear) {
printf("The queue has been empty.");
return -1;
}
ptrq->front = (++ptrq->front) % MAXSIZE;
return ptrq->Data[ptrq->front];
}
bool isEmpty(Queue Q) {
if (Q->front == Q->rear) {
return true;
}
else {
return false;
}
}
//DFS
void DFS(Graph G, Vertex x, bool b[]) {
//如果已经访问过直接退出
if (b[x]) {
return;
}
printf("%d ", x);
b[x] = true;
for (int i = 0; i < G->Nvertex; i++)
{
if (G->graph[x][i] != 0 && b[i] != true) {
DFS(G, i, b);
}
}
}
//BFS
void BFS(Graph G, Vertex x, bool b[]) {
//如果已经访问过直接退出
if (b[x]) {
return;
}
b[x] = true;
Queue Q = makeempty();
QueueAdd(x, Q);
while (!isEmpty(Q))
{
Vertex v = QueueDelete(Q);
printf("%d ", v);
for (int i = 0; i < G->Nvertex; i++)
{
if (G->graph[v][i] != 0 && b[i] != true) {
b[i] = true;
QueueAdd(i, Q);
}
}
}
}
int main()
{
Graph G = BuildGraph();
bool b[MAXSIZE];
memset(b, false, sizeof(b));
for (int i = 0; i < size; i++)
{
if (!b[i]) {
printf("{ ");
DFS(G, i, b);
printf("}\n");
}
}
memset(b, false, sizeof(b));
for (int i = 0; i < size; i++)
{
if (!b[i]) {
printf("{ ");
BFS(G, i, b);
printf("}\n");
}
}
return 0;
}
06-图2 Saving James Bond - Easy Version (25point(s))
This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the world's most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake filled with crocodiles. There he performed the most daring action to escape -- he jumped onto the head of the nearest crocodile! Before the animal realized what was happening, James jumped again onto the next big head... Finally he reached the bank before the last crocodile could bite him (actually the stunt man was caught by the big mouth and barely escaped with his extra thick boot).
Assume that the lake is a 100 by 100 square one. Assume that the center of the lake is at (0,0) and the northeast corner at (50,50). The central island is a disk centered at (0,0) with the diameter of 15. A number of crocodiles are in the lake at various positions. Given the coordinates of each crocodile and the distance that James could jump, you must tell him whether or not he can escape.
Input Specification:
Each input file contains one test case. Each case starts with a line containing two positive integers N (≤100), the number of crocodiles, and D, the maximum distance that James could jump. Then N lines follow, each containing the (x,y) location of a crocodile. Note that no two crocodiles are staying at the same position.
Output Specification:
For each test case, print in a line "Yes" if James can escape, or "No" if not.
Sample Input 1:
14 20
25 -15
-25 28
8 49
29 15
-35 -2
5 28
27 -29
-8 -28
-20 -35
-25 -20
-13 29
-30 15
-35 40
12 12
Sample Output 1:
Yes
Sample Input 2:
4 13
-12 12
12 12
-12 -12
12 -12
Sample Output 2:
No
题解:
此题无需用图,只需要将所有位置保存下来即可,做一个DFS寻找是否有出路。首先第1步是从圆点往出跳,给定了圆盘直径为15米,因为一个点到圆上的最短距离必定是连着圆心,故在第一跳检查是否能跳到时跳跃半径应该为圆盘半径加上它的最远跳跃距离。
每次对节点做DFS,应优先判断是否可以直接跳到岸上,因为给定了池塘的边界,所以只需要用池塘的边界值(50)减去那个点的对应坐标 X轴和Y轴取最小值 (比如(31,-45)这个点,到池塘边界处最少需要跳min(50-31,50-45)=5)。
代码:
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<stdbool.h>
#include<string.h>
#define MAXSIZE 101
bool vist[MAXSIZE];
int point[MAXSIZE][2];
int maxjumpdis,N;
bool firtJump(int p[]) {
if (sqrt(pow(p[0],2) + pow(p[1], 2))<=15+maxjumpdis) {
return true;
}
return false;
}
bool jump(int p1[],int p2[]) {
if (sqrt(pow(p1[0]-p2[0], 2) + pow(p2[1]-p1[1], 2)) <= maxjumpdis) {
return true;
}
return false;
}
bool isSafe(int p[]) {
if (50-abs(p[0])<=maxjumpdis|| 50 - abs(p[1]) <= maxjumpdis) {
return true;
}
return false;
}
bool DFS(int p[]) {
if (isSafe(p)) {
return true;
}
for (int i = 0; i < N; i++)
{
if (vist[i] != true && jump(point[i],p)) {
vist[i] = true;
bool safe = DFS(point[i]);
if (safe) {
return true;
}
}
}
return false;
}
void Save007() {
for (int i = 0; i < N; i++)
{
if (vist[i]!=true&&firtJump(point[i])) {
vist[i] = true;
bool safe = DFS(point[i]);
if (safe) {
printf("Yes\n");
return;
}
}
}
printf("No\n");
}
int main() {
memset(vist, false, sizeof(vist));
scanf("%d %d", &N, &maxjumpdis);
for (int i = 0; i < N; i++)
{
scanf("%d %d", &point[i][0], &point[i][1]);
}
Save007();
return 0;
}
06-图3 六度空间 (30point(s))
“六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”
“六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。
假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比。
输入格式:
输入第1行给出两个正整数,分别表示社交网络图的结点数N(1<N≤103 ,表示人数)、边数M(≤33×N,表示社交关系数)。随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到N编号)。
输出格式:
对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。
输入样例:
10 9
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
输出样例:
1: 70.00%
2: 80.00%
3: 90.00%
4: 100.00%
5: 100.00%
6: 100.00%
7: 100.00%
8: 90.00%
9: 80.00%
10: 70.00%
题解:
对每个节点做一次BFS,在之前的题中都是用一个bool数组储存是否已经访问过,这次则使用一个int数组(用来保存一个节点到另一个节点的距离),初始将其设置为-1,读入原点时将其置为0,每次作BFS,将该距离设置为指向他的那个节点的距离+1,如果距离小于等于6就计数,到了大于6时直接退出,因为广度优先搜索的特性,出现了距离大于6的节点后,之后所有的节点距给定节点的距离都会大于6,所以就不用再继续数去浪费时间了。
代码:
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include<stdbool.h>
#define WeightType int
#define MAXSIZE 1001
#define DataType int
#define Vertex int
#define ElementType Vertex
using namespace std;
int size;
typedef struct QNode* Queue;
struct QNode
{
ElementType Data[MAXSIZE];
int front;
int rear;
};
//Use the adjacency matrix to represent the graph
typedef struct GNode* Graph;
struct GNode
{
int Nvertex;
int Nedge;
WeightType graph[MAXSIZE][MAXSIZE];
DataType Data[MAXSIZE];
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
WeightType weight;
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end, WeightType weight) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
e->weight = weight;
return e;
}
//creat empty graph
Graph CreateGraph(int VertexNum) {
Graph G = (Graph)malloc(sizeof(struct GNode));
G->Nvertex = VertexNum;
G->Nedge = 0;
for (int i = 0; i <= G->Nvertex; i++)
{
for (int j = 0; j <= G->Nvertex; j++)
{
G->graph[i][j] = 0;
}
}
return G;
}
//insert edge
void InsertEdge(Graph G, Edge e) {
G->graph[e->begin][e->end] = e->weight;
//If it is an undirected graph, you need to add the following
G->graph[e->end][e->begin] = e->weight;
G->Nedge++;
}
//build graph
Graph BuildGraph() {
int Nvertex, Nedge;
scanf("%d %d", &Nvertex, &Nedge);
size = Nvertex;
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d", &begin, &end);
InsertEdge(G, BuildEdge(begin, end, 1));
}
return G;
}
Queue makeempty() {
Queue Q = (Queue)malloc(sizeof(struct QNode));
Q->front = 0;
Q->rear = 0;
return Q;
}
void QueueAdd(ElementType value, Queue ptrq) {
if ((ptrq->rear + 1) % MAXSIZE == ptrq->front) {
printf("The quque has been full.");
return;
}
ptrq->rear = (++ptrq->rear) % MAXSIZE;
ptrq->Data[ptrq->rear] = value;
}
ElementType QueueDelete(Queue ptrq) {
if (ptrq->front == ptrq->rear) {
printf("The queue has been empty.");
return -1;
}
ptrq->front = (++ptrq->front) % MAXSIZE;
return ptrq->Data[ptrq->front];
}
bool isEmpty(Queue Q) {
if (Q->front == Q->rear) {
return true;
}
else {
return false;
}
}
//BFS
int BFScountnum(Graph G, Vertex x, int dis[]) {
//如果已经访问过直接退出
dis[x] = 0;
Queue Q = makeempty();
QueueAdd(x, Q);
int sum = 1;
while (!isEmpty(Q))
{
Vertex v = QueueDelete(Q);
for (int i = 1; i <= G->Nvertex; i++)
{
if (G->graph[v][i] != 0 && dis[i] == -1) {
dis[i] = dis[v] + 1;
if (dis[i] > 6) {
break;
}
sum ++;
QueueAdd(i, Q);
}
}
}
return sum;
}
int main()
{
Graph G = BuildGraph();
int dis[MAXSIZE];
for (int i = 1; i <= size; i++)
{
memset(dis, -1, sizeof(dis));
printf("%d: ",i);
int sum = BFScountnum(G, i, dis);
printf("%.2lf%%\n",((double)sum)/size*100);
}
return 0;
}