数据结构与算法基础-学习-24-图的遍历之DFS(深度优先搜索)和BFS(广度优先搜索)

目录

一、遍历定义

二、遍历实质

三、DFS

四、BFS

五、宏定义

六、自定义类型

七、函数实现

1、DFS(邻接矩阵实现)

2、DFS(邻接表实现)

3、BFS(邻接矩阵实现)

4、BFS(邻接表实现)

5、打印邻接矩阵遍历顺序

 6、打印邻接表遍历顺序

八、遍历算法效率分析

1、DFS

2、BFS

九、Linux编译测试


一、遍历定义

从已给的连通图中某一顶点出发,沿着一些边访问遍图中所有顶点,且使每个顶点仅被访问一次,就叫做的图的遍历,它是图的基本运算。

二、遍历实质

找每个顶点的邻接点的过程。

三、DFS

深度优先搜索,英文全称Depth First Search。如下图进行举例说明。

这里以邻接矩阵表示无向图进行举例,生成内容如下:

[2023-5]--[ Debug ]--Printf AMGraph                     :
VertexArray    : [A ,B ,C ,D ,E ]
ArcArray       :
[32767 ,20    ,30    ,10    ,32767 ]
[20    ,32767 ,32767 ,32767 ,50    ]
[30    ,32767 ,32767 ,40    ,32767 ]
[10    ,32767 ,40    ,32767 ,60    ]
[32767 ,50    ,32767 ,60    ,32767 ]
CurVertexNum   : 5
CurArcNum      : 12

 我们还需要维护一个Visit数组,用于确认访问过的节点有哪些,0表示没有访问过,1表示访问过。

 例如我们从E出发,Visit数组变为:

从邻接矩阵上看,E->B和E->D,DFS算法是一条道走到黑,先扫到B节点,又发现B节点没有被访问,所以先走E->B。

[32767 ,50    ,32767 ,60    ,32767 ]

Visit数组变为:

从邻接矩阵上看B的情况,可以走B->A和B->E,先发现的A节点,又发现A节点没有被访问,所以先走B->A。

[20    ,32767 ,32767 ,32767 ,50    ]

Visit数组变为:

从邻接矩阵上看A的情况,可以走A->B和A->C,A->D,先发现的B节点,又发现B节点被访问过,所以跳过,再看C节点发现没有被访问,所以先走A->C。

[32767 ,20    ,30    ,10    ,32767 ]

Visit数组变为:

从邻接矩阵上看C的情况,可以走C->A和C->D,先发现的A节点,又发现A节点被访问过,所以跳过,再看D节点发现没有被访问,所以先走C->D。

[30    ,32767 ,32767 ,40    ,32767 ]

Visit数组变为:

 最后发现Visit数组中的所有节点被访问完,退出程序。

四、BFS

广度优先搜索,英文全称Breadth First Search,BFS类似于树的层次遍历,我们可以将图逆时针旋转90度,如下图进行举例说明。

旋转后:

这里以邻接矩阵表示无向图进行举例,生成内容如下:

[2023-5]--[ Debug ]--Printf AMGraph                     :
VertexArray    : [A ,B ,C ,D ,E ]
ArcArray       :
[32767 ,20    ,30    ,10    ,32767 ]
[20    ,32767 ,32767 ,32767 ,50    ]
[30    ,32767 ,32767 ,40    ,32767 ]
[10    ,32767 ,40    ,32767 ,60    ]
[32767 ,50    ,32767 ,60    ,32767 ]
CurVertexNum   : 5
CurArcNum      : 12

和DFS一样我们也需要维护一个Visit数组,用于确认访问过的节点有哪些,0表示没有访问过,1表示访问过。

我们还需要维护一个队列,没错思路和层次遍历一样。

 例如我们还是从E出发,Visit数组变为:

队列变为:

[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 4 ]
FrontIndex     : 0
RearIndex      : 1
SqQueueLen     : 1

从邻接矩阵上看E,E->B和E->D,BFS算法是广度优先,会先把E可以访问的节点先都走一遍,判断BD是否被访问,都没有被访问,那就依次访问E->B和E->D。

[32767 ,50    ,32767 ,60    ,32767 ]

Visit数组变为:

