(造轮子)C 创建队列和图实现广度优先算法(BFS)和深度优先算法(DFS)(数据结构)

本文详细介绍了如何使用C++实现广度优先搜索(BFS)和深度优先搜索(DFS)算法,涉及链表、队列的队头尾操作,以及图的邻接表存储结构。通过邻接表展示稀疏图的高效存储,并对比了BFS和DFS的时间和空间复杂度。
摘要由CSDN通过智能技术生成

链表、队列和图实现BFS和DFS算法(C+造轮子+详细代码注释)

1、队列的链式存储结构

  队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头节点,尾指针指向队尾节点,即单链表的最后一个节点(与顺序表存储结构不同)。
  队列链式存储类型描述:

struct Node{
    int data;
    struct Node* next;
};
typedef struct Node iNode;
struct Queue{
    iNode * front, *rear;
};
typedef struct Queue iQueue;

  链式队列基本操作:

//初始化队列
void initQueue(iQueue * queue);
//判断是否为空
bool isQEmpty(const iQueue * queue);
//元素入队
void enQueue(iQueue * queue, int adddata);
//元素出队
bool deQueue(iQueue * queue, int * popdata);
//显示队列
void showQueue(const iQueue *queue);

2、图的邻接表法存储结构

  当一个图为稀疏图时,使用邻接矩阵显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少不必要的空间的浪费。所谓邻接表,是指对图G中的每个顶点Vi建立一个单链表,第i个单链表的结点表示依附于顶点Vi的边表(对于有向图则成为出边表)。边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种节点:顶点表结点和边表结点。图的邻接表存储结构定义:

#define MaxVertexNum 20
struct Node{
    int data;
    struct Node* next;
};
typedef struct Node iNode;
struct Vertex{
    bool visittag ;
    iNode * head ;
};
typedef struct Vertex iVertex ;
struct Graph{
    iVertex graphVertex[MaxVertexNum];
    int vexnum;
};
typedef struct Graph iGraph ;

  图的邻接表存储方法有以下特点:

  1. G为无向图,则所需的存储空间为O(|V| + 2|E|);若G为有向图,所需的存储空间为O(|V| + |E|)。前者的倍数2是由于无向图中,每条边在邻接表中出现了两次。
  2. 对于稀疏图,采用邻接表表示将极大的节省存储空间。
  3. 在邻接表中,给定一节点,能很容易地找出它的所有临边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为O(n)。但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以迅速查到,而在邻接表中则需要在相应结点对应的边表中查找另一个结点,效率较低。
  4. 在有向图的邻接表表示中,求一个给定顶点的出度,只需计算其邻接表中的结点个书;但求其顶点的入度则需要遍历全部的邻接表。因此也有人采用逆邻接表的存储方式来加速求解给定顶点的入度。
  5. 图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。

  图的一些基本操作

//从文件读边的信息 建立邻接表结构的图
void initGraph(const char * filename, iGraph* graph);
//显示邻接表
void showGraph(const iGraph * graph);
//根据将图的结点的相邻节点,以读入顺序方向存储
void reverseGraph(iGraph* graph);
//释放图分配的的空间
void freeGraph(iGraph * graph);

3、线性表的链式表示:

  线性表的链时存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。为建立数据元素之间的线性关系,对每个链表结点,除存放元素自身的元素外,还需要存放一个指向其后继的指针单链表结构中data为数据域,存放数据元素;next为指针域,存放其后继结点的地址。
  单链表中结点类型的描述如下:

struct Node{
    int data;
    struct Node* next;
};

  链表的基本操作:

//显示链表元素
void showList(iNode * head);
//采用递归反向输出列表元素&&另一种方法:列表元素入栈然后出栈
void showBackListDigui(iGraph * graph, iNode * head);
//将单链表逆置(辅助空间复杂度为O(1))
iNode * reversList(iNode * head);
//释放链表空间
void freeList(iNode * head);

4、BFS和DFS算法

