第二章:线性表

2.1线性表的定义和基本操作

2.1.1线性表的定义

线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。
位序: 从1开始,表示线性表中的第几个元素
下标: 从0开始
线性表中存在唯一的“第一个”数据元素,又称表头元素;唯一的“最后一个”数据元素,又称表尾元素。除第一个元素外,每个元素有且仅有一个直接前驱,除最后一个元素外,每个元素有且仅有一个直接后继

2.1.2线性表的特点

  • 表中元素的个数有限
  • 表中元素具有逻辑上的顺序性,表中元素有其先后顺序
  • 表中元素都是数据元素,每个元素都是单个元素
  • 表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间
  • 表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容

注: 线性表是一种逻辑结构,即线性结构,表示元素之间一对一的相邻关系;顺序表和链表是指存储结构,即顺序存储和链式存储。

2.1.3线性表基本操作

  • InitList(&L):初始化表,构造一个空表
  • DestroyList(&L):销毁表,并释放线性表L所占用的内存空间
  • Length(L):求表长,即L中数据元素的个数
  • LocateElem(L,e):按值查找,在表L中查找具有给定关键字值的元素
  • GetElem(L,i):按位查找,获取表L中第i个位置的元素的值
  • ListInsert(&L,i,e):插入操作,在表L中的第i个位置插入指定元素e
  • ListDelete(&L,i,&e):删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值
  • PrintList(L):输出操作,按前后顺序输出线性表L中所有元素值
  • Empty(L):判空操作,若L为空表,则返回true,否则返回false

注: ”&“表示C++语言中的引用调用,将操作结果“带回来

void test(int x){
	x=1024;
	printf("test函数内部 x=%d\n",x);
}
int main(){
	int x=1;
	printf("调用test函数前 x=%d\n",x);
	test(x);
	printf("调用test函数后 x=%d\n",x);
}
/*
以上代码输出结果为:
调用test函数前 x=1
test函数内部 x=1024
调用test函数后 x=1
*/

//将test函数改为
void test(int &x){
	x=1024;
	printf("test函数内部 x=%d\n",x);
}
/*
以上代码输出结果为:
调用test函数前 x=1
test函数内部 x=1024
调用test函数后 x=1024
*/

//这就是&带回来效果

2.2线性表的顺序表示

2.2.1顺序表定义

线性表的顺序存储称为顺序表,它是一组地址连续 的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。

2.2.2顺序表特点

  • 逻辑顺序与物理顺序相同
  • 随机访问,即通过首地址和元素序号可在时间O(1)内找到指定的元素
  • 存储密度高,每个节点只存储数据元素
  • 拓展容量不方便
  • 插入删除操作不方便

2.2.3静态分配与动态分配

//静态分配
#define MaxSize 50     //定义线性表最大长度
typedef struct{
	ElemType data[MaxSize];//顺序表元素
	int length;//顺序表当前长度
}SqList;//顺序表类型定义
//静态分配示例:
#define MaxSize 10     //定义最大长度
typedef struct{
	int data[MaxSize];//用静态的“数组”存放数据元素
	int length;//顺序表当前长度
}SqList;//顺序表类型定义
void InitList(SqList &L){//初始化一个顺序表
	for(int i=0;i<MaxSize;i++){
		L.data[i]=0;//将所有元素设置默认初始值,消除脏数据,可省略,因为相关操作都与length有关
		L.length=0;//顺序表初始长度为0
	}
}
int main(){
	SqList L;//声明一个顺序表
	InitList(L);//初始化顺序表
	//...相关操作
	return 0;
}



//动态分配
#define InitSize 100    //表长度的初始定义
typedef struct{
	ElemType *data;//指示动态分配数组的指针
	int MaxSize,length;//数组最大容量和当前个数
}SeqList;//动态分配数组顺序表的类型定义
//动态分配示例:
#define InitSize 10    //默认最大长度
typedef struct{
	int *data;//指示动态分配数组的指针
	int MaxSize,length;//数组最大容量和当前个数
}SeqList;//动态分配数组顺序表的类型定义
void InitList(SeqList &L){//初始化
	//用malloc函数申请一片连续存储空间
	L.data=(int *)malloc(InitSize*sizeof(int));//申请空间类型强制转换为与顺序表数组类型一致
	L.length=0;
	L.MaxSize=InitSize;
}
void IncreaseSize(SeqList &L,int len){//动态增加数组长度
	int *q=L.data;
	L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
	for(int i=0;i<L.length;i++){
		L.data[i]=p[i];//将数据复制到新区域
	}
	L.MaxSize=L.MaxSize+len;//顺序表最大长度增加len
	free(p);//释放原来空间
}
int main(){
	SeqList L;//声明一个顺序表
	InitList(L);//初始化顺序表
	//...增加元素
	IncreaseSize(L,5);//扩展数组长度
	return 0;
}

