图数据结构,图的统一数据结构和非标准图的转化算法

图数据结构,图的统一数据结构和非标准图的转化算法

提示:大厂笔试面试都可能不咋考的数据结构:图

由于图的结构比较难,出题的时候,很难把这个图的数据搞通顺,而且搞通顺了题目也需要耗费太多时间,故笔试面试都不会咋考
笔试大厂考的就是你的贪心取巧策略和编码能力,这完全不必用图来考你,其他的有大量的选择
面试大厂考你的是优化算法的能力,但是图没啥可以优化的,只要数据结构统一,它的算法是固定死的,所以不会在面试中考!
万一考,那可能都是大厂和图相关的业务太多,比如美团高德地图啥的,这种考的少。

但不管考不考,我们要有基础,要了解图的数据结构和算法。万一考了呢,准备以备不时之需。

图的数据结构比较难,算法是固定的套路
咱们需要统一一种自己熟悉的图的数据结构,方便套用算法时好写!!


图的各种表示方法

图,就是一堆节点和连接这些节点的一堆边构成的数据结构:
在这里插入图片描述

多叉,各种连接节点,可有环,可有向,可无向
节点与节点之前可以有权重w

邻接表法

每个节点,它的邻居都是谁,放一个列表中
比如:
在这里插入图片描述
邻接表:
A:B D E
B:A C D
C:B
D:A B
E:A

邻接矩阵法

一个样本做行,一个样本做列,行列交叉的地方时两个点的权重,无穷就代表没有直接的路径
在这里插入图片描述
在这里插入图片描述

权重矩阵法

用N*3的矩阵,每一行,0位置是权重,1位置是起点,2位置是终点
在这里插入图片描述
w1 A B
w2 B D
w3 A D
w4 A E
w5 B C


图的统一表示方法:丰富图结构法

上面那三种图的表示法,统称为非标准的图的数据结构表示法
咱们为了方便地套用图的算法
要造一种统一的,自己熟悉的图的数据结构表示方法
我将其称之为:丰富图结构法

图是由一堆节点和一堆边构成的
(1)节点的信息要丰富:
每一个节点要有以下五大信息属性:
节点的,value
节点的入度:in,连入自己的边条数
节点的出度:out,自己连出的边条数
直接邻居:nexts:自己作为出发节点,出发去连向to的那些节点,直接邻居
直接邻边:edges:自己与直接邻居相连的边,直接邻边
在这里插入图片描述

(2)边的信息也要丰富:一条边必须具备仨属性:
出发节点:from
到达节点:to
连接from–to上的权重:weight
在这里插入图片描述
好,有了(1)的节点和(2)的边
边Edge类:

//边--和节点是相互渗透的俩数据结构
    public static class Edge{
        public int weight;
        public Node from;
        public Node to;
        public Edge(int w, Node a, Node b){
            weight = w;
            from = a;
            to = b;
        }
    }

节点类:Node

//节点
    public static class Node{
        public int in;
        public int out;
        public int value;
        public ArrayList<Node> nexts;
        public ArrayList<Edge> edges;//这里是相互渗透的,既然有邻居,就有边

        public Node(int v){
            value = v;//只需要给这么一个value即可
            in = 0;
            out = 0;
            nexts = new ArrayList<>();
            edges = new ArrayList<>();
        }
    }

咱们就可以组成图数据结构了
Graph类:
图里面就是一堆节点的集合,和一堆边的集合

点时通过哈希表来存的,value和节点包装一一对应:
HashMap<Integer, Node> nodes

边是哈希集:就是边的集合:
HashSet edges

我们当然随时可以获取图中节点的总个数
也可以获取边的总条数

//图结构玩起来,图右边,节也有边
    public static class Graph{
        public HashMap<Integer, Node> nodes;//v,Node
        public HashSet<Edge> edges;
        public Graph(){
            nodes = new HashMap<>();//一般节点有value,对应包装袋,都是用哈希表玩的,并查集就是这么玩的
            edges = new HashSet<>();
        }

        public int getNodeNum(){
            return nodes.size();
        }

        public int getEdgeNum(){
            return edges.size();
        }
    }

有了这个标准统一的图数据结构的话,咱就可以将非标准的图表示方法,转化为整个信息丰富的图结构了。
下面咱们来搞一下


非标准的图,转化为统一的图结构方法

给你一个非标准的图的表示方法,请你手撕代码,将其转化出一个统一的图结构:

不妨设给你的是权重矩阵法:

int[][] matrix

matrix是一个N*3的矩阵
w1 A B
w2 B D
w3 A D
w4 A E
w5 B C

咱们遍历一条一条的边,把from和to拿出来,建新节点,放入Graph
与此同时搞定每个节点的五大属性
然后把边也放进去,放入边的属性就搞定了

手撕代码转一下,返回一个统一的丰富图结构:

