三元组顺序表和广义表

1 稀疏矩阵:

  假设在m*n的矩阵中,有t个元素不为0.令q=t/(m*n),称q为矩阵的稀疏因子。通常认为q<=0.05的时候就认为稀疏矩阵。

2 三元组顺序表

如果对每一个元素都分配存储空间的话,矩阵含有大量的0则会造成资源浪费。所以一般我们采用压缩存储的方式,除了存储非0元素的值外,还要存储相应的行和列。因此,稀疏矩阵可以表示成为非0元的三元组及行列数唯一确定 。

  相关定义如下:

typedef int Status
#define MAX_ROW 100
#define MAX_SIZE 101
typedef int elemtype;
typedef struct
{
    int row;   //设定行索引值
    int col;    //设定列索引值
    elemtype value;    //设定元素值
}Triple;
typedef struct
{
    int rn;   //设定行数
    int cn;   //设定列数
    int tn;
    Triple data[MAX_SIZE];  // 设定 数组
}TMatrix;

  转置的操作(按照一定顺序进行的转置):

  这里我们注意,存储方式是按行存储的。

  时间复杂度为rn*tn. 当tn远远小于rn*cn的时候使用,否则时间复杂度相比传统方法大的多。(牺牲时间来优化存储)

//转置操作
void TransposeSMatrix(TMatrix M, TMatrix T)
{
    T.rn = M.cn; T.cn = M.rn; T.tn = M.tn;
    if (T.tn) {
        q = 1;
        for (col = 1; col < M.rn; ++col)
            for (p = 1; p < M.tn; ++p)
                if (M.data[p].cn == col) {
                    T.data[q].rn = M.data[p].cn; T.data[q].cn = M.data[p].rn;
                    T.data[q].value = M.data[p].value;
                }
    }
}

  快速转置:

//快速转置
void FastTranspodeSMtrix(TMatrix M, TMatrix T)
{
    T.rn = M.cn; T.cn = M.rn; T.tn = M.tn;
    if (T.tn) {
        for (col = 1; col < M.rn; ++col) num[col] = 0;
        for (t = 1; t <= M.tnl++t) ++num[M.data[t].cn];
        cpot[1] = 1;
        //求第col列中的第一个非0元素在M.data中的序号
        for (col = 2; col <= M.tn; ++p) cpot[col] = cpot[col - 1] + num[col - 1];
        for (p=1; p <= M.tn; ++p) {
            col = M.data[p].cn; q = cpot[col];
            T.data[q].rn = M.data[p].cn; T.data[q].cn = M.data[p].rn;
            T.data[q].value = M.data[p].value; ++cpot[col];
        }
    }
}

  这个算法相比前一个多了两个辅助向量。有两个单次循环,所以时间复杂度为rn+cn.在M的非0元素个数和rn*cn相同的数量级的时候,时间复杂度与经典算法相同。

3 行逻辑连接的顺序表:

   我们把指示“行”信息的辅佐数组cpot固定在稀疏矩阵的存储结构当中。

下述为两个矩阵相乘的算法:

typedef struct
{
    Triple data[MAX_SIZE];
    int rpos[MAX_ROW];
    int rn, cn, tn;
}RLSMatrix;


//三元组定义  默认行从1开始
void MultsMatrix(PLSMatrix a, RLSMatrix b, RLSMatrix c)
{
    elemtype ctemp[MAX_SIZE];
    int p, q, arow, ccol, brow, t;
    if (a.cn != b.rn) { cout << "Error" << endl; }
    else
    {
        c.rn = a.rn; c.cn = b.cn; c.tn = 0;
        if (a.tn * b.tn != 0)
        {
            for (arow = 1; arow <= a.rn; ++arow)  //处理a的每一行
            {
                ctemp[arow] = 0;   //当前行各元素累加器清0
                c.rpos[arow] = c.tn + 1; p = a.rpos[arow];
                for (; p < a.rpos[arrow + 1]; ++p)   //对当前行中的每一个非0元
                {
                    brow = a.data[p].col;      //找到对应在b中行号
                    if (brow < b.rn) t = b.rpos[brow + 1];
                    else t = b.tn + 1;
                    for (q = b.rpos[brow]; q < t; ++q)
                    {
                        ccol = b.data[q].col;
                        ctemp[ccol] += a.data[p].value * b.data[q].value;
                    }
                }
                for(ccol=1;ccol<=c.cn;++ccol)   //压缩存储在行的非0元
                    if (ctemp[ccol] != 0)
                    {
                        if (++c.tn > MAX_SIZE)
                        {
                            cout << "Error" << endl; exit(0);
                        }
                        else c.data[c.tn] = (arow, ccol, ctemp[ccol]);
                    }
            }
        }
    }
}

