大话数据结构 第三章 线性表

定义

线性表:零个或多个数据元素的有限序列
元素之间是有序的,线性表是有限的,线性表元素个数为线性表长度,长度为0时称为空表。

在这里插入图片描述

线性表的抽象数据类型

ADT 线性表(List)
Data
  线性表的数据对象集合为{a1,a2,…,an},每个元素的类型均为DataType。其中,除第一个元素a1外,每个元素有且只有一个直接前驱元素,除了最后一个元素an外,每个元素都有且只有一个直接后驱元素,数据元素之间的关系是一对一的关系。
Operation
  InitList(*L);  初始化操作,建立一个空的线性表L。
  ListEmpty(L);  若线性表为空,返回true,否则返回false。
  ClearList(*L);  将线性表清空
  GetElem(L,i,*e);  将线性表L中的第i个位置元素返回给e
  LocateElem(L,e);  在线性表L中查找与给定值e相等的元素,如果成功,返回该元素在表中序号表示成功,否则,返回0表示失败。
  ListInsert(*L,i,e);  在线性表L中的第i个位置插入新元素e
  ListDelete(*L,i,*e);  删除线性表L中第i个位置元素,并用e返回其值
  ListLength(L);  返回线性表L的元素个数

线性表的顺序存储结构

线性表的顺序存储结构,指的是用一段地址连续的存储单元一次存储线性表的数据元素

在这里插入图片描述

顺序存储方式

可以使用数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表向量的圆存储在数组中相邻的位置。

线性表的顺序存储的结果代码

typedef int ElemType;	//ElemType类型根据实际情况而定,假设为int
typedef struct{
	ElemType data[MAXSIZE];	//数组存储数据元素,最大值为MAXSIZE
	int length;	//线性表当前长度
}SqList;

顺序存储结构需要的三个属性:

  • 存储空间的其实位置:数组data,它的存储位置就是存储空间的存储位置
  • 线性表的最大存储容量:数组长度MaxSize
  • 线性表的当前长度:length
数组长度与线性表长度区别

数组长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。
线性表的长度是线性表中数据元素个数

地址计算方法

在这里插入图片描述

存储器中每个存储单元都有自己的编号,该编号称为地址。
LOC(ai)=LOC(a1)+(i-1)*c

在这里插入图片描述

通过这个公式,可以随时算出线性表中任意位置的地址,因此对每个线性表位置的存入或者去除数据,对于计算机来说都是相等时间,也即是时间复杂度为O(1)

顺序存储结构的插入与删除
获得元素操作
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
//Status是函数的类型,其值是函数结构状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:用e返回L中第i个元数的值
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;
}
插入操作

思路:

  • 如果插入位置不合理,抛出异常
  • 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
  • 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
  • 将要插入元素填入位置i处
  • 表长加1
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
Status ListInsert(SqList *L,int i,ElemType e)
{
	int k;
	if(L->length==MAXSIZE)
		return ERROR;
	if(i<1 || i>L->length+1)
		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;
}
	
删除操作

思路:

  • 如果删除位置不合理,抛出异常
  • 去除删除元素
  • 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
  • 表长减1
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L的第i个元素,并用e返回其值,L的长度减1
{
	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=i;k<L->length;k++)
			L->data[k-1]=L->data[k];
	}
	L->length--;
	return OK;
}
线性表顺序存储结构优缺点

优点:

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
  • 可以快速的存取表中任何位置的元素

缺点:

  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大是,难以确定存储空间的容量
  • 造成存储空间的“碎片”
线性表的链式存储结构
定义

  为了表示每个数据元素ai与其直接后继元素ai+1直接的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个知识其直接后继的信息,存储数据元素信息的域称为数据域
,存储直接后继位置的域称为指针域。这两部分信息组成数据元素ai的存储映像,称为结点

在这里插入图片描述

  链表中第一个结点的存储位置称为头指针,有时为了方便对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点

在这里插入图片描述

头指针和头结点

头指针:

  • 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
  • 头指针具有标识作用,所有常用头指针冠以链表的名字
  • 无论链表是否为空,头指针均不为空,头指针是链表的必要元素

头结点:

  • 头结点是为了操作的统一和方便设立的,放在第一元素的结点之前,其数据域一般无意义
  • 有了头结点,对在第一源结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了
  • 头结点不是链表的必须要素
