JAVA实践数组版图存储结构--邻接表

前言

个人一开始感觉略绕,绕来绕去的,不知道其他人是怎样感觉的。直到我后知后觉的发现,其本质是用新的边编号分配位置存前一个编号,没有就给-1。
当然这只是我的个人理解。

邻接表的好处是省空间啊,我粗暴的理解
当一个图是稀疏图,即
边数 < 顶点个数的平方
如10个顶点11条边,11条边远小于10^2 = 100.
使用邻接矩阵存储时,需要10*10的二维数组,粗暴的表示,就是100个变量。

而使用邻接表,如果数组实现的话,需要五个一维数组
3个存储起始顶点,末尾顶点,权值的数组,长度都为11
2个存储边的编号的数组,长度分别为10和11。(一个顶点个数,一个边的数量)
加起来54个变量。

20个顶点 21条边呢
邻接矩阵:400
邻接表:21*4 + 20 * 1 = 104

在Dijkstra算法求最短路径时
使用邻接矩阵的时间复杂度是O(n^2)
使用邻接表的时间复杂度是(n+m)O(logn)

你问我咋算的?我不会啊(逃
老实说,我算错了。

功能实现

使用五个一维数组,以邻接表的形式存储图

中文版参考

/**
 * 图的存储结构表示:邻接表
 *
 * 数组实现!
 *
 * first:存储顶点的第一条边,以队列的形式,最后输入的边。将成为第一条
 *      其编号1~n分别对应着顶点1~n
 *      如first的第一个位置,只是用于存储顶点1的边
 *      first的第二个位置,只是用于存储顶点2的边
 *
 *      那么,如果顶点1存在两条边,在读取第二条边时,将原先存储的第一条边移动到next数组
 * next:存储顶点的第二条边以后所有的边,包括第二条
 *
 * 以上,在没有下一条边的时候,将对应顶点的边编号设置为-1
 * 如顶点1初始时还没有边,那么first[0] = -1;
 *
 * 插入第1条边,顶点1→顶点4
 *      1   4   9
 *      first:1     next:-1
 * 插入第2条边,顶点1→顶点3
 *      1   3   8
 *      first:2     next:-1 1 顶点1的第一条边顺延成第二条边
 *      第2条边,所以编号是2
 *      next则根据[新的边]的[编号],给予顺延的边编号的位置存储
 *      新的边的编号是2,所以在next的第2个位置存储前一条边的编号1
 *
 * 本来有个问题,如何保证next存储的边不会冲突呢?
 * 后来把几个数据手动写出来,一看就发现想多了。
 *
 * 假设有n个顶点,first的位置就有n个,它肯定是不会重复的。
 *
 * 当为顶点k插入第i条边的时候,first[k] = i  注意:k是指顶点,不是下标。
 * 表示顶点k新的边的编号是i。
 *
 * 而next则根据[新的边]的[编号]存储,新的边的编号不就是i吗?
 * 而i一直在自增,也就是其位置一直在变化,所以肯定不会重复。
 *
 * 我的困惑没了,就不瞎扯了,直接实现。
 **/

代码实现

public class AdjacencyList {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //存储顶点起点
        int[] fromVertex;
        //存储顶点终点
        int[] toVertex;
        //存储顶点的权值
        int[] weightVertex;
        //存储顶点的个数
        int n;
        //存储顶点的边数
        int m;
        //存储访问时,n个顶点的第一条边的编号
        int[] first;
        //存储访问时,n个顶点除第一条边之外所有边的编号
        int[] next;

        //输入顶点个数
        n = in.nextInt();
        //输入边数
        m = in.nextInt();

        //进行初始化操作
        fromVertex = new int[m];
        toVertex = new int[m];
        weightVertex = new int[m];
        first = new int[n];
        next = new int[m];
        for (int i = 0; i < first.length; i++) {
            first[i] = -1;
        }

        //读取边
        for (int i = 0; i < m; i++) {
            //获取顶点起点
            fromVertex[i] = in.nextInt();
            //获取顶点终点
            toVertex[i] = in.nextInt();
            //边的权值
            weightVertex[i] = in.nextInt();


            //减一都是为了对应数组角标
            //next使用新的边的编号作为位置,如果暂时用不到next,其值应该为-1,表示结尾
            //first[fromVertex[i]]存着原顶点fromVertex[i]的第一条边
            next[i] = first[fromVertex[i] - 1];
            //存储新的顶点的起始边
            first[fromVertex[i] - 1] = i;
        }

        //遍历获取顶点1所有的边
        for (int i = 0; i < n; i++) {
            int v = first[i];
            while (v != -1) {
                System.out.println("From " + fromVertex[v] + " to " + toVertex[v] + ", weight:" + weightVertex[v]);
                v = next[v];
            }
        }
    }
}

结果

输入:
4 5
1 4 9
2 4 6
1 2 5
4 3 8
1 3 7
输出:
From 1 to 3, weight:7
From 1 to 2, weight:5
From 1 to 4, weight:9
From 2 to 4, weight:6
From 4 to 3, weight:8

结束语

还是再次总结一次

first和next里面所存储的,都是边的编号,编号从1开始递增

first则是根据分配每个顶点一个存储位置,用于存储需要查询顶点时可访问的第一条边的编号

next则是根据某个顶点[当前插入]的边的[编号]分配存储位置(假设有两条或以上的边),用于存储前一条边的编号

如果顶点2目前的第一条可访问边的编号是1
现在给顶点2插入第3条边,当前插入的边的编号就是3
那么前一条边的编号1移到next里去。
next[3] = 1(被顺延的那一条边的编号)

太太太TM绕了。

教练,我要学怎么算算法复杂度才不会算错!
我给你买《21天精通JAVA》
我不管,我要学算法
再加一本《花花公子》杂志
我要是会算法,我不会写不出……
你要是会算法,你TM还会在这破油站?

END

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值