队列弹出E,压入BD变为:

[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 1 ,3 ]
FrontIndex     : 1
RearIndex      : 3
SqQueueLen     : 2

队列中弹出B,从邻接矩阵上看B的情况,可以走B->A和B->E,发现A节点没有被访问,压入队列中,E被访问过,不压入,所以走B->A。

[20    ,32767 ,32767 ,32767 ,50    ]

Visit数组变为:

 队列,弹出B,压入A变为:

[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 3 ,0 ]
FrontIndex     : 2
RearIndex      : 4
SqQueueLen     : 2

弹出D,从邻接矩阵上看D的情况,可以走D->A和D->C,D->E,发现A节点被访问过,所以跳过,再看C节点发现没有被访问,所以先走D->C,发现所有节点都被访问,退出程序。

[10    ,32767 ,40    ,32767 ,60    ]

Visit数组变为:

五、宏定义

#define MAX_INT_TYPE_NUM      32767 //网中代表无穷大,也代表顶点个数。
#define MAX_VERTEX_NUM        10000 //顶点数组中存放顶点的最大个数。
#define NET_DIRECTION_FLAG    0     //有向网的标志
#define NET_UNDIRECTION_FLAG  1     //无向网的标志

//DFS,BFS
#define VISITED_FLAG          1
#define NOT_VISITED_FLAG      0

六、自定义类型

//DFS,BFS
typedef struct AccessSeqType
{
    VertexIndexType Array[MAX_VERTEX_NUM];
    VertexIndexType ArraySize;
}AccessSeqType;

这是一个访问顺序数组,方便查看访问顺序的。

邻接矩阵和邻接表的定义和相关代码可以参考之前的文章《数据结构与算法基础-学习-23-图之邻接矩阵与邻接表

七、函数实现

1、DFS(邻接矩阵实现)

void DepthFirstSearchUseAMGraph(AMGraph* AMG, VertexIndexType VertexIndex, VertexIndexType* VisitedArray, AccessSeqType* AccessSeq)
{
    AccessSeq->Array[AccessSeq->ArraySize] = VertexIndex;
    AccessSeq->ArraySize++;

    VisitedArray[VertexIndex] = VISITED_FLAG;
    VertexIndexType ColIndex;

    if(AccessSeq->ArraySize == AMG->CurVertexNum)//如果所有结点访问完成,退出。
    {
        return;
    }
    //GlobalCnt++;
    
    for(ColIndex = 0; ColIndex < AMG->CurVertexNum; ColIndex++)
    {
        //GlobalCycleCnt++;
        if(AMG->ArcArray[VertexIndex][ColIndex] != MAX_INT_TYPE_NUM && VisitedArray[ColIndex] == NOT_VISITED_FLAG)
        {
            DepthFirstSearchUseAMGraph(AMG, ColIndex, VisitedArray, AccessSeq);
            if(AccessSeq->ArraySize == AMG->CurVertexNum)//如果所有结点访问完成,退出。
            {
                break;
            }
        }
    }
}
参数名描述
AMG邻接矩阵。
VertexIndex从哪个顶点索引号开始搜索。
VisitedArray顶点访问数组。
AccessSeq顶点访问顺序数组。

2、DFS(邻接表实现)

void DepthFirstSearchUseAGraph(AGraph* AG, VertexIndexType VertexIndex, VertexIndexType* VisitedArray, AccessSeqType* AccessSeq)
{
    AccessSeq->Array[AccessSeq->ArraySize] = VertexIndex;
    AccessSeq->ArraySize++;

    VisitedArray[VertexIndex] = VISITED_FLAG;
    //GlobalCnt++;

    if(AccessSeq->ArraySize == AG->VertexNum)//如果所有结点访问完成,退出。
    {
        return;
    }
    
    ArcNode* TmpArcNode = AG->Vertices[VertexIndex].FirstArcNodePtr;;
 
    while(TmpArcNode != NULL)
    {
        //GlobalCycleCnt++;
        if(VisitedArray[TmpArcNode->EndVertexIndex] == NOT_VISITED_FLAG)
        {
            DepthFirstSearchUseAGraph(AG, TmpArcNode->EndVertexIndex, VisitedArray, AccessSeq);
            if(AccessSeq->ArraySize == AG->VertexNum)//如果所有结点访问完成,退出。
            {
                break;
            }
        }
        TmpArcNode = TmpArcNode->NextNodePtr;
    }
}
参数名描述
AG邻接表。
VertexIndex从哪个顶点索引号开始搜索。
VisitedArray顶点访问数组。
AccessSeq顶点访问顺序数组。

