第二章:线性表
线性表的逻辑结构
- 定义:线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列。其中n为表长。当n=0时 线性表是一个空表
- 特点:线性表中第一个元素称为表头元素;最后一个元素称为表尾元素。
除第一个元素外,每个元素有且仅有一个直接前驱。
除最后一个元素外,每个元素有且仅有一个直接后继。
线性表的顺序存储结构
- 线性表的顺序存储又称为顺序表。
它是用一组地址连续的存储单元(比如C语言里面的数组),依次存储线性表中的数据元素,从而使得逻
辑上相邻的两个元素在物理位置上也相邻。 - 建立顺序表的三个属性:
1.存储空间的起始位置(数组名data)
2.顺序表最大存储容量(MaxSize)
3.顺序表当前的长度(length) - 其实数组还可以动态分配空间,存储数组的空间是在程序执行过程中通过动态存储分配语句分配
- 总结:
- 1.顺序表最主要的特点是随机访问(C语言中基于数组),即通过首地址和元素序号可以在O(1)的时间内找到指定的元素。
- 2.顺序表的存储密度高,每个结点只存储数据元素。无需给表中元素花费空间建立它们之间的逻辑关系(因为物理位置相邻特性决定)
- 3.顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量元素。
顺序表的操作
- 1.插入
- 算法思路:
- 1.判断i的值是否正确
- 2.判断表长是否超过数组长度
- 3.从后向前到第i个位置,分别将这些元素都向后移动一位
- 4.将该元素插入位置i 并修改表长
- 分析:
- 最好情况:在表尾插入(即i=n+1),元素后移语句将不执行,时间复杂度为O(1)。
- 最坏情况:在表头插入(即i=1),元素后移语句将执行n次,时间复杂度为O(n)。
- 算法思路:
- 2.删除
- 算法思路:
- 1.判断i的值是否正确
- 2.取删除的元素
- 3.将被删元素后面的所有元素都依次向前移动一位
- 4.修改表长
- 分析
- 最好情况:删除表尾元素(即i=n),无须移动元素,时间复杂度为O(1)。
- 最坏情况:删除表头元素(即i=1),需要移动除第一个元素外的所有元素,时间复杂度为O(n)。
- 算法思路:
线性表基本操作主要有:
List MakeEmpty()
: 初始化一个空线性表 LElementType FindKth(int K,List L)
:根据位序 K,返回相应元素int Find(ElementType X,List L)
:在线性表 L 中查找 X 的第一次出现位置void Insert(ElementType X,int i,List L)
:在位序 i 前插入一个新元素 Xvoid Delete(int i,List L)
:删除指定位序 i 的元素int Length(List L)
:返回线性表 L 的长度 n
#include<stdio.h>
#include<malloc.h>
#define MAXSIZE 100 // MAXSIZE 定义为 Data 数组的大小
typedef int ElementType; // ElementType 可定义为任意类型
typedef struct LNode *List;
struct LNode{
ElementType Data[MAXSIZE];
int Last; // Last 定义线性表的最后一个元素
};
List L;
//访问下标为 i 的元素:L->Data[i]
//线性表的长度:L->Last+1
List MakeEmpty(); //初始化顺序表
int Find(ElementType X,List L); //查找 X 第一次出现的下标
void Insert(ElementType X,int i,List L); //在下标为 i 的地方插入 X
void Delete(int i,List L); //删除下标为 i 的当前值
ElementType FindKth(int K,List L); //返回下标为 K 的当前值
int Length(List L); //返回顺序表的长度
//初始化
List MakeEmpty(){
List L;
L = (List)malloc(sizeof(struct LNode));
L->Last = -1;
return L;
}
// 按值查找
int Find(ElementType X,List L){
int i=0;
while(i <= L->Last && L->Data[i] != X)
i++;
if(L->Last < i) //如果没找到,返回 -1
return -1;
else // 找到后返回下标
return i;
}
// 插入
void Insert(ElementType X,int i,List L){
int j;
if(L->Last == MAXSIZE-1){ //位置已满
printf("表满");
return;
}
if(i < 0 || L->Last+1 < i){ //位置越界,如果将数插入 L->Data[L->Last+1],下面都不用腾位置了
printf("位置不合法");
return;
}
for(j=L->Last;j>=i;j--) // 从后往前依次向后挪一个,给 a[i]腾出位置
L->Data[j+1] = L->Data[j];
L->Data[i] = X; //新元素插入
L->Last++; // Last仍然指向最后元素
return;
}
//删除
void Delete(int i,List L){
int j;
if(i < 0 || L->Last <i){ //位置越界,而删除最多到 L->Data[L->Last]
printf("L->Data[%d]不存在元素",i);
return;
}
for(j=i;j<=L->Last;j++) // 从前往后依次向前挪一个,将 a[i] 覆盖了
L->Data[j-1] = L->Data[j];
L->Last--; // Last仍然指向最后元素
return;
}
// 按序查找
ElementType FindKth(int K,List L){
if(K < 0 || L->Last < K){ //位置越界
printf("L->Data[%d]不存在元素",K);
return;
}
return L->Data[K];
}
//表长
int Length(List L){
return L->Last+1;
}
int main(){
int i=0;
L = MakeEmpty();
Insert(11,0,L);
printf("在线性表L-Data[0]插入11\n");
Insert(25,0,L);
printf("在线性表L-Data[0]插入25\n");
Insert(33,0,L);
printf("在线性表L-Data[0]插入33\n");
Insert(77,0,L);
printf("在线性表L-Data[0]插入77\n");
printf("此时的线性表为:");
for(i=0;i<Length(L);i++)
printf("%d ",L->Data[i]);
printf("\n");
printf("查找值为12的下标是:%d\n",Find(12,L));
printf("下标为3的线性表的值是:%d\n",FindKth(3,L));
Delete(2,L);
printf("删除线性表中下标为2的元素\n");
Delete(2,L);
printf("删除线性表中下标为2的元素\n");
printf("此时的线性表为:");
for(i=0;i<Length(L);i++)
printf("%d ",L->Data[i]);
printf("\n");
return 0;
}
线性表的链式存储结构
- 线性表的链式存储是指通过一组任意的存储单元来存储线性表中的数据元素。
- 头结点和头指针的区别?
- 不管带不带头结点,头指针始终指向链表的第一个结点,而头结点是带头结点链表中的第一个结点,结点内通常不存储信息
- 为什么要设置头结点?
- 1.处理操作起来方便 例如:对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了
- 2.无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了。
单链表的操作
- 1.头插法建立单链表:
- 建立新的结点分配内存空间,将新结点插入到当前链表的表头
- 2.尾插法建立单链表:
- 建立新的结点分配内存空间,将新结点插入到当前链表的表尾
- 3.按序号查找结点
- 在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。
- 4.按值查找结点
- 从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。
- 5.插入
- 插入操作是将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i−1个结点,再在其后插入新结点。
- 算法思路:
1.取指向插入位置的前驱结点的指针
① p=GetElem(L,i-1);
2.令新结点s的指针域指向p的后继结点
② s->next=p->next;
3.令结点p的指针域指向新插入的结点s
③ p->next=s;
- 6.删除
- 删除操作是将单链表的第i个结点删除。先检查删除位置的合法性,然后查找表中第i−1个结点,即被删结点的前驱结点,再将其删除。
- 算法思路:
1.取指向删除位置的前驱结点的指针 p=GetElem(L,i-1);
2.取指向删除位置的指针 q=p->next;
3.p指向结点的后继指向被删除结点的后继 p->next=q->next
4.释放删除结点 free(q);
#include<stdio.h>
#include<malloc.h>
typedef int ElementType; // ElementType 可定义为任意类型
typedef struct LNode *List;
struct LNode{
ElementType Data; //数据域
List Next; // 下一个链表的地址
};
List L;
List MakeEmpty(); //初始化链表
int Length(List L); // 以遍历链表的方法求链表长度
List FindKth(int K,List L); // 按序号查找
List Find(ElementType X,List L); // 按值查找
List Insert(ElementType X,int i,List L); //将 X 插入到第 i-1(i>0) 个结点之后
List Delete(int i,List L); // 删除第 i(i>0) 个结点
void Print(List L); // 输出链表元素
// 初始化链表
List MakeEmpty(){
List L = (List)malloc(sizeof(struct LNode));
L = NULL;
return L;
}
//求表长
int Length(List L){
List p = L;
int len=0;
while(p){ // 当 p 不为空
p = p->Next;
len++;
}
return len;
}
// 按序查找
List FindKth(int K,List L){
List p = L;
int i = 1; //从 1 开始
while(p && i<K){
p = p->Next;
i++;
}
if(i == K) // 找到了
return p;
else // 未找到
return NULL;
}
// 按值查找
List Find(ElementType X,List L){
List p = L;
while(p && p->Data!=X)
p = p->Next;
// 找到了,返回 p
// 未找到,返回 NULL,此时 p 等于 NULL
return p;
}
/* 插入
1. 用 s 指向一个新的结点
2. 用 p 指向链表的第 i-1 个结点
3. s->Next = p->Next,将 s 的下一个结点指向 p 的下一个结点
4. p->Next = s,将 p 的下一结点改为 s */
List Insert(ElementType X,int i,List L){
List p,s;
if(i == 1){ // 新结点插入在表头
s = (List)malloc(sizeof(struct LNode));
s->Data = X;
s->Next = L;
return s; //插入的结点为头结点
}
p = FindKth(i-1,L); // 找到第 i-1 个结点
if(!p){ // 第 i-1 个结点不存在
printf("结点错误");
return NULL;
}else{
s = (List)malloc(sizeof(struct LNode));
s->Data = X;
s->Next = p->Next; //将 s 的下一个结点指向 p 的下一个结点
p->Next = s; // 将 p 的下一结点改为 s
return L;
}
}
/* 删除
1. 用 p 指向链表的第 i-1 个结点
2. 用 s 指向要被删除的的第 i 个结点
3. p->Next = s->Next,p 指针指向 s 后面
4. free(s),释放空间
*/
List Delete(int i,List L){
List p,s;
if(i==1){ //如果要删除头结点
s = L;
if(L) // 如果不为空
L = L->Next;
else
return NULL;
free(s); // 释放被删除结点
return L;
}
p = FindKth(i-1,L); // 查找第 i-1 个结点
if(!p || !(p->Next)){ // 第 i-1 个或第 i 个结点不存在
printf("结点错误");
return NULL;
}else{
s = p->Next; // s 指向第 i 个结点
p->Next = s->Next; //从链表删除
free(s); // 释放被删除结点
return L;
}
}
// 输出链表元素
void Print(List L){
List t;
int flag = 1;
printf("当前链表为:");
for(t = L;t;t =t->Next){
printf("%d ",t->Data);
flag = 0;
}
if(flag)
printf("NULL");
printf("\n");
}
int main(){
L = MakeEmpty();
Print(L);
L = Insert(11,1,L);
L = Insert(25,1,L);
L = Insert(33,2,L);
L = Insert(77,3,L);
Print(L);
printf("当前链表长度为:%d\n",Length(L));
printf("此时链表中第二个结点的值是:%d\n",FindKth(2,L)->Data);
printf("查找22是否在该链表中:");
if(Find(22,L))
printf("是!\n");
else
printf("否!\n");
printf("查找33是否在该链表中:");
if(Find(33,L))
printf("是!\n");
else
printf("否!\n");
L = Delete(1,L);
L = Delete(3,L);
printf("----------删除后-----\n");
Print(L);
return 0;
}
循环链表&&静态链表
- 循环单链表:循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环
- 循环双链表:类比循环单链表,循环双链表链表区别于双链表就是首尾结点构成环
- 当循环双链表为空表时,其头结点的prior域和next域都等于Head。
- 静态链表:静态链表是用数组来描述线性表的链式存储结构。
- 数组第一个元素不存储数据,它的指针域存储第一个元素所在的数组下标。链表最后一个元素的指针域值为-1。