线性表链式存储结构代码描述

常用以下示意图来表示链表:

  • 带有头结点的单链表
    在这里插入图片描述
  • 空链表
    在这里插入图片描述
//线性表的单链表存储结构
typedef struct Node
{
	ElemType data;
	struct Node *next;
} Node;
typedef struct Node *LinkList;//定义LinkList
单链表的读取

获得链表第i个元素的思路:

  • 声明一个结点p指向链表第一个结点,初始化j从1开始
  • 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累计加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 否则查找成功,返回结点p的数据
//初始条件:链式线性表L已存在
//操作结果:用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;
	*e=p->data;
	return OK;
}
单链表的插入和删除
单链表的插入

单链表第i个元素插入结点的思路:

  • 声明一个结点p执行链表第一个结点,初始化j从1开始
  • 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累计加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 否则查找成功,在系统中生成一个空结点
  • 将数据元素e赋给s->data
  • 单链表的插入标准语句s->next=p->next; p->next=s;
  • 返回成功
//初始条件:链式线性表L已存在
//操作结果:在L中第i个位置之前插入新的元素e,L的长度加1
Status ListInsert(LinkList *L,int i,ElemType e)
{
	int j;
	LinkList p;
	LinkList s;
	p=*L;
	j=1;
	while(p && j<i)
	{
		p=p->next;
		++j;
	}
	if(!p || j>i)
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));//生成新结点
	s->data=e;
	s->next=p->next;
	p->next=s;
	return OK;
}
单链表的删除操作

单链表第i个元素删除结点的思路:

  • 声明一个结点p执行链表的第一个结点,初始化j从1开始
  • 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累计加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 否则查找成功,将欲删除的结点p->next赋值给q
  • 单链表的删除标准语句p-next=q->next
  • 将q结点中的数据赋给e,作为返回
  • 释放q结点
  • 返回成功
//初始条件:链式线性表L已存在
//操作结果:删除L的第i个元素,并用e返回其值,L的长度减1
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;
}
单链表的整表创建

单链表整表创建的思路:

  • 声明一结点p和计数器变量i
  • 初始化一空链表L
  • 让L的头结点指针指向NULL,即建立一个带头结点的单链表
  • 循环
    • 生成一新结点赋给p
    • 随机生成一数字赋给p的数据域p->data
    • 将p插入到头结点和新结点直接
//随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
void CreateListHead(LinkList * L,int n)
{
	LinkList p;
	int i;
	srand(time(0));	//初始化随机数种子
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = NULL;
	for(i=0;i<n;i++)
	{
		p = (LinkList)malloc(sizeof(Node));
		p->data = rand()%100+1;
		p->next = (*L)->next;
		(*L)->next = p;
	}
}
//随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
void CreateListTail(LinkList *L,int n)
{
	LinkList p,r;
	int i;
	srand(time(0));
	*L = (LinkList)malloc(sizeof(Node));
	r = *L;
	for(i=0;i<n;i++)
	{
		p = (Node*)malloc(sizeof(Node));
		p-data = rand()%100+1;
		r->next = p;
		r = p;
	}
	r-next = NULL;
}
单链表的整表删除

单链表整表删除的思路:

  • 声明一结点p和q
  • 将第一个结点赋值给p
  • 循环
    • 将下一个结点 赋值给q
    • 释放p
    • 将q赋值给p
//初始条件:顺序线性表L已存在,操作结果:将L重置为空表
Status ClearList(LinkList *L)
{
	LinkList p,q;
	p = (*L)->next;
	while(p)
	{	
		q = p->next;
		free(p);
		p = q;
	}
	(*L)->next = NULL;
	return OK;
}
单链表结构与顺序存储结构优缺点
  1. 存储分配方式
  • 顺序存储结构用一段连续的存储单位依次存储线性表的数据元素
  • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
  1. 时间性能
  • 查找
    • 顺序存储结构O(1)
    • 单链表 O(n)
  • 插入和删除
    • 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
    • 单链表在释放某位置的指针后,插入和删除时间仅为O(1)
  1. 空间性能
  • 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
  • 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制

由此可得经验性结论:

