【数据结构】—— chapter 02 线性表

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

在这里插入图片描述

2.1.1 线性表的定义

线性表是具有相同数据类型的n (n≥0) 个数据元素有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为
在这里插入图片描述
在这里插入图片描述

2.1.2 线性表的基本操作

在这里插入图片描述
线性表的基本操作:
在这里插入图片描述

2.1.3 小结

在这里插入图片描述

2.2 线性表的顺序表示

在这里插入图片描述

2.2.1 顺序表的定义

线性表的顺序存储又称顺序表。它是用一种地址连续的存储单元存储数据元素,使得逻辑上相邻的数据元素物理上也相邻。
(用存储位置的相邻来体现数据元素之间的逻辑关系)

在这里插入图片描述
注意:线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的。

1. 静态分配
在这里插入图片描述

#define MaxSize 10  //定义最大长度

typedef struct{
  ElemType data[MaxSize]; //用数组存放数据元素
  int length;   //顺序表的当前长度
}SqList;      //顺序表的类型定义

//初始化一个顺序表
//避免内存中会遗留“脏数据”
void InitList(SqList &L){
     for(int i=0;i<MaxSize;i++)
		 L.data[i] = 0;
	 L.length = 0;
}
int main()
{
   SqList L; //声明一个顺序表
   InitList(L);  //初始化顺序表
 
   return 0;
}

2. 动态分配
在这里插入图片描述
在这里插入图片描述

#include <stdio.h> 
#include <stdlib.h>  //malloc、free函数的头文件
#define InitSize 10  //表长度的初始定义

typedef struct{
  ElemType *data; //指示动态分配数组的指针
  int MaxSize;    //数组的最大容量
  int length;   //顺序表的当前长度
}SqList;      //顺序表的类型定义


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

//增加动态数组的长度
void IncreaseSize(SqList &L,int len){
   ElemType *p = L.data;
   L.data = (ElemType *)malloc(sizeof(ElemType)*(MaxSize+len)); //重新分配了15个存储单元
   for(int i=0;i<L.length;i++)
	   L.data[i] = p[i];   //将数据复制到新区域(时间开销大)
	L.MaxSize = L.MaxSize+len;
	free(p);
}

int main()
{
   SqList L; //声明一个顺序表
   InitList(L);  //初始化顺序表
   //.....随便插入几个元素
   IncreaseSize(L,5);
   return 0;
}

3. 顺序表的特点
在这里插入图片描述

4. 小结
在这里插入图片描述

2.2.2 顺序表上基本操作的实现

在这里插入图片描述
1. 插入
ListInsert(&L,i,e):插入操作。在表L中的 第i个(i是位序)位置上 插入指定元素e。
1 ≤ i ≤ L.length+1。
(插入、删除代码建立在顺序表的“静态分配”实现方式之上, “动态分配”也雷同。)

//在位序i插入元素e
bool ListInsert(SqList &L,int i,int e) {
	if (i<1 || i>L.length + 1)  return false;
	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 += 1;
	return true;
}

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

问题规模n= L.length (表长)
在这里插入图片描述
3. 删除
ListDelete(&L,i,&e):删除操作。删除表L中 第i个(i是位序)位置 的元素,并用e返回删除元素的值。
1 ≤ i ≤ L.length。

//删除位序i的元素,并用e带回
bool ListDelete(SqList& L, int i, int& e) {
	if (i<1 || i>L.length)  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. 删除操作的时间复杂度分析
在这里插入图片描述

2.3 线性表的链式表示

2.3.1 单链表的定义

1. 什么是单链表
在这里插入图片描述
2. 单链表中结点类型的描述以及单链表的表示
单链表中结点类型的描述:

typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

单链表的表示:
通常用头指针标识一个单链表
LNode * L; //声明一个指向单链表第一个结点的指针
或 LinkList L; //声明一个指向单链表第一个结点的指针(代码可读性更强)
在这里插入图片描述

3.不带头结点的单链表
L= Null 是空表。
在这里插入图片描述

#include <stdio.h>


typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L) {
	L = NULL;   //防止脏数据
	return true;
}

//判断单链表是否为空
bool IsEmpty(LinkList L) {
	if (L == NULL)  return true;
	else  return false;

}

void test() {
	LinkList L;  ////声明一个指向单链表的指针
	InitList(L);  //初始化一个空表
	//......后续代码......

}

4. 带头结点的单链表
L->next == NULL是空表。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>  

typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L) {
	L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
	if (L == NULL)  //malloc分配失败
		return false;
	L->next = NULL; 
	return true;
}

//判断单链表是否为空
bool IsEmpty(LinkList L) {
	if (L->next == NULL)  return true;
	else  return false;
}

