zoukankan      html  css  js  c++  java
  • 图的三种存储方式

    一、邻接矩阵

    适用:
    稠密图,就是说点数的平方与边数接近的情况,换句话说就是边特别多。

    不适用:
    稀疏图,就是点数的平方与边数差的特别多,边数少,但点数多,就不行了,因为空间占用太大了。

    实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1010; //图的最大点数量
    int n;
    int v[N][N];        //邻接矩阵
    /**
     * 测试数据
     4
     0 5 2 3
     5 0 0 1
     2 0 0 4
     3 1 4 0
     */
    int main() {
        cin >> n;
        //读入到邻接矩阵
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                cin >> v[i][j];
    
        //下面的代码将找到与点i有直接连接的每一个点以及那条边的长度
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (v[i][j]) cout << "edge from point " 
                    << i << " to point " << j << " with length " << v[i][j] << endl;
        return 0;
    }
    

    二、邻接表

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1010; //图的最大点数量
    struct Edge {       //记录边的终点,边权的结构体
        int to;         //终点
        int value;      //边权
    };
    int n, m; //表示图中有n个点,m条边
    vector<Edge> p[N];  //使用vector的邻接表
    
    /**
     * 测试数据
     4 6
     2 1 1
     1 3 2
     4 1 4
     2 4 6
     4 2 3
     3 4 5
     */
    int main() {
        cin >> n >> m;
        //m条边
        for (int i = 1; i <= m; i++) {
            int u, v, l;                //点u到点v有一条权值为l的边
            cin >> u >> v >> l;
            p[u].push_back({v, l});
        }
    
        //输出
        for (int i = 1; i <= n; i++) {
            printf("出发点:%d ", i);
            for (int j = 0; j < p[i].size(); j++)
                printf(" 目标点:%d,权值:%d;", p[i][j].to, p[i][j].value);
            puts("");
        }
    
        return 0;
    }
    

    三、链式前向星

    链式前向星是邻接表存图的第二种方法,它自己还有两种写法,比用向量存图的那种邻接表要快

    它是一种以边为主的存图方式,(idx)表示最后一条边的预存入的房间号, (head[i])表示以(i)为起点第一条边的房间号。

    每条边有三个属性:

    • (head[i])出发到哪个结点的边?
    • 这条边的边权是多少?
    • 这条边的下一条边是谁?(下一条边的房间号)

    链式前向星有三种变形,需要同学们都掌握,找一种自己最喜欢的背下来,其它两种要求能看懂,因为其它人写题解,可能使用了其它方式。

    1. AcWing方式(纯数组)

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;     //点数最大值
    int n, m;               //n个点,m条边
    
    //idx是新结点加入的数据内索引号
    //h[N]表示有N条单链表的头,e[M]代表每个节点的值,ne[M]代表每个节点的下一个节点号
    int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
    
    //链式前向星
    void add(int a, int b, int l) {
        e[idx] = b, ne[idx] = h[a], w[idx] = l, h[a] = idx++;
    }
    
    
    /**
     * 测试数据
     4 6
     2 1 1
     1 3 2
     4 1 4
     2 4 6
     4 2 3
     3 4 5
     */
    int main() {
        cin >> n >> m;
        //初始化为-1,每个头节点写成-1
        memset(h, -1, sizeof h);
    
        //m条边
        for (int i = 1; i <= m; i++) {
            int u, v, l;                //点u到点v有一条权值为l的边
            cin >> u >> v >> l;
            //加入到链式前向星
            add(u, v, l);
        }
    
        //遍历每个结点
        for (int i = 1; i <= n; i++) {
            printf("出发点:%d ", i);
            for (int j = h[i]; j != -1; j = ne[j])
                printf(" 目标点:%d,权值:%d;", e[j], w[j]);
            puts("");
        }
        return 0;
    }
    
    

    2. 结构体+数组

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;     //点数最大值
    int n, m, idx;          //n个点,m条边,idx是新结点加入的数据内索引号
    
    //链式前向星
    struct Edge {
        int to;     //到哪个结点
        int value;  //边权
        int next;   //同起点的下一条边的编号
    } edge[N << 1]; //同起点的边的集合 N<<1就是2*N,一般的题目,边的数量通常是小于2*N的,这个看具体的题目要求
    
    int head[N];    //以i为起点的边的集合入口处
    
    //加入一条边,x起点,y终点,value边权
    void add_edge(int x, int y, int value) {
        edge[++idx].to = y;         //终点
        edge[idx].value = value;    //权值
        edge[idx].next = head[x];   //以x为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
        head[x] = idx;              //更新以x为起点上一条边的编号
    }
    
    /**
     * 测试数据
     4 6
     2 1 1
     1 3 2
     4 1 4
     2 4 6
     4 2 3
     3 4 5
     */
    int main() {
        cin >> n >> m;
    
        //m条边
        for (int i = 1; i <= m; i++) {
            int u, v, l;                //点u到点v有一条权值为l的边
            cin >> u >> v >> l;
            //加入到链式前向星
            add_edge(u, v, l);
        }
    
        //遍历每个结点
        for (int i = 1; i <= n; i++) {
            printf("出发点:%d ", i);
            for (int j = head[i]; j; j = edge[j].next)  //遍历每个结点的每一条边
                printf(" 目标点:%d,权值:%d;", edge[j].to, edge[j].value);
            puts("");
        }
        return 0;
    }
    

    3. 结构体+数组(2)

    为什么链式前向星有两种实现方法呢?这其实是看(idx)用不用(0)的问题,如果它用了(0),那么就是在加边的最后需要++,如果不用(0),进来就++。

    第二个变化就是如果用了(0),那么(0)就不能用做默认值了,所以需要初始化memset(head,-1 ,sizeof head);

    第三个变化就是遍历时的条件变了,成了j!=-1,而不用(0)的就是j就行了,我个人还是喜欢用不带(0)的那个,就是上面的。是因为网上好多网友喜欢这种方式,如果我们看其它人的题解时,可能看不懂,所以也要了解一下。

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;     //点数最大值
    int n, m, idx;          //n个点,m条边,idx是新结点加入的数据内索引号
    
    //链式前向星
    struct Edge {
        int to;     //到哪个结点
        int value;  //边权
        int next;   //同起点的下一条边的编号
    } edge[N << 1]; //同起点的边的集合 N<<1就是2*N,一般的题目,边的数量通常是小于2*N的,这个看具体的题目要求
    
    int head[N];    //以i为起点的边的集合入口处
    
    //加入一条边,x起点,y终点,value边权
    void add_edge(int x, int y, int value) {
        edge[idx].to = y;           //终点
        edge[idx].value = value;    //权值
        edge[idx].next = head[x];   //以x为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
        head[x] = idx++;            //更新以x为起点上一条边的编号
    }
    
    /**
     * 测试数据
     4 6
     2 1 1
     1 3 2
     4 1 4
     2 4 6
     4 2 3
     3 4 5
     */
    int main() {
        cin >> n >> m;
    
        //初始化head数组
        memset(head, -1, sizeof head);
    
        //m条边
        for (int i = 1; i <= m; i++) {
            int u, v, l;                //点u到点v有一条权值为l的边
            cin >> u >> v >> l;
            //加入到链式前向星
            add_edge(u, v, l);
        }
    
        //遍历每个结点
        for (int i = 1; i <= n; i++) {
            printf("出发点:%d ", i);
            for (int j = head[i]; j != -1; j = edge[j].next)  //遍历每个结点的每一条边
                printf(" 目标点:%d,权值:%d;", edge[j].to, edge[j].value);
            puts("");
        }
        return 0;
    }
    
  • 相关阅读:
    配置Podfile 一个工程内的多个Target
    iOS开发中的测试框架
    iOS 设置button文字过长而显示省略号的解决办法
    iOS界面的绘制和渲染
    iOS单元测试
    iOS消息转发机制
    对runtime的总结:让你会用Runtime
    Xcode的Refactor使用
    工厂设计模式
    iOS中的数据存储
  • 原文地址:https://www.cnblogs.com/littlehb/p/15124529.html
Copyright © 2011-2022 走看看