要了解线性表,首先要了解线性结构
线性结构的特点
- 只有一个首节点和尾结点
- 除首尾节点外,其他节点只有一个直接前驱和直接后继
简而言之,线性结构反映结构的逻辑关系是一对一的
线性结构包含线性表,堆栈,队列,字符串,数组。其中最常用的是线性表。
线性表的定义
用数据元素的有限序列表示

二十六个英文字母组成的英文表,元素间关系是线性
同一线性表中的元素必定有相同特性
线性表中的数据类型可以是简单类型也可以是复杂类型
线性表的顺序表示
线性表的顺序表示可以称为顺序存储结构或顺序映像
顺序存储:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
顺序存储方法:用一组地址连续的存储单元存储。可通过数组实现
线性表的重要基本操作
[!NOTE]
若函数括号内为(SqList &L)表示引用传递,可能会修改参数。若是(SqList L,则表示值传递,不能修改原数据)
定义
结构体中定义地址和长度
1 |
|
初始化(两种方法,引用与指针)
用new分配空间,再设置长度为0(空表长度为0)
引用
1 | status InitList_Sq(SqList &L){//构造一个空的顺序表 |
指针
1 | status InitList_Sq(SqList &L){ |
销毁线性表
用delete删除线性表
1 | void DestroyList(SqList &L){ |
清空线性表
清空线性表只需把长度置0即可
1 | void ClearList(SqList &L){ |
求线性表长度
L.length
1 | int GetLength(SqList L){ |
判断是否为空
长度为0则返回1 表示空了
1 | int IsEmpty(SqList L){ |
取值(获取第i个数据)
线性表通过下标直接定位元素
1 | int GetElem(SqList L,int i,ElemType &e){ |
查找(找值为e的数据元素)
1 | int LocateELem(SqList L,ElemType e){ |
插入(第i个前插入e)
步骤
- 判断插入位置是否合法
- 判断存储空间是否已满
- 将n至i的元素依次向后移动一位空出i位置
- 将要插入的e放入第i个位置
- 表长加一,插入成功返回ok
注意,第i个前插入e,则e下标为i-1
1 | status ListInsert_Sq(SqList &L,int i,ElemType e){ |
[!NOTE]
各种位置插入(n+1个可能)的平均移动次数为n/2
删除(第i个)
步骤
- 判断i是否合法
- 将欲删除的元素保留在e中
- 将第i+1至n位元素向前移动一个位置
- 表长减一,删除成功返回ok
[!CAUTION]
这里判断合法,i不能超过length,但是插入时是不能超过i+1
1 | status ListDelete_Sq(SqList &L.int i){ |
若要考虑在各种位置删除(共n种可能)的平均移动次数,该如何计算?

顺序存储结构特点
查找插入删除的平均时间为 O(n)
空间复杂度为O(1)
什么是链表?
链表是一种用于存储数据的数据结构,通过像链条一般的指针连接元素(得名原因)。它是线性表的链式存储映像,称为线性表的链式存储结构
它的显著优势是删除和插入数据十分方便(数组要O(n),链表只需O(1)),但寻找和读取数据表现欠佳(数组只需O(1),链表需要O(n))。
链表的组成分类
链表由一系列节点(node)组成,每个节点(同结点)有两个部分:
- 数据域:存储实际数据,(如int value)
- 指针域: 存储下一个节点的地址(如 node *next)
链表分为,单链表,双链表,循环链表。
单链表
结点只有一个指针域,称为单链表或者线性链表。由表头唯一确定,所以可以用头指针的名字来命名。若头指针是L,则链表命名为L

注意:后文列举代码默认已定义Node结构体
1 | typedef struct LNode{ |
双向链表(应该不考,学有余力可看)
有两个指针域(有左右之分)的链表,称为双链表

1 | typedef struct DuLNode{ |
循环链表(也应该不考)
首尾相接的链表是循环列表。(尾节点的 next 指向头节点)由于这个特性,在插入数据时需要判断链表是否为空,为空则自身循环,不空则正常插入数据。(从循环链表中的任何一个节点都可以找到其他任何节点,而单链表不行)

1 | void insertNode(int i, Node *p) { |
链表常见运算符和关键字
初见代码你也许疑惑,new是什么?
它是c++中的关键字,用于动态分配内存。
例如
1 | int *p=new int //分配一个int 大小的内存 |
->则是c/c++中的箭头运算符,等价于,先解引用指针,然后访问成员。
1 | int main(){ |
链表的一些概念
我们还提到了头结点的概念,其实与之对应的还有尾结点,头指针,首元结点。
- 头指针:指向链表中第一个元素的指针
- 头结点:是在链表的首元结点之前附设的一个结点,**数据域内可以为空,**只存放表长和空表标志等信息。但此结点不能计入链表长度值
- 首元结点:链表中存储第一个数据元素a1的结点
按有无头结点划分
有头结点时,当当头结点的指针域为空时表示空表。
为什么要设置头结点?
便于首元结点的处理。
**首元结点的地址保存在头结点的指针域中,**所以在链表的第一个位置上的操作和其他位置一致,无需进行特殊处理。
便于空表和非空表的统一
无论链表是否为空,头指针都是指向头结点的非空指针。
链表的特点
结点在存储器中的位置是任意的,逻辑相邻的数据元素在物理上不一定相邻
访问时只能通过头指针进入链表,所以寻找第一个结点和最后一个结点的所需时间不等。
这种存取元素的方法称为顺序存取法
单向链表的操作(重点,老师说了要考,可能要写代码当大题)
- 初始化 2. 取值 3. 查找 4. 插入 5. 删除
链表运算时间
查找:因线性链表只能顺序存取,所有查找时要从头指针找起,O(n)
插入删除:不需要移动元素,只需要修改指针,只需O(1). 但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为 O(n) 。
初始化
- new分配一个内存
- 头结点的指针域置空
注意,后文列举代码默认有此环境
1 |
|
取值(第i个元素赋值为e)
- 创建指针变量p用来访问节点内容
- 创建计数器j=1用来记录遍历到第几个元素了
- 遍历直到第i个节点,p要非空
- 判断位置合不合法(p是不是空的,i的值是否合理)
- e->data赋值为e
- 返回1
1 | //获取链表中第i个元素的值 |
销毁
创造一个指针变量p临时保存要删除的节点
遍历链表所有节点(while(L)),p指向当前节点,L移向下一个节点
删除p指向的节点
直接
delete L后,L->next无法再访问(因为L指向的内存已释放),导致后续节点丢失。
1 | int Destroy(LinkList &L){ |
清空
- 同销毁,但是地址要保留,所以需要再开一个遍历q保存下一个节点的地址
- 创建两个指针变量pq
- p指向第一个节点,while(p)开始遍历,把下一个节点的地址保存在q里面
- 删除p,让q=p,继续删除
- 删除完后,头结点指针域为空
1 | int Clear(LinkList &L){ |
查找
查找在线性表L中查找值为e的数据元素
LNode *LocataElem_L
1 | //在链表中查询值为e的节点 |
查找L中值为e的位置序号,失败返回0
1 | int LocateELem_L (LinkList L,Elemtype e) { |
插入(第i个节点前)
- 创建p指向头结点
- 创建j=0表示头结点是第0个节点
- while循环寻找第i-1个节点并将p指向它
- 判断i的合法性,是否小于1或大于表长
- 生成新节点s,s的数据域置为e
- 新节点的next指向p的后继节点a1**(此时s还未接入链表)**
- p的后继节点指向S
1 | int ListInsert_L(LinkList &L,int i,ElemType e){ |
删除
删除第i个节点
找到ai-1存储位置p
临时保存结点ai的地址在q中,以备释放
令p->next指向ai的后继节点ai+1(绕过ai)
将ai的值保存在e中
释放ai空间

1 | int ListDelete_L(LinkList &L,int i,ElemType &e){ |
关于p->next = q->next的绕过原理
假设一个链表 头节点-> A->B->C->NULL
p指向节点A,则p->next指向节点B,即q。
q->next指向c。
p->next=q->next就是直接让A指向C
求表长
1 | int ListLength_L(LinkList L){ |
判断是否为空
1 | int ListEmpty(LinkList L){ |
有序链表合并
1 | // 函数功能:将两个非递减有序的带头结点的单链表La和Lb合并为一个新的非递减有序链表Lc |
//后面的大概率不考,难度比较高,只说了考单链表。有序链表合并
前插法建单链表
从一个空表开始重复读入数据
- 生成新结点
- 将读入数据放到新节点
- 将该新节点插入到链表
1 | void CreateList_F(LinkList &L,int n){ |

尾插法建单链表
1 | void CreateList_L(LinkList &L,int n){ |
插入(指定位置插入新节点)
算法步骤
找到插入位置的前驱节点,生成一个新结点,新结点的数据域置为x,新结点* s的指针域指向结点ai,结点*p的指针域指向新节点 *s
1 | //在第i个位置插入新元素e,所有要在i-1个节点后面插入 |
单向循环链表的操作及约瑟夫环
初始化
1 | // 通常在头文件中这样定义 |
判断是否为空
1 | int ListEmpty_CL(LinkList L){ |
结点p之前插入新结点s
1 | Status ListInsert_CL(LinkList &L,LNode *p,EleType e){ |
删除点p的后继节点
1 | Status ListDelete_CL(LinkList &L,LNode *p,EleType &e){ |
合并链表
Ta是链表a的尾指针,Ta->next指向链表a的头结点。
1 | LinkList Connect(LinkList Ta,Linklist Tb){ |
约瑟夫环

1 | void Josephus(int n, int m) { |
双向链表
定义初始化
1 | typedef struct DuLNode{ |
插入
1 | // 函数功能:在带头结点的双向链表L中,第i个位置之前插入新元素e |
删除
1 | // 函数功能:删除带头结点的双向链表L中第i个位置的元素,并通过e返回被删除的值 |
题目
对于循环队列,front=(初始+删除的位置)%size
rear=(初始+加入的位置)%size

顺序表中元素地址计算公式:第i个元素地址 = 首地址 + (i-1) × 元素长度。