2.3顺序表基本操作的实现

2.3.1插入

在顺序表L的第i(1<=i<=L.length+1)个位置插入新元素e。若i输入不合法,则返回false,表示插入失败;否则,将第i个元素及其后的所有元素依次往后移一个位置,腾出一个空位置插入新元素e,顺序表长度增加1,插入成功,返回true。

bool ListInsert(SqList &L,int i,ElemType e){
	if(i<1||i>L.length+1)//判断i范围是否有效
		return false;
	if(L.length>=MaxSize)//当存储空间已满,不能插入
		return false;
	for(int j=L.length;j>=i;j--)//将第i个及之后的元素后移
		L.data[j]=L.data[j-1];
	L.data[i-1]=e;//在位置i放入e
	L.length++;//线性表长度加1
	return true;
}

注: 区别位序和数组下标,时间复杂度:

  • 最好情况:在表尾插入(即i=n+1),元素后移语句不再执行,时间复杂度为O(1);
  • 最坏情况:在表头插入(即i=1),元素后移语句将执行n次,时间复杂度为O(n);
  • 平均情况:O(n)。

2.3.2删除

删除顺序表L中第i(1<=i<=L.length)个位置的元素,用引用变量e返回。若i不合法,则返回false;否则将被删元素赋给引用变量e,并将第i+1个元素及其后的所有元素往前移动一个位置,返回true。

bool ListDelete(SqList &L,int i,ElemType &e){
	if(i<1||i>L.length)//判断i的范围是否有效
		return false;
	e=L.data[i-1];//将被删除的值赋给e
	for(int j=1;j<L.length;j++)//将第i个位置后的元素前移
		L.data[j-1]=L.data[j];
	L.length--;//线性表长度减1
	return true;
}

注: 区别位序和数组下标,时间复杂度:

  • 最好情况:删除表尾元素(即i=n),无需移动元素,时间复杂度为O(1);
  • 最坏情况:删除表头元素(即i=1),需移动除表头元素外的所有元素,时间复杂度为O(n);
  • 平均情况:O(n)。

2.3.3查找

//按位查找:
ElemType GetElem(SqList L,int i){//i表示位序
	return L.data[i-1];//返回数值,静态动态存储都一样
}
//时间复杂度O(1)


//按值查找:
int LocateElem(SqList L,ElemType e){//在顺序表中查找第一个元素值等于e的元素,并返回其位序
	for(int i=0;i<L.length;i++)
		if(L.data[i]==e)
			return i+1;//下标为i的元素值等于e,返回其位序i+1
	return 0;//退出循环,查找失败
}
/*
时间复杂度:
最好情况:查找元素在表头,仅比较一次,时间复杂度为O(1)
最坏情况:查找元素在表尾,需比较n次,时间复杂度为O(n)
平均情况:O(n)
*/

2.4线性表的链式表示

2.4.1单链表的定义

线性表的链式存储又称单链表 ,它是指通过一组任意的存储单元来存储线性表中的数据元素

单链表特点:

  • 不需要大块存储单元
  • 附加指针,存在浪费存储空间的缺点
  • 非随机存取,查找特定结点需从头开始遍历
typedef struct LNode{//定义单链表结点类型
	ElemType data;//数据域
	struct LNode *next;//指针域
}LNode, *LinkList;//别名
/*
LNode * L:强调这是一个结点
LinkList L:强调这是一个单链表
*/


//不带头结点的单链表初始化:
bool InitList(LinkList &L){
	L=NULL;//置空,防止存在脏数据
	return true;
}
//判断单链表是否为空:
bool Empty(LinkList L){
	return (L==NULL);
}

//带头结点的单链表初始化:
bool InitList(LinkList &L){
	L=(LNode *)malloc(sizeof(LNode));//分配一个头结点
	if(L==NULL)//内存不足,分配失败
		return false;
	L->next=NULL;//头结点之后置空
	return true;
}
//判断单链表是否为空:
bool Empty(LinkList L){
	return (L->next==NULL);
}
/*
带头结点,写代码更方便;
不带头结点,写代码更麻烦:
对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑
对空表和非空表的处理需要用不同的代码逻辑
*/

