数据结构Review 第二章-线性表

第二章 线性表

一. 线性表的定义和基本操作

1. 线性表(Linear List)的定义

线性表是具有相同数据类型的n个数据元素组成的有限序列,n为表长,n=0时为空表。

若用L命名线性表,则一般表示为:L = (a1,a2,...,ai,...,an)

几个概念:

①位序:ai是线性表中的“第i个”

②a1是表头元素,an是表尾元素

③除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

2. 线性表的基本操作

①对数据的操作——创销,增删改查

②什么时候需要传入引用“&”:对参数的修改需要“带回来”

二. 顺序表的定义

1. 顺序表的定义

顺序表(Sequence List)——用顺序存储的方式实现线性表顺序存储

把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中。

C语言中sizeof()的用法。

 

顺序表的特点:

①随机访问,在O(1)时间内找到第i个元素

②存储密度高m,每个结点只存储数据元素

③拓展容量不方便(即使动态分配,也有时间复杂度高的问题)

④插入、删除数据元素不方便,需要移动大量元素

2. 静态分配顺序表

/*静态分配
*/
#define MaxSize 10
typedef struct {
	ElemType data[MaxSize];
	int length;
}SqList;//Sq:sequence 顺序,结构

void InitList(SqList& L) {
	for (int i = 0; i < MaxSize; i++){
		L.data[i] = 0;//不初始化可能有遗留的“脏数据”
	}
	L.length = 0;
}

 

C语言中声明顺序表示长度和内容都需要初始化,所有元素都设置为默认初始值

Q:“数组”存满了怎么办?

A:长度不可调(静态分配)

3. 动态分配顺序表

b951cfc9ba6c119299baa8b3d67e6e1f.png

/*动态分配
*/
#define InitSize 10
typedef struct {
	int* data;
	int MaxSize;
	int length;
}SqList;

void InitList(SqList& L) {
	L.data = (int*)malloc(InitSize * sizeof(int));
	L.length = 0;
	L.MaxSize = InitSize;
}

void IncreaseSize(SqList& L, int len) {
	int* p = 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 += len;
	free(p);
}

顺序表存满时,可再用malloc动态拓展顺序表的最大容量(需要强制类型转换,把malloc指针转换为原有顺序表中数据类型,如此在访问元素时方不出错)。需要把数据元素复制到新的存储区域,并用free函数释放原区域

#define定义函数,定义常量

三. 顺序表的插入删除

1. 插入listInsert(&L,i,e)

注意位序和数组下标(索引index)的关系,并从后面的元素依次移动

注意健壮性(可以用if判断),给出反馈,能处理异常情况

/*顺序表的插入
*/
bool ListInsert(SqList& L, int i, int e) {
	if (i<1 || i>L.length) return false;//判断i的范围是否合法
	if (L.length >= Maxsize) return false;//判断是否已满
	for (int j = L.length; j >= i; j--) {
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e;
	L.length++;
	return true;
}

2. 插入操作的时间复杂度

最坏情况:新元素插入到表头,将原有的n个元素向后移,i = 1,循环n次,最坏时间复杂度O(n)

平均:eq?T%28n%29%3Dnp&plus;%28n-1%29p&plus;%28n-2%29p&plus;...&plus;1*p%3D%5Cfrac%7Bn%28n&plus;1%29%7D%7B2%7D*%5Cfrac%7B1%7D%7Bn&plus;1%7D%3DO%28n%29

其中p为插入到每个位置的可能性,p=1/n+1.

3. 删除listDelete(&L,i,&e)

注意e为引用类型参数,因为要修改

注意先移动前面的元素

c23ae577451a4702973b62e8133e2626.png

/*顺序表的删除
*/
bool ListDelete(SqList& L, int i, int& e) {
	if (i < 1 || L.length < i) return false;
	e = L.data[i - 1];
	for (int j = i; j < L.length; j++) {
		L.data[j - 1] = L.data[j];
	}
	L.length--;
	return true;
}

 

4. 删除操作的时间复杂度

最坏情况:删除表头元素,循环(n-1)次,最坏时间复杂度O(n)

平均情况:eq?T%28n%29%3D%5Cfrac%7Bn%28n-1%29%7D%7B2%7D*p%3D%5Cfrac%7Bn%28n-1%29%7D%7B2%7D*%5Cfrac%7B1%7D%7Bn%7D%3DO%28n%29

四. 顺序表的查找

1. 按位查找getElem(L,i)

5b56ddbd5e9c4756538eda84a424a4d1.png

/*顺序表的按位查找
*/
ElemType GetElem(SqList L, int i) {
	return L.data[i - 1];
}

2. 按值查找locateElem(L,e)

遍历返回位序

68864ab23417f72afaacaeb9b92ee48c.png

/*顺序表的按值查找
*/
ElemType LocateElem(SqList L, ElemType e) {
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == e) {
			return i + 1;//i+1是位序
		}
		return 0;
	}
}

 

3. 按值查找时间复杂度

