算导实验八:图搜索BFS算法及存储优化

实验要求:

       针对无向图,通过邻接多重表方式进行存储。以节点A为起始节点,输出图的广度优先遍历的过程。

例如,对下图广度优先遍历的过程为:A-B-D-C-E。

 

程序输入:

              文件data.txt

               A,B,C,D,E,F,G,H
               A-B
               A-C
               C-D
               C-E
               D-E
               F-G

程序输出:

              图的广度优先遍历过程。

 

1.实验内容

针对无向图,通过邻接多重表方式进行存储。以节点A为起始节点,输出图的广度优先遍历的过程。例如,对下图广度优先遍历的过程为:A-B-D-C-E。

图1 示例图

2.实验目的

1.掌握无向图的邻接多重表存储结构。

2.掌握图的广度优先搜索算法。

3.邻接多重表定义

当用邻接表存储时,访问或删除一条边(Vi,Vj) 时需要同时访问两个链表ij 并分别找到对应的边结点,这给针对图的边的操作(标记或删除)带来不便利。邻接多重表因此而演变过来的。

定义邻接多重表类GraphMultiplyTable ,类中有三个内部类VertexEdgeGraphEdgeVertex 为图的顶点类;String 类型的属性vName 指明了该顶点的标签,Edge 的属性firstEdge 存储第一条边的指针;int 类型的color 表明当前结点的颜色,用于遍历时使用。

    /**
     * vName-->顶点名称
     * firstEdge-->顶点边链表的头结点
     */
    public static class Vertex
    {
        private String vName;
        private Edge firstEdge;
        private int color;

        public String getvName()
        {
            return vName;
        }

        public int getColor()
        {
            return color;
        }

        public void setColor(int color)
        {
            this.color = color;
        }

        public Vertex(String name)
        {
            this.vName = name;
        }

        public Edge getFirstEdge()
        {
            return firstEdge;
        }
    }

 Edge 类为图的边类,int 类型的属性iVexjVex 指明了当前边所连的两个结点在结点列表中的索引;Edge 类型的属性iLinkjLink 保存两个顶点的边链表指针。

    /**
     * iVex-->边的其中一个顶点A
     * <p>
     * iLink-->边中顶点A的边链表的指针
     * <p>
     * jVex-->边的另一个顶点B
     * <p>
     * jLink-->边中顶点B的边链表的指针
     */
    public static class Edge
    {
        private int iVex;
        private Edge iLink;
        private int jVex;
        private Edge jLink;

        public int getiVex()
        {
            return iVex;
        }

        public Edge getiLink()
        {
            return iLink;
        }

        public int getjVex()
        {
            return jVex;
        }

        public Edge getjLink()
        {
            return jLink;
        }

        public Edge()
        {
        }

        public Edge(int iVex, int jVex)
        {
            this.iVex = iVex;
            this.jVex = jVex;
        }
    }

GraphEdge 类具有两个String 类型的属性vexotherVex ,用来初始化具有指明的结点的边。

    /**
     * 边的两个顶点
     */
    public static class GraphEdge
    {
        private String vex;
        private String otherVex;

        public GraphEdge(String vexName, String otherVexName)
        {
            this.vex = vexName;
            this.otherVex = otherVexName;
        }
    }

GraphMultiplyTable 类中还有属性vertexArr ,用来保存图的结点。

    private List<Vertex> vertexArr; //顶点列表

4.源码+注释

4.1图的初始化

