資料結構第6節(圖(上))
圖(上)
在前幾節說過了樹結構,從這一節開始,開始進入圖。
什麼是圖
圖是由若干給定的頂點及連線兩頂點的邊所構成的圖形,這種圖形通常用來描述某些事物之間的某種特定關係。 頂點用於代表事物,連線兩頂點的邊則用於表示兩個事物間具有這種關係。
圖的表示(鄰接矩陣表示法)
有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 }
{ 3 5 }
{ 6 }
題解:
直接對所有節點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;
}