void test() {
	LinkList L;  ////声明一个指向单链表的指针
	InitList(L);  //初始化一个空表
	//......后续代码......

}

5. 小结
在这里插入图片描述

2.3.2 单链表上基本操作的实现

在这里插入图片描述在这里插入图片描述在这里插入图片描述

一、单链表的插入删除

1. 按位序插入
在这里插入图片描述
插入结点操作将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,在它后面插入结点。

(1) 带头结点
头结点可以看作“第0个”结点(这样写代码便于理解),j 初始设0

在这里插入图片描述

//在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,int e) {
	if (i < 1)   //i值不合法
		return false;
		
	int j = 0;  //当前p指向的结点位置
	LNode* p;   //指针p指向当前扫描到的结点
	p = L;

	while (j < i - 1 && p != NULL) {  //循环找到第i-1个结点
		p = p->next;
		j++;
	}

	if (p == NULL)  //i值不合法
		return false;

	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;

	return true;  //插入成功
}

最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)

(2) 不带头结点
不带头结点,那么 j 初始设1

在这里插入图片描述

//在第i个位置插入元素e,不带头结点
bool ListInsert(LinkList& L, int i, int e) {
	if (i < 1)   //i值不合法
		return false;

	if (i == 1) {  //插入第一个结点的操作与其他结点的操作不同
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = e;
		s->next = L;
		L = s;   //头指针指向新结点
		return true;
	}


	LNode* p;   //指针p指向当前扫描到的结点
	p = L;
	int j = 1;  //当前p指向的结点位置
	while (j < i - 1 && p != NULL) {  //循环找到第i-1个结点
		p = p->next;
		j++;
	}

	if (p == NULL)  //i值不合法
		return false;

	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;

	return true;  //插入成功

}

最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)

2. 指定结点的后插操作

//在p结点之后插入元素e
bool InsertNextNode(LNode *p, int 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;
}

时间复杂度:O(1)

3. 指定结点的前插操作
在这里插入图片描述
想在指定结点前插的话,有两种方法;
法一:循环查找p的前驱结点q,再对q后插。时间复杂度是O(n)。

在这里插入图片描述

时间复杂度:O(n)

法二:在p结点后插入结点s,然后将p结点的数据域与s结点的数据域交换。

//在p结点之前插入结点s
bool InsertPriorNode(LNode* p, LNode* s) {
	if (p == NULL || s == NULL)
		return false;
	s->next = p->next;
	p->next = s;    //s连到p之后

	int temp = p->data; //交换数据域部分 
	p->data = s->data;
	s->data = temp;
	return true;
}

时间复杂度:O(1)

4. 按位序删除
在这里插入图片描述
(1) 带头结点的:

//删除第i个位置的元素,并用e返回
bool ListDelete(LinkList& L, int i, int& e) {
	if (i < 1) return false;
	LNode* p;   //p指向当前扫描到的结点
	p = L;
	int j = 0;   //当前p指向的是第几个结点

	while (j < i - 1 && p != NULL) {
		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;
	free(q);      //释放结点的存储空间
	return true;  //删除成功

}

最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)

(2) 不带头结点的:

bool ListDelete(LinkList& L, int i, int& e) {
	if (i < 1) return false;
	LNode* p;   //p指向当前扫描到的结点
	p = L;
    int j = 1;   //当前p指向的是第几个结点
    
	if (i == 1) {
		LNode* s;
		s = L;
		L = s->next;
		free(s);
	}
    
    //.......剩下的与上面 带头结点代码中while开始到结束 的一样。
}

最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)

5. 指定结点的删除
在这里插入图片描述

删除结点p,需要修改其前驱结点的next指针。有两个方法。
法1:传入头指针,循环寻找p的前驱结点。

时间复杂度:O(n)

法2:将删除结点的数据域与后驱结点的数据域交换,然后直接删除后驱结点就好。
( 类似于指定结点前插的实现)
如果p是最后一个结点,不能 用方法二 ,只能从表头开始依次寻找p的前驱,使用方法一。
这里可以发现单链表的局限性:无法逆向检索,有时候不太方便。

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

时间复杂度:O(1)

6. 小结
在这里插入图片描述

二、单链表的查找

1. 按位查找
在这里插入图片描述
在这里插入图片描述

时间复杂度:O(n)

2. 按值查找

//按值查找,找到数据域==e的结点
LNode* LocateElem(LinkList L, int e) {
	LNode* p = L ->next;
	//从第1个结点开始查找数据域为e的结点
	while (p != NULL && p->data != e)
		p = p->next;
	return p; //找到后返回该结 点指针,否则返回NULL
}

时间复杂度:O(n)

