图的概念+存储+遍历(JAVA)

图的存储结构

十字链表(针对有向图)

由于简单的拉链法对有向图太不友好(统计入度需要扫描全表),所以引入十字链表。
十字链表由数组+链表组成,数组存放节点对象,链表存放边对象。
这样的优势是,顺着出边表可以找到所有以该节点为出点的边,顺着入边表可以找到所有以该节点为入点的边。

  /**
     * 十字链表
     * 针对有向图
     */
    public static class AcrossLinkedList{
        //数组部分
        NodeElement[] elements = new NodeElement[10];

        /**
         * 用于描述节点
         */
        public static class NodeElement{
            //以该节点为出点的边的链表的头节点
            EdgeElement outerEdgeElement;
            //以该节点为入点的边的链表的头节点
            EdgeElement innerEdgeElement;
            //该节点中存储的数据
            Object data;

            public NodeElement(EdgeElement outerEdgeElement, EdgeElement innerEdgeElement, Object data) {
                this.outerEdgeElement = outerEdgeElement;
                this.innerEdgeElement = innerEdgeElement;
                this.data = data;
            }
        }

        /**
         * 用于描述边
         */
        public static class EdgeElement{
            //从哪个节点出
            NodeElement outerNode;
            //到哪个节点入
            NodeElement innerElement;
            //出链表中下一个边节点:即下一个出节点相同的边
            EdgeElement outerNextEdge;
            //入链表中下一个边节点:即下一个入节点相同的边
            EdgeElement innerNextEdge;

            public EdgeElement(NodeElement outerNode, NodeElement innerElement, EdgeElement outerNextEdge, EdgeElement innerNextEdge) {
                this.outerNode = outerNode;
                this.innerElement = innerElement;
                this.outerNextEdge = outerNextEdge;
                this.innerNextEdge = innerNextEdge;
            }
        }

        public static void main(String[] args) {
            AcrossLinkedList acrossLinkedList = new AcrossLinkedList();
            NodeElement a = new NodeElement(null, null, "A");
            NodeElement b = new NodeElement(null, null, "B");
            NodeElement c = new NodeElement(null, null, "C");
            NodeElement d = new NodeElement(null, null, "D");

            EdgeElement bc = new EdgeElement(b, c, null, null);
            EdgeElement ac = new EdgeElement(a, c, null, bc);
            EdgeElement cd = new EdgeElement(c, d, null, null);
            EdgeElement cb = new EdgeElement(c, b, cd, null);
            EdgeElement da = new EdgeElement(d, a, null, null);
            EdgeElement ab = new EdgeElement(a, b, ac, cb);

            a.outerEdgeElement=ab;
            b.outerEdgeElement=bc;
            c.outerEdgeElement=cb;
            d.outerEdgeElement=da;

            a.innerEdgeElement=da;
            b.innerEdgeElement=ab;
            c.innerEdgeElement=ac;
            d.innerEdgeElement=cd;

            acrossLinkedList.elements[0] = a;
            acrossLinkedList.elements[1] = b;
            acrossLinkedList.elements[2] = c;
            acrossLinkedList.elements[3] = d;

            System.out.println(acrossLinkedList.elements);

        }
    }

邻接多重表(针对无向图)

同样,如果用简单的拉链法表示无向图,如果要删除某条边,则需要删除两处(比如删除ab边,就得删除ab节点和ba节点)。
邻接多重表中,为了使删除方便,希望所有依附于同一节点的边都在一个链表里,同时不论ab还是ba,只用一个节点表示。

 

 public static class multiAjacent{
        /**
         * 用于描述节点
         */
        public static class MaNode{
            //用于存放数据
            Object data;
            //用于指向边
            MaEdge maEdge;

            public MaNode(Object data, MaEdge maEdge) {
                this.data = data;
                this.maEdge = maEdge;
            }
        }

        /**
         * 用于描述边
         */
        public static class MaEdge{
            MaNode from;
            MaNode to;
            //下一条依附于from节点的边
            MaEdge belongFromEdge;
            //下一条依附于to节点的边
            MaEdge belongToEdge;

            public MaEdge(MaNode from, MaNode to, MaEdge belongFromEdge, MaEdge belongToEdge) {
                this.from = from;
                this.to = to;
                this.belongFromEdge = belongFromEdge;
                this.belongToEdge = belongToEdge;
            }
        }
    }

图的遍历

从图中某一顶点出发,遍历访问其他所有顶点,且使得每一个顶点只被访问一次
广度优先搜索 类似于树的层序遍历,设有向图如图, 则BFS算法流程如下:

  1. 申请一个访问状态队列A,A[0]=A节点是否被访问过,A[1]表示B节点是否被访问过,依次类推
  2. 申请一个辅助队列B,用于存放待处理节点
  3. A节点入辅助队列
  4. 若辅助队列不为空,则处理辅助队列中的节点(此时队列中只有A节点),A节点出队,修改访问状态队列中A的被访问状态为true,遍历从A出去的弧,把被这些弧的另外一个节点入辅助队列
  5. 循环步骤4,直到访问状态队列中的所有节点访问状态均为true为止

代码为:

