无向图邻接表之深度优先算法和广度优先算法的理解与C#代码实现

基本数据的类型是Node<T>类型,包含了以下字段和对应的属性

private T data;

图的顶点是顺序存储结构,无向图的顶点是VexNode<T>类型,每个顶点都对应一个链式存储结构,来保存该顶点和其他顶点的边。VexNode<T>类包含了以下字段和对应的属性

private Node<T> data;
private AdjListNode<T> firstAdj;

每个顶点对应的链表的节点是AdjListNode<T>类型,节点的数据结构如下。比如图有两个顶点A、B且二者有边,A在数组的位置是0 ,B的位置是1。那么顶点A的链表中,就存在一个邻接表节点,其字段adjvex就等于顶点B在数组中的索引。

//其在邻接表顶点数组的索引
private int adjvex;
//下一个节点的引用域
private AdjListNode<T> next;

图的邻接表类中包含了两个字段,邻接表顶点数组和访问数组。邻接表顶点数组就是图的顶点按顺序存储结构存储,访问数组和顶点数组长度相同,索引也一一对应,数组成员值为0代表遍历算法没有访问,值为1代表该顶点被访问了。以下是图的邻接表的构造器,包含了两个数组的初始化。

public GraphAdjList(Node<T>[] nodes)
{
    //邻接表顶点数组的长度为构造器传入的Node类型数组参数的长度
    adjList = new VexNode<T>[nodes.Length];
    //遍历并初始化邻接表数组的数据为对应的nodes数组成员,其第一个邻接节点为空
    for(int i = 0; i < nodes.Length; i++)
    {
        adjList[i].Data = nodes[i];
        adjList[i].FirstAdj = null;
    }
    //遍历算法的访问数组的长度为邻接表顶点数组的长度
    visited = new int[adjList.Length];
    //遍历并初始化访问数组的值,其值为邻接表节点在数组中的序号
    for(int i = 0; i < visited.Length; i++)
    {
        visited[i] = 0;
    }
}

深度优先算法的原理

遍历访问数组,根据索引找到没有被访问的顶点。拿到该顶点在访问数组的索引后就确定了顶点在顶点数组中的位置,利用链表的next遍历所有与顶点相连的顶点。如果与该顶点相连的顶点没有被访问,就递归调用自身,再访问与相连顶点所相连的顶点。所以深度优先算法的本质是递归。

//无向图的深度优先算法
public void DFS()
{
    for(int i = 0; i < visited.Length; i++)
    {
        //遍历访问数组
        if (visited[i] == 0)
        {
            DFSAL(i);
        }
    }
}
public void DFSAL(int i)
{
    //从第i个顶点开始出发,第i顶点设为1表示被访问
    visited[i] = 1;
    //声明邻接表结点p为顶点数组的第i个成员的第一个邻接表结点
    AdjListNode<T> p = adjList[i].FirstAdj;
    while(p!=null)
    {
        //判断节点p所对应在顶点数组中的顶点是否被访问过
        if (visited[p.AdjVex] == 0)
        {
            //如果没被访问过,递归调用自身
            DFSAL(p.AdjVex);
        }
        //p为p的next,遍历这条链表
        p = p.Next;
    }
}

广度优先算法的原理

广度优先算法需要用到队列,声明一个整型队列(该队列数据结构是前几章里书的作者自己实现的,API与C#的Queue有所不同),将第i个顶点入队后开始循环,直到队列为空结束。循环中整型变量k接收出队的顶点索引,找到索引对应的在顶点数组中的顶点,以及其对应的链表,声明一个邻接表节点为链表第一个节点的引用,遍历链表(即遍历与该顶点相邻的顶点,将相邻的顶点设为访问过)。访问过相邻的顶点后,将相邻的顶点入队,遍历结束。再次循环,将队中刚才入队的顶点依次取出遍历其相邻的顶点,直到全部都被访问过。

//广度优先算法
public void BFS()
{
    //遍历访问数组
    for (int i = 0; i < visited.Length; ++i)
    {
        if (visited[i] == 0)
        {
            BFSAL(i);
        }
    }
}
//从某个顶点出发进行广度优先遍历
public void BFSAL(int i)
{
    //从第i个顶点开始出发,第i顶点设为1表示被访问
    visited[i] = 1;
    //声明一个整型队列,长度为顶点的数量
    CSeqQueue<int> cq = new CSeqQueue<int>(visited.Length);
    //将第i个顶点入队
    cq.In(i);
    //队列为空时结束循环
    while (!cq.IsEmpty())
    {
        //声明整形变量k,接收出队的数据
        int k = cq.Out();
        //声明邻接表节点类型变量p,为邻接顶点数组中第k个顶点的第一个邻接表节点(链式)
        AdjListNode<T> p = adjList[k].FirstAdj;
        // 节点p为空时结束循环
        while (p != null)
        {
            //如果p节点的相邻顶点(p节点在图中的相邻顶点)的访问数组为0,说明该相邻顶点没有被访问过
            if (visited[p.AdjVex] == 0)
            {
                //将该序号的顶点设为访问过
                visited[p.AdjVex] = 1;
                //将该顶点的序号入队
                cq.In(p.AdjVex);
            }
            //访问p节点(在链表上)的下一个节点
            p = p.Next;
        }
    }
}

注:本博文是《数据结构(C#语言版)》一书的学习笔记

扫描二维码关注公众号,回复: 3113948 查看本文章

猜你喜欢

转载自blog.csdn.net/u012187817/article/details/82319076