一. 十字链表的本质
- 首先十字链表有一行头指针(RHead), 和一列头指针(CHead), 它们都可以保存一个结点的地址(PointPtr)
- 每个结点保存以下信息: 行号(i), 列号(j), 值(v), 下方结点(down), 右方结点(right)
- 初始化的时候, 各头指针的值都是 NULL
- 可以把每一行, 每一列都看成是一个链表
- 当插入元素的时候, 假设元素的位置是(i, j), 先确定它在第 i 行链表的位置, 再确定它在第 j 列链表的位置
- 这样就构造出了一个十字链表
二. 结点和整个矩阵的设计
1. 结点的设计
根据每个结点需要保存的信息, 可以知道结点应该如下:1
2
3
4
5
6//设计结点
typedef struct point
{
int i, j, v; //行号, 列号, 值
struct point * right, * down; //右侧元素, 下方元素
}Point, *PointPtr;
2. 矩阵的设计
矩阵肯定包括行数(mu), 列数(nu), 非零元素个数(tu), 而且还需要一行(RHead)和一列头指针(CHead), 每个头指针所包含的元素类型都是一样的, 所以一行和一列头指针可以分别用数组保存起来.
由于事先不知道数组的长度, 所以这两个数组应该是动态分配的.
动态分配之后肯定是赋值给指针, 那么是什么类型的指针呢?
答案是: 因为元素类型是 PointPtr , 所以是 PointPtr * 类型的.
那么问题又来了, 为什么元素类型是 PointPtr 的呢?
因为结点是 Point 类型的, 而动态分配结点的返回值是 PointPtr 类型的.
(头指针保存一个结点的地址, 结点又保存下一个结点的地址)1
2
3
4
5typedef struct
{
int mu, nu, tu; //稀疏矩阵的行数, 列数, 元素个数
PointPtr * Rhead, * Chead; //两个指针数组
}TenList;
三. 矩阵的初始化
1. 输入矩阵的信息
1 | do |
2. 初始化各头指针
1 | //分配两个指针数组 |
3. 插入元素
根据元素个数循环:1
for(k=1; k<=tu; k++)
然后输入元素, 确保元素的行号, 列号无误之后, 动态分配一个新结点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17do
{
flag = 0;
printf("请输入结点的行号, 列号与值: \n");
scanf("%d %d %d", &i, &j, &v);
//确保行号, 列号无误
if(i<=0 || j<=0)
{
flag = 1;
}
}while(flag);
//动态分配一个结点, 并给结点赋值
temp = malloc(sizeof(Point));
temp->i = i;
temp->j = j;
temp->v = v;
至此, 得到了一个新的结点, 然后就到了重点: 在十字链表中插入这个结点.
插入一个结点(i, j), 首先在第 i 行链表中插入, 然后在第 j 列链表中插入. 对我而言还是有点难理解, 但就是这样做的.
在某一行链表中插入, 有两种情况, 一种是链表还没有结点, 直接插入. 还有一种是链表已经有了结点, 在合适的地方插入新结点.
1 | //先确定新结点在某一行中的位置 |
在某一列中插入结点的方法跟上面很相似:1
2
3
4
5
6
7
8
9
10
11if(T->Chead[j]==NULL || T->Chead[j]->i>i)
{
temp->down = T->Chead[j];
T->Chead[j] = temp;
}
else
{
for(p=T->Chead[j]; p->down!=NULL && p->down->i<i; p=p->down);
temp->down = p->down;
p->down = temp;
}
四. 完整代码
1 |
|