您的位置:首页 > 其它

稀疏矩阵的十字链表存储

2014-10-25 14:05 302 查看
什么是稀疏矩阵?

矩阵中含有的非零元占矩阵总元素比例小于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!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: