目录
前言
本篇是对于学习到的数据结构线性表,广义表,多重链表的总结,内容有点小多。
线性结构
线性结构是数据结构里面最基础也是最简单的一种数据结构类型,其中典型的就是 线性表。
线性表及其实现
同一个问题可以有不同的表示(存储)方法,在数据结构中最常见的有两种方法:数组和链表
线性表(Linear List):由同类型数据元素构成有序序列的线性结构
- 表中元素个数称为线性表的长度
- 线性表没有元素时,称为空表
- 表起始位置称表头,表结束位置称表尾
线性表的抽象数据类型描述
类型名称:线性表(List)
抽象数据类型包含两个要素:这个类型所包含的数据对象集;在对象集上进行的操作。
数据对象集:线性表是n(大于等于0)个元素构成的有序序列(a1,a2,a3……)
操作集:线性表L属于List,整数i表示位置,元素X属于ElementType(整型、实型、结构),线性表的基本操作主要有:
- List MakeEmpty(): 初始化一个空线性表L;
- ElementTpe FindKth( int K,List L): 根据位序K,返回相应元素;
- int Find(ElementType X,List L): 在线性表L中查找X的第一次出现位置;
- void Insert( ElementType X,int i,List L):在位序i前插入一个新元素X;
- void Delete(int i,List L):删除指定位序i的元素;
- int Length(List L): 返回线性表L的长度n。
线性表的顺序存储实现
利用数组的连续存储空间顺序存放线性表的各元素
数组可以放不同长度的线性表,是连续存放的。我们需要一个指针Last来指示在数组里存放的线性表中最后一个元素的位置
typedef struct LNode*List;
struct LNosde{
ElementType Data[MAXSIZE]; //定义一个数组,它的分量类型是ElementType
int Last; //再定义一个Last,代表线性表的最后一个元素
};
struct LNode L;
List PtrL;
这样构成了一个结构,那么这个结构就可以抽象的实现线性表
访问下标为i的元素:L.Data[i] 或 PtrL->Data[i]
线性表的长度:L.Last+1 或 PtrL->Last+1
主要操作的实现
1.初始化(建立空的顺序表)
List MakeEmpty()
{
List PtrL;
PtrL = (List)malloc(sizeof(struct LNode)); //通过malloc函数申请一个包含数组和
//代表最后一个元素的Last的结构
PtrL->Last = -1; //把结构的Last赋值为-1
return Ptrl; //返回结构的指针
}
2.查找
int Find(ElementType X,List PtrL)
{
int i = 0;
while(i<=PtrL->Last && PtrL->Data[i]!=X)
i++;
if(i>PtrL->Last)
return -1; //如果没找到,返回-1
else return i; //找到后返回的是存储位置
}
查找成功的平均比较次数为(n+1)/2,若是运气好,一次就找到了,运气不好最后一个找到
平均时间性能为O(n)
3.插入
在线性表的第i(i>=1且i<=n+1)个位置上插入一个值为X的新元素
线性表在数组里表示,下标是从0开始的,所以实际上,我们要把X放在i-1的下标上。因为是连续存放的,所以要把下标为i-1及其之后的元素全部向后挪一位,腾出i-1的位置,然后把X放在里面。移动时,要从最后一个开始挪。
void Insert(ElementType X,int i,List Ptrl)
{
int j;
if(PtrL->Last == MAXSIZE-1){ //MAXSIZE是数组的大小。若表空间已满,不能插入
printf("表满");
return;
}
if(i<1 || i>PtrL->Last+1){ //检查插入位置的合法性
printf("位置不合法");
return;
}
for(j = PtrL->Last;j>=i-1;j--)
PtrL->Data[j+1]=PtrL->Data[j]; //将ai~an倒序向后移动
PtrL->Data[i-1]=X; //新元素插入
PtrL->Last++; //Last仍指向最后元素
return;
}
整个算法的复杂性在for循环里,平均移动次数为n/2;平均时间性能为O(n)
4.删除
删除表的第i个(i>=1且i<=n)个位置上的元素
实际上删除的位置对应的下标为i-1,所以要把下标为i及其之后的元素向前挪。
void Delete(int i,List PtrL)
{
int j;
if(i<1||i>PtrL->Last+1){ //检查空表及删除位置的合法性
printf("不存在第%d个元素",i);
return;
}
for(j=i;j<=PtrL->Last;j++)
PtrL->Data[j-1]=PtrL->Data[j];//将ai+1~an顺序向前移动
PtrL->Last--; //Last仍指向最后元素
return;
}
平均移动次数为(n-1)/2,平均时间性能为O(n)
线性表的链式储存实现
不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间是逻辑关系。
插入、删除不需要移动数据元素,只需要修改“链”
typedef struct LNode *List;
struct LNode{
ElementType Data; //代表结点所对应的数据
List Next; //代表下一个结点的位置
};
struct Lnode L;
List PtrL;
主要操作的实现
1.求表长(链表遍历)
int Length(List PtrL)
{
List p=PtrL; //p指向表的第一个结点
int j=0;
while(p){ //p不等于NULL,就循环下去
p=p->Next;
j++; //当前p指向的是第j个结点
}
return j;
}
时间性能为O(n)
2.查找
(1)按序号查找:FindKth;
List FindKth(int K,List PtrL)
{
List p=PtrL; //p是临时变量,指向链表的表头
int i=1;
while(p!=NULL && i<K){
p=p->Next;
i++;
}
if(i==K)
return p; //找到第K个,返回指针
else return NULL;//否则返回空
}
(2)按值查找:Find
List Find(ElementType X,List PtrL)
{
List p=PtrL;
while(p!=NULL && p->Data!=X)
p=p->Next;
return p; //返回结点所在的地址
}
平均时间性能O(n)
3.插入(一定要知道插入结点的前面一个是谁)
在第i-1(i>=1且i<=n+1)个结点后插入一个值为X的新结点
(1)先构造一个新结点,用s指向;
(2)再找到链表的第i-1个结点,用p指向;
(3)然后修改指针,插入结点(p之后插入新结点是s)
s->Next = p->Next;
p->Next = s; //这两个步骤不可交换
List Insert(ElementType X,int i,List PtrL)
{
List p,s;
if(i == 1){ //新结点插入在表头
s=(List)malloc(sizeof(struct LNode));//申请、填装结点
s->Data = X;
s->Next = PtrL;
return s; //返回新表头指针
}
p = FindKth(i-1,PtrL); //查找第i-1个结点
if(p == NULL){ //第i-1个不存在,不能插入
printf("参数i错");
return NULL;
}else{
s=(List)malloc(sizeof(struct LNode)); //申请、填装结点
s->Data = X;
s->Next = p->Next; //新结点插入在第i-1个结点后面
p->Next = s;
return PtrL;
}
}
4.删除
删除链表的第i(i>=1且i<=n)个位置上的结点
(1)先找到链表的第i-1个结点,用p指向;
(2)再用指针s指向要被删除的结点(p的下一个结点)
(3)然后修改指针,删除s所指结点;
(4)最后释放s所指结点的空间。
p->Next = s->Next;
List Delete(int i,List PtrL)
{
List p,s;
if(i==1){ //若要删除的是表的第一个结点
s=PtrL; //s指向第一个结点
if(PtrL!=NULL)PtrL = PtrL->Next; //从链表中删除
else return NULL;
free(s); //释放被删除的结点
return PtrL;
}
p=FindKth(i-1,PtrL); //查找第i-1个结点
if(p==NULL){
printf("第%d个结点不存在",i-1); return NULL;
}else if(p->Next == NULL){
printf("第%d个结点不存在",i); return NULL;
}else{
s=p->Next; //s指向第i个结点
p->Next =s->Next; //从链表中删除
free(s); //释放被删除结点
return PtrL;
}
}
广义表(Generalized List)
【例】给定二元多项式如何表示?
【分析】可以将上述二元多项式看成关于x的一元多项式
所以,上述二元多项式可以用“复杂”链表表示为:
- 广义表是线性表的推广
- 定义线性表而言,n个元素都是基本的单元素
- 广义表中,这些元素不仅可以是单元素也可以是另一个广义表
typedef struct GNode *GList;
struct GNode{
int Tag; //标志域:0表示结点是单元素,1表示结点是广义表
union{ //子表指针域Sublist与单元素数据域Data复用,即共用存储空间
ElementType Data;
GList SubList;
}URegion;
GList Next; //指向后继结点
};
Tag | Data | Next |
SubList |
广义表实际上就是多重链表
多重链表
多重链表:链表中的结点可能同时隶属于多个链
- 多重链表中结点的指针域会有多个,如前面例子包含了Next和SubList两个指针域
- 但包含两个指针域的链表并不一定是多重链表,比如在双向链表不是多重链表。
多重链表有广泛的用途:
基本上如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储
【例】矩阵可以用二维数组表示,但二维数组表示有两个缺陷:
- 一是数组的大小需要事先确定
- 对于“稀疏矩阵”,将造成大量的存储空间浪费
【分析】采用一种典型的多重链表——十字链表来存储稀疏矩阵
- 只存储矩阵非0元素项
结点的数据域:行坐标Row、列坐标Col、数值Value
2.每个结点通过两个指针域,把同行、同列串起来;
- 行指针(或称为向右指针)Right
- 列指针(或称为向下指针)Down
1>用一个标识域Tag来区分头结点和非0元素结点;
2>头结点的标志值为“Head”,矩阵非0元素结点的标志值为“Term”