目录
线性表基本概念
一、线性表的定义
线性表是零个或多个有限的有顺序的类型相同的数据元素的集合。
假定线性表的数据对象集合为{a1,a2,……,an},每个元素的类型均为DataType。其中,除了第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每个元素有且只有一个直接后继元素。数据元素之间的关系是一对一关系。
二、线性表的顺序存储结构
1、定义
线性表的顺序存储结构指的是用一段地址连续的存储单元依次存储线性表的数据元素。
2、顺序存储结构代码
#define MAXSIZE 100 //线性表的最大长度
#define LISTINCREMENT 10 //线性表存储空间的分配增量
typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为int
typedef struct{
ElemType data[MAXSIZE]; //数组存储数据元素
int length; //顺序表当前长度
int listsize; //当前分配的存储容量
}Sqlist;
数据长度与线性表长度区别:数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。线性表长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。
地址计算方法
C语言中数组的第一个下标是0,即线性表的第i个元素存储在数组下标为i-1的位置
3、线性表顺序结构的插入与删除
a、获得元素
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status; //Status是函数的类型,值是函数结果状态码
Status GetElem(Sqlist L, int i, ElemType &e){
if(L.length==0 || i<1 || i > L.length) return ERROR;
e = L.data[i-1];
return OK;
}
b、插入
Status ListInsert(Sqlist &L, int i, ElemType e){
int k;
if(L.length==MAXSIZE){
L.length += LISTINCREMENT; //若线性表长度等于数组长度,动态增加数组容量
}
if(i<1 || i>L.length - 1){//i不在范围内
return ERROR;
}
if(i<=L.length){
for(k=L.length-1;k>=i-1;k--){
L.data[k+1]=L.data[k]; //将插入位置后的数据元素全部向后移动一位
}
}
L.data[i-1]=e; // 插入新元素
L.length++;
return OK;
}
c、删除
Status ListDelete(Sqlist &L, int i, ElemType &e){
int k;
if(L.length==0) return ERROR; //线性表为空
if(i<1 || i>L.length) return ERROR;
e = L.data[i-1];
if(i<L.length){ //如果删除的不是最后一位
for(k=1;k<L.length;k++)
L.data[k-1] = L.data[k];
}
L.length--;
return OK;
}
插入和删除的时间复杂度:
最好的情况是插入到最后一个位置或者删除最后一个元素,无需移动元素,此时时间复杂度为O[1];
最坏的情况是元素插入到第一个元素或者删除第一个元素,那么所有的元素都要向前或向后移动,此时时间复杂度为O[n]
平均移动次数和最中间的元素的移动次数相同,为(n-1)/2
4、线性表顺序结构的优缺点
优点 | 缺点 |
|
|
三、线性表链式存储结构
1、定义
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。者意味着这些元素可以存在内存未被占用的任意位置。
在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针后链。这两部分信息组成的数据元素的存储映像,称为结点。n个结点链结成一个链表,即为线性表的链式存储结构。链表中每个结点只包含一个指针域的称为单链表。
链表中第一个结点的存储位置叫做头指针,最后一个结点的指针为空
为了方标对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点(头结点的数据域可以不存储任何信息)
头指针与头结点的异同
头指针 头结点
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
- 头结点是为了操作的统一和方标而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)
- 有了头结点,对在第一元素结点前插入结点和山粗第一结点,其操作与其它结点就统一了
- 头结点不是链表的必须要素
2、线性表链式存储结构代码
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
3、单链表的读取
/*用e返回L中第i个元素的值*/
Status GetElem(LinkList L, int i, ElemType &e){
int j;
LinkList p;
p = L->next;
j = 1;
while(p && j < i){
p = p->next;
++j;
}
if(!p || j>i) return ERROR; //第i个元素不存在
e = p->data;
return OK;
}
4、单链表的插入
/*在L第i个元素之前插入新的数据元素e*/
Status ListInsert(LinkList &L,int i,ElemType e){
int j;
LinkList p,s;
p = L;
j = 1;
while(p && j<i){
p = p->next;
++j;
}
if(!p || j>i) return ERROR;
s = new Node;//为s结点申请空间
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
5、单链表的删除
/*删除L的第i个元素*/
Status ListDelete(LinkList &L, int i, ElemType &e){
int j;
LinkList p,q;
p = L;
j = 1;
while(p->next && j<i){
p = p->next;
++j;
}
if(!(p->next) || j>i) return ERROR;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return OK;
}
6、单链表的整表创建
a、头插法:插入点始终在表头位置,被插元素总是新的表头(逆序创建)
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
int CreateListHead(LinkList &L, int n){
LinkList p;
int i;
srand(time(0));//初始化随机数种子
L = new Node;
if(!L) return ERROR;
L->next = NULL;
for(i=0; i<n; i++){
p = new Node;
p->data = rand() % 100 + 1;
p->next = L->next;
L->next = p;
}
return OK;
}
b、尾插法 :插入点始终在表尾位置,被插元素总是新的表尾(正序创建)
/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
Status CreateListTail(LinkList &L, int n){
LinkList p,r;
int i;
srand(time(0));//初始化随机数种子
L = new Node;
if(!L) return ERROR;
r = L; //r为指向尾部的结点
for(i=0; i<n; i++){
p = new Node;
p->data = rand() % 100 + 1;
r->next = p;
r = p;
}
r->next = NULL;
return OK;
}
7、单链表整表删除
/*将单链表重置为空表*/
Status ClearList(LinkList &L){
LinkList p,q;
p = L->next;
while(p){
q = p->next;
free(p);
p = q;
}
L->next=NULL;
return OK;
}
8、逆置单链表
//法1;
//头插法逆置单链表
void ReverseLinkList1(LinkList &L) {
if (!L->next) return;
LinkList p, q;
p = L->next;
L->next = NULL;
while (p) {
q = p->next;
p->next = L->next;
L->next = p;
}
}
//法2:
//将所有结点的next指针逆转
void ReverseLinkList2(LinkList &L) {
if (!L->next) return;
LinkList p, q, r;
p = L->next;
q = p->next;
p->next = NULL;
while (q) {
r = q->next;
q->next = p;
p = q;
q = r;
}
L->next = p;
}
9、单链表结构与顺序存储结构优缺点
存储分配方式 | 时间性能 | 空间性能 |
|
|
|
-
通过上表的对比,我们无法说那个好或那个不好,需要根据实际情况来选择。若线性表需要频繁查找,很少进行插入和删除操作,宜采用顺序存储结构。若需要频繁插入和删除,宜采用单链表结构。
-
当线性表中的元素个数变化较大或者不知道有多大时,最好采用单链表结构,这样可以不需要考虑空间的大小问题。而如果事先知道线性表的大致长度,使用顺序存储结构效率更高。
四、静态链表
1、定义
使用数组代替指针来描述单链表。首先让数组的元素都是由两个数据域组成,data和cur。也就是说,数组的下标都对应一个data和一个cur。也就是说,数组的每一个下标都对应一个data和一个cur。数据域data用来存放数据元素,而游标cur相当于单链表中的next指针,存放该元素的后继在数组总的下标。我们将这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。
2、线性链表的静态链表存储结构
/*线性表的静态链表存储结构*/
typedef struct{
ElemType data;
int cur;//游标(Cursor),为0时表示无指向
}Component,StaticLinkList[MAXSIZE];
将数组第一个和最后一个元素作为特殊元素,不存数据。我们通常把未被使用的数组称为备用链表。数组第一个元素(下标为0的元素)的cur存放备用链表的第一个结点的下标;数组的最后一个元素的cur存放第一个有数值的元素的下标,相当于单链表中的头结点作用。
3、初始化静态链表
/*将一维数组space中各分量链成一备用链表*/
Status InitList(StaticLinkList space){
int i;
for(i=0; i<MAXSIZE-1; i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0;
return OK;
}
4、静态链表的插入
静态链表需要解决的是:如何用静态模拟动态链表的结构的存储空间的分配,需要时申请,无用时释放
int ListLength(StaticLinkList L){
int j=0;
int i = L[MAXSIZE-1].cur;
while(i){
i=L[i].cur;
j++;
}
return j;
}
/*若备用空间链表非空,返回分配的结点下标,否则返回0*/
int Malloc_SLL(StaticLinkList space){
int i = space[0].cur;//当前数组第一个元素的cur的值就是要返回的第一个备用空间的下标
if(space[0].cur)
space[0].cur = space[i].cur;//由于要拿出一个分量来使用,需要把它的下一个分量用来做备用
return i;
}
/*在L中第i个元素之前插入新的数据元素*/
Status ListInsert(StaticLinkList L, int i, ElemType e){
int j,k,n;
k = MAXSIZE -1;//k为最后一个元素的下标
if(i<1 || i>ListLength(L) + 1) return ERROR;
j = Malloc_SLL(L);//获得空间分量的下标
if(j){
L[j].data = e;
for(n = 1; n <= i-1; n++){
k = L[k].cur;
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
}
return ERROR;
}
5、静态链表的删除
/*将下标为k的空闲结点回收到备用链表*/
void Free_SSL(StaticLinkList space, int k){
space[k].cur = space[0].cur;//把第一个元素cur赋值给要删除的分量cur
space[0].cur = k;//把要删除的分量下标赋值给第一个元素的cur
}
/*删除L中第i个数据元素*/
Status ListDelete(StaticLinkList &L, int i){
int j,k;
if(i<1 || i>ListLength(L)) return ERROR;
k = MAXSIZE - 1;
for(j=1; j<= i-1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L,j);
return OK;
}
6、静态链表优缺点
优点 | 缺点 |
|
|
五、循环链表
1、定义
将单链表中终端结点的指针端由空指针改为指向头节点,使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
六、双向链表
1、定义
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针。
2、线性表的双向链表存储结构
typedef struct DulNode{
ElemType data;
struct DulNode *prior;//直接前驱指针
struct DulNode *next;//直接后继指针
}DulNode, *DuLinkList;
双向链表是单链表的扩展,操作与单链表类似,需要注意的是在插入和删除时要改变两个指针变量。