链式前向星
在图算法中,有很多数据结构可以存下一张图,如果边的数量m很多(m约等于n^2)和节点数量n的平方相当,那么可以采用邻接矩阵
存储,也就是个二维数组。
但是如果是稀疏图的话,邻接矩阵显得十分浪费。此时可以使用链式前向星
来存储。
用C++的结构来说明就是:
//==============================================define
struct EDGE {//一个边的结构体
int next;
int to;
int w;
} edge[MAXN];
int head[MAXN];//一个头节点数组
int cnt;//一个计数器
void add(int u,int v,int w) //添加一条u-->v的有向边,且权值为w
{
edge[cnt].next=head[u];
edge[cnt].to=v;
edge[cnt].w=w;
head[u]=cnt;
cnt++;
}
//==============================================api
add(u,v,w);//添加有向边
add(u,v,w);//添加无向边
add(v,u,w);
//==============================================use
int main(int argc, char **argv) {//遍历该数据结构
addedge(1, 2, 1);
addedge(1, 3, 1);
addedge(1, 4, 1);
addedge(2, 3, 1);
int x = 1; // name of start node
printf("node 1 points to the following nodes:\n");
for (int i = head[x]; i != 0; i = edge[i].next) {
printf("node %d\n", edge[i].to);
}
return 0;
}
使用起来就是一个全局的计数器cnt
,用于标记每一条边的编号。
next
表示从该头节点u
出发的上一条边。
to
表示该条边指向的尾结点v
。
w
表示该条边的权重。(有时候可能没有)
head[u]
表示从当前u
节点出发的边的编号cnt
。
相当于用这种方式将他们连接起来,形成一个邻接表。
用JAVA的表示就是:
int[] head = new int[N], to = new int[M], next = new int[M], weight = new int[M];
int cnt;
public void add(int u,int v,int w){//添加一条u到v的边
to[cnt] = v;
next[cnt] = head[u];
head[u] = cnt;
weight[cnt] = w;
cnt++;
}
//遍历由a出发的所有边,可以这样写:
for (int i = head[a]; i != -1; i = next[i]) {
int b = to[i], c = weight[i]; // 存在由 a 指向 b 的边,权重为 c
}
举个栗子:
https://leetcode.cn/problems/find-eventual-safe-states/description/
802. 找到最终的安全状态
有一个有 n 个节点的有向图,节点按 0 到 n - 1 编号。图由一个 索引从 0 开始 的 2D 整数数组 graph表示, graph[i]是与节点 i 相邻的节点的整数数组,这意味着从节点 i 到 graph[i]中的每个节点都有一条边。
如果一个节点没有连出的有向边,则该节点是 终端节点 。如果从该节点开始的所有可能路径都通向 终端节点 ,则该节点为 安全节点 。
返回一个由图中所有 安全节点 组成的数组作为答案。答案数组中的元素应当按 升序 排列。
示例一:
输入:graph = [[[[1,2],[2,3],[5],[0],[5],[],[]]]]
输出:[2,4,5,6]
解释:示意图如上。
节点 5 和节点 6 是终端节点,因为它们都没有出边。
从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6 。
证明就省略了,直接看这个:
https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247489706&idx=1&sn=771cd807f39d1ca545640c0ef7e5baec
大概意思就是要反向一个拓扑排序,那么原本入度为0的点集{x}
现在出度就为0。也就是说它能够作为终止节点。
那些减去{x}
对应入度之后入度为0的点,也是安全的(不在环之内)。
class Solution {
int N = (int)1e4+10, M = 4*N;
int idx;
int[] head = new int[N], to = new int[M], next = new int[M];
int[] cnts = new int[N];
public void add(int u,int v){//添加一条边 u-->V
next[idx] = head[u];//第idx条边的next是当前头节点指向的边。
to[idx] = v;//第idx条边指向节点v
head[u] = idx++;//更新当前头节点指向的最后一条边。
}
//添加有向边:add(u,v,w);
//添加无向边:add(u,v,w); add(v,u,w);
public List<Integer> eventualSafeNodes(int[][] graph) {
int n = graph.length;
Arrays.fill(head,-1);
for (int i=0;i<n;i++){
for (int j:graph[i]){
add(j,i);
cnts[i]++;//标记节点i的出度,也就是反向图的入度。
}
}
Deque<Integer> d = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
if(cnts[i]==0) d.addLast(i);//入度为0的入队列
}
while (!d.isEmpty()){
int p = d.pollFirst();
for(int i=head[p];i!=-1;i=next[i]){
int j = to[i];
if (--cnts[j]==0) d.addLast(j);//拓扑排序的过程,如果一个点的入度清零,那么这个点入队。
}
}
List<Integer> ans = new ArrayList<>();
for (int i = 0; i < n; i++) {
if(cnts[i]==0) ans.add(i);//最终入度为0的点,就是不在环中的。
}
return ans;
}
}
来源:
https://zhuanlan.zhihu.com/p/629306659?utm_id=0
https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247489706&idx=1&sn=771cd807f39d1ca545640c0ef7e5baec