3、BFS(邻接矩阵实现)

void BreadthFirstSearchUseAMGraph(AMGraph* AMG, VertexIndexType VertexIndex, VertexIndexType* VisitedArray, AccessSeqType* AccessSeq)
{
    JudgeAllNullPointer(VisitedArray);
    JudgeAllNullPointer(AccessSeq);

    SqQueue* SQ              = NULL;
    VertexIndexType* TmpElem = (VertexIndexType*)MyMalloc(sizeof(VertexIndexType));
    VertexIndexType  i       = 0;

    InitSqQueue(&SQ,INT_TYPE_FLAG);//初始化循环顺序队   
    EnterSqQueue(SQ, &VertexIndex);//将第一个结点压入循环顺序队。
    VisitedArray[VertexIndex]              = VISITED_FLAG;
    AccessSeq->Array[AccessSeq->ArraySize] = VertexIndex;
    AccessSeq->ArraySize++;

    while(GetSqQueueLen(SQ) != 0)
    {
        PrintfSqQueue(SQ);
        LeaveSqQueue(SQ, TmpElem);
        for(i = 0; i < AMG->CurVertexNum; i++)
        {
            if(AMG->ArcArray[*TmpElem][i] != MAX_INT_TYPE_NUM && VisitedArray[i] == NOT_VISITED_FLAG)
            {
                EnterSqQueue(SQ, &i);
                VisitedArray[i]                        = VISITED_FLAG;
                AccessSeq->Array[AccessSeq->ArraySize] = i;
                AccessSeq->ArraySize++;
                if(AccessSeq->ArraySize == AMG->CurVertexNum)//如果所有结点访问完成,退出。
                {
                    break;
                }
            }
        }
        if(AccessSeq->ArraySize == AMG->CurVertexNum)//如果所有结点访问完成,退出。
        {
            break;
        }
    }

    DestroySqQueue(&SQ);
    free(TmpElem);
    TmpElem = NULL;

    Log("Breadth First Search Use AMGraph OK\n",Debug);
}
参数名描述
AMG邻接矩阵。
VertexIndex从哪个顶点索引号开始搜索。
VisitedArray顶点访问数组。
AccessSeq顶点访问顺序数组。

感觉访问顺序数组满退出这一块,两次break,写成goto是不是好些,大家有想法的可以指点小弟一二。

4、BFS(邻接表实现)

void BreadthFirstSearchUseAGraph(AGraph* AG, VertexIndexType VertexIndex, VertexIndexType* VisitedArray, AccessSeqType* AccessSeq)
{
    JudgeAllNullPointer(VisitedArray);
    JudgeAllNullPointer(AccessSeq);

    SqQueue* SQ              = NULL;
    VertexIndexType* TmpElem = (VertexIndexType*)MyMalloc(sizeof(VertexIndexType));
    ArcNode*         TmpNode = NULL;

    InitSqQueue(&SQ,INT_TYPE_FLAG);//初始化循环顺序队   
    EnterSqQueue(SQ, &VertexIndex);//将第一个结点压入循环顺序队。
    VisitedArray[VertexIndex]              = VISITED_FLAG;
    AccessSeq->Array[AccessSeq->ArraySize] = VertexIndex;
    AccessSeq->ArraySize++;

    while(GetSqQueueLen(SQ) != 0)
    {
        LeaveSqQueue(SQ, TmpElem);
        TmpNode                                = AG->Vertices[*TmpElem].FirstArcNodePtr;
        while(TmpNode != NULL)
        {
            if(VisitedArray[TmpNode->EndVertexIndex] == NOT_VISITED_FLAG)
            {
                EnterSqQueue(SQ, &(TmpNode->EndVertexIndex));
                VisitedArray[TmpNode->EndVertexIndex]  = VISITED_FLAG;
                AccessSeq->Array[AccessSeq->ArraySize] = TmpNode->EndVertexIndex;
                AccessSeq->ArraySize++;
                if(AccessSeq->ArraySize == AG->VertexNum)//如果所有结点访问完成,退出。
                {
                    break;
                }
            }
            TmpNode = TmpNode->NextNodePtr;
        }
        if(AccessSeq->ArraySize == AG->VertexNum)//如果所有结点访问完成,退出。
        {
            break;
        }
    }

    DestroySqQueue(&SQ);
    free(TmpElem);
    TmpElem = NULL;
    Log("Breadth First Search Use AGraph OK\n",Debug);
}
参数名描述
AG邻接表。
VertexIndex从哪个顶点索引号开始搜索。
VisitedArray顶点访问数组。
AccessSeq顶点访问顺序数组。