GraphMultiplyTable 类中具有init 方法,接收顶点集vexArr 和边集edgeList ,来初始化一个图。对于顶点集的初始化较为简单,只需将vexArr 中的标签分别创建vertex 对象并保存在vertexArr 中即可。初始化边集时,首先获取边的两个顶点vexotherVex ,若在顶点集中包含这两个顶点的话,则获取这两个顶点,并构造两个顶点的边,然后采用头插法将新边插入到两个顶点的边链表当中。

    /**
     * 初始化整个图
     *
     * @param vexArr   顶点集
     * @param edgeList 边集
     */
    public void init(List<String> vexArr, List<GraphEdge> edgeList)
    {
        initVexArr(vexArr);
        initEdge(edgeList);
    }

    /**
     * 初始化顶点集
     *
     * @param vexArr 顶点集
     */
    private void initVexArr(List<String> vexArr)
    {
        vertexArr = new ArrayList<>(vexArr.size());
        for (int i = 0; i < vexArr.size(); i++)
        {
            Vertex vertex = new Vertex(vexArr.get(i));
            vertexArr.add(vertex);
        }
    }

    /**
     * 初始化边集
     *
     * @param edgeList 边集
     */
    private void initEdge(List<GraphEdge> edgeList)
    {
        for (int i = 0; i < edgeList.size(); i++)
        {
            GraphEdge graphEdge = edgeList.get(i);
            //如果顶点集中包含这两个顶点
            if (isContains(graphEdge.vex) && isContains(graphEdge.otherVex))
            {
                //获取顶点的下标
                int vIndex = getVexIndex(graphEdge.vex);
                int oIndex = getVexIndex(graphEdge.otherVex);
                //获取顶点
                Vertex vertex = vertexArr.get(vIndex);
                Vertex oVertex = vertexArr.get(oIndex);
                //构造两个顶点的边
                Edge edge = new Edge(vIndex, oIndex);
                //头插法插入vertex的边
                if (vertex.firstEdge == null) vertex.firstEdge = edge;
                else
                {
                    Edge vexNextEdge = vertex.firstEdge;
                    edge.iLink = vexNextEdge;
                    vertex.firstEdge = edge;
                }
                //头插法插入oVertex的边
                if (oVertex.firstEdge == null) oVertex.firstEdge = edge;
                else
                {
                    Edge oVexNextEdge = oVertex.firstEdge;
                    edge.jLink = oVexNextEdge;
                    oVertex.firstEdge = edge;
                }
            }
        }
    }

4.2 BFS算法

bfs方法接收一个图的邻接表graph和开始遍历的结点source。首先对图的结点做预处理,将所有的结点设为白色。接下来初始化一个遍历队列queue,将源节点source加入到queue当中,并将source的颜色设为灰色。然后调用subBfs方法进行遍历,当队列不空时进行循环,首先取出队首元素赋给vertex,获取vertex的边链表指针edge。对于边链表中的每一条边edge,当edge不为null时,如果该edge的另一个顶点的颜色为WHITE,则将其加入到queue中并将其颜色设为灰色。当edge为null时,说明已经遍历完与vertex相连的所有的边,于是将vertex的颜色设为BLACK,并输出vertex的标签vName,而后继续循环。当该方法执行结束时,以BFS的策略输出了graph当中的一个连通分量。当以指定的source调用subBfs结束时,需检查vertexArr中的所有节点是否均不为WHITE,若还有节点为WHITE,则说明该图graph有多个连通分量,需继续遍历。

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2022/1/2  10:57
 */

import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;

public class BFS
{
    private static final int BLACK = 0;
    private static final int WHITE = 1;
    private static final int GRAY = 2;

    public void bfs(GraphMultiplyTable graph, GraphMultiplyTable.Vertex source)
    {
        List<GraphMultiplyTable.Vertex> vertexArr = graph.getVertexArr();

        //预处理 所有节点设为白色
        for (GraphMultiplyTable.Vertex vertex : vertexArr)
            vertex.setColor(WHITE);

        //初始化队列,源节点设为灰色
        Queue<GraphMultiplyTable.Vertex> queue = new ArrayDeque<>();
        queue.add(source);
        source.setColor(GRAY);
        subBfs(queue, graph);

        //检查是否还有未遍历结点
        for (GraphMultiplyTable.Vertex vertex : vertexArr)
        {
            if (vertex.getColor() == WHITE)
            {
                Queue<GraphMultiplyTable.Vertex> queue1 = new ArrayDeque<>();
                queue1.add(vertex);
                vertex.setColor(GRAY);
                subBfs(queue1, graph);
            }
        }
    }