3. 求表的长度
求表长操作就是计算单链表中数据结点(不含头结点)的个数。设置一个计数器len。
(1)带头结点的:

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

(2)不带头结点的:
只需要把len初始值改为1即可。

(1) (2) 时间复杂度都是:O(n)

4. 小结
在这里插入图片描述

三、单链表的建立

如果给你很多个数据元素(ElemType),要把它们存到一个单链表里边,怎么搞?
Step 1:初始化一个单链表
Step 2:每次取一个数据元素,插入到表尾/表头
这里只写带头结点的。
1. 尾插法
在这里插入图片描述
在这里插入图片描述

//尾插法建立单链表
LinkList List_Taillnsert(LinkList& L) {
	int x;
	LNode *s,*r = L;  //r是表尾指针
	L = (LNode*)malloc(sizeof(LNode));  //头结点

	scanf_s("%d", &x); //输入结点的值

	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;   //r指向新的表尾结点;永远保持r指向最后一个结点
		scanf_s("%d", &x);
	}
	r->next = NULL;  //尾结点指针置空
	return L;
}

时间复杂度: O(n)

2. 头插法
在这里插入图片描述
在这里插入图片描述

//头插法
LinkList Head_Insert(LinkList &L) {
	int x;
	LNode* s; 
	L = (LNode*)malloc(sizeof(LNode));  //头结点
	L->next = NULL;  //初始为空链表
	
	scanf_s("%d",&x);

	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;

		s->next = L->next;
		L->next = s;
		scanf_s("%d",&x);
	}
	return L;
}

时间复杂度: O(n)

3. 小结
在这里插入图片描述

2.3.3 双链表

以下代码都是基于带头结点的
单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入、删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。
为了克服单链表的上述缺点,引入了双链表,双链表结点中有两个指针 prior和next,分别指向其前驱结点和后驱结点。如下图:
在这里插入图片描述
1. 双链表的初始化

//双链表
typedef struct DNode {
	int data;
	struct DNode* prior;
	struct DNode* next;
}DNode,*DLinkList;

//双链表的初始化
bool InitList(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;
}

2. 双链表的插入
如果p是最后一个结点,就要加上一句 if(p->next != NULL) //如果p结点有后继结点。
注意:1 2步必须在4之前。

//双链表的插入,在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s) {
	if (p == NULL || s == NULL) return false;
    
	s->next = p->next;   // 1
	if(p->next != NULL)  //如果p结点有后继结点
	    p->next->prior = s;   // 2
	s->prior = p;       // 3
	p->next = s;        // 4
	return true;

}

3. 双链表的删除
如果p是最后一个结点,就要加上一句 if(q->next != NULL) //q结点不是最后一个结点

//双链表的删除,删除p结点的后继节点
bool DeleteNextDNode(DNode* p) {
	if (p == NULL)  return false;
	DNode* q = p->next; //要删除的结点q
	if (q == NULL)  return false; //若p没有后继
	p->next = q->next;
	if(q->next != NULL)  //q结点不是最后一个结点
	    q->next->prior = p;
	free(q);    //释放结点空间
	return true;
}

4. 双链表的遍历
在这里插入图片描述
5. 小结
在这里插入图片描述

2.3.4 循环链表

在这里插入图片描述

1. 循环单链表

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

循环单链表的初始化:

//初始化循环单链表
bool InitList(LinkList& L) {
	L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点
	if (L == NULL)  return false;
	L->next = L;  
	return true;
}

//判断循环单链表是否为空
bool Empty(LinkList L) {
	if (L->next == L)  return true;
	else  return true;
}

2. 循环双链表
在这里插入图片描述在这里插入图片描述
循环双链表的初始化:

 //初始化循环双链表
bool InitDLinkList(DLinkList &L) {
	L = (DNode*)malloc(sizeof(DNode)); //分配一个头结点
	if (L == NULL)  return false;
	L->prior = L;
	L->next = L;
	return true;
}

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

循环双链表的插入:
在这里插入图片描述
循环双链表的删除:
在这里插入图片描述
3. 小结
在这里插入图片描述

2.3.5 静态链表

在这里插入图片描述

1. 什么是静态链表
其实就是数组,只是每个位置存储的是结点类型的元素,指针域放的是下一个元素的下标。
在这里插入图片描述
2. 如何定义一个静态链表
在这里插入图片描述
3. 简述基本操作的实现
在这里插入图片描述在这里插入图片描述
4. 小结
在这里插入图片描述

2.3.6 顺序表和链表的比较

在这里插入图片描述
1. 逻辑结构
在这里插入图片描述
2. 物理结构
在这里插入图片描述
3. 基本操作
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
4. 顺序表与链表的适用场景
在这里插入图片描述
5. 知识回顾&&重要考点
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章