算法描述

   广度优先搜索(Breadth-First-Search, BFS)类似于二叉树的层序遍历算法。基本思想是:首先访问起始节点 v ,接着由 v 出发,依次访问 w1,w2,…, wi 的所有未被访问过的邻接顶点;在从这些访问过的顶点出发,访问他们所有未被访问过的邻接顶点,直至途中所有顶点都被访问。若此时图中尚有结点未被访问,则另选图中一个未被访问结点作为始点,重复上述过程,直至图中所有结点都被访问到为止。Dijistra单元最短路径算法和Prim最小生成树算法也应用了类似的思想。广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不想深度优先搜索那样存在回退的情况,因此他不是一个递归算法。为了实现逐层的访问必须借助一个辅助队列,以记忆正在访问的顶点的下一层顶点。
   深度优先搜索(Depth-First-Search,DFS) 与广度优先搜索不同,DFS类似于树的先序遍历。这种搜索算法遵循的搜索策略是尽可能深的搜索一个图。其基本思想:首先访问图中某一起始顶点 v ,然后由 v 出发,访问与 v 邻接且未被访问的任一顶点 w1 ,再访问与 w1 邻接且未被访问的任一顶点 w2 … ,重复上述过程。当不能再继续向下访问时,依次退回到最近被访问的顶点,若他还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,直到图中所有顶点均被访问过谓之。

//深度优先搜索
void GraphDFS(iGraph * graph, int start);
//广度优先搜索
void GraphBFS(iGraph * graph, iQueue *queue, int start);
//访问顶点
void visitVertext(iGraph * graph, int n);

性能分析

   BFS性能分析:无论是邻接表还是邻接矩阵的存储方式,BFS算法都要借助一个辅助队列 Q ,n 个顶点均需入队一次,在最坏的情况下,空间复杂度为 O(|V|) 。采用邻接表存储方式时,每个顶点均需搜索一次(或入队一次),故时间复杂度为 O(|V|) ,在搜索任一顶点的邻接点时,每条边至少访问一次,故时间复杂度为 O(|E|),算法总的时间复杂度为 O(|V| + |E|)。采用邻接矩阵存储方式时,查找每个顶点的邻接点所需的时间为 O(|V|) ,故算法总的时间复杂度为O(|V|^2)。
   DFS性能分析:DFS算法是一个递归算法,需要借助一个递归工作栈,故其空间复杂度为 O(|V|) 。遍历图的过程实际上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所用的存储结构。采用邻接矩阵存储方式时,查找每个顶点的邻接点所需的时间为 O(|V|) ,故算法总的时间复杂度为O(|V|^2)。采用邻接表存储方式时,查找所有顶点的邻接点所需的时间为 O(|E|) , 访问顶点所需的时间为 O(|V|),此时算法总的时间复杂度为 O(|V| + |E|)。

完整的程序如下:

#include<stdio.h>
#include<stdlib.h>
#include<stddef.h>

#define bool	_Bool
#define true	1
#define false	0
#define nullptr NULL
#define MaxVertexNum 4040  //图的最多的顶点个数

/*  ———————————————————Graph、Queue的结构—————————————————— */
//链表、队列的结点结构,保存图的相邻节点信息结构
struct Node{
    int data;
    struct Node* next;
};
typedef struct Node iNode;
//队列的基本结构
struct Queue{
    //头指针和尾指针
    iNode * front, *rear;
};
typedef struct Queue iQueue;
//保存图顶点信息的结构
struct Vertex{
    //bool值表示当前结点是否已被遍历
    bool visittag ;
    //存有相邻节点信息链表的头结点
    iNode * head ;
};
typedef struct Vertex iVertex ;
//图的基本结构
struct Graph{
    //分配MaxVertexNum个iVertex的连续空间,存储图顶点
    iVertex graphVertex[MaxVertexNum];
    //图总结点个数
    int vexnum;
};
typedef struct Graph iGraph ;

/*  ———————————————————所有实现的函数—————————————————— */
//具体的函数功能可以查看函数具体时实现前面的说明
void showList(iNode * head);
void showBackListDigui(iGraph * graph, iNode * head);
iNode * reversList(iNode * head);
void freeList(iNode * head);

void initGraph(const char * filename, iGraph* graph);
void showGraph(const iGraph * graph);
void reverseGraph(iGraph* graph);
void freeGraph(iGraph * graph);

void initQueue(iQueue * queue);
bool isQEmpty(const iQueue * queue);
void enQueue(iQueue * queue, int adddata);
bool deQueue(iQueue * queue, int * popdata);
void showQueue(const iQueue *queue);
void freeQueue(iQueue * queue);
void addListToQueue(const iGraph * graph, iQueue* queue,iNode* head);