    private void subBfs(Queue<GraphMultiplyTable.Vertex> queue,
                        GraphMultiplyTable graph)
    {
        //队列不空循环
        while (!queue.isEmpty())
        {
            GraphMultiplyTable.Vertex vertex = queue.poll();
            GraphMultiplyTable.Edge edge = vertex.getFirstEdge();
            while (edge != null)
            {
                if (graph.getVertexArr().get(edge.getjVex()).getColor() == WHITE)
                {
                    queue.add(graph.getVertexArr().get(edge.getjVex()));
                    graph.getVertexArr().get(edge.getjVex()).setColor(GRAY);
                }
                edge = edge.getiLink();
            }
            vertex.setColor(BLACK);
            System.out.print(vertex.getvName() + " ");
        }
        System.out.println();
    }
}

5.算法测试结果

读取data.txt中的数据,顶点为A,B,C,D,E,F,G,H,边集为A-B,A-C,C-D,C-E,D-E,F-G可知该图有三个连通分量ABCDE、FG、H。输入程序当中进行遍历,输出结果如下图所示

 

6.实验过程中遇到的困难及收获

通过本次实验实现了无向图的邻接多重表存储方式,对图的各种存储方式有了更深的理解,采用邻接表在对边进行操作时(如删除),需要两次操作,因为一条边在两个链表里面存储,而邻接多重表的优点就在于对边操作时只需要一次操作,这就意味着边只存储一次,可以更便捷的遍历;但是邻接多重表的实现复杂,结点中引入了许多指针域。

7.实验源代码

BFS.java

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2022/1/2  10:57
 */

import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;

public class BFS
{
    private static final int BLACK = 0;
    private static final int WHITE = 1;
    private static final int GRAY = 2;

    public void bfs(GraphMultiplyTable graph, GraphMultiplyTable.Vertex source)
    {
        List<GraphMultiplyTable.Vertex> vertexArr = graph.getVertexArr();

        //预处理 所有节点设为白色
        for (GraphMultiplyTable.Vertex vertex : vertexArr)
            vertex.setColor(WHITE);

        //初始化队列,源节点设为灰色
        Queue<GraphMultiplyTable.Vertex> queue = new ArrayDeque<>();
        queue.add(source);
        source.setColor(GRAY);
        subBfs(queue, graph);

        //检查是否还有未遍历结点
        for (GraphMultiplyTable.Vertex vertex : vertexArr)
        {
            if (vertex.getColor() == WHITE)
            {
                Queue<GraphMultiplyTable.Vertex> queue1 = new ArrayDeque<>();
                queue1.add(vertex);
                vertex.setColor(GRAY);
                subBfs(queue1, graph);
            }
        }
    }

    private void subBfs(Queue<GraphMultiplyTable.Vertex> queue,
                        GraphMultiplyTable graph)
    {
        //队列不空循环
        while (!queue.isEmpty())
        {
            GraphMultiplyTable.Vertex vertex = queue.poll();
            GraphMultiplyTable.Edge edge = vertex.getFirstEdge();
            while (edge != null)
            {
                if (graph.getVertexArr().get(edge.getjVex()).getColor() == WHITE)
                {
                    queue.add(graph.getVertexArr().get(edge.getjVex()));
                    graph.getVertexArr().get(edge.getjVex()).setColor(GRAY);
                }
                edge = edge.getiLink();
            }
            vertex.setColor(BLACK);
            System.out.print(vertex.getvName() + " ");
        }
        System.out.println();
    }
}

GraphMultiplyTable.java

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2022/1/2  11:00
 */

import java.util.ArrayList;
import java.util.List;

/**
 * 无向图的邻接多重表的实现<p>
 * 优点: 使用邻接表在对边进行操作时(如删除),需要两次操作,因为一条边在两个链表里面存储,
 * 而邻接多重表的优点就在于对边操作时只需要一次操作,这就意味着边只存储一次
 */
public class GraphMultiplyTable
{
    /**
     * vName-->顶点名称
     * firstEdge-->顶点边链表的头结点
     */
    public static class Vertex
    {
        private String vName;
        private Edge firstEdge;
        private int color;

        public String getvName()
        {
            return vName;
        }

        public int getColor()
        {
            return color;
        }

        public void setColor(int color)
        {
            this.color = color;
        }

        public Vertex(String name)
        {
            this.vName = name;
        }

        public Edge getFirstEdge()
        {
            return firstEdge;
        }
    }

