第一章 绪论
第二章 线性表
本篇文章为408王道数据结构第二章线性表的全部笔记,代码大部分为自己敲出后对比修改,可能会有出入和错误,若发现欢迎指正
(1)线性表是具有 * 相同数据类型 * 的n(n为非数整数)个 * 数据元素 * 的* 有限序列 *
(2)其中表长为n,当n=0时线性表是一个空表。
(3)若用L命名线性表,则一般表示为:L=(a1,a2 … aj)
(4)下标1、2、3是元素在线性表中的位序
注意:线性表的位序是从1开始的,而数组的下标是从0开始的
(5)a1是表头元素,aj是表尾元素
(6)除了第一个元素外,每个元素有且仅有一个直接前驱
(7)除了最后一个元素外,每个元素有且仅有一个直接后继
初始化表:构造一个空的线性表L,分配内存看见
InitList(&L)
销毁操作:销毁线性表并释放线性表所占内存
Destory(&L)
插入操作:在第i个位置上插入元素e
ListInsert(&L,i,e)
删除操作:删除表中第i个位置的元素,并用e返回被删除元素的值
ListDelete(&L,i,e)
按值查找操作:在表L中查找具有给定关键字值的元素
LocateElem(L,e)
按位查找操作:获取表L中第i个位置的元素的值
GetElem(L,i)
求表长:返回线性表的表长
Length(L)
输出操作:按顺序输出线性表中所有元素值
PrintList(L)
判空操作,判断线性表是否为空
Empty(L)
(1)物理结构为顺序存储方式实现的线性表
(2)存储特点
(3)第i个元素的地址=LOC(L)+ i * 数据元素的大小
顺序表的静态实现
//顺序表的静态实现
#define MaxSize 10
typedef struct sat_SqList {
int data[MaxSize];//顺序表的总容量,不可改变
int length;//当前顺序表的长度
}sat_SqList;
静态顺序表的初始化
//初始化顺序表
bool InitList(sat_SqList& L)
//细节一:这里初始化的顺序表要返回回去,因此要用引用符号
{
int i;
for (i = 0; i < MaxSize; i++)
{
L.data[i] = -1;//防止脏数据
}
L.length = 0;
return true;
}
顺序表的动态实现
#define InitSize 10
//顺序表的动态实现
typedef struct dy_List {
int* data;
int Maxsize;
int length;
}dy_SqList;
动态顺序表的初始化
//初始化动态分配实现的顺序表
bool dy_InitList(dy_SqList& L)
{
L.data = (int*)malloc(sizeof(int) * InitSize);
L.Maxsize = InitSize;
L.length = 0;
return true;
}
增加动态顺序表的长度
bool dy_AddLength(dy_SqList& L, int add_len)
{
int* temp = L.data;//待会要把原来的数据cv到新开的区域
L.data = (int*)malloc(sizeof(int) * (L.Maxsize + add_len));//重新开一个更大的区域而不是在原来的区域后面增开区域
int i;
for (i = 0; i < L.length; i++)//开始cv
{
L.data[i] = temp[i];
}
L.Maxsize += add_len;//调整最大容量
free(temp);//释放原来的空间
return true;
}
打印顺序表
bool PrintList(sat_SqList L)
{
int i;
for (i = 0; i < L.length; i++)
{
printf("%d\n", L.data[i]);
}
return true;
}
顺序表的插入
//静态顺序表的插入操作
bool InSert_SqList(sta_SqList& L,int i,int e)
{
//输入:顺序表L,插入位置i,插入元素e
//功能:在L的第i个位置插入元素e
//输出:插入是否成功
//构建代码健壮性
if (L.length == MaxSize)//存满了
{
printf("the list have no room to insert elem!\n");
return false;
}
if (i > L.length+1)
{
printf("the position is too big!\n");
return false;
}
if (i < 1)
{
printf("the position is invaild\n");
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;
//该段代码的时间复杂度为O(n)
}
顺序表的删除
//静态顺序表的删除操作
bool Delete_SqList(sta_SqList& L, int i,int &e)
{
//输入:顺序表L,删除位置i,存储被删除元素用的e
//功能:删除L的第i个位置的元素并存放在e中
//输出:删除是否成功
//构建代码健壮性
if (L.length == 0)//没得删
{
printf("the list have no elem to delete!\n");
return false;
}
if (i < 1)
{
printf("the position is invaild\n");
return false;
}
//代码功能实现
e = L.data[i - 1];
int j;
for (j = i - 1; j < L.length - 1; j++)//位序和下标非常的绕
{
L.data[j] = L.data[j + 1];
}
L.length--;
//结尾处理
return true;
//该段代码的时间复杂度为O(n)
}
顺序表的按位查找
//顺序表的按位查找
int pos_GetElem(sta_SqList& L, int i)
{
if (L.length == 0)
{
printf("the list have no elem to find!\n");
return false;
}
if (i<1 || i>L.length)
{
printf("the position is invaild\n");
return false;
}
return L.data[i - 1];
}
顺序表的按值查找
//顺序表的按值查找
int elem_GetElem(sta_SqList& L, int e)
{
if (L.length == 0)
{
printf("the list have no elem to find!\n");
return false;
}
int i;
for (i = 0; i < L.length; i++)
{
if (L.data[i] == e)//对于更复杂的数据的对比会更复杂
{
return i+1;
}
}
return -1;
}
(1)优点:可以随机存储,存储密度高
(2)缺点:要求大片连续空间,改变容量不方便,一开始设置很大的空间又导致浪费
(1)含义:线性表的链式存储,指通过一组任意的存储单元来存储线性表中的数据元素
(2)特点:链表中每个结点除了存放数据元素外,还要存储指向下一个节点的指针,造成浪费存储空间的缺点
(3)分类:带头结点和不带头结点
单链表结构体的定义
//定义一个单链表结构体数据类型:数据+结点
typedef struct LNode {//typedef关键字:数据类型的重命名
int data;//数据域,每个结点存放一个数据元素,元素类型格局实际自定义即可
struct LNode* next;//指针域,指针指向下一个结点
}LNode,*LinkList;//前者是一个结点,后者是指向struct LNode的一个指针
链表的声明
//定义链表的两种方式,两种方式的强调侧重点不同
LNode* L1;
LinkList L2;//代码可读性更强
带头结点
//初始化一个带头结点的单链表
bool head_InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LNode));//开辟空间
if (L == NULL)//内存不足,开辟失败
{
return false;
}
L->next = NULL;//除了头结点暂时没有其他的结点
return true;
}
不带头结点
//初始化不带头结点的单链表
bool InitList(LinkList& L)
//1.这个布尔类型的返回值实在是秒到家了
//2.传参的时候运用了引用,使这步初始化修改的是全局的L
{
L == NULL;//防止地址有脏数据
return true;
}
//不带头结点的单链表,头指针所指的第一个结点就存储数据
带头结点与不带头结点的对比
带头结点写代码更方便,不带头结点写代码更麻烦,因为对于第一个数据结点和其他的数据结点在各操作的处理上是不一样的
辨析:成员运算符(
.
)和指向运算符(->
)
.
)左边是一个普通的结构体变量(->
)左边是一个结构体指针(*(*p).next).data
和p->data->next
都是下一个结点中存放的数据带头结点版
bool head_LinkInsert(LinkList &L, int i, int e)
//在第i个位置插入元素e
//注意:头结点指向的结点才是第一个结点
{
//代码健壮性
if (L1->next == NULL)
{
printf("warnning:the list is empty\n");
return false;
}
if (i <= 0)//位置数字太小
{
printf("then position that you want to insert is error!\n");
return false;
}
//代码功能实现
LNode* p = L;//当前位置指向的结点
int j = 1;//当前指向第几个结点
while (j < i)//开始查找目标结点的前一个结点
{
p = p->next;
if (p == NULL)
{
printf("the position that you want to insert is invaild!\n");
return false;
}
j++;
}
LNode* aim = (LNode*)malloc(sizeof(LNode));//开辟空间存储我们要存的元素以及他的结点
if (aim == NULL)//每次创建新结点都记得判断是否创建成功
{
printf("sorry,we failed to dominate a new adress when we create a new Node!\n");
return false;
}
aim->data = e;
aim->next = p->next;
p->next = aim;
//收尾工作
return true;
}
不带头结点版
bool LinkInsert(LinkList& L, int i, int e)
{
//代码健壮性
if (L == NULL)
{
printf("the list is empty!\n");
return false;
}
if (i <= 0)
{
printf("the position that you want to insert is invaild!\n");
return false;
}
//代码功能实现
if (i == 1)
{
LNode* aim = (LNode*)malloc(sizeof(LNode));//开辟空间存储我们要存的元素以及他的结点
if (aim == NULL)//每次创建新结点都记得判断是否创建成功
{
printf("sorry,we failed to dominate a new adress when we create a new Node!\n");
return false;
}
aim->data = e;
aim->next = L;
L = aim;
return true;
}
else {
LNode* p = L;//当前位置指向的结点
int j = 1;//当前指向第几个结点
while (j < i - 1)//开始查找目标结点的前一个结点
{
p = p->next;
if (p == NULL)
{
printf("the position that you want to insert is invaild!\n");
return false;
}
j++;
}
LNode* aim = (LNode*)malloc(sizeof(LNode));//开辟空间存储我们要存的元素以及他的结点
if (aim == NULL)//每次创建新结点都记得判断是否创建成功
{
printf("sorry,we failed to dominate a new adress when we create a new Node!\n");
return false;
}
aim->data = e;
aim->next = p->next;
p->next = aim;
//收尾工作
return true;
}
}
注:按序插入的两种代码需要注意:L是指向第一个结点(不带头结点)或者头结点的指针,而不是一个结点,也就是说他的next直接结束第二个结点(不带头结点的单链表)或者第一个结点
bool InsertNextNode(LNode* p,int e)
{
//搭建健壮性
if (p == NULL)
{
printf("the Node that you provie is NULL!!!\n");
return false;
}
//代码功能实现
LNode* insert = (LNode*)malloc(sizeof(LNode));
if (insert == NULL)//每次创建新结点都记得判断是否创建成功
{
printf("sorry,we failed to dominate a new adress when we create a new Node!\n");
return false;
}
insert->data = e;
insert->next = p->next;
p->next = insert;
//结尾处理
return true;
}
bool InsertPriorNode(LNode* p, int e)
{
//健壮性构建
if (p == NULL)
{
printf("the Node that you provied is NULL!\n");
return false;
}
//代码功能实现
LNode* insert = (LNode*)malloc(sizeof(LNode));
if (insert == NULL)//每次创建新结点都记得判断是否创建成功
{
printf("sorry,we failed to dominate a new adress when we create a new Node!\n");
return false;
}
insert->data = p->data;
insert->next = p->next;
p->data = e;
p->next = insert;
//结尾处理
return true;
}
bool head_DeleteNode(LinkList L, int i,int &e)
{
//功能介绍:删除单链表L中位序为i的结点,并返回其存储的数据(为了便于返回,这里e用了引用)
//健壮性搭建
if (L == NULL)
{
printf("the List that you provied is NULL\n");
return false;
}
if (i < 1)
{
printf("the position is invaild!\n");
return false;
}
//代码功能实现
LNode* aim = L;//注意:这个地方直接赋值L就好了,不需要开辟空间,开辟空间是我要自己创建一个结点存东西,可以直接赋值的时候不需要
int j = 0;
if (j < i - 1)
{
aim = aim->next;
if (aim == NULL)
{
printf("the position that you want to delete is invaild!\n");
return false;
}
j++;
}
if (aim->next == NULL)
{
printf("the position that you want to delete is empty!\n");
return false;
}
LNode* q = aim->next;//为了后面的free我需要再找个地方存位序对应的结点
e = q->data;
aim->next = q->next;
//结尾处理
free(q);
return true;
}
bool DeleteNode(LNode* p)
{
//代码健壮性
if (p == NULL)
{
printf("the Node that you want to delete is NULL\n");
return false;
}
//代码功能实现
if (p->next == NULL)
{
p = NULL;
return true;
}
LNode* q = p->next;
p->data = q->data;
p->next = q->next;
//结尾处理
free(q);
return true;
}
注意:查找模块只提供带头结点版,不带头结点自行思考编写
LNode* head_GetElem(LinkList L, int i)
{
//输入:单链表L,查找位置i
//输出:L表中第i个元素的位置的结点
//健壮性构建
if (L == NULL)
{
printf("the list that you provied is NULL!\n");
return NULL;
}
if (i < 0)
{
printf("the position that you provied is invaild!\n");
return NULL;
}
//代码功能实现
int j = 0;
LNode* now_node = L;
while (j < i)
{
now_node = now_node->next;
if (now_node == NULL)
{
printf("the position is too big!\n");
return false;
}
j++;
}
//结尾处理
return now_node;
}
//时间复杂度为O(n)
LNode* head_LocateElem(LinkList L,int e)
{
//输入:单链表L、想要查找的值e
//输出:第一次出现值为e的结点
//健壮性构建
if (L == NULL)
{
printf("the list that you provied is NULL!\n");
return NULL;
}
//代码功能实现
LNode* aim = L->next;
while (aim->data != e)
{
aim = aim->next;
if (aim == NULL)
{
printf("fail to research!\n");
return NULL;
}
}
//结尾处理
return aim;
}
//时间复杂度为O(n)
bool head_TailInsertLink(LinkList &L,int e)
{
//输入:单链表L以及要插入的元素e
//功能:将e插入单链表L的尾部
//输出:插入是否成功
//健壮性构建
if (L == NULL)
{
printf("the list is NULL\n" );
return false;
}
//功能实现
LNode* tail = L;
while (tail->next != NULL)
{
tail = tail->next;
}
LNode* aim;
aim = (LNode*)malloc(sizeof(LNode));
if (aim == NULL)
{
printf("there is something wrong when we create a new LNode\n");
return false;
}
aim->data = e;
aim->next = NULL;
tail->next = aim;
//结尾处理
return true;
}
//上面这种算法的时间复杂度是O(n的平方)
LinkList new_TailInsert(LinkList& L)//由于是进行修改类型的操作,因此要传引用
{
//代码功能实现
int insert_data;
L = (LinkList)malloc(sizeof(LNode));//从零开始建立
LNode* aim;//存放新结点
LNode* tail = L;//存放最后一个结点
scanf("%d", &insert_data);
printf("please input a data that you wan to insert,if you do not need to insert,input 9999 and the process to insert will be end!\n");
while (insert_data != 9999)
{
aim = (LNode*)malloc(sizeof(LNode));
if (aim == NULL)
{
printf("there is something wrong when we create a new LNode\n");
return NULL;
}
aim->data = insert_data;
aim->next = NULL;
tail->next = aim;
tail = aim;//r始终指向最后一个结点,可以极大程度减少时间复杂度
printf("please input a data that you wan to insert,if you do not need to insert,input 9999 and the process to insert will be end!\n");
scanf("%d", &insert_data);
}
//结尾处理
printf("End of operation!\n");
return L;
}
LinkList HeadInsertLink(LinkList& L)
{
//功能实现
L = (LinkList)malloc(sizeof(LinkList));
L->next = NULL;//防止脏数据
LNode* aim = (LNode*)malloc(sizeof(LNode));
int insert_data;
printf("please input a data that you wan to insert,if you do not need to insert,input 9999 and the process to insert will be end!\n");
scanf("%d", &insert_data);
while (insert_data != 9999)
{
aim->data = insert_data;
aim->next = L->next;
L->next = aim;
printf("please input a data that you wan to insert,if you do not need to insert,input 9999 and the process to insert will be end!\n");
scanf("%d", &insert_data);
}
//结尾处理
printf("End of operation!\n");
return L;
}
思考:以上两种操作如果是不带头结点要怎么处理呢?
求带头结点的单链表的长度
int head_length(LinkList L)
{
//输入:单链表L
//输出:链表长度length
//代码功能实现
if (L == NULL)
{
return 0;
}
int length = 0;
LNode* aim = L->next;
while (aim != NULL)
{
length++;
aim = aim->next;
}
return length;
}
//时间复杂度为O(n)
判空(不带头结点版)
//不带头结点的单链表的判空
bool Empty(LinkList L)//注意:这里只是判断一下,并没有进行什么操作,因此不需要引用传参
{
return (L == NULL);
//或者如下代码
if (L == NULL)
{
return true;
}
return false;
}
判空(带头结点版)
//带头结点的单链表的判空
bool head_Empty(LinkList L)
{
return(L->next == NULL);
}
//注意:头指针L指向头结点,头结点不存储数据
(1)优点:不要求大片连续空间,改变容量方便
(2)缺点:不可随机存取,要耗费一定空间存放指针
//创建一个双链表结构体
typedef struct DNode
{
int data;
struct DNode* next;
struct DNode* prior;
}DNode, * DLinkList;
//双链表的初始化(带头结点版)
bool head_InitDLinkList(DLinkList& L)
{
//功能实现
L = (DNode*)malloc(sizeof(DNode));
if (L == NULL)
{
printf("there is no enough room for us to create a new LNode\n");
return false;
}
L->next = NULL;
L->prior = NULL;
//结尾处理
printf("succeed to create!\n");
return true;
}
//双链表判空(带头结点版)
bool head_JudgeEmpty(DLinkList L)
{
if (L->next == NULL)
{
return true;
}
return false;
}
//双链表的后插操作
bool InsertNextDList(DNode* p, DNode* s)
{
//输入:插入位置p以及待插入结点s
//功能:在p结点之后插入s结点
//输出:插入是否成功
//健壮性构建
if (p == NULL || s == NULL)
{
printf("the node is NULL!\n");
return false;
}
//代码功能实现
s->next = p->next;
p->next = s;
if (s->next != NULL)
{
s->next->prior = s;
}
s->prior = p;
//结尾处理
return true;
}
//双链表的删除操作
bool DeleteDList(DNode* p)
{
//输入:删除结点的前驱结点p
//功能:删除p结点的后继结点
//输出:删除是否成功
//健壮性构建
if (p == NULL)
{
printf("the node is NULL!\n");
return false;
}
//代码功能实现
if (p->next == NULL)
{
printf("the Node that you want to delete is NULL!\n");
return false;
}
DNode* aim = p->next;
p->next = aim->next;
if (p->next != NULL)
{
p->next->prior = p;
}
free(aim);
//结尾处理
return true;
}
//双链表的后向遍历
bool FindAll(DLinkList L)
{
//输入:待遍历双链表L
//功能:遍历双链表L
//输出:遍历是否成功
//健壮性构建
if (L == NULL)
{
printf("the node is NULL!\n");
return false;
}
//代码功能实现
DNode* aim = L;
while (aim->next != NULL)
{
printf("%d\n", aim->data);
aim = aim->next;
}
return true;
}
初始化一个循环单链表
bool InitCricleList(LinkList& L)
{
L = (LinkList)malloc(sizeof(LinkList));
if (L == NULL)
{
printf("there is no enough room for us to create a new LNode\n");
return false;
}
L->next = L;
return true;
}
循环单链表的判空
bool JudgeCricleListEmpty(LinkList L)
{
if (L->next == L)
{
return true;
}
return false;
}
prior
域和next
域都等于L初始化循环单链表
bool InitCircleDList(DLinkList& L)
{
L = (DLinkList)malloc(sizeof(DLinkList));
if (L == NULL)
{
printf("there is no enough room for us to create a new LNode\n");
return false;
}
L->next = L;
L->prior = L;
return true;
}
循环双链表的判空
bool JudgeCricleDListEmpty(DLinkList L)
{
if (L->next == L)
{
return true;
}
return false;
}
注意:关于循环链表的其他操作自己理解编写
(1)含义:分配一整片连续的内存空间,各个结点集中安置
(2)组成:每一个结点由数据和游标组成,存在在静态链表中的某一个位置,游标充当指针的作用,存在下一个结点所在位置的下标
(3)特别声明一:由0号结点(下标)充当头结点
(4)当游标为-1时,说明该结点是表尾结点
(5)个人理解:逻辑有序但物理乱序的数组
定义一个静态链表结构体
#define MaxSize 10
//常见方式
struct StaList_1
{
int data;
int next;
};
struct StaList a[MaxSize];
//课本的方式
typedef struct StationaryList
{
int data;
int next;
}StaList[MaxSize];
//和*LinkList的*代表LinkList是一个指向结构体的指针一样,[MaxSize]代表StaList是一个以该结构体为元素类型的数组,元素个数为MaxSize
StaList a;
静态链表的初始化
bool InitStaList(StaList& a)
{
a[0].next = -1;
//a[0]一定是头结点,其他的才是乱序
//注意:静态链表各结点是变量不是指针,因此用点不用箭头
int i;
for (i = 1; i < MaxSize; i++)
{
a[i].next = -2;//清理脏数据
}
return true;
}
插入位序为i的结点
bool InsertStaLink(StaList& a, int i, int e)
{
//健壮性构建
if (i >= MaxSize || i <= 0)
{
printf("the position is invaild!\n");
return false;
}
if (a[0].next == -1 && i != 1)
{
printf("the oprate is invaild!\n");
return false;
}
int j = 0;
while (j < i - 1)
{
j = a[j].next;
if (a[j].next == -1)
{
printf("the position is too big!\n");
return false;
}
}
int m;
for (m = 1; m < MaxSize; m++)
{
if (a[m].next == -2)
{
break;
}
}
if (m == MaxSize)
{
printf("there is no empty room to insert!\n");
return false;
}
a[m].next = a[j].next;
a[j].next = m;
return true;
}
(1)优点:增删操作不需要大量移动元素
(2)缺点:不能随机存取,容量固定不可变
(1)操作系统中的文件分配表
(1)都是线性表,都是线性结构
(1)顺序存储
(2)随机存储
(3)存储密度更高
(4)空间分配不方便
(5)逻辑上相邻的元素对应的物理存储位置也相邻
(1)链式存储
(2)易于分配空间
(3)改变容量容易
(4)需要额外空间存储指针,存储密度低
(5)不可随机存储
(6)逻辑上相邻的元素对应的物理存储位置不一定相邻
(1)创建(链表可扩容)
(2)销毁
(3)增(链表更好)
(4)删(链表更好)
(5)改
(6)查(顺序表更佳)
难以估计表长和存储规模时,选择链式存储
当经常进行按序号访问数据元素或者插入删除的操作时,选择链式存储
顺序表的实现思路比链式简单
本篇文章详细讲解了线性表的相关内容,在理解的同时要注意实操!