2.4.2单链表插入删除操作

//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p,ElemType e){
	if(p==NULL)//无效结点
		return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));//申请一个结点
	if(s==NULL)//因为内部原因,申请失败,发生可能极小
		return false;
	s->data=e;//元素e保存至s结点中
	s->next=p->next;//s的指针域指向p的指针域,即将p之后结点连接到s后
	p->next=s;//将结点s连接到p之后
	return true;
}
//前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode *p,ElemType e){
	if(p==NULL)//无效结点
		return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));//申请一个结点
	if(s==NULL)//因为内部原因,申请失败,发生可能极小
		return false;
	//偷天换日:
	s->next=p->next;//将p之后结点连接在s上
	p->next=s;//将s连接在p上
	s->data=p->data;//将p中元素复制到s中
	p->data=e;//e覆盖p中元素
}
/*
时间复杂度O(1)
*/

//按位序插入:在带头结点的单链表L中的第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)//非法位序
		return false;
	LNode *p;//指针p当前扫描到的结点
	int j=0;//当前p指向的是第几个结点
	p=L;//扫描开始位置,L指向头结点,头结点是第0个节点,不存数据
	while(p!=NULL && j<i-1){//循环找到第i-1个结点
		p=p->next;
		j++;
	}
	return InsertNextNode(p,e);//当前p扫描到第i-1个位置,执行后插法
}
/*
最好时间复杂度:O(1),插在表头,即i=1
最坏时间复杂度:O(n),插在表尾,i=n
平均时间复杂度:O(n)
*/
//按位序插入:在不带头结点的单链表L中的第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)//非法位序
		return false;
	if(i==1){//插入第一个结点的操作与其他结点不同
		LNode *s=(LNode *)malloc(sizeof(LNode))
		s->data=e;
		s->next=L;
		L=s;//头指针指向新的结点
		return true;
	}
	LNode *p;//指针p当前扫描到的结点
	int j=1;//当前p指向的是第几个结点
	p=L;//扫描开始位置,L指向头结点,头结点是第0个节点,不存数据
	while(p!=NULL && j<i-1){//循环找到第i-1个结点
		p=p->next;
		j++;
	}
	return InsertNextNode(p,e);//当前p扫描到第i-1个位置,执行后插法
}


//按位序删除:删除带头结点的单链表L的第i个元素并返回删除元素e
bool ListDelete(LinkList &L,int i,ElemType e){
	if(i<1)//非法位序
		return false;
	LNode *p;//指针p当前扫描到的结点
	int j=0;//当前p指向的是第几个结点
	p=L;//扫描开始位置,L指向头结点,头结点是第0个节点,不存数据
	while(p!=NULL && j<i-1){//循环找到第i-1个结点
		p=p->next;
		j++;
	}
	if(p==NULL)//无效结点,i值不合法
		return false;
	if(p->next==NULL)//第i-1结点后以无其他结点
		return false;
	LNode *q=p->next;//令q指向被删结点
	e=q->data;//用e返回元素的值
	p->next=q->next;//将*q结点从链中断开
	free(q);//释放结点的存储空间
	return true;//删除成功
}

//删除指定结点
bool DeleteNode(LNode *p){
	if(p==NULL)
		return false;
	if(p->next==NULL)
		return false;//最后一个元素无法删除
	LNode *q=p->next;//令q指向*p的后继结点
	p->data=q->data;//和后继节点交换数据域
	p->next=q->next;//将*q结点从链中断开
	free(q);//释放后继节点存储空间
	return true;
}

2.4.3单链表查找与求表长操作

//查找带头结点的单链表L的第i个元素(按位查找)
LNode * GetElem(LinkList L,int i){
	if(i<0)
		return NULL;
	LNode *p;//指针p当前扫描到的结点
	int j=0;//当前p指向的是第几个结点
	p=L;//扫描开始位置,L指向头结点,头结点是第0个节点,不存数据
	while(p!=NULL && j<i){//循环找到第i个结点
		p=p->next;
		j++;
	}
	return p;
}
/*
平均时间复杂度O(n)
*/
//拓展:改写插入操作
//按位序插入:在带头结点的单链表L中的第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)//非法位序
		return false;
	LNode *p = GetElem(L,i-1);
	return InsertNextNode(p,e);//当前p扫描到第i-1个位置,执行后插法
}
/*
封装的好处:避免重复代码,简洁易维护
*/

