图和两种遍历,Saving James Bond

图可以作为表示指定环境内所有对象的关系的形式。树可以认为是图的子集,图中节点的联系可能是成环的。

图的元素包括边和点,按照不同的需要可能侧重表达点之间的关系或者点本身的信息。

int graph[100][100] = {0};
...

void InsertEdge(int ver_a, int ver_b, int weight)
{
    graph[ver_a][ver_b] = weight;
    return;
}

如上例所示为侧重表示边的图,可以使用二维数组存储这样的图,而数组的序号作为点的代号。上例中的 graph[1][2] 即表示“从 1 点 到 2 点的值(这个值可能是距离或者其他指定含义的值)”,而 graph[2][1] 就是“从 2 到 1 的值”了。

如果给出了一系列对象之间的联系,就可以简单地为这些对象编个号,然后在矩阵中定义它们之间的边。


还有一种表示形式是记录点而非边,这在一个给定的坐标系内比较常见。

int vertex[100][2] = {0};
bool isVertex[100] = {0};
...

void InsertVertex(int x, int y, int num)
{
    vertex[num][0] = x;
    vertex[num][1] = y;
    isVertex[num] = true;
    return;
}


和上一种形式相比它较不直观,如果要为这些点定义边可能需要其他手段 (比如根据两点间距离判断,如果它们足够近就认为是联通的)。

bool IsEdge(int x1, int x2, int y1, int y2, int lenth)
{
    return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) <= lenth*lenth;
}


遍历整个图的方式包括 深度优先搜索 (Depth First Search, DFS) 和 广度优先搜索 (Breadth First Search, BFS)。

深度优先搜索,行为类似二叉树的前序遍历。即从起点开始,每访问一个节点便查询该节点的邻接点并访问找到的第一个邻接点,直到访问的节点没有未访问过的邻接点,再从最近访问过的点中寻找还可以访问的点。

void BFS(int vertexNum)
{
    Visited[vertexNum] = true;    //访问当前节点
    for (int w = 0; w < GraphLen; w++)
    {
        if (!Visited[w] && IsEdge(vertexNum, w))    //对所有未访问过、且是当前点的邻接点的 w
            DFS(w);
    }
    return;
}


广度优先搜索,行为类似二叉树的层序遍历。和层序遍历一样,广度优先搜索需要一个队列来保存访问某个点时该点的所有邻接点。

void BFS(int vertexNum)
{
    visited[vertexNum] = true;
    EnQueue(queue, vertexNum);
    
    while (!EmptyQueue(queue))
    {
        v = PopQueue(queue);        //从队列中弹出的点用于查找所有的邻接点进队列
        for (w = 0; w < GraphLen; w++)
        {
            if (!Visited[w] && IsEdge(v, w));
                {
                    Visited[w] = True;
                    EnQueue(w);
                }
        }
    }
    return;
}

下面是两道关于图遍历的题目,见于 中国大学MOOC-陈越、何钦铭-数据结构 在PTA发布的习题。题目背景如下:


/*
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.
现在让我们假设《Live and Die》中詹姆斯邦德的处境,世界顶级的间谍被一组毒贩俘虏。
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.
假设这个湖是一个100×100的方形。
Assume that the center of the lake is at (0,0) and the northeast corner at (50,50).
假设湖中心坐标是 (0, 0),东北角坐标是 (50, 50)。
The central island is a disk centered at (0,0) with the diameter of 15.
湖心岛中心在 (0, 0) ,是一个直径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.
每个测试样例中,第一行给定两个整数 N (≤100) 和 D,分别为鳄鱼的数量和詹姆斯一次跳跃的最远距离。
Then N lines follow, each containing the (x,y) location of a crocodile.
Note that no two crocodiles are staying at the same position.
之后 N 行,每行给出一对鳄鱼的坐标 (x, y)。任意两条鳄鱼不会出现在同样的位置。
*/
题目有“简单模式” 和“困难模式”。


“简单模式”的要求:

/*
Output Specification:
输出格式:

For each test case, print in a line "Yes" if James can escape, or "No" if not.
对每个测试样例,根据詹姆斯能否成功逃脱,在一行内输出 "Yes" 或者 "No"。

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
*/

可以在建立图后使用任意一种遍历方式判断:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<stdbool.h>

#define MaxAssume 100
#define HALF_LAND 7.5       //小岛的半径


int crocodiles[2][MaxAssume] = {0};     // 存放已经记录的鳄鱼坐标
int N;      // 存放鳄鱼数量
int D;      // 詹姆斯可行的的跳跃距离


bool CanEscape(int x, int y)
{
    return x <= D || y <= D || (MaxAssume - x) <= D || (MaxAssume - y) <= D;
}