void GraphDFS(iGraph * graph, int start);
void GraphBFS(iGraph * graph, iQueue *queue, int start);
void visitVertext(iGraph * graph, int n);
/*  ———————————————————main函数—————————————————— */
int main() {
    //保存图信息的文件
    const char * filename = "/home/wang/projects/workspace/main/data-bfs-shiyan.txt";
//     const char * filename = "/home/wang/projects/workspace/main/data-bfs-test";
    //初始化一个图grapg
    iGraph *graph = (iGraph *)malloc(sizeof(iGraph));
    initGraph(filename,graph);
    //初始化一个队列queue
    iQueue * queue = (iQueue *)malloc(sizeof(iQueue));
    initQueue(queue);
    //打印图的邻接表结构信息
    showGraph(graph);
    //将图的结点的邻接表信息反转
    reverseGraph(graph);
    showGraph(graph);
    //深度遍历
    printf("DFS........\n");
    GraphDFS(graph,1);
    
//     //广度遍历
//     printf("BFS............\n");
//     GraphBFS(graph,queue,1);
    
    //释放图和队列分配的空间
    freeGraph(graph); freeQueue(queue);
}

/*  ———————————————————BFS和DFS算法的实现—————————————————— */
/*递归实现图的深度遍历
 *假设图所有节点都是连通的
  *深度优先算法DFS*/
void GraphDFS(iGraph* graph, int start)
{
    //如果graph的第start个顶点未被遍历,则visit该顶点,并递归遍历该顶点的相邻节点
    if(!graph->graphVertex[start].visittag){
        //visit第start个顶点
        visitVertext(graph,start);
        //通过递归对graph的第start个顶点的相邻节点进行深度遍历;
        iNode * currnode = graph->graphVertex[start].head;
        while(currnode){
            GraphDFS(graph,currnode->data);
            currnode=currnode->next;
        }
    }
}

 /*增加队列空间实现图的广度遍历
  * 假设图所有节点都是连通的
  *广度优先算法BFS*/
void GraphBFS(iGraph* graph, iQueue * queue, int start)
{
    //visit初始顶点start
    visitVertext(graph,start);
    //初始顶点start入队
    enQueue(queue,start);
    //依次弹出队列中的元素并遍历,知道队列为空
    while(!isQEmpty(queue)){
        int popdata;
        //popdata保存下一个出队的顶点
        deQueue(queue,&popdata);
        //遍历当前顶点的相邻顶点点
        iNode * curr = graph->graphVertex[popdata].head;
        while(curr){
            /*如果的出队的顶点popdata的相邻顶点curr->data的visit tag为false,表示该顶点未被遍历
             * 则visit该顶点curr->data,并将该顶点放入队列queue中*/
            if(!graph->graphVertex[curr->data].visittag){
                //visit顶点curr->data
                visitVertext(graph,curr->data);
                //将顶对入队queue
                enQueue(queue,curr->data);
            }
            curr = curr->next;
        }
    }
    printf("\n");
}
/*  实现链表节点的反转
 *用到3个iNode指针nodea, nodeb, head*/
iNode * reversList(iNode* lhead)
{
    //列表没有或只有一个元素时不需要处理
    if(!lhead || !lhead->next)
        return lhead;
    //列表元素反转需要用到3个指针
    iNode *head = lhead;
    iNode *nodea = head->next;
    iNode * nodeb = head->next;
    head->next = nullptr;
    //反转的辅助空间复杂度为O(1)
    while(nodea){
        nodeb = nodea->next;
        nodea->next = head;
        head = nodea;
        nodea = nodeb;
    }
    return head;
}

/*visit graph的节点n,
 *修改结点n的标记值
 * 输出显示节点值*/
void visitVertext(iGraph * graph ,int nVertex)
{
    //将顶点的标记值标记为true
    graph->graphVertex[nVertex].visittag = true;
    printf("%d ",nVertex);
}

/*  ———————————————————Graph的相关函数的实现—————————————————— */

/*从文本中读取图结点间边的信息并建图
 *以邻接表的形式保存图信息*/
void initGraph(const char* filename, iGraph* graph)
{
    //初始化Graph的空间和值
    graph->vexnum = MaxVertexNum;
    int i =0 ;
    while(i < graph->vexnum){
        graph->graphVertex[i].head = nullptr;
        graph->graphVertex[i++].visittag = false;
    }
    //从文件中读取图的边的信息,并构建邻接矩阵
    int vernum, adjnum;
    FILE * filep = fopen(filename, "r");
    if(filep){
        while(fscanf(filep, "%d %d" ,&vernum,&adjnum)!= EOF){
            iNode * addadjnode = (iNode *)malloc(sizeof(iNode));
            if(addadjnode){
                addadjnode->data = adjnum;
                addadjnode->next = graph->graphVertex[vernum].head;
                graph->graphVertex[vernum].head = addadjnode;
            }
        }
        graph->vexnum = vernum ;
    }
    else printf("file erro");
    fclose(filep);
}

