文章目录
- 线性表
- 考纲
- 线性表的定义和基本操作
- 1. 定义
- 2. 线性表的基本操作
- 线性表的顺序表示
- 1. 顺序表的定义
- 2. 顺序表基本操作的实现
- 初始化
- 插入-时间复杂度O(n)
- 删除-时间复杂度O(n)
- 按值查找-时间复杂度O(n)
- 线性表的链式表示
- 1. 单链表的定义
- 2. 单链表基本操作的实现
- 单链表的初始化
- 求表长 O(n)
- 按序号查找结点 O(n)
- 按值查找结点 O(n)
- 插入结点 O(n)
- 删除结点 O(n)
- 头插法建立单链表 O(n)
- 尾插法建立单链表 O(n)
- 3. 双链表
- 双链表的插入
- 双链表的删除
- 4. 循环链表
- 循环单链表
- 循环双链表
- 5. 静态链表
- 小结
线性表
考纲
- 线性表基本概念和实现
- 顺序存储和链式存储
- 线性表的应用
线性表的定义和基本操作
1. 定义
线性表:具有相同数据类型的n个数据元素的有限序列。
n为表长,值为0表示空表。第一个元素和最后一个元素叫表头元素和表尾元素。
元素有且只有一个前驱和后继,表头元素无前驱,表尾元素无后继。
线性表特点:
- 元素个数有限
- 元素逻辑上有顺序性
- 元素都是数据元素
- 元素的数据类型相同,占用存储空间大小相同
- 元素具有抽象性,不考虑元素所表示的内容,只讨论元素间的逻辑关系。
线性表、顺序表和链表的比较:
线性表表示的时一种逻辑结构,顺序表和链表表示的是存储结构
2. 线性表的基本操作
InitList(&L) //初始化表,建立一个空表
Length(L) //表长
LocateElem(L,e) //按值查找,e是值
GetElem(L,i) //按位查找,i是位置
ListInsert(&L,i,e) //将e插入到L表的i位置
ListDelete(&L,i,e) //删除L表中i位置的元素e
PrintList(L) //按顺序输出表L的元素
Empty(L) //判空,若L为空则返回True
DestroyList(&L) //销毁L表,释放内存
线性表的顺序表示
1. 顺序表的定义
顺序表:按顺序存储的线性表。任意一个数据元素都可以实现随机存取。
特点:表中元素的逻辑顺序和存储的物理顺序相同。
位序:元素在顺序表的位置。
线性表中元素的位序是从1开始的,数组中元素的下标是从0开始的。
一维数组静态分配,大小和空间事先固定,空间占满会溢出,程序崩溃。
//静态分配的顺序表 存储结构 描述
#define MaxSize 50 //顺序表最大长度
typedef struct{
ElemType data[MaxSize]; //元素,ElemType是数据类型
int Length; //顺序表的当前长度
}SqLst; //顺序表的类型定义
一维数组动态分配时,一旦数据空间占满,另开辟更大的存储空间,将原来表中的元素拷贝到新空间。
//静态分配的顺序表 存储结构 描述
#define InitSize 100 //表长度的初始定义
typedef struct{
ElemType *data; //指示动态分配数组的指针
int MaxSize,Length; //数组的最大容量和当前个数
}SeqList; //动态分配 顺序表的类型定义
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize); //c初始动态分配语句
L.data = new ElemType[InitSize]; //c++初始动态分配语句
动态分配不是链式存储,是顺序存储!!!
注意看这两段代码的不同之处!
malloc(memory allocation的缩写)方法用于动态分配具有指定大小的单个大块内存。它返回void类型的指针,其指针类型可以灵活转换,同时使用默认garbage value(内存中存储的随机值)初始化内存块。其语法格式为:
ptr = (指针转换格式*) malloc(分配的字节数)
顺序表的优点:随机访问、存储密度高。
顺序表的缺点:元素的插入和删除需要移动大量元素;需要连续的存储空间。
2. 顺序表基本操作的实现
只描述 初始化、插入、删除、按值查找 这四个操作。
初始化
动态分配和静态分配的初始化操作有所不同。
之前学习到,初始化操作的代码是:InitList(&L);
静态分配的顺序表,分配的空间固定,初始化只需要长度设置为0
//SqList L
void InitList(SqList &L){
L.Length = 0;
}
动态分配的顺序表要分配存储空间,定义长度为0,设置最大容量。
void InitList(SeqList &L){
L.data = (ElemType *)malloc(InitSize*sizeof(ElemType));
L.Length = 0;
L.MaxSize = InitSize;
}
插入-时间复杂度O(n)
在顺序表L的第i个位置上插入元素e
若i不合法,返回false;i合法则第i个元素及其后面的元素全部后移1位,插入元素,L长度加一,返回true。
bool ListInsert(SqList &L,int i,ElemType e){
if(i<1 | i>L.Length+1)
return false;
if(L.length >= MaxSize)
return false;
for(j=L.Length;j>i;j--)
L.data[j] = L.data[j-1]; //从最后一个元素开始后移,不理解就在纸上写一个数组,后移观察下标变化
L.data[i-1] = e;
L.length++;
return true;
}
删除-时间复杂度O(n)
在顺序表L的第i个位置上删除元素e,和插入异曲同工。插入操作元素是后移,删除操作元素是前移。
bool ListInsert(SqList &L,int i,ElemType e){
if(i<1 | i>L.Length+1)
return false;
L.data[i-1] = e; //将删除的元素赋值
for(j=i;j<L.length;j++)
L.data[j-1] = L.data[j]; //从第i个元素开始前移
L.Length--;
return true;
}
按值查找-时间复杂度O(n)
在顺序表中查找元素值等于e的元素,返回位序i。
int LocateElem(SqList &L,ElemType e){
int i; //定义位序
for(i=0,i<L.Length;i++)
if(L.data[i] = e)
return i+1;
return 0; //退出循环,说明查找失败
}
加油加油,学习过半~~
线性表的链式表示
1. 单链表的定义
单链表:线性表的链式存储。不可随机存取
单链表的结构:data为数据域,存放数据元素;next为指针域,存放后继结点的地址。
单链表结点类型的描述如下:
typedef struct DNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
使用头指针L来标识一个单链表,指出链表的起始地址,头指针为NULL标识一个空表。
头结点一般不携带信息。
通常单链表附加一个头结点,头指针L指向头结点。不带头结点的话,指针L指向第一个数据结点。
引入头结点的两个优点:
1.无需对第一个数据结点进行特殊处理
2.无论链表是否为空,头指针都指向头结点的非空指针(空表中的头结点的指针域为空),因此空表和非空表的处理得到了统一。
2. 单链表基本操作的实现
无特殊说明,本节默认携带头结点
单链表的初始化
带头结点:创建头结点,头指针指向头结点,头结点next域初始化为NULL。
bool InitList(LinkList &L){
L = (LNode*)malloc(sizeof(LNode)); //创建头结点
L->next = NULL;
return true;
}
不带头结点:头指针初始化为NULL
bool InitList(LinkList &L){
L = NULL; //头指针初始化为NULL
return true;
}
求表长 O(n)
计算单链表中数据结点的个数。
设置一个计数变量len
,访问一个结点便+1,直到访问到空结点。
int length(LinkList L){
int len = 0; //计数变量
LNode *p = L;
while(P->next!=NULL){
p = p->next;
len++;
}
return len;
}
单链表的长度不包含头结点!!
按序号查找结点 O(n)
从第一个结点开始,沿着next域从前往后寻找,直到找到第i个结点,返回该结点的指针。
LNode *GetElem(LinkList L,int i){
LNode *p = L; //p指向第一个结点
int j = 0;
while(p!NULL && j<i){
p=p->next; //指针向后移动
j++;
}
return p;
}
按值查找结点 O(n)
从第一个结点开始,依次比较各结点的数据域,当某结点的data域等于要查找的e值,则返回该指针
LNode *LocateElem(LinkList L,ElemType e){
LNode *p = L->next;
while(p->data!=e && p!=NULL){ //不满足条件,指针就后移
p=p->next;
}
return p;
}
插入结点 O(n)
将值为x的结点插入到单链表的第i个位置。
①和②的顺序一定不能反,不然会丢失后面的数据,操作就失败了
bool ListInsert(LinkList &L,int i,ElemType e){
LNode *p = L; //p指向第一个结点
int j = 0; //记录当前结点的位序
while(p!=NULL && j<i-1){ //j小于i-1时指针就后移,直到找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL)
return false;
LNode *s = (LNode*)malloc(sizeof(LNode)); //创建s
s->data = e; //data数据域值为e
s->next = p->next; //①
p->next = s; //②
return true;
}
上述插入为 后插
前插:在某结点前插入一个元素
前插可以转化为后插操作来实现:就是正常进行后插操作后,前后两个结点的data互换一下,这样在逻辑上也就实现了效果。
s->next = p->next; p->next = s; temp = p->data; //借助temp作为容器,暂放数据 p->data = s->data; s->data = temp;
删除结点 O(n)
删除单链表的第i个节点
bool ListDelete(LinkList &L,int i,ElemType &e){
LNode *p = L; //p指向头结点
int j = 0; //计数
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
while(p==NULL)
return false;
LNode *q = p->next; //q指向p的下一个结点
q->data = e; //记录一下被删除的结点
p->next = q->next;
free(q); //释放内存
return true;
}
头插法建立单链表 O(n)
LinkList List_HeadInsert(LinkList &L){
LNode *p;int x; //设置元素类型
L = (LNode *)malloc(sizeof(LNode)); //创建头结点
L->next = NULL; //初始化为空表
scanf("%d",&x); //输入结点的值
while(x!==9999){
p = (LNode*)malloc(sizeof(LNode)); //创建新结点
p->data = x;
p->next = L->next; //①
L->next = p; //②
scanf("%d",&x);
}
return L;
}
读入数据的顺序和生成的链表的元素的顺序是相反的!,可以用来实现链表的逆置。
尾插法建立单链表 O(n)
增加一个尾指针r,始终指向单链表的最后一个元素。
LinkList List_TailInsert(LinkList &L){
int x;
L = (LNode *)malloc(sizeof(LNode)); //创建头结点
LNode *s,*r = L; //r为尾指针,指向L
scanf("%d",&x); //输入结点的值
while(x!==9999){
s = (LNode*)malloc(sizeof(LNode)); //创建新结点
s->data = x; //赋值
r->next = s;
r=s; //指针后移
scanf("%d",&x);
}
r->next = NULL; //尾指针的next域置空
return L;
}
3. 双链表
单链表结点中只要一个指向其后继的指针,使得单链表只能从前往后依次遍历。
双链表中有两个指针,分别为prior
和next
,分别指向直接前驱和直接后继。
双链表的数据结构描述如下:
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
双链表的按值查找和按位查找和单链表相同,为O(n)
插入和删除操作的时间复杂度为O(1)
双链表的插入
主要操作代码如下:
① s->next = p->next;
p->next->prior = s;
s->prior = p;
④ p->next = s;
插入的语句不唯一,但是①必须要在④的前面!!!
双链表的删除
p->next = q->next;
q->next->prior = p;
free(q);
4. 循环链表
循环单链表
其和单链表的区别:最后一个结点的next不是NULL,而是指向头结点,从而形成一个环。
循环单链表的插入和删除算法和单链表几乎一样,但是操作若是在表尾进行,循环单链表不需要判断是否处于表尾。
循环单链表可以从表中的任意位置开始遍历整个单链表。
循环双链表
在循环双链表中,头结点的prior
指针还要指向表尾结点。
5. 静态链表
静态链表是用数组来描述线性表的链式存储结构,结点也有data
和next
。
但是这里的指针指的是 结点在数组中的相对地址,称为游标。如下图,a的next存储的是1,对应的是b,b的next存储的是6,对应的是c。
静态链表需要一块连续的存储空间。
图有点黑因为宿舍关灯了hhh
静态链表的结构类型的描述如下:
#define MaxSize 50;
typedef struct {
ElemType data;
int next;
}SLinkList[MaxSize];
小结
基础知识部分完成
后续将 课后题 拉出来详解
多敲代码,掌握基础结构的书写!!!
持续更新~~