十字链表记录稀疏矩阵,可以在有效存取的同时避免空间的浪费。
组成:
对于十字链表的每个结点都有两个指针域,一个是行指针Right,一个是列指针Down,还有一块数据域用来存取行坐标Row,列坐标Col和数值Value。
在稀疏矩阵的十字链表实现中要注意头结点和非零结点的区分。因此可以在结构中先加入一个Tag来标志下这个节点是头结点还是非零结点,然后在结构中采用联合union来存放这两种不同的类型的结点(就是将他们融合在一起),
对于矩阵的入口就是第一个结点,要记录这个矩阵的基本信息,比如几行几列,有多少个非零元素。注意每行每列的头结点,是一个数组。头结点指后面的行和列结点,行和列结点指对应的数据。
因为十字链表结点里面有一个行指针和一个列指针,所以就可以理解为一个结构里由两个链表,一个行链表和一个列链表,然后每一行每一列的结点都是一个循环链表,最后都要指回这个行或列结点的头结点,如果没有后继结点就要指回自己,对于矩阵中就是有一列或者一行是空的也要有结点表示。
定义:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include <stdbool.h>
//这边是十字链表矩阵
typedef int ElementType;
typedef enum { Head, Term } NodeTag;//定义枚举类型,用01来区分Head和Term
struct TermNode {
//用来表示记录一个非零结点的信息
int Row, Col;//记录行列的坐标位置
ElementType Value;//记录对应点处的值
};
typedef struct MNode* PtrToMNode;//这是个指针类型的矩阵结点,表示这个结点的内容
struct MNode {
PtrToMNode Down, Right;//分别用来链接下边的结点和右边的结点
NodeTag Tag;//结点类型标记,0为Head表示头结点,1为Term表示非零结点;
union {
PtrToMNode Next;//表示头结点,记录下一个位置;
struct TermNode Term;//表示一个数据域的类型
}URegion;//用联合来表示存放的内容:是数据域还是头结点
};
typedef PtrToMNode Matrix;//把指针类型的矩阵结点定义为矩阵类型
功能实现:
输出十字链表:
//输出十字链表
void PutMatrix(Matrix M) {
Matrix a, b;
printf("矩阵-> 行:%d 列:%d\n", M->URegion.Term.Col, M->URegion.Term.Row);//打印矩阵入口结点的基本信息
a = M->Right;//a指向矩阵结点的下一个行列头结点,准备遍历
while (a!=M) {//a结点就是遍历头结点的遍历,每次while都指向下一个行列的的头结点
//输出方式要么一行一行遍历,要么一列一列遍历
b = a->Right;//b指向a所在该行列头结点的下一个结点,即数据结点
//这里b也可以是a的down,取决于遍历方式
//每次while进来都是a头结点里面行列的数据结点
while (b!=a) {//只要b没指回头结点就继续进行
printf("行:%d\t列:%d\t值:%d\n", b->URegion.Term.Col, b->URegion.Term.Row, b->URegion.Term.Value);//输出结点信息
b = b->Right;
}
a=a->URegion.Next;
}
}
创建十字链表:
//创建十字链表
Matrix createMatrix(Matrix MN,int M ,int N,int ab[][4]) {//也可以传入一个矩阵判断非零节点创建十字链表。
//ab为一个二维数组,*((int*)array + n*i + j);表示行列位置
int max = (M > N ? M : N);
int i, j;
Matrix* Mhead = (Matrix*)malloc(max * sizeof(Matrix));//根据行列最大值创建一个指针数组,保存头结点
//用数组的原因是方便行列数据的结点的插入,实现行节点和列结点时循环链表的实现
//
//创建头结点
//MN = (Matrix)malloc(sizeof(struct MNode));
MN->URegion.Term.Col = M;
MN->URegion.Term.Row = N;
//
//利用尾插法插入行列头结点的循环链表
Matrix a, b, c;
a = MN;//指向头结点
for (i = 0; i < max; i++) {//尾插法
Mhead[i] = (Matrix)malloc(sizeof(struct MNode));
Mhead[i]->Tag = 0;
Mhead[i]->Down = Mhead[i]->Right = Mhead[i];//结点创建后行列一开始指向自己
if (a->Tag == 1) {
a->Down = Mhead[i];
a->Right = Mhead[i];
}
else {
a->URegion.Next = Mhead[i];
}//做一个结点的链接
//注意这里uregion是联合体公用域会修改行列值
a = Mhead[i];//指向结点的之后后移
}
a->URegion.Next = MN;//注意最后要指回去构成循环链表
//数据载入
for (i = 0; i < M; i++) {//行遍历
for (j = 0; j < N; j++) {//列遍历
if (ab[i][j] != 0) {//结点判断
b = (Matrix)malloc(sizeof(struct MNode));//生成一个结点,并进行赋值
b->Tag = 1; b->URegion.Term.Value = ab[i][j];
b->URegion.Term.Col = i; b->URegion.Term.Row = j;
//在行表中插入,形成一个行的循环链表
c = Mhead[i];//找到对应的行
while (c->Right != Mhead[i] && c->Right->URegion.Term.Row < j) {
//在这一行找到合适的列插入
c = c->Right;
}
b->Right = c->Right;
c->Right = b;
//在列表中插入,形成一个列的循环链表
c = Mhead[j];//找到对应的列
while (c->Down != Mhead[j] && c->Down->URegion.Term.Col< i) {
//在这一行找到合适的行插入
c = c->Down;
}
b->Down = c->Down;
c->Down = b;
}
}
}
return MN;
}
函数使用:
#include<stdio.h>
#include<stdlib.h>
#include <stdbool.h>
int main() {
int ab[3][4] = { 0,0,0,0,0,1,0,0,0,1,0,1 };//二维数组传入函数必须要有列数
//或者传入二维数组指针,要开辟新空间,就是新建一个指针类型的二维数组
// 对这个二维数组要空间,先是分配双指针类型的行,再是对每行分配列空间
// 最后利用for取copy二维数组
//或者把ab转为int**类型然后传入函数用利用二维数组地址连续*((int *)p + i * col + j)去遍历读取
Matrix MN = (Matrix)malloc(sizeof(struct MNode));
MN->Tag = 1; MN->Down = MN; MN->Right = MN;
MN= createMatrix(MN, 3, 4, ab);
PutMatrix(MN);
return 0;
}
汇总:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include <stdbool.h>
//这边是十字链表矩阵
typedef int ElementType;
typedef enum { Head, Term } NodeTag;//定义枚举类型,用01来区分Head和Term
struct TermNode {
//用来表示记录一个非零结点的信息
int Row, Col;//记录行列的坐标位置
ElementType Value;//记录对应点处的值
};
typedef struct MNode* PtrToMNode;//这是个指针类型的矩阵结点,表示这个结点的内容
struct MNode {
PtrToMNode Down, Right;//分别用来链接下边的结点和右边的结点
NodeTag Tag;//结点类型标记,0为Head表示头结点,1为Term表示非零结点;
union {
PtrToMNode Next;//表示头结点,记录下一个位置;
struct TermNode Term;//表示一个数据域的类型
}URegion;//用联合来表示存放的内容:是数据域还是头结点
};
typedef PtrToMNode Matrix;//把指针类型的矩阵结点定义为矩阵类型
//输出十字链表
void PutMatrix(Matrix M) {
Matrix a, b;
printf("矩阵-> 行:%d 列:%d\n", M->URegion.Term.Col, M->URegion.Term.Row);//打印矩阵入口结点的基本信息
a = M->Right;//a指向矩阵结点的下一个行列头结点,准备遍历
while (a!=M) {//a结点就是遍历头结点的遍历,每次while都指向下一个行列的的头结点
//输出方式要么一行一行遍历,要么一列一列遍历
b = a->Right;//b指向a所在该行列头结点的下一个结点,即数据结点
//这里b也可以是a的down,取决于遍历方式
//每次while进来都是a头结点里面行列的数据结点
while (b!=a) {//只要b没指回头结点就继续进行
printf("行:%d\t列:%d\t值:%d\n", b->URegion.Term.Col, b->URegion.Term.Row, b->URegion.Term.Value);//输出结点信息
b = b->Right;
}
a=a->URegion.Next;
}
}
//创建十字链表
Matrix createMatrix(Matrix MN,int M ,int N,int ab[][4]) {//也可以传入一个矩阵判断非零节点创建十字链表。
//ab为一个二维数组,*((int*)array + n*i + j);表示行列位置
int max = (M > N ? M : N);
int i, j;
Matrix* Mhead = (Matrix*)malloc(max * sizeof(Matrix));//根据行列最大值创建一个指针数组,保存头结点
//用数组的原因是方便行列数据的结点的插入,实现行节点和列结点时循环链表的实现
//
//创建头结点
//MN = (Matrix)malloc(sizeof(struct MNode));
MN->URegion.Term.Col = M;
MN->URegion.Term.Row = N;
//
//利用尾插法插入行列头结点的循环链表
Matrix a, b, c;
a = MN;//指向头结点
for (i = 0; i < max; i++) {//尾插法
Mhead[i] = (Matrix)malloc(sizeof(struct MNode));
Mhead[i]->Tag = 0;
Mhead[i]->Down = Mhead[i]->Right = Mhead[i];//结点创建后行列一开始指向自己
if (a->Tag == 1) {
a->Down = Mhead[i];
a->Right = Mhead[i];
}
else {
a->URegion.Next = Mhead[i];
}//做一个结点的链接
//注意这里uregion是联合体公用域会修改行列值
a = Mhead[i];//指向结点的之后后移
}
a->URegion.Next = MN;//注意最后要指回去构成循环链表
//数据载入
for (i = 0; i < M; i++) {//行遍历
for (j = 0; j < N; j++) {//列遍历
if (ab[i][j] != 0) {//结点判断
b = (Matrix)malloc(sizeof(struct MNode));//生成一个结点,并进行赋值
b->Tag = 1; b->URegion.Term.Value = ab[i][j];
b->URegion.Term.Col = i; b->URegion.Term.Row = j;
//在行表中插入,形成一个行的循环链表
c = Mhead[i];//找到对应的行
while (c->Right != Mhead[i] && c->Right->URegion.Term.Row < j) {
//在这一行找到合适的列插入
c = c->Right;
}
b->Right = c->Right;
c->Right = b;
//在列表中插入,形成一个列的循环链表
c = Mhead[j];//找到对应的列
while (c->Down != Mhead[j] && c->Down->URegion.Term.Col< i) {
//在这一行找到合适的行插入
c = c->Down;
}
b->Down = c->Down;
c->Down = b;
}
}
}
return MN;
}
int main() {
int ab[3][4] = { 0,0,0,0,0,1,0,0,0,1,0,1 };//二维数组传入函数必须要有列数
//或者传入二维数组指针,要开辟新空间,就是新建一个指针类型的二维数组
// 对这个二维数组要空间,先是分配双指针类型的行,再是对每行分配列空间
// 最后利用for取copy二维数组
//或者把ab转为int**类型然后传入函数用利用二维数组地址连续*((int *)p + i * col + j)去遍历读取
Matrix MN = (Matrix)malloc(sizeof(struct MNode));
MN->Tag = 1; MN->Down = MN; MN->Right = MN;
MN= createMatrix(MN, 3, 4, ab);
PutMatrix(MN);
return 0;
}