//查找带头结点的单链表L的的数据域为e的结点(按值查找)
LNode * LocateElem(LinkList L,ElemType e){
	LNode *p=L->next;
	//从第一个结点开始查找数据域为e的结点
	while(p!=NULL && p->data!=e)
		p=p->next;
	return p;//找到后返回该结点指针,否则返回NULL(没找到就已经指向了NULL)
}

//求表的长度
int Length(LinkList L){
	int len=0;
	LNode *p=L;
	while(p->next!=NULL){
		p=p->next;
		len++;
	}
	return len;
}

2.4.4单链表的建立

//尾插法建立单链表
LinkList List_TailInsert(LinkList &L){
	int x;//设数据域为整型
	InitList(L);//初始化带头结点单链表
	LNode *s,*r=L;//r为表尾指针
	scanf("插入数据:%d",&x);
	while(x!=0000){
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		r->next=s;
		//以上三行代码等价于:InsertNextNode(r,x);//后插操作,内有*s
		r=s;//r指向新的表尾结点
		scanf("插入数据:%d",&x);
	}
	r->next=NULL;//表尾结点指针置空
	return L;
}
/*
时间复杂度:O(n)
*/
//尾插法笨方法,时间复杂度O(n^2)
LinkList list_TailInsert_II(LinkList &L){
	InitList(L);//初始化带头结点单链表
	int x,length=0;//数据域设为整型,长度初始化
	scanf("插入数据:%d",&x);
	while(x!=0000){
		ListInsert(L,length+1,x);//按位序插入:在带头结点的单链表L中的第length+1个位置插入元素x
		length++;
		scanf("插入数据:%d",&x);
	}
	return L;
}

//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){
	int x;
	InitList(L);//初始化
	scanf("插入数据:%d",&x);
	while(x!=0000){
		InsertNextNode(L,x);//后插操作,头结点后插入x
		scanf("插入数据:%d",&x);
	}
	return L;
}
/*
时间复杂度O(n)
*/

2.4.5双链表

双链表结点中有两个指针prior和next,分别指向其前驱结点和后继结点。

//双链表结点类型描述
typedef struct DNode{//D:double
	ElemType data;//数据域
	struct DNode *prior,*next;//前驱和后继指针
}DNode,*DLinkList;

//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L){
	L=(DNode *)malloc(sizeof(DNode));//分配一个头结点
	if(L==NULL)//内存不足,分配失败
		return false;
	L->prior=NULL;//头结点的prior永远指向NULL
	L->next=NULL;//头结点之后暂时还没有结点
	return true;
}

//判断双链表是否为空(带头结点)
bool Empty(DLinkList L){
	if(L->next==NULL)
		return true;
	else
		return false;
}

//双链表的插入:在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
	if(p==NULL || s==NULL)
		return false;
	s->next=p->next;
	if(p->next!=NULL)
		p->next->prior=s;
	s->prior=p;
	p->next=s;
	return true;
}

//双链表删除:删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
	if(p==NULL)	return false;
	DNode *q=p->next;//找到p的后继结点
	if(q==NULL)	return false;//p没有后继
	p->next=q->next;
	if(q->next!=NULL)//q结点不是最后一个结点
		q->next->prior=p;
	free(q);//释放q
	return true;
}
//双链表的销毁
void DestoryList(DLinkList &L){
	//循环释放头结点的后继结点
	while(L->next!=NULL)
		DeleteNextDNode(L);
	free(L);//释放头结点
	L=NULL;//头指针指向NULL
}
/*
双链表不可随机存取,按位查找、按值查找都只能用遍历的方式实现,时间复杂度O(n)
*/

2.4.6其他链表

  • 循环单链表:最后一个结点指针指向头结点
  • 循环双链表:头结点prior指针指向尾结点;尾结点next指针指向头结点
  • 静态链表:数组

2.5顺序表和链表的比较

  1. 存取方式
    顺序表:顺序存取和随机存取
    链表:顺序存取
  2. 逻辑结构与物理结构
    顺序表:逻辑上相邻,物理上也相邻
    链表:物理不相邻,逻辑关系用指针链接
  3. 查找、插入和是删除操作
    顺序表:无序按值查找O(n),有序按值查找O(log2n);按位查找O(1);插入删除需要移动大量元素
    链表:按值查找O(n);按位查找O(n);插入删除只需要修改相关指针
  4. 空间分配
    顺序表:静态存储空间不可扩充,动态操作麻烦
    链表:操作灵活高效
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

终究成为社畜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值