一:概念
既然要用链表节点来模拟矩阵中的非零元素,肯定需要如下5个元素(row,col,val,down,right),其中:
row:矩阵中的行。
col:矩阵中的列。
val:矩阵中的值。
right:指向右侧的一个非零元素。
down:指向下侧的一个非零元素。
现在我们知道单个节点该如何表示了,那么矩阵中同行的非零元素的表示不就是一个单链表吗?比如如下:
那么进一步来说一个多行的非零元素的表示不就是多个单链表吗,是的,这里我把单链表做成循环链表,我们来看看如何用十字链表来表示稀疏矩阵。
从上面的十字链表中要注意两个问题:
第一:这里有一个填充色的节点,是十字链表中的总结点,它是记录该矩阵中的(row,col,value)和一个指向下一个头节点的next指针。
第二:每个链表都有一个头指针,总结点用next指针将它们贯穿起来。
二、稀疏矩阵的创建过程
1、稀疏矩阵节点的定义
typedef struct OLNode {
int i, j; //行号与列号
ElemType e; //值
struct OLNode *right, *down; //指针域
}OLNode, *OList;
2、为了创建稀疏矩阵,除了需要知道行数、列数和非零个数以外,还需要创建行头指针和列头指针,定义如下:
typedef struct {
OLink *Rhead, *Chead;
int mu, nu, tu; // 稀疏矩阵的行数、列数和非零元个数
}CrossList;
3、稀疏矩阵的代码
int CreateSMatrix(CrossList *M)
{
int i, j, m, n, t;
int k, flag;
ElemType e;
OLNode *p, *q;
//输入稀疏矩阵的基本信息
if (M->Rhead)
DestroySMatrix(M); do {
flag = 1;
printf("输入需要创建的矩阵的行数、列数以及非零元的个数");
scanf("%d%d%d", &m, &n, &t);
if (m<0 || n<0 || t<0 || t>m*n)
flag = 0;
}while (!flag);
M->mu = m;
M->nu = n;
M->tu = t;
//创建行链表头数组
M->Rhead = (OLink *)malloc((m+1) * sizeof(OLink));
if(!M->Rhead)
exit(-1);
//创建列链表头数组
M->Chead = (OLink *)malloc((n+1) * sizeof(OLink));
if(!(M->Chead))
exit(-1);
for(k=1;k<=m;k++) // 初始化行头指针向量;各行链表为空链表
M->Rhead[k]=NULL;
for(k=1;k<=n;k++) // 初始化列头指针向量;各列链表为空链表
M->Chead[k]=NULL;
//输入各个结点
for (k=1; k<=t; ++k)
{
do {
flag = 1;
printf("输入第%d个结点行号、列号以及值", k);
scanf("%d%d%d", &i, &j, &e);
if (i<=0 || j<=0)
flag = 0;
}while (!flag); p = (OLink) malloc (sizeof(OLNode));
if (NULL == p)
exit(-1);
p->i = i;
p->j = j;
p->e = e;
//节点的行插入
if(NULL==M->Rhead[i] || M->Rhead[i]->j>j) //-----(1)
{
// p插在该行的第一个结点处
// M->Rhead[i]始终指向它的下一个结点
p->right = M->Rhead[i];
M->Rhead[i] = p;
}
else // 寻查在行表中的插入位置
{
//从该行的行链表头开始,直到找到
for(q=M->Rhead[i]; q->right && q->right->j < j; q=q->right) ; //----(2)
p->right=q->right; // 完成行插入
q->right=p;
}
//节点的列插入
if(NULL==M->Chead[j] || M->Chead[j]->i>i)
{
p->down = M->Chead[j];
M->Chead[j] = p;
}
else // 寻查在列表中的插入位置
{
//从该列的列链表头开始,直到找到
for(q=M->Chead[j]; q->down && q->down->i < i; q=q->down)
;
p->down=q->down; // 完成行插入
q->down=p;
}
} return 1;
}
4、代码的部分解释
(1)怎么去找到要插入的正确位置。当行中有结点的时候我们无非就是插入到某个结点之前或者之后。那么我们再回到前面,在我们定义Rhead的时候就说过,某一行的表头指针指向的就是该行中第一个结点的地址。我们假设该行中已经有了一个结点我们称它为A结点,如果要插在A结点之前那么A结点的列号必定是大于我们输入的结点(我们称它为P结点)的列号的。我们的插入操作就要修改头指针与p结点的right域。就像链表中的插入。那么当该行中没有结点的时候我们怎么去插入?同样是修改头指针让它指向我们的P结点,同样要修改P结点的right域。看,我们可以利用if语句来实现这两种条件的判断。
(2)重点关注:for循环后紧跟" ; ",
现在我们再想一下怎么去插入到某一个结点的后面? 我们新创建的P结点要插入到现有的A结点的后面,那么P的列号必定是大于A的列号,那么我们只要找到第一个大于比P的列号大的结点B,然后插入到B结点之前!如果现有的结点没有一个结点列号是大于P结点的列号的,那么我们就应该插入到最后一个结点之后!所以我们首先要寻找符合条件的位置进行插入 。