若线性表需要频繁查找,很少进行插入和删除操作,采用顺序存储结构;
若需要频繁插入删除操作,采用单链表结构
若线性表中的元素个数变化较大或者根本不知道多大时,最好采用单链表结构

静态链表
定义

  Basic、Fortran等早期的编程高级语言,没有指针,因此无法使用链表结构,因此使用数组来代替指针,用数组描述的链表叫做静态链表,也称为游标实现法。
线性表的静态链表存储结构

#define MAXSIZE 1000
typedef struct
{
	ElemType data;
	int cur;
} Component,StaticLinkList[MAXSIZE];

  数组元素由两个数据域组成,data和cur,data用来存放数据元素,cur相当于单链表中的next指针,存放该元素后继在数组中的下标。
  其中,将数组第一个和最后一个做特殊元素处理,不存数据,如图所示在这里插入图片描述
上图的初始化数组状态如下所示

Status InitList(StaticLinkList spacr)
{
	int i;
	for(i=0;i<MAXSIZE-1;i++)
		space[i].cur = i+1;
	space[MAXSIZE-1].cur = 0;
	return OK;
}

假设已经将数据存入静态链表,如“甲”,“乙”,“丙”,“丁”,“戊”,“己”,“庚”等数据,这静态链表如下所示
在这里插入图片描述

静态链表的插入操作

获取备用空间下标

//若备用空间链表非空,则返回分配的结点下标,否则返回0
int Malloc_SLL(StaticLinkList space)
{
	//当前数组第一个元素cur存的值,即第一个备用空间的下标
	int i = space[0].cur;
	if(space[0].cur)
		//下一个备用空间的下标
		space[0].cur = space[i].cur;
	return i;
}

插入操作

Status ListInsert(StaticLinkList L,int i,ElemType e)
{
	int j,k,l;
	k = MAXSIZE - 1;//最后一个元素的下标
	if(i<1 || i>ListLength(L)+1)
		return ERROR;
	j = Malloc_SLL(L);//空闲下标
	if(j)
	{	
		L[j].data = e;
		//找到第i个元素之前的位置
		for(l=0;l<=i-1;l++)
			k = space[k].cur;
		//把第i-1个元素的cur值赋给新元素
		L[j].cur = L[k].cur;
		//把新元素的下标赋给第i-1个元素
		L[k].cur = j;
		return OK;
	}
	return ERROR;
}

//初始条件:静态链表L已存在。操作结果:返回L中的数据个数
int ListLength(StaticLinkList L)
{
	int j=0;
	int i=L[MAXSIZE-1].cur;
	while(i)
	{
		i = L[i].cur;
		j++;
	}
	return j;
}

  当要在“乙”,“丁”之间插入“丙”时,输入i的值为3.

静态链表的删除操作
//删除在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;
}
//将下标为k的空闲结点回收到备用链表
void Free_SSL(StaticLinkList space,int k)
{
	space[k].cur = space[0].cur;
	space[0].cur = k;
}
静态链表优缺点

优点:

  • 在插入和删除操作时,**只需要修改游标,不需要移动元素,**改进了顺序存储结构中插入和删除时需要大量移动的缺点
    缺点:
  • 没有解决连续存储分配代理的表长难以确定的问题
  • 失去了顺序存储结构随机存取的特性
循环链表
定义

将单链表中终端节点的指针端由空指针改为指向头结点,就使得整个单链表形成一个环,这种头尾相连的单链表称为单循环链表,简称循环链表

空链表
在这里插入图片描述
非空循环链表
在这里插入图片描述

使用尾指针

在这里插入图片描述
将两个循环链表合并为一个链表
在这里插入图片描述
合并图解
在这里插入图片描述
实现代码

p = rearA->next;
rearA->next = rearB->next->next;
rearB->next = p;
free(p);
双向链表

在单链表的每个结点中,再设置一个指向其前驱结点的指针域。

typedef struct DulNode
{
	ElemType data;
	struct DulNode *prior;
	struct DulNode *next;
} DulNode,*DulinkList;

空链表
在这里插入图片描述
非空双向链表
在这里插入图片描述

插入操作

如图所示,实现将结点s插入到结点p和p->next之间
在这里插入图片描述
代码实现

s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
删除操作

如图所示,删除结点p
在这里插入图片描述
代码实现

p->prior->next = p->next;
p->next-prior = p->prior;
free(p);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值