目录
第二章 线性表
1.定义:
线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为
L=(a1,a2,.....an)
a1是表头元素,an是表尾元素。除a1外都有前驱,除an外都有后继
线性表是一种逻辑结构,表示元素之间一对一的相邻关系。
线性表包括顺序表和链表,顺序表里面元素的地址是连续的。链表里面节点的地址不一定连续的,是通过指针连起来的。顺序表和链表是指存储结构。
2.线性表的基本操作
InitList(&L):初始化线性表
Length(L):求表长
LocateElem(L,e):按值查找操作
GetElem(L,i):按位查找操作
ListInsert(&L,i,e):插入操作
ListDelete(&L,i,&e):删除操作
PrintList(L):输出操作
Empty(L): 判空操作
DestroyList(&L):销毁操作
注意:&表示c语言中的引用调用,在c语言中用指针也可达到同样效果。
请参考
c语言 引用调用_C中按值调用和按引用调用之间的区别_culing2941的博客-CSDN博客
3.顺序表
线性表的顺序存储又称顺序表,逻辑地址上相邻的地址元素,物理地址也相邻。
特点:表中元素逻辑顺序与物理顺序相同,用数组实现的,一维数组可以是静态的,也可以是动态的。
静态分配
#define MaxSize 50//定义线性表最大长度
typedef struct{
ElemType data[MaxSize];//顺序表元素
int length;//顺序表当前长度
}SqList//顺序表类型定义
动态分配
#define InitSize 100//初始化表长度
typedef struct{
ElemType *data;//指示动态分配数组的指针
int MaxSize,length;//数组最大容量和当前个数
}SeqList//类型定义
动态分配初始化语句:
c: L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);
c++: L.data=new ElemType[InitSize];
注:动态分配不是链式存储,他是顺序存储结构,物理结构并没有发生变化,依然是随机存取方式,只是分配的空间大小可以在运行时动态决定。
注:线性表从1开始,而数组从0开始。
优点:随机访问特性,查找O(1)时间,存储密度高;逻辑上相邻的元素,物理上也相邻;
缺点:插入删除需移动大量元素。
顺序表的基本操作
插入操作:
最好情况:表尾插入,时间复杂度o(1)
最坏情况:表头插入,时间复杂度o(n)
平均情况:o(n/2)=o(n)
删除操作:
最好情况:表尾删除,时间复杂度o(1)
最坏情况:表头删除,时间复杂度o(n)
平均情况:o(n/2)=o(n)
查找操作:
int LocateElem(SqList L,ElemType e){
int i;
for(i=0;i<L.length;i++{
if(L.data[i]==e)
return i+i;//返回其位序
return 0;//查找失败
}
最好情况:表头查找,时间复杂度o(1)
最坏情况:表尾查找或查找失败,时间复杂度o(n)
平均情况:o(n/2)=o(n)
4.链表
与顺序存储相比,允许存储空间不连续,插入删除时不需要移动大量的元素,只需修改指针即可,但查找某个元素,只能从头遍历整个链表。且不能随机存取。
单链表:
定义:
单链表分为带头结点和不带头结点两种,不管有没有头结点,头指针都指向链表的第一个节点(头结点是第一个结点)。
头结点:数值域可不设任何信息,头结点的指针域指向链表的第一个元素。
带头节点的好处有:
(1)链表第一位置节点上的操作和其它位置上的操作一致
(2)无论链表是否为空,头指针都指向头结点(非空),空表和非空表处理一样
单链表基本操作
1.头插法建立新链表
读入数据顺序与生成链表元素顺序是相反的。每个节点插入时间为o(1),设单链表长为n,则总时间复杂度为o(n)
如果没有设立头结点,则需要在每次插入新节点后,将他的地址赋值给头指针L。
2.尾插法
LinkList List_TailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));
LNode *s,*r=L;//r为尾指针
scanf("%d",&x);//输入结点值
while(x!=9999){//9999表示结束
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;//r指向新的表尾指针
scanf("%d",&x);
}
r->next=NULL;//尾指针指置空
return L;
}
时间复杂度与头插法相同
3.按序号查找结点值
时间复杂度o(n)
4.按值查找表结点
时间复杂度o(n)
5.插入结点操作
本算法主要时间开销在查找i-1元素上,时间复杂度为o(n),若结点已给定,则在其后插入结点时间复杂度为o(1)。
6.删除结点操作
时间复杂度为o(n)
7.求表长操作
双链表
由于单链表插入删除都得从头遍历,比较麻烦,时间复杂度高。所以我们引入了双链表来解决
主要就是结点中有两个指针,分别指向前驱和后继
typedef struct DNode{
ElemType data;//数据域
struct DNode *prior,*next;//前驱后继指针
}DNode, *DLinkList;
由于引入了双指针,所有我们的插入删除时间复杂度变为了o(1)
1.插入
2.删除
循环链表
1.循环单链表
引用这种后,我们对表头表尾操作的时间复杂度都变为了o(1)
2.循环双链表
静态链表
借助数组来描述线性表的链式存储结构,结点也有数据域和指针域。
左边为静态链表示例,右图为静态链表对应的单链表
#define MaxSize 50//定义静态链表的最大长度
typedef struct{
ElemType data;//存储数据类型
int next;//下一个元素的数组下标
}SLinkList[MaxSize];