/*  将图所有Vetex的相邻节点值的列表反转 */
void reverseGraph(iGraph* graph)
{
    int i = 0;
    while(i < graph->vexnum){
        graph->graphVertex[i].head = reversList(graph->graphVertex[i].head);
        i++;
    }
}
/*  ———————————————————Queue的相关函数的实现—————————————————— */
/*  初始化队列queue */
void initQueue(iQueue* queue)
{
    iNode * ihead = (iNode *) malloc (sizeof(iNode));
    ihead->data = rand()%10;
    ihead->next = nullptr;
    queue->front = ihead;
    queue->rear = ihead;
}

/* 队列非空时弹出front指向的节点
 * 将节点data传给popdata
 *释放队列弹出的值的空间*/ 
bool deQueue(iQueue* queue, int* popdata)
{
    //出队时先判断当前队列是否为空
    if(isQEmpty(queue))
        return false;
    //popdata记录出队值
    iNode * qhead = queue->front->next;
    *popdata = qhead->data;
    //队列头指针后移并释放空间
    queue->front ->next = qhead->next;
    if (qhead == queue->rear)
        queue->rear = queue->front;
    free(qhead);
    return true;
}

/* 将adddata入队列queue
 * 创建节点插入到队尾*/
void enQueue(iQueue* queue, int adddata)
{
    //创建新结点保存入队数据
    iNode * addnode = (iNode *)malloc(sizeof(iNode));
    addnode->next = nullptr;
    addnode->data = adddata;
    //将入队结点插入到队列的尾指针后面
    queue->rear->next = addnode;
    //更新尾指针
    queue->rear = addnode;
}

/*  判断队列queue是否为空
 *空->true    非空->false*/
bool isQEmpty(const iQueue* queue)
{
    //三目运算符判断队列是否为空
    return (queue->front==queue->rear)?true:false;
}
/*  ———————————————————List的相关函数的实现—————————————————— */

/*  递归算法 反向输出链表中每个节点的值*/
void showBackListDigui(iGraph * graph,iNode* head)
{
    //如若当前非最后一个元素,继续递归
    if(head->next)
        showBackListDigui(graph,head->next);
    if(head) visitVertext(graph,head->data);
}

/*  没什么大的作用,把图的某个节点的所有相邻节点放到队列中*/
void addListToQueue(const iGraph * graph, iQueue* queue, iNode* head)
{
    showList(head);
    iNode * lhead = head;
    while(lhead){
        if(!graph->graphVertex[lhead->data].visittag)
            enQueue(queue,lhead->data);
        lhead = lhead->next;
    }
    showQueue(queue);
}

/*  ———————————————List、Queue、Graph信息显示和空间释放——————————————— */
/* 打印list元素的值 */
void showList(iNode* head)
{
    iNode * currnode = head;
    while(currnode){
        printf("%d ",currnode->data);
        currnode = currnode->next;
    }
    printf("\n");
}
/*显示当前Queue的值
 *queue 为空显示 queue empty
 * 非空传入头节点,作为链表输出元素值*/
void showQueue(const iQueue* queue)
{
    if(isQEmpty(queue))
        printf("queue empty\n");
    else {
        showList(queue->front->next);
    }
}
/* 打印构建的图的值 */
void showGraph(const iGraph* graph)
{
    int i = 0;
    printf("graph vertex %d\n",graph->vexnum);
    while(i < graph->vexnum){
        printf("vertex %d:",i);
        showList(graph->graphVertex[i].head);
        i++;
    }
}
/*  释放List的分配的空间*/
void freeList(iNode * head){
//     printf("free list\n") ;
    iNode * curnode ;
    while(head){
        curnode = head;
        head = head->next ;
        free(curnode);
    }
}
/*  释放Queue分配的空间 */
void freeQueue(iQueue * queue){
    iNode *head ;
    printf("free queue\n");
    freeList(queue->front);
    free(queue);
}

/*  释放Graph分配的空间 */
void freeGraph(iGraph* graph)
{
    printf("free graph\n");
    int i = 0;
    while(i < graph->vexnum){
        freeList(graph->graphVertex[i].head);
        i++;
    }
    free(graph);
}

程序运行结果:
在这里插入图片描述

测试数据:

0 1
0 4
0 7
0 2
0 3
0 6
1 0
1 2
1 3
2 1
2 3
2 7
3 1
3 2
4 0
4 5
4 6
5 4
6 4
7 0
7 2

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值