最坏情况:最坏时间复杂度O(n)

平均情况:eq?T%28n%29%3D%5Cfrac%7Bn&plus;1%7D%7B2%7D%3DO%28n%29

 

五. 单链表的定义

1. 什么是单链表

每个结点除了存放数据元素外,还要存储指向下一个结点的指针。

优点:不要求大片连续空间,改变容量方便。

缺点:不可随机存取,要耗费一定空间存放指针,无法逆向检索。

逻辑结构:线性表

 

2. 单链表的代码实现

typedef struct LNode {
	ElemType data;//数据域
	struct LNode* next;//指针域
}LNode,*LinkList;

 

1.声明不带头结点的单链表

/*声明不带头结点的单链表*/
bool InitList(LinkList& L) {
	L = NULL;//防止脏数据
	return true;
}

//判空
bool Empty(LinkList L) {
	return(L == NULL);
}

2.带头结点的单链表(头结点不存储数据)

/*带头结点的单链表*/
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);
}

六. 单链表的插入与删除

1. 按位序插入(带头结点)

/*按位序插入(带头结点)*/
bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1)return false;
	LNode* p;
	int j = 0;
	p = L;
	while (p != NULL && j < i - 1) {//找到第(i-1)个结点
		p = p->next;
		j++;
	}
	if (p == NULL)return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;//将s连接到p后面
	return true;
}

所有的增删操作均可以画图分析

cdcb533d112f4b49a308b46a795db5a2.jpeg

2. 按位序插入(不带头结点)

/*按位序插入(不带头结点)*/
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;
	int j = 1;
	p = L;
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}
	if (p == NULL)return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

3. 指定结点的后插操作

/*指定结点的后插操作*/
//在给定结点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;
	s->next = p->next;
	p->next = s;
	return true;
}

可见此部分代码与按位序插入部分的代码有所重合,所以可以调用此部分的函数来替代(封装的思想)

4. 指定结点的前插操作

/*给定结点的前插操作*/
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->next = s;
	s->data = p->data;
	p->data = e;
	return true;

5. 按位序删除(带头结点)

/*按位序删除(带头结点)*/
bool ListDelete(LinkList& L, int i, ElemType e) {
	if (i < 1)return false;
	LNode* p;
	int j = 0;
	p = L;
	//找到需要修改的结点的前一个结点,修改指针指向
	while (p != NULL && j < i - 1) {//找到第(i-1)个结点
		p = p->next;
		j++;
	}
	if (p == NULL)return false;
	if (p->next == NULL)return false;//指定位序处为空
	LNode* q = p->next;
	e = q -> data;
	p->next = q->next;
	free(q);
	return true;
}

4c75d645fe844f3eb6cc4933ffd72610.jpeg

6. 指定结点的删除

/*指定结点的删除*/
bool DeleteNode(LNode* p) {
	if (p == NULL)return false;
	LNode* q = p ->next;
	p->data = p->next->data;
	p->next = q->next;
	free(q);
	return true;
}

188ff49f85114c729e36e525360cc721.jpeg

七. 单链表的查找

1. 按位查找

e58cd16f66764a029c79843e75963f2f.jpeg

/*按位查找*/
LNode* GetElem(LinkList L, int i) {
	if (i < 0)return NULL;
	LNode* p;
	int j = 0;
	p = L;//头结点相当于第0个结点
	while (p != NULL && j < i){
		p = p->next;
		j++;
	}
	return p;
}

又在封装后可以替换掉前面查找位序对应的代码

2. 按值查找

/*按值查找*/
LNode* LocateElem(LinkList L,ElemType e){
	LNode* p = L->next;//从第1个结点开始
	while (p != NULL && p->data != e) {
		p = p->next;
	}
	return p;//遍历完了没找到会返回NULL
}

3.求表长

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

八. 单链表的建立

1.尾插法建立单链表

①初始化单链表

②变量length统计长度

③while循环插入元素到尾部

/*尾插法*/
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));
	L->next = NULL;
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999) {//停止输入所需要输入x的值,自己定义的
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}

2. 头插法建立单链表(逆置)

①初始化单链表

②while循环插入元素到头部

可发现只需要在尾插法代码上稍作修改

/*头插法*/
LinkList List_HeadInsert(LinkList& L) {
	LNode* s;
	int x;
	L = (LinkList)malloc(sizeof(LNode));//LinkList和LNode*等价,只是强调不同的内容
	L->next = NULL;
	scanf("%d", &x);
	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d", &x);
	}
	return L;
}

3. 给出一个LinkList L,用头插法逆置

/*逆置*/
LinkList List_Reverse(LinkList L) {
	Lr = (LinkList)malloc(sizeof(LNode));
	Lr->next = NULL;
	LNode* s;
	LNode* p = L->next;
	while (p != NULL) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = p->data;
		s->next = Lr->next;
		Lr->next = s;
		p = p->next;
	}
	return Lr;
}

九. 双链表