4 十字链表

  当矩阵的非0元个数和位置在操作过程中变化较大时,就不宜采用顺序结构来表示三元组的线性表。例如,将矩阵B加到矩阵A,由于非0元的操作会引起数组中的变化。所以,采用链性表更加方便。

  除了行,列和值外,还有两个指针,用以标识同行中的下一个非0元和同列中的下一个非0元。

 

typedef struct Clnode
{
    int i, j;
    elemtype e;
    struct Clnode* down, * right;
}OLNode;
//下面是整个稀疏矩阵的定义
typedef struct
{
    int mu; int nu; int tu;
    OLNode* rhead;
    OLNode* chead;
}CrossList;
void CreateSMatrix_OL(CressList& M)
{
    int m, n, t;
    //创建稀疏矩阵。采用十字链表表示
    if (M) free(M);
    cout << '请输入矩阵的M的行数,列数 和非0元素个数' << endl;
    cin >> m >> n >> t;
    M.mu = m; M.nu = n; M.tu = t;      //数额uM的行数,列数和非0元素个数
    if (!(M.rhead = (OLNode*)malloc((m + 1) * sizeof(OLNode)))) cout << "输入数据出现错误" << endl;
    if (!(M.chead = (OLNode*)malloc((n + 1) * sizeof(OLNode)))) cout << "输入数据出现错误" << endl;
    M.rhead[] = M.chead[] = NULL;
    for (cin >> i >> j >> e; i != 0; cin >> i >> j >> e;) {
        if (!(p = (OLNode*)malloc(sizeof(OLNode))))cout << "数据分配出现错误" << endl;
        p->i = i; p->j = j; p->e = e;       //生成节点
        if (M.rhead[i] == NULL || M.rhead[i]->j > j) { p->right = M.rhead[i]; M.rhead[i] = p; }
        else {        //寻找在行表中插入的位置
            for (q = M.rhead[i]; (q->right) && q->right->j < j; q = q->right);
            p->right = q->right; q->right = p;
        }
        if (M.chead[j] == NULL || M.chead[j]->i > i) { p->down = M.chead[j]; M.chead[j] = p; }
        else {        //寻找在列表中插入的位置
            for (q = M.chead[i]; (q->down) && q->down->j < j; q = q->down);
            p->down = q->down; q->down = p;      //完成列的插入
        }
    }
}

5 广义表

  广义表是线性表的推广。

  广义表一般记作:

      LS = (a1, a2, a3,..., an)

  每个元素可以是单个元素,也可以是广义表,分别称为广义表的原子和子表。

  当广义表非空的时候,我们习惯称第一个元素为表头,称其余的元素为表尾(a2, a3, a4,..., an)。

  值得提醒的是列表()和(())不同。前者为空表,长度为0;后者长度为1,可分解得到表头,表尾均为空表()。

  这种灵活性就需要用到链式存储结构。需要两种结构的节点,一种是原子的节点,用以表示原子,由标志域,值域组成;另一种是列表,由标值域,标识表头的指针域和值域组成。

 

1 求广义表的深度(递归):

  广义表的深度就是 各个子表深度的最大值加一,注意区分长度;

  我们用ATOM(Tag)为0表示原子,反之为子表。

typedef struct GLNode {
    ElemTag tag;   //公共部分,用于区分原子节点和表节点
    int atom;
    GLNode* hp;
    GLNode* tp;
}*GList;
//求解表深度
int GListDepth(GList L)
{
    int dep, max;
    GList pp;
    //采用头尾链表存储结构,求广义表L的深度
    if (!L) return 1;    //空表深度为一
    if (L->tag == ATOM) retun 0;   //原子深度为0
    for (max = 0, pp = L; pp; pp = pp->tp)
    {
        dep = GListDepth(pp->hp);  //求以pp->hp为头指针的子表深度
        if (dep > max)max = dep;
    }
    return max + 1;
}

2 递归复制广义表

  分别递归的复制表头和表尾:

//复制广义表
void CopyGlist(GList& T, GList L)
{
    if (!L)T = NULL;   //复制空表
    else {
        if(!(T=(GList)malloc(sizeof(GLNode)))cout<<"数据分配出现错误"<<endl;   //建表节点
            T->tag=L->tag;
            if(L->tag==0)T->atom=L->atom;    //复制单原子
            else {
                CopyGlist(T->hp,L->hp);
                CopyGlist(T->tp,L->tp);
            }
    }
}

 

posted @ 2020-03-31 16:47  为红颜  阅读(1605)  评论(0编辑  收藏  举报