//复习,转化统一的丰富图结构:
    public static Graph createGraphFromOther(int[][] matrix){
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return null;

        int N = matrix.length;//行,N条边
        Graph ans = new Graph();

        for (int i = 0; i < N; i++) {
            //读取
            int weight = matrix[i][0];
            int fromValue = matrix[i][0];
            int toValue = matrix[i][0];

            Node from = new Node(fromValue);
            Node to = new Node(toValue);
            //这里节点没有在图中,添加
            if (!ans.nodes.containsKey(fromValue)) ans.nodes.put(fromValue, from);//value属性就有了,对应节点
            if (!ans.nodes.containsKey(toValue)) ans.nodes.put(toValue, from);

            from = ans.nodes.get(fromValue);
            to = ans.nodes.get(toValue);//重新从graph中获取这俩点
            //添加他们的属性
            from.out++;
            to.in++;//各自作为出发点,到达点的情况不一样
            from.nexts.add(to);//to是from的直接邻居

            //造边,做from的直接邻居
            Edge newEdge = new Edge(weight, from, to);
            from.edges.add(newEdge);//这个节点的边是列表

            //图的一堆边中,也要加集合,
            ans.edges.add(newEdge);//这个是图的边集合哦,不一样的
        }

        //每条边都搞定
        return ans;
    }


    public static void test(){
        int[][] matrix = {
                {1,1,2},
                {2,2,3},
                {3,1,3}
        };
        Graph graph = generatGrap(matrix);
        System.out.println(graph.getEdgeNum());
        System.out.println(graph.getNodeNum());

        Graph graph2 = createGraphFromOther(matrix);
        System.out.println("\n复习:graph2的节点");
        for(Node cur:graph2.nodes.values()) System.out.print(cur.value +" ");
        System.out.println("\n复习:graph2的边");
        for(Edge edge:graph2.edges) System.out.print(edge.weight +" ");
        System.out.println();
        System.out.println(graph2.getEdgeNum());
        System.out.println(graph2.getNodeNum());

    }

    public static void main(String[] args) {
        test();
    }

结果:

3
3

复习:graph2的节点
1 2 3 
复习:graph2的边
3 2 1 
3
3

可怕的邻接表法,将其转化为统一的图结构,双向连接的边

邻接表就是给你一个邻接表,挺复杂的
类似:邻接表:List< HashMap<Node, List> > list
A:B D E
B:A C D
C:B
D:A B
E:A
很麻烦

//如果要是邻接表法呢?
    // 直接从上面这个函数总改变,无非就是要搞出来from--to和权,没啥大不了
    public static Graph createGraphFromOther2(List< HashMap<Node, List<Node>> > list){
        if (list == null || list.size() == 0 || list.get(0).size() == 0) return null;

        int N = list.size();//行,N条边
        Graph ans = new Graph();

        for (int i = 0; i < N; i++) {
            for (Map.Entry<Node, List<Node>> entry :list.get(i).entrySet()) {
                //读取
                int weight = 1;//邻接表的话,暂时默认权重为1咯
                Node from = entry.getKey();//它本身就一个节点,其实重复了,骚得很
                for(Node to:entry.getValue()){//所有邻居
                    //造节点,放入图中
                    //这里节点没有在图中,添加
                    if (!ans.nodes.containsKey(from.value)) ans.nodes.put(from.value, from);//value属性就有了,对应节点
                    if (!ans.nodes.containsKey(to.value)) ans.nodes.put(to.value, to);

                    //添加他们的属性
                    from.out++;
                    to.in++;//各自作为出发点,到达点的情况不一样
                    from.nexts.add(to);//to是from的直接邻居

                    //造边,做from的直接邻居--保证两个点都没有来过,才这样
                    //图的一堆边中,也要加集合,
                    Edge newEdge = new Edge(weight, from, to);
                    from.edges.add(newEdge);//这个节点的边是列表
                    ans.edges.add(newEdge);//这个是图的边集合哦,不一样的
                }
            }
        }

        //每条边都搞定
        return ans;
    }

    public static void test2(){
        int[][] matrix0 = {
                {1,1,2},
                {2,2,3},
                {3,1,3}
        };
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);

        List<Node> list1 = new LinkedList<>();
        list1.add(n2);
        list1.add(n3);//1节点的邻居
        List<Node> list2 = new LinkedList<>();
        list2.add(n1);
        list2.add(n3);//2节点的邻居
        List<Node> list3 = new LinkedList<>();
        list2.add(n1);
        list2.add(n2);//3节点的邻居

        HashMap<Node, List<Node>> map = new HashMap<>();
        map.put(n1, list1);
        map.put(n2, list2);
        map.put(n3, list3);//邻接表每一行,放一起
        List<HashMap<Node, List<Node>>> matrix = new LinkedList<>();
        matrix.add(map);//邻接表成型

        Graph graph2 = createGraphFromOther2(matrix);
        System.out.println("\n复习:graph2的节点");
        for(Node cur:graph2.nodes.values()) System.out.print(cur.value +" ");
        System.out.println("\n复习:graph2的边");
        for(Edge edge:graph2.edges) System.out.print(edge.weight +" ");
        System.out.println();
        System.out.println(graph2.getEdgeNum());
        System.out.println(graph2.getNodeNum());

    }

    public static void main(String[] args) {
//        test();
        test2();
    }
复习:graph2的节点
1 2 1 
复习:graph2的边
1 1 1 1 1 1 
6
3

没事,边都是双向,可以


总结

提示:重要经验:

1)图结构由一堆节点和一堆边构成,但是节点和边有丰富的属性信息,这个是统一的图结构
2)遇到其他的表示方法,要想办法转换为统一的图结构,这样方便撸算法
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值