开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第36天,点击查看活动详情
✨欢迎关注🖱点赞🎀收藏⭐留言✒
🔮本文由京与旧铺原创,csdn首发!
😘系列专栏:算法学习
💻首发时间:🎞2022年12月31日🎠
🀄如果觉得博主的文章还不错的话,请三连支持一下博主哦
🎧作者是一个新人,在很多方面还做的不好,欢迎大佬指正,一起学习哦,冲冲冲
邻接表
邻接矩阵是使用顺序表的形式储存顶点之间的关系,而邻接表是通过链表的形式表示顶点间的关系,本质上的逻辑是一样的。
假设图一共有个顶点,则邻接表由个链表组成,每个结点表示一个顶点,链表中储存该顶点指向了哪一些点。
对于带权值的图,也是可以使用邻接表表示的,只不过链表的结点是一个类或者数组,里面储存了顶点和到该顶点的权值大小。
下面同理,我们来尝试实现一个邻接表,邻接表是基于链表实现的,链表可以使用数组实现也可以使用结点来实现,一般使用结点进行实现,但是在竞赛当中是基于数组实现的,本文为了与邻接矩阵统一,还是使用结点实现吧。
邻接表是由多个带头的链表实现的,每个链表结点中至少需要next域和数据域,数据域包括顶点和边的权值,所以我们需要设计一个结点,需要包含链表的指针域和顶点和边的权值。
class GNode {
public GNode next;
public int weight;
public String point;
public GNode(){}
public GNode(String point, int weight) {
this.point = point;
this.weight = weight;
}
}
下面就是设计邻接表类,我们需要一个顶点数组,一个顶点数组与链表下标的对应关系,也就是一个哈希表,还需要一个是否为有向图的判断标志,初始化的时候就不需要去初始化链表数组了,只需要初始化顶点数组和哈希表对应关系即可。
//存储顶点
private String[] points;
//每个顶点的边
private GNode[] edges;
//是否为有向图
private boolean isDirect;
//将顶点与边下标映射
private HashMap<String, Integer> hash = new HashMap<>();
构造方法和实现邻接矩阵时一样写两个,一个不设定图的类型,默认为无向图,另外一个构造方法可以指定是否为有向图。
private void init(String[] points) {
this.points = points.clone();
int size = this.points.length;
edges = new GNode[size];
//初始化哈希表
for (int i = 0; i < size; i++) {
hash.put(points[i], i);
}
}
public GraphNode1(String[] points) {
init(points);
}
public GraphNode1(String[] points, boolean isDirect) {
this.isDirect = isDirect;
init(points);
}
最后就是边的添加和邻接表的输出了,添加边的思路其实和邻接矩阵是一样的,不过要注意的一点是,在添加边时,需要在对应的链表上先搜索一遍,如果已经存在终点顶点,那就不用再去添加了。
基本思路如下:
- 获取两个顶点对应的下标,如果在顶点集合中不存在,直接返回
false。 - 添加边时,先判断边集合中是否存在该边,存在就返回
false。 - 如果边集合中不存在目标边,则新建结点进行链表头插。
- 如果是无向图还需要方向添加边。
- 如果不带权值添加边,以默认权值
1进行添加。
/**
*
* @param start 起点
* @param end 终点
* @param weight 权重
* @return 返回值
*/
public boolean addEdge(String start, String end, int weight) {
int si = hash.getOrDefault(start, -1);
int ei = hash.getOrDefault(end, -1);
if (si == -1 || ei == -1) return false;
//添加边
boolean ret = addEdgeFunc(si, end, weight);
if (!ret) {
System.out.println("顶点已经存在!");
return false;
}
//如果是无向图需要反向添加
if (!isDirect) {
addEdgeFunc(ei, start, weight);
}
return true;
}
public boolean addEdge(String start, String end) {
return addEdge(start, end, 1);
}
private boolean addEdgeFunc(int si, String end, int weight) {
GNode cur = edges[si];
while (cur != null) {
//查找是否已经存在
if (end.equals(cur.point)) return false;
cur = cur.next;
}
GNode node = new GNode(end, weight);
//头插
cur = edges[si];
node.next = cur;
edges[si] = node;
return true;
}
邻接表的输出代码实现:
//输出
public void print() {
int n = points.length;
for (int i = 0; i < n; i++) {
System.out.print(points[i] + " -> ");
GNode cur = edges[i];
while (cur != null) {
System.out.print("(" + cur.point + "," + cur.weight + ") -> ");
cur = cur.next;
}
System.out.println("null");
}
}
最后一步就是测试我们所写的代码是否正确,测试代码如下,与测试邻接矩阵代码基本上一模一样:
public static void testMa2() {
String[] s = {"1", "2", "3", "4"};
//无向图测试
GraphNode1 gn1 = new GraphNode1(s);
//无权值
gn1.addEdge("1", "2");
gn1.addEdge("1", "3");
gn1.addEdge("1", "4");
gn1.addEdge("3", "2");
gn1.addEdge("4", "3");
//输出
System.out.println("无向图,无权值:");
gn1.print();
//无向图测试
GraphNode1 gn2 = new GraphNode1(s);
//无权值
gn2.addEdge("1", "2", 4);
gn2.addEdge("1", "3", 7);
gn2.addEdge("1", "4", 6);
gn2.addEdge("3", "2", 5);
gn2.addEdge("4", "3", 3);
//输出
System.out.println("无向图,有权值:");
gn2.print();
//有向图测试
GraphNode1 gn3 = new GraphNode1(s, true);
//无权值
gn3.addEdge("1", "2");
gn3.addEdge("1", "3");
gn3.addEdge("1", "4");
gn3.addEdge("3", "2");
gn3.addEdge("4", "3");
//输出
System.out.println("有向图,无权值:");
gn3.print();
//有向图测试
GraphNode1 gn4 = new GraphNode1(s, true);
//有权值
gn4.addEdge("1", "2", 4);
gn4.addEdge("1", "3", 7);
gn4.addEdge("1", "4", 6);
gn4.addEdge("3", "2", 5);
gn4.addEdge("4", "3", 3);
//输出
System.out.println("有向图,有权值:");
gn4.print();
}
测试结果如下,与我们所画的分析图是一致的: