图的表示与入度
虽然好像没什么人看,但是我还是写一下,佛系。就当给自己做一个备忘录好了!总算,进入到图系列了。图的部分,问题很多,算法很多,应用也很广。图的两个比较重要的基石就是图的表示和图的遍历。有了这两个才能更好地搭建其他的算法。
图的表示
我们知道G = (V, E),所以最原始的表示方式是,int n, int[][] edges。其中,n表示有n个节点,分别为{0, 1, 2, 3, ..., n-1},edges表示所有的边, edges = new int[n][2],edges[i]表示第i条边,而edges[i][0]表示起点,edges[i][1]表示终点。有关面向对象的思考: 其实可以把edges看作是一个对象数组, 每一个对象edge=edges[i], 是int[2], 包含了两个属性edge[0]和edge[1],实际上并不需要显式地进行class的定义。
邻接链表法
本来想直接做后面的题目,但是这些基础的操作没有定义好,做后面的题目会比较麻烦。好的,我们开始介绍邻接链表的使用吧。邻接链表每一个元素是每个点相邻的点组成的链表, 比如点0与1,2,3相邻,那么0的邻接链表就是{1, 2, 3}。 下面给出代码示例。
class Solution {
private List<Integer>[] adjs;
public void func1(int n, int[][] edges) {
adjs = new List[n]; // 数组初始化
for (int i = 0 ; i < n ; i++) {
adjs[i] = new LinkedList<>(); // 元素初始化
}
for (int i = 0; i < edges.length;i++) {
int[] edge = edges[i];
int from = edge[0];
int to = edge[1];
adjs[from].add(to); // 利用边元素进行初始化
// 如果是无向图
// adjs[to].add(from);
}
}
}
注意,上面使用的是LinkedList,原因是: 首先, 一般遍历的时候,都是逐个遍历的,并不需要按照下标来访问元素,因此使用数组和使用链表没有区别;其次, 给链表新增元素很容易。当然,如果你知道邻接的元素不超过一定数量m时,你可以使用int[n][m+1] adjs, 其中adjs[i][0]表示stackSize, 而ajds[i][1]~adjs[i][m]是一个stack。LeetCode传送门——不邻接植花 。
另外,如果需要给每条边加上weight,就需要使用对象了。
class Solution {
private List<Node>[] adjs;
public void func1(int n, int[][] edges) {
adjs = new List[n]; // 数组初始化
// 暂略
}
private class Node {
public int val;
public int weight;
public Node(int val, int weight) {
this.val = val;
this.weight = weight;
}
}
}
邻接矩阵法
以后再写
入度
入度的意思,也就是进入该点的边的数量。入度的用途其实蛮大的,比如找出拓扑排序中的起点,或者判断一些特殊的点。解题的时候,不妨从入度或者出度比较特殊的点开始。代码如下:
class Solution {
public void calculateIndegree(int n, int[][] edges) {
int[] indegrees = new int[n];
for (int i = 0 ; i < edges.length;i++) {
int[] edge = edges[i];
int from = edge[0];
int to = edge[1];
indegrees[to] += 1;
}
}
}
LeetCode传送门——课程表 。