数据结构-U2 线性表
2.1 线性表
线性表
是具有相同数据类型的n个元素的有限序列。有前驱和后继。
是一种逻辑结构。
根据不同的存储方式,分为:
- 顺序存储→顺序表
- 链式存储→单链表,双链表,循环链表,静态链表
线性表的基本操作
初始化表,InitList(&L) 初始化表,构造一个空的线性表。
求表长,Length(L) 返回线性表L的长度,即L中数据元素的个数。
按位(键位)查找操作,GetElem(L,i) 获取表中第i个位置的元素的值。
按值(关键字)查找操作,LocateElem(L,e) 获取表L中查找具有给定值e的元素。
插入操作,ListInsert(&L,i,e) 在表L中的第i个位置上,插入指定元素e。
删除操作,ListDelete(&L,i,&e) 删除表L中第i个位置的元素,并用e返回删除元素的值。
只需要读取L → 用
L(传值)需要修改L本身 → 用
&L(传地址)
2.2 线性表的顺序表示
顺序表
线性表的顺序存储,叫顺序表。
优点:①随机存取(适合用数组来描述)。②存储密度高。
缺点:①插入和删除麻烦(插入要移动n/2个元素,删除要移动(n-1)/2个元素)。②存储需要分配连续空间。
sizeof(ElemType):每个数据元素所占用存储空间的大小
数组的下标【即索引】从0开始。
线性表的下标【即位置】从1开始。
基本操作
1.顺序表的初始化
1 | // SqList L; // 声明一个顺序表 |
2.插入操作
在顺序表L的第i个位置插入新元素e(1≤i≤L.length+1)。
位置是从1开始的,索引是从0开始的。
length是长度,数值上=位置最大值,或=索引+1。
最好时间复杂度:O1
最坏时间复杂度:On
平均时间复杂度:On(平均移动次数n/2)
1 | bool ListInsert {SqList &L,int i,ElemType e}{ |
3.删除操作
删除顺序表L第i个位置(索引i-1⭐)的元素,用引用变量e返回。
最好时间复杂度:O1
最坏时间复杂度:On
平均时间复杂度:On(平均移动次数n-1 /2)
1 | bool ListDelete(SqList &L,int i,ElemType &e){ |
4.按值查找
在顺序表L中查找第一个元素等于e的元素,并返回其位序。
最好时间复杂度:O1
最坏时间复杂度:On
平均时间复杂度:On(平均比较次数n+1 /2)
1 | int LocalElem(SqList L,ElemType e){ |
大题部分
10. 【2010 统考真题】
设将 $n$($n > 1$)个整数按序放到一维数组 $R$ 中。设计一个在时间和空间两方面都尽可能高效的算法,将 $R$ 中保存的序列循环左移 $p$($0 < p < n$)个位置,即将 $R$ 中的数据由
$$
(X_0, X_1, \cdots, X_{n-1})
$$
变换为
$$
(X_p, X_{p+1}, \cdots, X_{n-1}, X_0, X_1, \cdots, X_{p-1}).
$$
要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++ 或 Java 语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1 | // 1.算法思想:三次逆置,前面部分逆置,后面部分逆置,整体再逆置; |
11. 【2011 统考真题】
一个长度为 $L$($L \geq 1$)的升序序列 $S$,处在第 $\lfloor L/2 \rfloor$ 个位置的数称为 $S$ 的中位数。例如,若序列
$$
S_1 = (11, 13, 15, 17, 19)
$$
则 $S_1$ 的中位数是 15。两个序列的中位数是它们所有元素的升序序列的中位数。例如,若
$$
S_2 = (2, 4, 6, 8, 20)
$$
则 $S_1$ 和 $S_2$ 的中位数是 11。现在有两个等长升序序列 $A$ 和 $B$,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列 $A$ 和 $B$ 的中位数。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++ 或 Java 语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1 | // 1.算法思想: |
12. 【2013 统考真题】
已知一个整数序列
$$
A = (a_0, a_1, \cdots, a_{n-1})
$$
其中 $0 \leq a_i < n$($0 \leq i < n$),若存在
$$
a_{p_1} = a_{p_2} = \cdots = a_{p_m} = x
$$
且 $m > n/2$($0 \leq p_k < n, 1 \leq k \leq m$),则称 $x$ 为 $A$ 的主元素。例如
$$
A = (0, 5, 5, 3, 5, 7, 5, 5)
$$
则 5 为主元素;又如
$$
A = (0, 5, 5, 3, 5, 1, 5, 7)
$$
则 $A$ 中没有主元素。假设 $A$ 中的 $n$ 个元素存储在一个一维数组中,请设计一个尽可能高效的算法,找出 $A$ 的主元素。若存在主元素,则输出该元素;否则输出 -1。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++ 或 Java 语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1 | // 1.算法思想: |
13. 【2018 统考真题】
给定一个含 $n$($n \geq 1$)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如,数组
$$
{-5, 3, 2, 3}
$$
中未出现的最小正整数是 1;数组
$$
{1, 2, 3}
$$
中未出现的最小正整数是 4。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++ 语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1 | // 1.算法思想: |
14. 【2020 统考真题】
定义三元组 $(a, b, c)$($a, b, c$ 均为整数)的距离为
$$
D = |a - b| + |b - c| + |c - a|.
$$
给定 3 个非空整数集合 $S_1, S_2$ 和 $S_3$,按升序排列存储在 3 个数组中。请设计一个尽可能高效的算法,计算并输出所有可能的三元组 $(a, b, c)$($a \in S_1, b \in S_2, c \in S_3$)中的最小距离。例如
$$
S_1 = {1, 0, 9},\quad S_2 = {25, -10, 10, 11},\quad S_3 = {2, 9, 17, 30, 41}
$$
则最小距离为 2,相应的三元组为 $(9, 10, 9)$。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 语言或 C++ 语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
1 | // 1.算法思想:对于上面的公式,实际上等于三个数中最大值与最小值的差的两倍。 |
2.3 线性表的链式表示
单链表
线性表的链式存储,也称单链表。
对于每个链表节点,除存放元素自身的信息外,还需要存放一个指向其后继的指针。
| data | next | :data为数据域,存放数据元素。next为指针域,存放后继节点的地址。
缺点:是非随机存储的存储结构,需要依次查找。
通常来说,用一个头指针L(或head)来标识一个单链表,指出链表的起始地址。
表尾节点的指针域为NULL(用^表示)。头指针为NULL时表示一个空表。
头节点:单链表第一个数据节点之前可以增加一个头节点(Optional)
- 好处:①可以使在链表第一个位置上的操作和别的无不同。②无论链表是否为空,头指针都是指向头节点的,是非空指针。

基本操作
1.单链表的初始化
带头节点的单链表的初始化:创造一个头节点,并让头指针指向头节点,头节点的next域初始化为NULL
1 | bool InitList(LinkList &L){ |
不带头节点的单链表的初始化:只需将头指针L初始化为NULL
1 | bool InitList(LinkList &L){ |
2.求表长(带头节点版本)
计算单链表中数据结点的个数
表长不包括头节点长度的。
时间复杂度:On
1 | int Length(LinkList L){ |
不带头节点的实现
1 | int Length(LinkList L){ |
3.按序号查找结点
寻找第i个结点,返回该节点的指针。
1 | LNode *GetElem(LinkList L,int i){ |
4.按值查找表结点
若某结点的data域等于给定值e,返回该节点的指针。
时间复杂度为:On
1 | LNode *LocateElem(LinkList L,ElemType e){ |
5.插入结点操作
将值为x的新节点插入到单链表的第i个位置。(后插:找到待插入位置的前驱,即第i-1个结点,再在其后插入)
时间复杂度:On
1 | bool ListInsert(LinkList &L,int i,ElemType e){ |
交换两个已存在结点的位置
1 | // 先把s移动到p后面,再交换数据 |
真正的交换指针
1 | // 找到p的前驱prev_p和s的前驱prev_s |
1 | 初始状态: |
6.删除结点操作
将单链表的第i个结点删除。【查找表中第i-1个结点(即被删结点的前驱,时期连接到第i+1个结点,再删除第i个结点】
时间复杂度:On
1 | bool ListDelete(LinkList &L,int i,ElemType &e){ |
7.采用头插法建立单链表
将新节点插入到当前链表的表头,即头节点之后
1 | LinkList List_HeadInsert(LinkList &L){ |
如果单链表不带头节点,则上述代码中哪些地方需要修改?→主要修改的地方:因为在头部插入了新节点,每次插入新节点后,都需要将它的地址赋值给头指针L。
双链表
单链表结点只有一个指向后继的指针,所以只能从前往后遍历,要访问某个结点的前驱(插入,删除操作时),只能从头开始遍历。On
双链表,有两个指针prior和next。表头结点的prior域和尾结点next域都是NULL(^)
优点:在插入和删除操作上,比单链表快,O1。
1 | typedef struct DNode{ //定义双链表结点类型 |
基本操作
1.插入操作
在双链表中p所指的结点之后插入结点*s
1 | s->next = p->next; |
2.删除操作
1 | p->next = p->next->next; |
循环链表(单)
和单链表的区别:循环单链表的表中最后一个结点的指针不是NULL,而改为指向头节点,从而整个链表形成一个环。
循环单链表的判空条件:不是头节点的指针是否为空,而是头节点的指针是否等于头指针L。
优点:循环单链表可以从表中的任意一个结点开始遍历整个链表,而不是只能从表头开始遍历。(只是效率是On)
有时,对循环单链表可以不设头指针仅设尾指针,对表头表尾插入元素都只要O(1)。
静态链表
静态链表是用数组描述的链式存储结构。
与普通链表中的指针不同的是,这里的指针是结点在数组中的相对地址(数组下标),也称游标。
Tips:
在尾部添加元素→用尾指针
双向遍历/插入/删除→双链表
循环问题→循环链表
顺序表和链表的比较
存取读写:链表只能从表头开始依次顺序存取。
查找:
- 对于按值查找:顺序表无序时:两者均为On;顺序表有序时,可采用折半查找,Olog₂n。
- 对于按序号查找:顺序表O1,链表On。
插入和删除:顺序表需要移动半个表长的元素。链表只需修改相关结点的指针域(good!)
大题部分

1 | // 1.算法思想:先设置两个指针p和q,初始时都指向链表的第一个有效节点(即头节点list的下一个结点) |

1 | // 1.算法思想:两个链表在合并前的长度可以不同,如果直接用两个指针从各自的头部同时出发,它们无法同时到达公共结点。我们要做的,就是消除这个长度差。 |

1 |

1 |




