稀疏矩阵的十字链表存储
2014-10-25 14:05
302 查看
什么是稀疏矩阵?
矩阵中含有的非零元占矩阵总元素比例小于5%就可以了。《数据结构》书上举例为了方便操作就没那么苛刻。但是在存取操作上多一点少一点有什么关系,只要方便我们理解算法与存储结构都是好的。
今天我以稀疏矩阵为例,实际更侧重说明链表操作。其实链表是有很多优点的,在内存利用率,插入删除操作,扩展性都优于数组;但是因为链表的操作对于初学者过于麻烦(难以理解),所以一般见到链表、指针就躲之不及。其实,像我这样勇敢的去了解它,你会爱上它。因为它真的很神奇,而且写出来的代码页很赏心悦目。
先有一个结构体。
书上定义两个结构体,一个定义普通数据节点,一个定义头结点。我觉得没必要。
为什么要用OLNode和*OLink两个名字代表同样的结构体:
一切的原因都是为了漂亮的代码。
也可以这么写:
接下来就是创建头结点了。
h为总头结点,保存有right的位数和down的位数即每行每列的位数。
p,q为辅助节点,使h可以向right向down拓展节点。
通过以上步骤,形成了一行一列的头结点(h为总的,其余的为right方向或down方向的子头结点);
接下来就可以向这些节点中插入数据节点链接成表。
链表操作情况下,一般要另设两个节点(视具体情况),一个保存头结点位置,一个保存现在所处位置。
比如:
以上我们要打印LinkList中所有节点数据,为了不至于让打印之后L找不到了,所以设了一个p1,。
链表合并也类似。
以上就设置了pa,pb代替la,lb进行操作,Lc指向pc,操作后pc链表也等于Lc了。
说完了插入,我们就要检验自己以上过程正不正确,则需要打印出链表。
以上是打印到文件中,我们可以打印到屏幕。
十字链表的打印和插入很像,我的思想是这样:
1 先找到第0行链表即h = h->down;(下表从0开始)
2 用p代替第0行的子头结点操作,q记录下p的初始位置。
3 因为这一行有元素节点,则最后这个节点的right一定会重新指向这一行头结点,即p,所有2中我叫q几下p的位置,好设置循环条件。
4 循环直到p->right == q; 其中一直打印p->right节点中的三元组(行号,列号,元素值)。
完成了以上步奏,可以先在主函数中运行一下看是否正确,确定正确之后再进行下一个操作。
这种方式很有效,一点一点写代码,一部分一部分的写,先验证再继续,大家把这种方式叫“迭代式开发”;
下面做稀疏矩阵的转置。
我一开始的想法是这样:
1 和打印的过程类似,找到链表中的元素所在,将行号,列号颠倒,再将此作为一个新的节点插入(用insert函数)到原来的节点中去。
然后将这个节点的函数值设为空。
按上述操作写的话出现了些问题,我就没有继续做了。
还有一个简单方法,大家一开始想到的应该都是这样。
1 设置另一个链表为转置链表,原链表是i * j的,我就创建一个j * i的;
2 和打印的过程类似,将找到的值不打印而颠倒行号列号插入转置链表;
上述方法过去偷懒,而且没有利用原链表空间,造成了浪费。
总结:写这篇文章的目的肯定不是为了启发后人,而是对自己的总结。如果我能讲得稍微明白一点看得人能看懂,说明我也理解了;顺便做下记录,日后自己忘了也可以随时回顾。
hail hydra!
矩阵中含有的非零元占矩阵总元素比例小于5%就可以了。《数据结构》书上举例为了方便操作就没那么苛刻。但是在存取操作上多一点少一点有什么关系,只要方便我们理解算法与存储结构都是好的。
今天我以稀疏矩阵为例,实际更侧重说明链表操作。其实链表是有很多优点的,在内存利用率,插入删除操作,扩展性都优于数组;但是因为链表的操作对于初学者过于麻烦(难以理解),所以一般见到链表、指针就躲之不及。其实,像我这样勇敢的去了解它,你会爱上它。因为它真的很神奇,而且写出来的代码页很赏心悦目。
先有一个结构体。
typedef struct node { int i,j; int v; struct node *right,*down; }OLNode,*OLink;
书上定义两个结构体,一个定义普通数据节点,一个定义头结点。我觉得没必要。
为什么要用OLNode和*OLink两个名字代表同样的结构体:
OLink h; h = (OLink)malloc(sizeof(OLNode))
一切的原因都是为了漂亮的代码。
也可以这么写:
OLNode *h; h = (OLNode *)malloc(sizeof(OLNode));
接下来就是创建头结点了。
OLink init(OLink &h,int m,int n) { OLink q,p; h = (OLink)malloc(sizeof(OLNode)); h->i = m; h->j = n; p = h; for(int j = 0; j < n;j++) { q = (OLink)malloc(sizeof(OLNode)); q->i = -1; q->j = j; q->down = q; p->right = q; p = q; } p->right = h; p = h; for(int i = 0; i < m;i++) { q = (OLink)malloc(sizeof(OLNode)); q->i = i; q->j = -1; q->right = q; p->down = q; p = q; } p->down = h; return h; }
h为总头结点,保存有right的位数和down的位数即每行每列的位数。
p,q为辅助节点,使h可以向right向down拓展节点。
通过以上步骤,形成了一行一列的头结点(h为总的,其余的为right方向或down方向的子头结点);
接下来就可以向这些节点中插入数据节点链接成表。
void insert(OLink h,int i,int j,int e) { OLink q,p,sr,s; if(i < 0 || j < 0 || i >= h->i || j >= h->j) return; //q = new OLNode; q =(OLink)malloc(sizeof(OLNode)); q->i = i; q->j = j; q->v = e; p = h; for(int k = 0; k <= i;k++) p = p->down; sr = p; s = p->right; while(s != p && q->j > s->j) { sr = s; s = s->right; } sr->right = q; q->right = s; p = h; for(int k = 0; k<=j;k++) p = p->right; sr = p; s = p->down; while(s!=p && q->i > s->i) { sr = s; s = s->down; } sr->down = q; q->down = s; return; }
链表操作情况下,一般要另设两个节点(视具体情况),一个保存头结点位置,一个保存现在所处位置。
比如:
void ListTraverse(LinkList &L,void(*visit)(ElemTp)) { LinkList p1 = L->next; while(p1!=NULL){ visit(p1->data); p1 = p1->next; } }
以上我们要打印LinkList中所有节点数据,为了不至于让打印之后L找不到了,所以设了一个p1,。
链表合并也类似。
void MergeList(LinkList &La,LinkList &Lb,LinkList &Lc) { LinkList pa,pb,pc; pa = La->next; pb = Lb->next; Lc = pc = La; while(pa&&pb) { if(pa->data <= pb->data) { pc->next = pa; pc = pa; pa = pa->next; } else { pc->next = pb; pc = pb; pb = pb->next;} } pc->next = pa? pa:pb; }
以上就设置了pa,pb代替la,lb进行操作,Lc指向pc,操作后pc链表也等于Lc了。
说完了插入,我们就要检验自己以上过程正不正确,则需要打印出链表。
void print(OLink h,FILE *fp,char *str) {//输出到文件中 OLink p,q; p = (OLink)sizeof(OLNode); int len = h->i; fprintf(fp,"%s\n",str); for(int i = 0; i < len;i++) { h = h->down; p = h; q = p; while(p->right!=q) { p = p->right; fprintf(fp,"行号:"); fprintf(fp,"%d",p->i); fprintf(fp," 列号:"); fprintf(fp,"%d",p->j); fprintf(fp," 值:"); fprintf(fp,"%d\n",p->v); } }
以上是打印到文件中,我们可以打印到屏幕。
十字链表的打印和插入很像,我的思想是这样:
1 先找到第0行链表即h = h->down;(下表从0开始)
2 用p代替第0行的子头结点操作,q记录下p的初始位置。
3 因为这一行有元素节点,则最后这个节点的right一定会重新指向这一行头结点,即p,所有2中我叫q几下p的位置,好设置循环条件。
4 循环直到p->right == q; 其中一直打印p->right节点中的三元组(行号,列号,元素值)。
完成了以上步奏,可以先在主函数中运行一下看是否正确,确定正确之后再进行下一个操作。
这种方式很有效,一点一点写代码,一部分一部分的写,先验证再继续,大家把这种方式叫“迭代式开发”;
下面做稀疏矩阵的转置。
我一开始的想法是这样:
1 和打印的过程类似,找到链表中的元素所在,将行号,列号颠倒,再将此作为一个新的节点插入(用insert函数)到原来的节点中去。
然后将这个节点的函数值设为空。
按上述操作写的话出现了些问题,我就没有继续做了。
还有一个简单方法,大家一开始想到的应该都是这样。
1 设置另一个链表为转置链表,原链表是i * j的,我就创建一个j * i的;
2 和打印的过程类似,将找到的值不打印而颠倒行号列号插入转置链表;
void change(OLink h,OLink h1) {/* 利用另一个链表来转置 */ OLink p,q; int a,b,v; p = (OLink)sizeof(OLNode); int len = h->i; for(int i = 0; i < len;i++) { h = h->down; p = h; q = p; while(p->right!=q) { p = p->right; a = p->j; b = p->i; v = p->v; insert(h1,a,b,v); } } }
上述方法过去偷懒,而且没有利用原链表空间,造成了浪费。
总结:写这篇文章的目的肯定不是为了启发后人,而是对自己的总结。如果我能讲得稍微明白一点看得人能看懂,说明我也理解了;顺便做下记录,日后自己忘了也可以随时回顾。
hail hydra!
相关文章推荐
- 稀疏矩阵的十字链表存储
- 5.3稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储表示
- 稀疏矩阵的十字链表存储表示
- 稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储的思路
- 稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储表示
- 数据结构_数组与广义表_矩阵的十字链表存储稀疏矩阵相加
- 稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储的思路
- 稀疏矩阵的十字链表存储的思路
- 数据结构:稀疏矩阵的十字链表存储
- 稀疏矩阵的十字链表存储表示
- 稀疏矩阵的十字链表存储
- 矩阵的压缩存储(稀疏矩阵的十字链表存储、稀疏矩阵的三元组行逻辑链接的顺序表存储表示、稀疏矩阵的三元组顺序表存储表示)
- javascript实现数据结构:稀疏矩阵的十字链表存储表示
- 十字链表储存稀疏矩阵及矩阵相乘
- 用典型的多重链表(十字链表)存储稀疏矩阵