5、打印邻接矩阵遍历顺序

Status AMGraphTraverse(void (*Func)(AMGraph*,VertexIndexType,VertexIndexType*, AccessSeqType*),AMGraph* AMG, VertexIndexType VertexIndex)
{
    JudgeAllNullPointer(AMG);

    VertexIndexType* VisitedArray = (VertexIndexType*)MyCalloc(AMG->CurVertexNum, sizeof(VertexIndexType));
    AccessSeqType* AccessSeq      = (AccessSeqType*)MyCalloc(1, sizeof(AccessSeqType));
    AccessSeq->ArraySize          = 0;

    Func(AMG, VertexIndex, VisitedArray, AccessSeq);

    char* string = (char*)MyCalloc(STR_TYPE_SIZE, sizeof(char));
    CopyStr* PS  = (CopyStr*)malloc(sizeof(CopyStr));
    InitCopyStr(PS);
    ExecCopyStr(PS,"Traverse Use AMGraph               : [");
    VertexIndexType i;
    for(i = 0; i < AccessSeq->ArraySize; i++)
    {
        sprintf(string,"%d ,", AccessSeq->Array[i]);
        ExecCopyStr(PS,string);
    }
    PS->String[PS->StrEffectiveLen-1] = ']';
    ExecCopyStr(PS,"\n");

    free(AccessSeq);
    free(VisitedArray);
    AccessSeq    = NULL;
    VisitedArray = NULL;

    PS->String[PS->StrEffectiveLen] = '\0';
    Log(PS->String, Debug);
    DestroyCopyStr(PS);
    free(string);
    string       = NULL;
    return SuccessFlag;
}   
参数名描述
FuncDFS或BFS函数指针。
AMG邻接矩阵。
VertexIndex从哪个顶点索引号开始搜索。

 6、打印邻接表遍历顺序

Status AGraphTraverse(void (*Func)(AGraph*,VertexIndexType,VertexIndexType*,AccessSeqType*),AGraph* AG, VertexIndexType VertexIndex)
{
    JudgeAllNullPointer(AG);

    VertexIndexType* VisitedArray = (VertexIndexType*)MyCalloc(AG->VertexNum, sizeof(VertexIndexType));
    AccessSeqType* AccessSeq      = (AccessSeqType*)MyCalloc(1, sizeof(AccessSeqType));
    AccessSeq->ArraySize          = 0;

    Func(AG, VertexIndex, VisitedArray, AccessSeq);

    char* string = (char*)MyCalloc(STR_TYPE_SIZE, sizeof(char));
    CopyStr* PS  = (CopyStr*)malloc(sizeof(CopyStr));
    InitCopyStr(PS);
    ExecCopyStr(PS,"Traverse Use AGraph                : [");
    VertexIndexType i;
    for(i = 0; i < AccessSeq->ArraySize; i++)
    {
        sprintf(string,"%d ,", AccessSeq->Array[i]);
        ExecCopyStr(PS,string);
    }
    PS->String[PS->StrEffectiveLen-1] = ']';
    ExecCopyStr(PS,"\n");

    free(AccessSeq);
    free(VisitedArray);
    AccessSeq    = NULL;
    VisitedArray = NULL;

    PS->String[PS->StrEffectiveLen] = '\0';
    Log(PS->String, Debug);
    DestroyCopyStr(PS);
    free(string);
    string       = NULL;
    return SuccessFlag;
}
参数名描述
FuncDFS或BFS函数指针。
AG邻接表。
VertexIndex从哪个顶点索引号开始搜索。

