链式前向星

在图算法中,有很多数据结构可以存下一张图,如果边的数量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

posted on 2023-09-12 15:11  枫沰  阅读(17)  评论(0编辑  收藏  举报

导航