資料結構第七節(圖(中))
圖(中)
在上一節的時候曾說過了圖的兩種遍歷方式,在這一節將使用他們做更深層的應用,研究從一個點到另一個點的最短距離。
最短路徑問題#
單源無權圖的最短路徑#
基本思想是,按照非遞減的順序,找出各個點的最短路。
很容易想到按照非遞減的順序,也就是優先從原點開始,不斷的計算與他相距最近的點的距離,整個的過程就是一個BFS。
在bfs的過程中,我們之前是用一個布林型別的陣列來儲存一個節點是否被訪問。現在我們可以將其改成為int型別的二維陣列,同時該陣列還需要實現兩個功能,對於第I個節點Vertex i,p[ i ][ 0 ]用於儲存它到原點的距離,而p[ i ][ 1 ]用於儲存最短路徑中他被哪個節點所指。在輸出最短路徑時,我們只需要沿著它的路徑倒著走直到原點,反一下就是正確的路徑了。
//無全權圖最短路徑
int p[MAXSIZE][2];
void UnweightShortestPath(Graph G, Vertex x) {
for (int i = 0; i < MAXSIZE; i++)
{
memset(p[i], -1, sizeof(p[i]));
}
//自己到自己的距離是0
p[x][0] = 0;
Queue Q = makeempty();
QueueAdd(x, Q);
while (!isEmpty(Q))
{
Vertex v = QueueDelete(Q);
for (int i = 0; i < G->Nvertex; i++)
{
if (G->graph[v][i] != 0 && p[i][0]==-1) {
p[i][0] = p[v][0] + 1;
p[i][1] = v;
QueueAdd(i, Q);
}
}
}
int end;
scanf_s("%d", &end);
stack<int> v;
}
單源有權圖的最短路徑(Dijkstra演算法)#
Dijkstra演算法的基本演算法思想是:建立一個集合S,不斷的向該集合中新增頂點,每次移出時確定了該頂點的最短的。隨著不斷的新增,移除使得所有頂點的最短路徑都被確定為最小。(貪心演算法的思想)
具體實現是首先從原點開始,將其標記為已讀(代表該點在整個圖中最短路徑確定 ),將它的所有連線點加入集合中,並將這些點到原點的最短距離設定為權值,並標記他們指向原點(在確定了最短路徑列印時需要用)。
隨後從集合中移除距離最小的一個,將該節點標記為已讀(該點最短路徑一定已經確定,因為他是原點的連線點中距離最短的那個,如果從原點經過其他的節點到他,距離必定會更大)。
將該節點的鄰接點加入集合,並將這些點到原點的最短距離設定為移除點的最短距離加上自身到移除點的距離和自己原來最短距離中的最小值。
即dp[i]=Mindp[i]=Min{dp[i],dp[v]+weight[v][i]dp[i],dp[v]+weight[v][i]}(dp[i]dp[i]表示i點到遠點的最短距離weight[v][i]weight[v][i]v到i的距離)。
遞迴的則執行直到所有的點都被集合收入又移出結束。
注意到:在上面的演算法中我們是不斷的距離從近到遠,在圖中如果有負值出現的話,我們將會在這個負值圈內打轉。該演算法再存在負值的權值是無效的。
Copy#define INFINITY 10000000
//不同的編輯器該值並不相同,可能會報導致值為0
//find MinDis
Vertex findMin(Graph G,int dis[],bool collection[]) {
Vertex minV = 0;
int minDis = INFINITY;
for (Vertex i = 0; i < G->Nvertex; i++)
{
if (collection[i] == false && dis[i] < minDis) {
minDis = dis[i];
minV = i;
}
}
//如果最短距離被改變,說明有找到
if (minDis < INFINITY) {
return minV;
}
return notFound;
}
//Dijkstra
//注意,在該有權值圖中,應是無邊連線的兩點為正無窮大
bool Dijkstra(Graph G, Vertex x) {
//save information
int shortDis[MAXSIZE];
bool collection[MAXSIZE];
int path[MAXSIZE];
memset(shortDis, INFINITY, sizeof(shortDis));
memset(collection, false, sizeof(collection));
memset(path, -1, sizeof(path));
//先將起始點的連線點距離初始化
for (Vertex i = 0; i <= G->Nvertex; i++)
{
shortDis[i] = G->graph[x][i];
if (shortDis[i] < INFINITY) {
path[i] = x;
}
}
collection[x] = true;
shortDis[x] = 0;
while (true)
{
Vertex v = findMin(G, shortDis, collection);
if (v == notFound) {
break;
}
collection[v] = true;
for (Vertex i = 0; i <= G->Nvertex; i++)
{
if (G->graph[v][i] < INFINITY && collection[i] == false) {
//存在負值圈演算法無法正常進行
if (G->graph[v][i] < 0) {
return false;
}
/*
* 移除點的最短距離加上自身到移除點的距離小於自己原來最短距離。
*/
if (shortDis[i] > shortDis[v] + G->graph[v][i]) {
shortDis[i] = shortDis[v] + G->graph[v][i];
path[i] = v;
}
}
}
}
return true;
}
多源有權圖的最短路徑(Floyd演算法)#
在上面的方法中為了求某一點到與固定點的最短距離,使用Dijkstra演算法,現在我們要求任意兩點間的最短距離,我們當然可以將所有的點都用一次Dijkstra演算法來得到,以後更簡單更快的方法是使用Floyd演算法。
Floyd演算法的演算法思路是:
用一個二維陣列dp[ ][ ]來儲存第I個點到第J這個點的最短距離。
每次迴圈嘗試在I到J的路徑中,加入第K個節點(k = 1.....N)試試會不會讓路徑變短,如果路徑會變短即(dp[i][j]<dp[i][k]+dp[k][j]dp[i][j]<dp[i][k]+dp[k][j]),就更新dp[i][j]=dp[i][k]+dp[k][j]dp[i][j]=dp[i][k]+dp[k][j],否則不變。經過K輪迴圈之後,我們將會得到所有的任意兩點間的最短路徑同樣的 。
該演算法如何進行:
初始的時候我們令陣列儲存的是原來圖中兩點的距離,並且將自己指向自己的距離設為1。在插入第1個節點時(在程式中下標為0),我們注意到在起始點或結束點有該第一個節點的話,是不會改變的,即dp[i][0]==dp[i][0]+dp[0][0]dp[i][0]==dp[i][0]+dp[0][0]或dp[0][j]==dp[0][0]+dp[0][j]dp[0][j]==dp[0][0]+dp[0][j],注意到dp[i][j]=Min(dp[i][0]+dp[0][j],dp[i][j])dp[i][j]=Min(dp[i][0]+dp[0][j],dp[i][j])。迴圈著往下走我們發現,每次dp[i][j]dp[i][j]的值改變,一定是由第K行和第K列決定,而該列在此輪外迴圈中,值絕對不會發生改變。
同上面的演算法一樣該演算法在存在負值圈的時候,也會出問題。
Copybool Floyd(Graph G) {
int dp[MAXSIZE][MAXSIZE];
int path[MAXSIZE][MAXSIZE];
for (int i = 0; i <= G->Nvertex; i++)
{
for (int j = 0; j <= G->Nvertex; j++)
{
if (i == j) {
dp[i][j] = 0;
}
else {
dp[i][j] = G->graph[i][j];
}
}
}
for (int k = 0; k < G->Nvertex; k++)
{
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
if (dp[i][j] > dp[i][k] + dp[k][j]) {
dp[i][j] = dp[i][k] + dp[k][j];
if (i == j && dp[i][j] < 0) {
return false;
}
path[i][j] = k;
}
}
}
}
return true;
}
課後練習題(三個小題)#
07-圖4 哈利·波特的考試 (25point(s))#
哈利·波特要考試了,他需要你的幫助。這門課學的是用魔咒將一種動物變成另一種動物的本事。例如將貓變成老鼠的魔咒是haha,將老鼠變成魚的魔咒是hehe等等。反方向變化的魔咒就是簡單地將原來的魔咒倒過來念,例如ahah可以將老鼠變成貓。另外,如果想把貓變成魚,可以通過念一個直接魔咒lalala,也可以將貓變老鼠、老鼠變魚的魔咒連起來念:hahahehe。
現在哈利·波特的手裡有一本教材,裡面列出了所有的變形魔咒和能變的動物。老師允許他自己帶一隻動物去考場,要考察他把這隻動物變成任意一隻指定動物的本事。於是他來問你:帶什麼動物去可以讓最難變的那種動物(即該動物變為哈利·波特自己帶去的動物所需要的魔咒最長)需要的魔咒最短?例如:如果只有貓、鼠、魚,則顯然哈利·波特應該帶鼠去,因為鼠變成另外兩種動物都只需要念4個字元;而如果帶貓去,則至少需要念6個字元才能把貓變成魚;同理,帶魚去也不是最好的選擇。
輸入格式:
輸入說明:輸入第1行給出兩個正整數N (≤100)和M,其中N是考試涉及的動物總數,M是用於直接變形的魔咒條數。為簡單起見,我們將動物按1~N編號。隨後M行,每行給出了3個正整數,分別是兩種動物的編號、以及它們之間變形需要的魔咒的長度(≤100),數字之間用空格分隔。
輸出格式:
輸出哈利·波特應該帶去考場的動物的編號、以及最長的變形魔咒的長度,中間以空格分隔。如果只帶1只動物是不可能完成所有變形要求的,則輸出0。如果有若干只動物都可以備選,則輸出編號最小的那隻。
輸入樣例:
6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
輸出樣例:
4 70
題解:
建立一張圖後,直接弗洛伊德,因為對該題,要從一隻動物變成其他動物,所以是一個無向連通圖。弗洛伊德後如果發現從一節點到另一節點無法達到(值為無窮大 )說明只帶一條動物必定無法完成任務,直接返回0。可以帶抑制動物完成任務的話,依次掃描每隻動物變成其他動物的最大咒語長度,取最小值輸出。
#include <cstdio>
#include<algorithm>
#include <stdlib.h>
#include <string.h>
#define WeightType int
#define MAXSIZE 100
#define Vertex int
#define notFound -1
#define INFINITY 10000000
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];
};
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] = INFINITY;
}
}
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);
Graph G = CreateGraph(Nvertex);
for (int i = 0; i < Nedge; i++)
{
Vertex begin, end;
WeightType weight;
scanf("%d %d %d", &begin, &end, &weight);
InsertEdge(G, BuildEdge(begin-1, end-1, weight));
}
return G;
}
bool Floyd(Graph G,int dp[][MAXSIZE]) {
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
if (i == j) {
dp[i][j] = 0;
}
else {
dp[i][j] = G->graph[i][j];
}
}
}
for (int k = 0; k < G->Nvertex; k++)
{
for (int i = 0; i < G->Nvertex; i++)
{
for (int j = 0; j < G->Nvertex; j++)
{
if (dp[i][j] > dp[i][k] + dp[k][j]) {
dp[i][j] = dp[i][k] + dp[k][j];
}
}
}
}
return true;
}
void findMin(Graph G,int dp[][MAXSIZE]) {
int mindis = INFINITY;
int minpat;
for (int i = 0; i < G->Nvertex; i++)
{
int linemindis = 0;
for (int j = 0; j < G->Nvertex; j++)
{
if (dp[i][j] == INFINITY) {
printf("0\n");
return ;
}
else if (dp[i][j]>linemindis) {
linemindis = dp[i][j];
}
else {
}
}
if (linemindis<mindis) {
mindis = linemindis;
minpat = i + 1;
}
}
printf("%d %d\n",minpat,mindis);
}
int main() {
Graph G = BuildGraph();
int dp[MAXSIZE][MAXSIZE];
Floyd(G,dp);
findMin(G, dp);
return 0;
}
07-圖5 Saving James Bond - Hard Version (30point(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 a shortest path to reach one of the banks. The length of a path is the number of jumps that James has to make.
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, if James can escape, output in one line the minimum number of jumps he must make. Then starting from the next line, output the position (x,y) of each crocodile on the path, each pair in one line, from the island to the bank. If it is impossible for James to escape that way, simply give him 0 as the number of jumps. If there are many shortest paths, just output the one with the minimum first jump, which is guaranteed to be unique.
Sample Input 1:
17 15
10 -21
10 21
-40 10
30 -50
20 40
35 10
0 -10
-25 22
40 -40
-30 30
-10 22
0 11
25 21
25 10
10 10
10 35
-30 10
Sample Output 1:
4
0 11
10 21
10 35
Sample Input 2:
4 13
-12 12
12 12
-12 -12
12 -12
Sample Output 2:
0
題解:
我的思路是通過,先將所有點讀入,並將它們記作0.....N。再用這些點之間是否連通(能不能從一個點跳到另一個點)去建立圖。隨後按照無權圖的單元最短路徑演算法,計算出所有通路,選擇那些可以跳到岸邊的點,檢查他們是否可以連線到原點(用上一個點是否指向-1判斷),同時選擇最短的那一條,如果出現距離相同的點,路徑倒回到第一跳的時候,尋找最短的那個。
注意在第一跳直接可以跳到岸上的時候,另外做一個處理,因為原點的指向也是-1 。
#include <cstdio>
#include<algorithm>
#include <stdlib.h>
#include <string.h>
#include <stack>
#define WeightType int
#define MAXSIZE 101
#define Vertex int
#define notFound -1
#define DataType int
#define INFINITY 1000000
using namespace std;
int Gsize = 0;
int maxjumpdis;
#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;
}
}
//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][2];
};
typedef struct ENode* Edge;
struct ENode
{
Vertex begin;
Vertex end;
DataType data[2];
};
//build edge
Edge BuildEdge(Vertex begin, Vertex end) {
Edge e = (Edge)malloc(sizeof(struct ENode));
e->begin = begin;
e->end = end;
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] = 1;
//If it is an undirected graph, you need to add the following
G->graph[e->end][e->begin] = 1;
G->Nedge++;
}
bool firtJump(int p[]) {
if (sqrt(pow(p[0], 2) + pow(p[1], 2)) <= 7.5 + 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;
}
//build graph
Graph BuildGraph() {
int Npoint, maxjump;
scanf("%d %d", &Npoint, &maxjump);
Gsize = Npoint + 1;
maxjumpdis = maxjump;
Graph G = CreateGraph(Npoint + 1);
G->data[0][0] = 0;
G->data[0][1] = 0;
for (int i = 1; i <= Npoint; i++)
{
scanf("%d %d", &G->data[i][0], &G->data[i][1]);
}
for (int i = 0; i < Npoint; i++)
{
for (int j = i + 1; j <= Npoint; j++)
{
if (i == 0) {
if (firtJump(G->data[j])) {
InsertEdge(G, BuildEdge(i, j));
}
}
else {
if (jump(G->data[i], G->data[j])) {
InsertEdge(G, BuildEdge(i, j));
}
}
}
}
return G;
}
int mindisFromO(Graph G,int a,int b) {
if ((pow(G->data[a][0], 2) + pow(G->data[a][1], 2)) <= (pow(G->data[b][0], 2) + pow(G->data[b][1], 2))) {
return a;
}
return b;
}
//count short dis
int p[MAXSIZE][2];