八、遍历算法效率分析

1、DFS

用邻接矩阵表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为O(n^2)。

用邻接表表示图,虽然有2e个表结点,但只需要扫描e个结点即可完成遍历,加上访问n个头节点的时间,时间复杂度为O(n+e)。

2、BFS

使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行(n个元素),总的时间代价为O(n^2)。

用邻接表表示图,虽然有2e个表结点,但只需要扫描e个结点即可完成遍历,加上访问n个头节点的时间,时间复杂度为O(n+e)。

九、Linux编译测试

[gbase@czg2 Graph]$ make
gcc -Wall -Wextra -O3 ../Log/Log.c ../PublicFunction/PublicFunction.c ../PublicFunction/SqQueue/SqQueue.c Graph.c MinimumSpanningTree.c main.c -o TestGraph -I ../Log/ -I ../PublicFunction/ -I ../Select/ -I ../PublicFunction/SqQueue/
[gbase@czg2 Graph]$ ./TestGraph 
[2023-5]--[ Info  ]--Create Net Data                    : OK
[2023-5]--[ Info  ]--Create Undirection Net Use AMGraph : OK
[2023-5]--[ Debug ]--Printf AMGraph                     :
VertexArray    : [A ,B ,C ,D ,E ]
ArcArray       :
[32767 ,20    ,30    ,10    ,32767 ]
[20    ,32767 ,32767 ,32767 ,50    ]
[30    ,32767 ,32767 ,40    ,32767 ]
[10    ,32767 ,40    ,32767 ,60    ]
[32767 ,50    ,32767 ,60    ,32767 ]
CurVertexNum   : 5
CurArcNum      : 12
[2023-5]--[ Info  ]--Create Undirection Net Use AGraph  : OK
[2023-5]--[ Debug ]--Printf AGraph                      :
A : [(2, 30, 0x6f18b0),(1, 20, 0x6f1870),(3, 10, (nil))]
B : [(4, 50, 0x6f18d0),(0, 20, (nil))]
C : [(3, 40, 0x6f1910),(0, 30, (nil))]
D : [(4, 60, 0x6f1950),(2, 40, 0x6f1890),(0, 10, (nil))]
E : [(3, 60, 0x6f1990),(1, 50, (nil))]
VertexNum      : 5
ArcNum         : 12
[2023-5]--[ Debug ]--Traverse Use AMGraph               : [4 ,1 ,0 ,2 ,3 ]
[2023-5]--[ Debug ]--Traverse Use AGraph                : [4 ,3 ,2 ,0 ,1 ]
[2023-5]--[ Debug ]--Init SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 4 ]
FrontIndex     : 0
RearIndex      : 1
SqQueueLen     : 1
Flag           : INT_TYPE_FLAG
[2023-5]--[ Debug ]--Leave SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 1 ,3 ]
FrontIndex     : 1
RearIndex      : 3
SqQueueLen     : 2
Flag           : INT_TYPE_FLAG
[2023-5]--[ Debug ]--Leave SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--SqQueue Data   :
Data           : [ 3 ,0 ]
FrontIndex     : 2
RearIndex      : 4
SqQueueLen     : 2
Flag           : INT_TYPE_FLAG
[2023-5]--[ Debug ]--Leave SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Destroy SqQueue Normal
[2023-5]--[ Debug ]--Breadth First Search Use AMGraph OK
[2023-5]--[ Debug ]--Traverse Use AMGraph               : [4 ,1 ,3 ,0 ,2 ]
[2023-5]--[ Debug ]--Init SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Leave SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Leave SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Enter SqQueue Normal
[2023-5]--[ Debug ]--Destroy SqQueue Normal
[2023-5]--[ Debug ]--Breadth First Search Use AGraph OK
[2023-5]--[ Debug ]--Traverse Use AGraph                : [4 ,3 ,1 ,2 ,0 ]
[2023-5]--[ Info  ]--Destory Net Data                   : OK
[2023-5]--[ Info  ]--Destory Undirection Net Use AMGraph: OK
[2023-5]--[ Info  ]--Destory Undirection Net Use AGraph : OK

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值