图的三种存储方式

一、邻接矩阵

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

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

实现代码

#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;
}
posted @ 2021-08-10 16:32  糖豆爸爸  阅读(738)  评论(0编辑  收藏  举报
Live2D