//两点间距离公式,注意到传入的坐标数据在程序处理中 +50
bool CanJump(int x1, int y1, int x2, int y2)
{
    if (x1 == 50 && y1 == 50)
    {
        return pow(x2-x1, 2) + pow(y2-y1,2) > pow(D+HALF_LAND, 2)? false: true;
    }

    return pow(x2-x1, 2) + pow(y2-y1, 2) > pow(D, 2)? false: true;
}


//访问坐标点的标记,标记为 true 就是被詹姆斯踩过的鳄鱼
bool Visited[MaxAssume][MaxAssume] = {0};
void VisitNode(int x, int y)
{
    Visited[x][y] = true;
    //printf("Visit point(%d, %d)\n", x-50, y-50);

    return;
}


//魔改的深度优先,把判断条件改成能够跳过去,如果判断可以逃脱就显示信息然后直接退出程序
void DFS(int x1, int y1)
{
    int count;
    int x2, y2;
    VisitNode(x1, y1);

    if (CanEscape(x1, y1))
    {
        printf("Yes\n");
        exit(0);
    }

    for (count = 0; count < N; count++)
    {
        x2 = crocodiles[0][count];
        y2 = crocodiles[1][count];
        if(!Visited[x2][y2] && CanJump(x1, y1, x2, y2))
        {
            DFS(x2, y2);
        }
    }
    return;
}


int main(void)
{
    int xc, yc;

    scanf("%d %d", &N, &D);
    getchar();

    int count;
    for(count = 0; count < N; count++)
    {
        scanf("%d %d", &xc, &yc);
        getchar();
        crocodiles[0][count] = xc + 50;
        crocodiles[1][count] = yc + 50;
    }

    //从原点 (进入程序的坐标+50) 开始的深度优先访问,如果程序跑完了 DFS() 就说明到不了岸
    DFS(50, 50);
    printf("No\n");

    return 0;
}
  

“困难模式”的要求:

/*
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.
在随后的若干行内输出每次跳过鳄鱼的坐标 (x, y),每个坐标占一行,方向从湖心岛到岸上。
If it is impossible for James to escape that way, simply give him 0 as the number of jumps.
如果詹姆斯没可能逃出生天,只给他一个“0”作为他跳跃的次数。
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
*/

对“困难模式”,需要寻找一种最优的路径(幸运的是这里只需要找出第一跳所需最近的一步),所以要用到广度优先搜索。

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<stdbool.h>
#include<limits.h>

#define MaxAssume 100
#define HALF_LAND 7.5       // 小岛的半径


int crocodiles[2][MaxAssume+1] = {0};   // 存放已经记录的鳄鱼坐标,第0行存横坐标,第1行存纵坐标
int N;      // 存放鳄鱼数量
int D;      // 詹姆斯可行的的跳跃距离
int Path[MaxAssume+1] = {0};        // 詹姆斯逃脱的路径表示,以 crocodiles[][] 里鳄鱼的序号表示


bool CanEscape(int x, int y)
{
    return (x <= D || y <= D || (MaxAssume - x) <= D || (MaxAssume - y) <= D);
}


// 两点间距离公式,注意到传入的坐标数据在程序处理中 +50
bool CanJump(int x1, int y1, int x2, int y2)
{
    if (x1 == 50 && y1 == 50)
    {
        if (pow(x2, 2) + pow(y2, 2) <= pow(HALF_LAND, 2))   // 鳄鱼在小岛内
            return false;
        return pow((x2 - x1), 2) + pow((y2-y1), 2) > pow(D+HALF_LAND, 2)? false: true;
    }

    return pow(x2-x1, 2) + pow(y2-y1, 2) > pow(D, 2)? false: true;
}


// 访问坐标点的标记,标记为 true 就是被詹姆斯踩过的鳄鱼
bool Visited[MaxAssume][MaxAssume] = {0};
void VisitNode(int x, int y)
{
    Visited[x][y] = true;
    //printf("Visit point(%d, %d)\n", x-50, y-50);

    return;
}


// 一个简单队列, 与函数 BFS() 耦合
int Queue[MaxAssume] = {0};
int Qhead = 0, Qtail = 0;
bool QEmpty = true;
void InQueue(int a)
{
    if (Qhead == Qtail && !QEmpty)
    {
        printf("Queue was full.\n");
        exit(1);
    }

    Qtail = (Qtail + 1) % MaxAssume;
    Queue[Qtail] = a;
    QEmpty = false;

    return;
}


int PopQueue(void)
{
    if (Qhead == Qtail && QEmpty)    // 队列空
        return INT_MAX;

    QEmpty = true;
    Qhead = (Qhead + 1) % MaxAssume;

    return Queue[Qhead];
}