    /**
     * iVex-->边的其中一个顶点A
     * <p>
     * iLink-->边中顶点A的边链表的指针
     * <p>
     * jVex-->边的另一个顶点B
     * <p>
     * jLink-->边中顶点B的边链表的指针
     */
    public static class Edge
    {
        private int iVex;
        private Edge iLink;
        private int jVex;
        private Edge jLink;

        public int getiVex()
        {
            return iVex;
        }

        public Edge getiLink()
        {
            return iLink;
        }

        public int getjVex()
        {
            return jVex;
        }

        public Edge getjLink()
        {
            return jLink;
        }

        public Edge()
        {
        }

        public Edge(int iVex, int jVex)
        {
            this.iVex = iVex;
            this.jVex = jVex;
        }
    }

    /**
     * 边的两个顶点
     */
    public static class GraphEdge
    {
        private String vex;
        private String otherVex;

        public GraphEdge(String vexName, String otherVexName)
        {
            this.vex = vexName;
            this.otherVex = otherVexName;
        }
    }

    private List<Vertex> vertexArr; //顶点列表

    public List<Vertex> getVertexArr()
    {
        return vertexArr;
    }

    /**
     * 初始化整个图
     *
     * @param vexArr   顶点集
     * @param edgeList 边集
     */
    public void init(List<String> vexArr, List<GraphEdge> edgeList)
    {
        initVexArr(vexArr);
        initEdge(edgeList);
    }

    /**
     * 初始化顶点集
     *
     * @param vexArr 顶点集
     */
    private void initVexArr(List<String> vexArr)
    {
        vertexArr = new ArrayList<>(vexArr.size());
        for (int i = 0; i < vexArr.size(); i++)
        {
            Vertex vertex = new Vertex(vexArr.get(i));
            vertexArr.add(vertex);
        }
    }

    /**
     * 初始化边集
     *
     * @param edgeList 边集
     */
    private void initEdge(List<GraphEdge> edgeList)
    {
        for (int i = 0; i < edgeList.size(); i++)
        {
            GraphEdge graphEdge = edgeList.get(i);
            //如果顶点集中包含这两个顶点
            if (isContains(graphEdge.vex) && isContains(graphEdge.otherVex))
            {
                //获取顶点的下标
                int vIndex = getVexIndex(graphEdge.vex);
                int oIndex = getVexIndex(graphEdge.otherVex);
                //获取顶点
                Vertex vertex = vertexArr.get(vIndex);
                Vertex oVertex = vertexArr.get(oIndex);
                //构造两个顶点的边
                Edge edge = new Edge(vIndex, oIndex);
                //头插法插入vertex的边
                if (vertex.firstEdge == null) vertex.firstEdge = edge;
                else
                {
                    Edge vexNextEdge = vertex.firstEdge;
                    edge.iLink = vexNextEdge;
                    vertex.firstEdge = edge;
                }
                //头插法插入oVertex的边
                if (oVertex.firstEdge == null) oVertex.firstEdge = edge;
                else
                {
                    Edge oVexNextEdge = oVertex.firstEdge;
                    edge.jLink = oVexNextEdge;
                    oVertex.firstEdge = edge;
                }
            }
        }
    }

    /**
     * 顶点列表中是否包含当前顶点
     *
     * @param vName 顶点名称
     */
    private boolean isContains(String vName)
    {
        for (Vertex vertex : vertexArr)
        {
            if (vertex.vName.equals(vName)) return true;
        }
        return false;
    }

    private int getVexIndex(String vName)
    {
        for (int i = 0; i < vertexArr.size(); i++)
        {
            if (vertexArr.get(i).vName.equals(vName)) return i;
        }
        return -1;
    }

    public void print()
    {
        for (Vertex vertex : vertexArr)
        {
            System.out.println("顶点 " + vertex.vName + " 的所有边: ");
            int vIndex = getVexIndex(vertex.vName);
            Edge cursor = vertex.firstEdge;
            while (cursor != null)
            {
                System.out.print(cursor.iVex + "---" + cursor.jVex + " ||");
                if (cursor.iVex == vIndex)
                {
                    cursor = cursor.iLink;
                }
                else
                {
                    cursor = cursor.jLink;
                }
            }

            System.out.println();
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值