1. 双链表与单链表的区别

①单链表:无法逆向检索

②双链表:可进可退,存储密度更低

2. 单链表的初始化

/*初始化双链表*/
typedef struct DNode {
	ElemType data;
	struct DNode* prior, * next;
}DNode,*DLinkList;

bool InitDLinkList(DLinkList &L){
	//分配头结点(DNode*和DLinkList是等价的,只是强调的东西不同)
    L = (DNode*)malloc(sizeof(DNode);
	if (L == NULL) return false;
	L->prior = NULL;
	L->next = NULL;
	return true;
}

0478533daf5c47388a221446f18132fb.jpeg

3. 判空(与单链表相同)

//判空
bool Empty(DLinkList L) {
	return(L ->next == NULL);
}

4. 双链表的后插法

d579b4e102714202b10b9847d205213f.jpeg428735d59bb74ee69a311a2d10bb0c01.jpeg

/*双链表的后插法*/
//在p结点后面插入s结点
bool InsertNextNode(DNode* p, DNode* s) {
	s->next = p->next;
	p->next->prior = s;
	s->prior = p;
	p->next = s;
}

//更具有健壮性的代码(针对在尾指针处插入)
bool InsertNextNode(DNode* p, DNode* s) {
	if(p == NULL || s == NULL)return false;
	s->next = p->next;
	if (p->next != NULL) p->next->prior = s;//p有后继
	s->prior = p;
	p->next = s;
	return true;
}

5. 双链表的删除和销毁

/*双链表的删除*/
bool DeleteNextNode(DNode* p) {//删除p的后继
	if (p == NULL)return false;
	DNode* q = p->next;//找到下一个结点
	if (q == NULL)return false;//p没有后继
	p->next = q->next;
	if (q->next != NULL)q->next->prior = p;
	free(q);
	return true;
}
//双链表的销毁
void DestroyList(DLinkList& L) {
	while (L->next != NULL) DeleteNextNode(L);
	free(L);
	L == NULL;
}

6. 双链表的遍历

①后向遍历 ②前向遍历(跳过头结点)

//后向遍历
while (p != NULL){
	p = p->next;
}

//向前遍历
while (p->prior != NULL) {
	p = p->prior;
}

十. 循环链表

1. 循环单链表(可循环遍历各个结点)

①初始化

054939a1f2614b709f2613a2c56e9a04.jpeg

分配头结点后,把头结点next指向表头。

L->next=L;

②判断某个结点是否为表尾

/*判断某个结点是否为表位*/
bool IsTail(LinkList L, LNode* p) {
	return(p->next == L);
}

2.循环双链表

表头prior指向表尾,表尾next指向表头。

①初始化

ea114348eb8a4453a382ed2f80464698.jpeg

分配头结点后

L->next=L;

L->prior=L;

②判空与循环单链表类似

③插入时不用判断后继是否为空

④删除

p->next=q->next;

q->next->prior=p;

free(q);

 

十一. 静态链表(逻辑相邻,可以物理不相邻)

1. 静态链表的定义(用数组实现的链表)

分配一整片连续的内存空间,各个结点集中安置。

c395e4b9a48f401cbb5a6f54d19342ca.jpeg

每个数据元素4B,每个游标4B(每个结点一共8B)

设起始的地址为addr,则eq?e_%7B1%7D的地址为(addr+8*2)

2. 静态链表的初始化

/*静态链表的初始化*/
#define MaxSize 10
struct Node{
	ElemType data;
	int next;//下一个元素的数组下标
};

void testSLinkList() {
	struct Node a[MaxSize];
}

把a[0]的next设为-1,把其他结点的next设为特殊值,如-2。

3. 查找

从头结点出发挨个往后遍历结点。O(n)

4. 插入位序为i的结点

①找到一个空结点(判空操作),存入数据元素

②从头结点出发找到位序为(i-1)的结点

③修改新的结点的next(游标)

④修改(i-1)号结点的next(游标)

5. 删除某个结点

①从头结点出发找到要删除的结点的前驱结点

②修改前驱结点的游标

③被删除的结点的next改为-2

6. 优缺点

①优点:增删不需要移动大量元素

②缺点:不能随机存取,只能从头结点开始往后查,容量固定。

适用于不支持指针的低级语言;数据元素表容量固定不变的场景(如操作系统的文件分配表FAT)。

十二. 顺序表和链表的比较

1. 逻辑结构上都是线性表

2. 存储结构

顺序存储:①优点:支持随机存取,存储密度高

                  ②缺点:大片连续空间分配不方便,改变容量不方便

链式存储:①优点:离散的小空间分配方便,改容量方便

                  ②缺点:不可随机存取,存储密度低

3. 基本操作

①表长难以预估,经常增删——链表

②表长可以预估,经常查——顺序表

4. 开放问题答题框架

①逻辑结构-->②存储结构-->③基本操作重点

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WISHMELUCK1'

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

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

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

打赏作者

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

抵扣说明:

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

余额充值