// 固定起点的广度优先访问(BFS). 使用队列, 类似二叉树中序遍历.
void BFS(int* pathTails)
{
    int croc;
    int V, W;
    // 访问原点,从原点能跳的进队列(按编号)
    VisitNode(50, 50);
    crocodiles[0][0] = 50;
    crocodiles[1][0] = 50;
    InQueue(0);
    Path[0] = -1;

    int x1, y1, x2, y2;
    // 队列不空,就找队列里记录的鳄鱼里能跳到的其它鳄鱼
    while (!QEmpty || Qhead != Qtail)
    {
        V = PopQueue();
        for (W = 1; W <= N; W++)
        {
            x1 = crocodiles[0][V], y1 = crocodiles[1][V];
            x2 = crocodiles[0][W], y2 = crocodiles[1][W];

            if ( CanJump(x1, y1, x2, y2) &&
                !Visited[x2][y2] )
            {
                VisitNode(x2, y2);
                InQueue(W);
                Path[W] = V;

                if ( CanEscape(x2, y2) )
                {
                    pathTails[W] = 1;
                }
            }
        }
    }
    return;
}


// 一个简单栈
int crocStack[MaxAssume];
int StackHead = 0;
void InStack(int a)
{
    if (StackHead >= MaxAssume)
    {
        fprintf(stderr, "Stack was full.\n");
        exit(1);
    }
    crocStack[StackHead] = a;
    StackHead++;
    return;
}


int PopStack(void)
{
    if (StackHead < 1)
        return INT_MIN;

    StackHead--;
    return crocStack[StackHead];
}


int main(void)
{
    int count;
    int xc, yc;
    int PathTail;

    scanf("%d %d", &N, &D);
    getchar();
    // 能一步跳到岸的情形
    if (D >= 50)
    {
        printf("1");
        exit(0);
    }

    for (count = 1; count <= N; count++)
    {
        scanf("%d %d", &xc, &yc);
        getchar();

        crocodiles[0][count] = xc + 50;
        crocodiles[1][count] = yc + 50;
    }

    // Exit 保存可逃脱的路径尾,序号代表路径尾的鳄鱼,内容存储第一跳的距离。
    int* Exit = (int*)calloc(N+1, sizeof(int));
    for (count = 1; count <= N; count++)
        Exit[count] = -1;

    BFS(Exit);

    int dx, dy;
    int* minSteps = (int*)calloc(N+1, sizeof(int));
    for (count = 0; count <= N; count++)
        minSteps[count] = INT_MAX;

    for (count = 1; count <= N; count++)
    {
        if (Exit[count] != -1)  // 选择出口
        {
            PathTail = count;
            while (PathTail != 0)
            {
                InStack(PathTail);
                if (Path[PathTail] == 0)    // 判断第一跳
                {
                    // 求算 PathTail 和 Path[PathTail] 之间的距离并赋值给 Exit[count]
                    dx = crocodiles[0][PathTail] - crocodiles[0][Path[PathTail]];
                    dy = crocodiles[1][PathTail] - crocodiles[1][Path[PathTail]];
                    Exit[count] = dx * dx + dy * dy;
                    minSteps[count] = StackHead+1;
                    // printf("minSteps[%d] = %d\n", count, minSteps[count]);
                }
                PathTail = Path[PathTail];
            }
        }
        StackHead = 0;
    }

    // 比较最少跳数和最短第一跳
    int p_minFirstStep = 0, minFirstStep = INT_MAX;
    for (count = 1; count <= N; count++)
    {
        // printf("%d ", Exit[count]);
        if (Exit[count] != -1)
        {
            // 选出逃脱步数最少的路径尾
            if (minSteps[count] < minSteps[0])
            {
                minSteps[0] = minSteps[count];
                p_minFirstStep = count;
                minFirstStep = Exit[count];
            }

            // 逃脱步数同为目前的最小值,但第一跳更短
            else if (minSteps[count] == minSteps[0] &&
                     Exit[count] < minFirstStep)
            {
                minFirstStep = Exit[count];
                p_minFirstStep = count;
            }
        }
    }

    PathTail = p_minFirstStep;       // 逃脱前最后跳过鳄鱼的编号
    // printf("\nPathTail = %d\n", PathTail);
    while (PathTail != 0)
    {
        InStack(PathTail);
        PathTail = Path[PathTail];
    }

    if (StackHead == 0)    // 无法逃脱的情形,栈内没有记录任何步骤
    {
        printf("0");
    }
    else
    {
        printf("%d\n", StackHead+1);
        // 显示完整的路径
        while (StackHead != 0)
        {
            count = PopStack();
            printf("%d %d\n", crocodiles[0][count] - 50, crocodiles[1][count] - 50);
        }
    }

    free(Exit);
    free(minSteps);

    return 0;
}
   

猜你喜欢

转载自blog.csdn.net/asura319/article/details/79281034