public static void BFS(AcrossLinkedList acrossLinkedList){
            //申请一个节点被访问状态序列
            Map<String,Boolean> visited = new HashMap<>(4);
            visited.put("A",false);
            visited.put("B",false);
            visited.put("C",false);
            visited.put("D",false);
            //申请一个存放待处理节点的队列
            Queue<NodeElement> assistanceQueue = new LinkedList<>();
            //循环直到所有节点访问状态均为true为止
            for (NodeElement element : acrossLinkedList.elements) {
                assistanceQueue.offer(element);
                while (!assistanceQueue.isEmpty()){ //当队列不为空
                    //顺序弹出队列中的节点
                    NodeElement poll = assistanceQueue.poll();
                    //若已经访问过,则跳过
                    if (visited.get(poll.data)){
                        continue;
                    }
                    System.out.println(poll.data); // 输出
                    visited.put(poll.data.toString(),true);//将访问状态改为true
                    EdgeElement edge = poll.outerEdgeElement;
                    while (edge!=null){
                        //将边的另一头入队
                        assistanceQueue.offer(edge.innerElement);
                        edge = edge.outerNextEdge;
                    }
                }
            }
        }
    }

知识点:

  1. 最坏情况下,空间复杂度为o(|v|),即辅助队列满的情况
  2. 时间复杂度=每个节点入队一次的复杂度+每条边处理一次的复杂度=o(|v|+|E|),相比于邻接矩阵o(|v|2)快了很多
  3. 邻接链表产生的广度优先生成树是不唯一的,邻接矩阵是唯一的

 

深度优先搜索

类似于树的先序遍历,先根,然后递归左孩子,再递归右孩子,设待遍历的无向图如图

其搜索流程是先<0,1> -> <1,2> -> <2,3> -> <3,4> 尽可能深的搜索

/**
     * 多重邻接表
     */
    public static class MultiAdjacent{
        //数组部分
        MaNode[] elements = new MaNode[4];
        /**
         * 用于描述节点
         */
        public static class MaNode{
            //用于存放数据
            Object data;
            //用于指向边
            MaEdge maEdge;

            public MaNode(Object data, MaEdge maEdge) {
                this.data = data;
                this.maEdge = maEdge;
            }
        }

        /**
         * 用于描述边
         */
        public static class MaEdge{
            MaNode from;
            MaNode to;
            //下一条依附于from节点的边
            MaEdge belongFromEdge;
            //下一条依附于to节点的边
            MaEdge belongToEdge;

            public MaEdge(MaNode from, MaNode to, MaEdge belongFromEdge, MaEdge belongToEdge) {
                this.from = from;
                this.to = to;
                this.belongFromEdge = belongFromEdge;
                this.belongToEdge = belongToEdge;
            }
        }

        public static void main(String[] args) {
            MultiAdjacent multiAdjacent = new MultiAdjacent();
            MaNode a = new MaNode("A", null);
            MaNode b = new MaNode("B", null);
            MaNode c = new MaNode("C", null);
            MaNode d = new MaNode("D", null);

            MaEdge ab = new MaEdge(a, b, null, null);
            MaEdge ac = new MaEdge(a, c, ab, null);
            MaEdge ad = new MaEdge(a, d, ac, null);
            MaEdge bc = new MaEdge(b, c, null, ac);
            MaEdge cd = new MaEdge(c, d, null, ad);

            a.maEdge = ad;
            b.maEdge = bc;
            c.maEdge = cd;
            d.maEdge = null;

            multiAdjacent.elements[0] = a;
            multiAdjacent.elements[1] = b;
            multiAdjacent.elements[2] = c;
            multiAdjacent.elements[3] = d;

            //申请一个节点被访问状态序列
            Map<String,Boolean> visited = new HashMap<>(4);
            visited.put("A",false);
            visited.put("B",false);
            visited.put("C",false);
            visited.put("D",false);

            for (MaNode element : multiAdjacent.elements) {
                DFS(element,visited);
            }
        }

        /**
         * 类似于树的先序遍历,访问根节点,递归处理belongFromEdge,再递归处理belongToEdge
         */
        public static void DFS(MaNode root,Map<String,Boolean> visited){
            //递归出口
            if (root==null){
                return;
            }
            //若已经访问过,则跳过
            if (visited.get(root.data)){
                return;
            }
            //否则,先访问根节点
            System.out.println(root.data);
            visited.put(root.data.toString(),true);

            MaEdge maEdge = root.maEdge;
            if (maEdge!=null){
                //递归“左子树” belongFromEdge
                if (maEdge.belongFromEdge != null){
                    DFS(maEdge.belongFromEdge.to,visited);
                }
                //递归“右子树” belongToEdge
                if (maEdge.belongToEdge != null){
                    DFS(maEdge.belongToEdge.from,visited);
                }
            }
        }
    }

结果:

知识点:

  1. 时间复杂度同样是o(|v|+|e|)
  2. 空间复杂度即递归栈的大小,最好的情况类似于平衡二叉树o(log2|v|),最坏情况o(|v|)
  3. 邻接矩阵的深度优先生成树是唯一的,多重邻接表的深度优先生成树是不唯一的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值