最近参加了中兴捧月的比赛,特地挑了道图算法的题来做。经过几天的学习和思考,利用广度搜索来解决“无权图两点最短路径”,存储结构上,用链式前向星处理“稀疏图”能达到一个较低的时间复杂度。

那么什么是链式前向星呢? 其实它是一种邻接表的数组实现方式,使用它,既能减少代码的复杂度,也能在一定的条件下节省存储空间,下面我们先学习一下这种数据存储结构(下面的课件摘自:http://malash.me/200910/linked-forward-star/


链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯


链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯

链式前向星及其简单应用 - Malash - Malash的OI生涯


链式前向星及其简单应用 - Malash - Malash的OI生涯


由上可见,链式前向星其实模拟了指针的过程,这样实现起来比邻接链表要更为简单,编写效率可以说是相当高。

利用链式前向星,可以快速有效地解决诸如:SPFA,图的遍历等等问题。


接下来下面我们可以先来看看题目:

在一个网络拓扑中(可以支持数千个点的规模),边是双向的,两点之间最多有一条边,所有边的距离相等(也就是权重为1),给出源和目的两个点,需要找出满足条件的路径。

1。找出源和目的之间的一条主用路径。

2。找出源和目的之间的一条备用路径。备用路径和主用路径至少有一个点或边不相同。

关于备用路径可能满足下列约束:

1)和主用路径没有相同的中间节点。

2)和主用路径没有相同的边。

例如:

示例数据

A_NE_ID,Z_NE_ID

2,28

4,48

9,45

10,1

10,11

11,12

11,2

12,13

13,14

15,23

16,24

17,25

18,19

18,1

20,21

20,29

20,3

21,22

22,23

23,24

24,32

26,27

26,34

27,28

27,35

28,36

29,37

30,31

32,40

32,33

32,31

33,25

33,41

36,44

37,38

37,45

39,30

39,4

40,5

40,49

43,42

43,50

43,6

44,43

45,7

45,8

46,37

46,47

47,48

48,40

拓扑图描述文件

003602603.png

拓扑图文件说明,出于简化的目的,网络拓扑节点用数字表示。

003605901.png


程序支持命令行参数,

/f后表示拓扑图文件

/s 表示源节点

/d表示目的节点

/c 表示条件(取值1-2,满足上面两个约束条件之一)

/o 表示输出结果文件

如cpath.exe  /ftopolink_example01.txt  /s20  /d32 /c2 /otopolink_result01.txt

表示根据拓扑图文件topolink_example01.txt,计算节点20和节点32之间的主用和备用路径,备用路径要求满足约束条件2。

输出结果文件topolink_result01.txt内容可能的为

main:  20, 21 ,22, 23,24, 32

backup: 20,29,37, 46,47,48, 40,32

解决思路如下:


003941353.png

首先我们来解决如何定义一个图:


typedef struct EDGE
{
    int to;     //边上的节点
    int next;   //下一个节点
    int weight; //权重
}EDGE;
typedef struct GRAPH
{
    int *vertex;    //顶点集
    EDGE *edge;     //边集
    int index;      //下标值
    int vertexSize; //顶点个数
    int edgeSize;   //边个数
    int *pre;       //前一个访问对象
    int *visited;   //遍历过的点
}GRAPH;

然后我们如何构建一个无权图呢?只需要将边插入两个顶点中就好了(与邻接链表是一样)

void graphInsertE(GRAPH *g, int u, int v, int w)
{
    int index = g->index;
    g->edge[index].to = v;
    g->edge[index].next = g->vertex[u];
    g->edge[index].weight = w;
    g->vertex[u] = index++;
    g->index = index;
}

如何去搜寻图中最短的路径呢?其实就是去做一个广度搜索,如果遇到了我们要找的节点,就停止搜索就好了

/*******************************************************
* Function Name: BFSSearch
* Purpose: 使用广度优先找寻最短路径
* Params :
*   @GRAPH *g 图指针
*   @int s 源节点
*   @int d 目标节点
*******************************************************/
void BFSSearch(GRAPH *g, int s, int d)
{
    //初始化一个队列
    QUEUE *q = initQueue(g->vertexSize);
    int qfront, temp;
    int flag = TRUE, i;
    //从源节点开始找寻
    g->visited[s] = 1;
    in(q, s);
    //直到找到目标节点或者队列为空才停止
    while(flag && !isQueueEmpty(*q))
    {
        out(q, &qfront);
        for(i = g->vertex[qfront];flag && i;i = g->edge[i].next)
        {
            if(g->edge[i].weight == 1)
            {
                temp = g->edge[i].to;
                if(!g->visited[temp])
                {
                    in(q, temp);
                    g->pre[temp] = qfront;
                    g->visited[temp] = 1;
                }
                if(temp == d)
                {
                    flag = FALSE;
                }
            }
        }
    }
    //释放队列
    destroyQueue(&q);
}


好了,大概的讲解就到这里,如果说大家想要了解更多的话,源代码就在附件中,可以自己下载调试