java算法竞赛图论必备技能->链式前向星 及 三种常见图的存储方式

        与其说java,不如说几乎所有算法竞赛中链式前向星是必不可少的技巧。其用途就是使用最少的空间来存储图,同时加快遍历速度。比如下面这组数据


第一行为三个正整数 n, m, s。
第二行起 m 行,每行三个非负整数 ui, vi, wi,表示从 ui 到 vi 有一条权值为 wi 的有向边。
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4


        这里先讲一下邻接表和邻接矩阵,若直击链式前向星请看最后面部分。 

一. 邻接矩阵

        邻接矩阵是最简单的存储图的方式,直接开一个二维数组 int[][] g = new int[?][?] 进行图的存储。g[i][j] 表示结点 i 和结点 j 之间存在路径的情况,无权图中,0表示不存在路径,1表示存在路径。有权图中0表示不存在路径,0以外的数表示该路径的权值。

        邻接矩阵的优点很明显,就是简便。问题就是占用空间较大,遍历耗时长。打个比方,假如1≤n≤10000; 那么开出来的数组为 int[][] g = new int[10001][10001],大小为10000*10000,占用了很大的空间。遍历图的时候,由于存储了很多无用的信息,会导致遍历的时候浪费了很多时间。

public class Main {
    static int n,m;
    static int[][] g = new int[10001][10001];
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) throws IOException {
        n=sc.nextInt();
        m=sc.nextInt();
        //有向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            g[u][v]=1;
        }
        //无向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            g[u][v]=1;
            g[v][u]=1;
        }
        //有向 有权 图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            g[u][v]=w;
        }
        //无向 无权 图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            g[u][v]=w;
            g[v][u]=w;
        }
    }
}

二. 邻接表

        不像c++那样可以使用vector,在java中vector几乎很少使用,至少目前来说我还没见过用vector的。一个很好的解决方式是用ArrayList。邻接表原理很简单,每一个1到n的结点都是一个单独的头结点,各种形成链表。每一个结点所连通的结点,都记录在该节点所形成的链表里面。也就是说,我们只记录可以去到的结点,那些无法直接到达的结点不记录,相当于只记录邻接矩阵中 g[i][j] == 1 的结点(i,j),g[i][j] == 0 的话,这个结点就不记录。在算法竞赛中我们就没有必要使用链表了,直接使用ArrayList效率一样的。

        邻接表占用空间明显比邻接矩阵少,而且只存储了有效信息,遍历速度也快很多。

无权图:

public class Main {
    static int n,m;
    static List<List<Integer>> g = new ArrayList<>();
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        n=sc.nextInt();
        m=sc.nextInt();
        for(int i=0;i<=n;i++) g.add(new ArrayList<>());
        //无权 有向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            g.get(u).add(v);
        }
        /*
        //无权 无向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            g.get(u).add(v);
            g.get(v).add(u);
        }
        */
    }
}

        有权图麻烦一些,因为要记录权值,我们需要定义一个class,用来记录 可以到达的点 和 这一条边的权值 ,其实就是记录边的信息。

class node{
    int v;
    int w;
    public node(int v,int w){
        this.v=v;
        this.w=w;
    }
}

public class Main {
    static int n,m;
    static List<List<node>> g = new ArrayList<>();
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        n=sc.nextInt();
        m=sc.nextInt();
        for(int i=0;i<=n;i++) g.add(new ArrayList<>());
        //有权 有向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            g.get(u).add(new node(v,w));
        }
        /*
        //有权 无向图
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            g.get(u).add(new node(v,w));
            g.get(v).add(new node(u,w));
        }
        */
    }
}

三. 链式前向星

        链式前向星可以说是最好的存储图的方式了,虽然比前面两种复杂不少,但是空间使用 和 遍历效率都是极限的存在。很多难一点的图论算法题几乎都可以使用它,甚至有些不用它的话会运行超时。而对于天生就比c++慢很多的java而言,链式前向星无疑是救星!链式前向星和邻接表非常相似,c++里面可以使用结构体来构造前向星,但是java与之对应的class太臃肿,每一个class在堆中都要new出来,非常消耗性能。数据稍微大一点的题目,java直接鸡鸡。这时候最原始的数组表示法就来了!!!       

        下面是 有权有向图 的存储(其他形式的图的存储都可以从这里推出来,不多bb):

public class Main {
    static int N = 10001;
    static int M = 10001;
    static int n,m,cnt;
    static int[] vv = new int[M];    //存储点u可以直接到达的点,相当于存储边
    static int[] ww = new int[M];    //存储边的权值
    static int[] to = new int[M];    //用于连接同一个点u可以直接到达的点
    static int[] he = new int[N];    //用于存储遍历的第一个点的信息
    static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) {
        n=sc.nextInt();
        m=sc.nextInt();
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            addEdge(u,v,w);
        }
    }

    public static void addEdge(int u,int v,int w) {
        vv[++cnt] = v;
        ww[cnt]=w;
        to[cnt] = he[u];
        he[u] = cnt;
    }
}

        那么链式前向星究竟是如何存储图的呢,强烈建议把上面的数据自己模拟一下。只可意会不可言传,模拟一遍数据之后读者就恍然大悟了!

第一行为三个正整数 n, m, s。
第二行起 m 行,每行三个非负整数 ui, vi, wi,表示从 ui 到 vi 有一条权值为 wi 的有向边。
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

        那么,我们又该如何遍历图呢?

public class Main {
    static int N = 10001;
    static int M = 10001;
    static int n,m,cnt;
    static int[] vv = new int[M];
    static int[] ww = new int[M];
    static int[] to = new int[M];
    static int[] he = new int[N];
    static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) {
        n=sc.nextInt();
        m=sc.nextInt();
        for(int i=1;i<=m;i++){
            int u=sc.nextInt();
            int v=sc.nextInt();
            int w=sc.nextInt();
            addEdge(u,v,w);
        }
        
        for(int u=1;u<=n;u++){
            //u是当前结点
            for(int i=he[u];i>0;i=to[i]){
                //int i=he[u];i>0;i=to[i] ,这一段的意思是遍历u结点所有直接相连通的结点
                int v=vv[i];    //v就是u结点直接连通的结点
                int w=ww[i];    //w是u->v这条边的权值
            }
        }
    }

    public static void addEdge(int u,int v,int w) {
        vv[++cnt] = v;
        ww[cnt]=w;
        to[cnt] = he[u];
        he[u] = cnt;
    }
}

        不理解的小伙伴再模拟一遍数据吧!!!

       over!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玛卡左家陇分卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值