数组
数组这部分内容在写《线性表》的的时候介绍过,所以这里就略过一部分内容(略过的内容在这里),写一写前边没写过的。
由于数组中各元素具有统一的类型,并且数组元素的下标一般具有固定的上界和下界,因此,数组的处理比其它复杂的结构更为简单。多维数组是向量的推广。
数组的顺序表示和实现
由于计算机的内存结构是一维的,因此用一维内存来表示多维数组,就必须按某种次序将数组元素排成一列序列,然后将这个线性序列存放在存储器中。
二维数组矩阵的存储方式
但是这种存储方式会面临一个问题,当矩阵中非零元素呈某种规律分布或者矩阵中出现大量的零元素的情况下,看起来存储密度仍为1,但实际上占用了许多单元去存储重复的非零元素或零元素,这对高阶矩阵会造成极大的浪费, 为了节省存储空间, 我们可以对这类特殊矩阵进行压缩存储:即为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
例:
因为该矩阵所有数据沿对角线对称,称为对称矩阵,所以只存图中黄色部分数据即可
稀疏矩阵
稀疏矩阵简单说,设矩阵A中有s个非零元素,若s远远小于矩阵元素的总数(即s<<m×n),则称A为稀疏矩阵。
精确地说,设在的矩阵A中,有s个非零元素。令 e=s/(m*n),称e为矩阵的稀疏因子。通常认为e≦0.05时称之为稀疏矩阵。
由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。一个三元组 (i,j,aij) 唯一确定了矩阵A的一个非零元。因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。
例:
可表示成:
这样在高阶矩阵中就大大节省了存储空间。
当然除了这种三元表存储方法来节省存储空间,还可以用链式存储结构达到目的。
链式存储结构
用带行指针向量的单链表表示,每行的非零元用一个单链表存放,设置一个行指针数组,指向本行第一个非零元结点;若本行无非零元,则指针为空,表头结点与单链表结点类型定义。
节点:
typedef struct node
{
int col;
int val;
struct node *link;
}JD;
如图所示:
这种方法在图里面叫邻接表,具体可以看看我这篇博客数据结构:图(基础概念及操作,图文解释)
广义表
广义表可看成是一种特殊的线性表,其特殊在于,表中的数所元素本身也是一种线性表。
定义
广义表是线性表的扩展。一般记作LS=(a1,a2······an)。LS是广义表的名字,n为广义表的长度,广义表可看成是一种特殊的线性表,其特殊在于,表中的数所元素本身也是一种线性表。 a1称为表头,其余元素组成的表(a2······an)是LS的表尾。
特点
列表的元素可以是子表,而子表的元素还可以是子表······由此,列表是一个多层次结构。 列表可以为其他列表所共享。(即已存在的列表可以为其他列表的子表)例:A=(e)。B=( c )。C=(A,B)。A,B列表可以共享给列表C。
列表可以是一个递归的表,即列表也可以是其本身的一个子表。
注意: 列表 () 和列表 (()) 不同,前者为空表,长度为0,后者长度为1,分解为表头和表尾都为 ()。
广义表的存储结构
广义表有两种存储结构,掌握一种即可。
例:
A=() 空表,长度为0
B=(e) 只有一个原子,长度为1
C=(a,(b,c,d)) 两个原子a和子表(b,c,d),长度为2
D=(A,B,C) 三个原子分别为子表A,B,C,长度为3
E=(a,E) 递归表,长度为2,相当于(a,(a,(·····)))
第一种伪代码:
typedef struct GLNode{
bool tag; //公共部分,0为原子,1为子表
union{
AtomType atom; //原子的数据部分,AtomType由用户自己定义int、char····
struct GLNode *hp,*tp; //指针域,分别指向表头和表尾
}at;
}GList;
上面例子第一种存储结构示意图
第二种伪代码:
typedef struct GLNode{
bool tag; //公共部分,0为原子,1为子表
union{ //原子节点和表节点的联合部分
AtomType atom; //原子的数据部分,AtomType由用户自己定义int、char····
struct GLNode *hp; //指针域,指向表头
}at;
struct GLNode *tp; //相当于线性表next指针,指向下一个元素节点
}
上面例子第二种存储结构示意图
第二种实现
#include<stdio.h>
int r = 0;
typedef struct GLNode {
bool tag; //公共部分,0为原子,1为子表
union { //原子节点和表节点的联合部分
int atom;
struct GLNode* hp; //指针域,指向表头
};
struct GLNode *tp; //相当于线性表next指针,指向下一个兄弟元素节点
}GList;
GList *creat(char s[])//递归建立广义表
{
GList* p;
char ch = s[r++];
if (ch != '\0') {
p = (GList*)malloc(sizeof(GList));
if (ch == '(') { //是表头,建立子表
p->tag = 1; //置标志为1,此节点为表头节点
p->hp = creat(s);
}
else if(ch==')'){ //此子表已结束,p节点置空
p = NULL;
}
else { //此外即为原子节点
p->tag = 0;
p->atom = ch;
}
}
else {
p = NULL;
}
ch = s[r++]; //取广义表中下一个元素
if (p != NULL) { //说明子表未结束
if (ch == ',') { //说明还有兄弟元素
p->tp = creat(s);
}
else { //说明没有兄弟元素了
p->tp = NULL;
}
}
return p;
}
oid dele(GList* p)//删除广义表
{
GList* p1, *p2;
if (p->at.hp == NULL){ //若为空表删除根节点,结束程序
free(p);
return;
}
else
p1 = p->at.hp; //否则进入其子表
while (p1 != NULL) {
if (p1->tag == 0) { //若为原子节点
p2 = p1->tp; //记录p1的兄弟节点
free(p1);
p1 = p2; //更新p1
}
else { //若为表头节点,递归遍历
p2 = p1->tp;
dele(p1);
p1 = p2;
}
}
free(p); //删除根节点
}
void print(GList* p)
{
if (p != NULL) //如果p不为空
{
if (p->tag == 0) //如果元素是原子
printf("%c", p->atom); //打印数值
else
{
printf("("); //是子表打印'('
if (p->hp != NULL) //递归输出广义表
{
print(p->hp);
}
printf(")"); //几次由这里进入递归,就有几个子表
}
if (p->tp != NULL) //广义表是否为遍历完毕
{
printf(","); //打印','
print(p->tp); //递归输出广义表
}
}
}
int main()
{
char s[15] = "(a,b,(c,d,e))";
GList* p = creat(s);
print(p);
return 0;
}
简单来讲:就是遇到表头节点就要递归一次进入子表。有点类似遍历n叉树。
好了,数组和广义表就到这里了,我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。