前言
个人一开始感觉略绕,绕来绕去的,不知道其他人是怎样感觉的。直到我后知后觉的发现,其本质是用新的边编号分配位置存前一个编号,没有就给-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还会在这破油站?