zoukankan      html  css  js  c++  java
  • 【学习笔记 3】图和二叉树的存储

    图常用的存储方式有两种,一种是邻接矩阵,另一种是邻接表(前向星)

    邻接矩阵

    这种方法也是我最早会的一种方法,空间复杂度为 (O(n^2)),其中 (n) 为节点的个数。该方法使用简单,并且常数较小,一般用来存储稠密图。

    邻接矩阵为一个 (n imes n) 的矩阵,其中 (a_{i,j}) 表示从 (i) 节点到 (j) 节点边的权值。如果这条边不存在,(a_{i,j}) 就为 (infty)。当 (i=j) 时,(a_{i,j}=0)。如果是无向图,那么此邻接矩阵就是对称的。

    如果存储的是不带权的图,就可以用 (1) 表示有边直接联通,(0) 表示不直接

    举个例子,看下面这张图。

    那么它所对应的邻接矩阵 (a) 就是:

    [a= egin{bmatrix} 0 & 1 & 0 & 0 & 1 & 0 \ 0 & 0 & 1 & 0 & 0 & 0 \ 1 & 0 & 0 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 & 0 & 1 \ 0 & 0 & 1 & 0 & 0 & 1 \ 0 & 0 & 0 & 0 & 0 & 0 \ end{bmatrix} ]

    用代码就是:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,u,v,w;
    bool a[105][105];
    int main()
    {
        cin>>n>>m;  //有 n 个节点,m 条边。 
        for(register int i=0;i<m;++i)
        {
        	cin>>u>>v>>w;  //有一条从 u 到 v,权值为 w 的边。  
        	a[u][v]=w;
        	//a[v][u]=w;     如果是无向图还要反向存一次。 
        }
        return 0;
    }                             
    

    邻接表(链式前向星)

    邻接矩阵是相当于存节点,而邻接表就是相当于存边。如果一个图是稀疏图,那么邻接矩阵所含的信息就很少,就会浪费空间。这时,邻接表就是一个更好的选择。

    一般来说,邻接表是由一个结构体链表和一个 (head) 数组来实现。链表中的每个元素表示 (1) 条边,存储该边的权值、到达的节点、和出发的节点引出的上一条边。(head [ i ]) 表示从 (i) 节点引出的最后一条边。

    代码(vector 实现链表):

    #include<bits/stdc++.h>
    using namespace std;
    struct node
    {
    	int ne,to,val;
    }now;
    vector<node> a;
    int n,m,u,v,w,cnt;
    int head[1005];
    inline void add(int u,int v,int w)  //建造一条边。
    {
    	now.val=w; //权值。
    	now.to=v;  //到达的节点。
    	now.ne=head[u];  //出发的节点引出的最后一条边。
    	head[u]=++cnt;  //更新 head。
    	a.push_back(now);
    }
    int main()
    {
        cin>>n>>m;  //有 n 个节点,m 条边。 
        a.push_back(now); //为了方便,a[0]不要,从a[1]开始存。
        for(register int i=0;i<m;++i)
        {
        	cin>>u>>v>>w;  //有一条从 u 到 v,权值为 w 的边。 
        	add(u,v,w);
        	//add(v,u,w);     如果是无向图还要反向存一次。 
        }
        return 0;
    }
    

    至于为什么要有一个 (head) 数组,它是用来遍历图时要用的。从节点 (i) 遍历图时,从 (a[head[i]]) 开始遍历,每次遍历完后,再遍历 (a[head[i]].ne) (ldotsldots) 直到该节点所有的边都被访问过,即 (a[head[i]].ne=0) 时。

    此方法较节省空间,空间复杂度为 (O(m))(m) 为边的数量。但是此方法的常数比邻接矩阵大。

    二叉树

    我们知道,二叉树是一种特殊的树,正是因为它的特殊性质,让它有许多神奇的存储方法。在这里我讲三种方法:线性存储、二叉链表存储、三叉链表存储。

    线性存储

    线性存储非常巧妙的利用的二叉树的性质,主要用于存储完全二叉树和满二叉树。

    具体是怎么存储的呢?我们来结合图来了解。

    上面的这颗二叉树,我们在经过观察,不难得出,按照这种编号方式,(i) 号节点的左儿子的编号为 (2 imes i),右儿子的编号是 (2 imes i+1)

    这样一来,我们就可以将二叉树巧妙地存储在数组里了。上面这颗二叉树的线性存储就是:

    通过这种方式,可以快速的找到节点和对应的左右儿子,十分方便快捷。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,mp[256];  //mp 用来记录节点对应的下标。 
    char a,b,c,tree[10000];
    int main()
    {
        cin>>n;  //有 n 个节点。
        cin>>a>>b>>c;  //根节点要单独处理一下。 
        tree[1]=a;
        tree[1*2]=b;
        tree[1*2+1]=c;
        mp[b]=1*2;
        mp[c]=1*2+1;
        for(register int i=1;i<n;++i)
        {
        	cin>>a>>b>>c;
        	tree[mp[a]*2]=b;  //找到下标,分别存储。 
        	tree[mp[a]*2+1]=c;
        	mp[b]=1*2;  //记录下标。 
        	mp[c]=1*2+1;
        }
        return 0;
    }
    

    但其的缺点就是,在存储不完全二叉树时就会显得浪费空间。

    二叉链表

    二叉链表又叫儿子表示法,顾名思义,链表中的元素分别记录节点本身、左儿子和右儿子。此方法节省空间,也是竞赛中最常用的方法。

    它的优点很多,我就不一一列举,但它唯一也是最大的缺点是:无法从儿子节点直接找到父亲节点。

    代码实现(vector 实现链表):

    #include<bits/stdc++.h>
    using namespace std;
    struct node
    {
    	char data,cl,cr;
    }now;
    vector<node> tree;
    int n,mp[256];  //mp 用来记录节点对应的下标。 
    char a,b,c;
    int main()
    {
        cin>>n;
        for(register int i=0;i<n;++i)
        {
        	cin>>now.data>>now.cl>>now.cr;  //输入。 
        	mp[now.data]=i;  //记录下标。 
        	tree.push_back(now);  //存进链表。 
        }
        return 0;
    }
    

    三叉链表

    三叉链表又叫父亲儿子表示法,它既存储节点的儿子,也存储节点的父亲。经常用于解决较复杂的问题。它可以通过一个节点找到父亲,也可以找到儿子。如果该节点是根节点,它的父亲就指向 NULL(vector 链表中用 0 代替)。该方法使用灵活,但是缺点就是,较浪费空间,操作麻烦。

    代码实现(vector 实现链表):

    #include<bits/stdc++.h>
    using namespace std;
    struct node
    {
    	char data,cl,cr,fa;
    }now;
    vector<node> tree;
    int n,mp[256];  //mp 用来记录节点对应的下标。 
    char a,b,c,f[256];
    int main()
    {
        cin>>n;
        for(register int i=0;i<n;++i)
        {
        	cin>>now.data>>now.cl>>now.cr;  //输入。 
        	now.fa=f[now.data];
        	mp[now.data]=i;  //记录下标。
    		f[now.cr]=f[now.cl]=now.data; 
        	tree.push_back(now);  //存进链表。 
        }
        return 0;
    }
    

    小结

    树和图的存储就开始涉及高级数据结构了。面对不同种类的树和图,不同的问题,我们采取不同存储方法,灵活运用,才能真正掌握。

  • 相关阅读:
    CSS3 鲜为人知的属性-webkit-tap-highlight-color的理解
    14 个折磨人的 JavaScript 面试题
    JavaScript 开发的45个技巧2
    JavaScript 开发的45个技巧
    JavaScript 中的 this !
    JavaScript里的循环方法:forEach,for-in,for-of
    JS类型判断typeof PK {}.toString.call(obj)
    Object.prototype.toString()
    MinGW gcc 生成动态链接库 dll 的一些问题汇总 (补充)
    Selenium之偷懒教程
  • 原文地址:https://www.cnblogs.com/win10crz/p/12835730.html
Copyright © 2011-2022 走看看