本文内容源于对《数据结构(C语言版)》(第2版)、王道讲解学习所得心得、笔记整理和总结。
1、有序表(Order List):数据元素相互之间可以比较,且数据元素在线性表中依值非递减或非递增有序排列。
2、有序集合:集合中的元素有序排列。
求解有序集合的并集问题,考点为有序表的合并,其又可分为顺序有序表的合并、链式有序表的合并。本文以举例子说明此两种合并,部分题目内含多种解法,讲解详细。
其中,顺序有序表的合并,类似于归并排序算法,所以,可搭配以下链接进行学习:
【考研】数据结构考点——归并排序_住在阳光的心里的博客-CSDN博客
【考研】《数据结构》知识点总结.pdf_考研数据结构知识点总结背诵-其它文档类资源-CSDN文库
已知两个有序集合 A = (3,5,8,11),B = (2,6,8,9,11,15,20),数据元素按值非递减有序排列,现要求一个新的集合
,使集合 C 中的数据元素仍按值非递减有序排列。
解析:记三个线性表 LA、LB 和 LC分别表示集合 A 、B 和 C,其表长分别为 m 、n 和 m + n 。
可先假设 LC 为空表,再将 LA 或 LB 中的元素逐个插入 LC 中。
因为 LC 是按值非递减有序排列,所以可设两个指针 pa 和 pb 分别指向 LA、LB 中的某个元素,若设 pa 、pb 当前所指元素分别为 a 、b,则当前应插入到 LC 中的元素 c 为
,显然,指针 pa 和 pb 分别指向两个有序表的第一个元素,在所指元素插入 LC 之后,在 LA 或 LB 中顺序后移。
以下是有序表的顺序存储结构和链式存储结构相应合并算法的实现。
1、创建一个表长为 m + n 的空表 LC。
2、指针 pc 初始化,指向 LC 的第一个元素。
3、指针 pa 和 pb 初始化,分别指向 LA 和 LB 的第一个元素。
4、当指针 pa 和 pb 均未到达相应表尾时,则依次比较 pa 和 pb 所指向的元素值,从 LA 或 LB 中“摘取” 元素值较小的结点插入到 LC 的最后。
5、如果 pb 已到达 LB 的表尾,依次将 LA 的剩余元素插入 LC 的最后。
6、如果 pa 已到达 LA 的表尾,依次将 LB 的剩余元素插入 LC 的最后。
- //顺序表的存储结构
- typedef struct{
- ElemType *elem;
- int length;
- }SqList;
-
- //已知顺序有序表 LA 和 LB 的元素按值非递减排列
- //归并 LA 和 LB 得到新的顺序有序表 LC, LC 的元素也按值非递减排列
- void MergeList_Sq (SqList LA, SqList LB, SqList &LC){
- int m = LA.length, n = LB. length;
- LC.length = m + n; //新表长度为待合并两表的长度之和
-
- LC.elem = new ElemType[LC.length]; //为合并后的新表分配一个数组空间
- pc = LC.elem; //指针 pc 指向新表的第1个元素
-
- //指针 pa 和 pb 的初值分别指向两个表的第1个元素
- pa = LA.elem;
- pb = LB.elem;
-
- pa_last = LA.elem + m - 1; //指针 pa_last 指向 LA 的最后一个元素
- pb_last = LB.elem + n - l; //指针 pb_last 指向 LB 的最后一个元素
-
- while((pa <= pa_last) && (pb <= pb_last)){ // LA 和 LB 均未到达表尾
- if(*pa <= *pb) *pc++ = *pa++; //依次“摘取”两表中值较小的结点插入到 LC 的最后
- else *pc++ = *pb++;
- }
-
- while(pa <= pa_last) *pc++ = *pa++; //LB已到达表尾,依次将 LA 的剩余元素插入 LC 的最后
- while(pb <= pb_last) *pc++ = *pb++; //LA已到达表尾,依次将 LB 的剩余元素插入 LC 的最后
- }
(1)时间复杂度:O(m + n)
(2)空间复杂度:O(m + n)
【补充:合并线性表的完整代码,看下方】
- //C语言
- #include
- #include
-
- #define MaxSize 10
-
- //顺序表的存储结构
- typedef struct{
- int *elem;
- int length;
- }SqList;
-
- //已知顺序有序表 LA 和 LB 的元素按值非递减排列
- //归并 LA 和 LB 得到新的顺序有序表 LC, LC 的元素也按值非递减排列
- void MergeList_Sq(SqList LA, SqList LB, SqList *LC){
- int m = LA.length, n = LB.length;
- LC->length = m + n; //新表长度为待合并两表的长度之和
-
- LC->elem = (int*)malloc(MaxSize * sizeof(int)); //为合并后的新表分配一个数组空间
- int *pc = LC->elem; //指针 pc 指向新表的第1个元素
-
- //指针 pa 和 pb 的初值分别指向两个表的第1个元素
- int *pa = LA.elem;
- int *pb = LB.elem;
-
- int *pa_last = LA.elem + m - 1; //指针 pa_last 指向 LA 的最后一个元素
- int *pb_last = LB.elem + n - 1; //指针 pb_last 指向 LB 的最后一个元素
-
- while((pa <= pa_last) && (pb <= pb_last)){ // LA 和 LB 均未到达表尾
- if(*pa <= *pb) *pc++ = *pa++; //依次“摘取”两表中值较小的结点插入到 LC 的最后
- else *pc++ = *pb++;
- }
-
- while(pa <= pa_last) *pc++ = *pa++; //LB已到达表尾,依次将 LA 的剩余元素插入 LC 的最后
- while(pb <= pb_last) *pc++ = *pb++; //LA已到达表尾,依次将 LB 的剩余元素插入 LC 的最后
- }
-
- //创建顺序表
- void initList(SqList* L){
- L->elem = (int*)malloc(MaxSize * sizeof(int)); //构造一个空的顺序表,动态申请存储空间
-
- if (!L->elem){
- printf("初始化失败");
- exit(0);
- }
- L->length = 0; //空表的长度初始化为0
- }
-
- //插入函数,其中,e为插入的元素,i为插入到顺序表的位置
- void insertList(SqList* L, int i, int e){
- if ((i > L->length + 1) || (i < 1)) { //i值不合法
- printf("插入位置有问题\n");
- return;
- }
-
- if (MaxSize == L->length) {
- printf("当前存储空间已满\n");
- return;
- }
- //插入操作,需要将自插入位置之后的所有元素全部后移一位
- for (int j = L->length - 1; j >= i - 1; j--) {
- L->elem[j + 1] = L->elem[j];
- }
-
- L->elem[i - 1] = e; //后移完成后,直接插入元素
- L->length++; //表长加一
- }
-
- //输出顺序表中的元素
- void displayList(SqList L){
- for (int i = 0; i < L.length; i++) {
- printf("%d ", L.elem[i]);
- }
- printf("\n");
- }
-
- int main(){
- SqList LA, LB, LC;
-
- initList(&LA);
- initList(&LB);
- initList(&LC);
-
- for (int i = 1; i <= MaxSize; i++) {
- LA.elem[i - 1] = i;
- LA.length++;
- }
-
- for (int j = 1; j <= MaxSize; j++) {
- LB.elem[j - 1] = j + 2;
- LB.length++;
- }
-
- printf("顺序表LA(元素按值非递减排列):\n");
- displayList(LA);
-
- printf("顺序表LB(元素按值非递减排列):\n");
- displayList(LB);
-
- printf("归并 LA 和 LB 得到新的顺序有序表 LC:\n");
- MergeList_Sq(LA, LB, &LC);
- displayList(LC);
- }
运行结果如下:

1、指针 pa 和 pb 初始化,分别指向 LA 和 LB 的第一个结点。
2、LC 的结点取值为 LA 的头结点。
3、指针 pc 初始化,指向 LC 的头结点。
4、当指针 pa 和 pb 均未到达相应表尾时,则依次比较 pa 和 pb 所指向的元素值,从 LA 或 LB 中“摘取”元素值较小的结点插人到LC的最后。
5、将非空表的剩余段插入到 pc 所指结点之后。
6、释放 LB 的头结点。
- //已知单链表 LA 和 LB 的元素按值非递减排列
- //归并 LA 和 LB 得到新的单链表 LC, LC 的元素也按值非递减排列
- void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC){
- //pa 和 pb 的初值分别指向两个表的第一个结点
- pa = LA->next;
- pb = LB->next;
-
- LC = LA; //用 LA 的头结点作为 LC 的头结点
- pc = LC; //pc 的初值指向 LC 的头结点
-
- while (pa && pb){ //LA和LB均未到达表尾,依次“摘取”两表中值较小的结点插入到 LC 的最后
- if (pa->data <= pb->data){ //“摘取” pa 所指结点
- pc->next = pa; //将 pa 所指结点链接到 pc 所指结点之后
- pc = pa; //pc 指向 pa
- pa = pa->next; //pa 指向下一结点
- }
- else{ //“摘取” pb 所指结点
- pc->next = pb; //将 pb 所指结点链接到 pc 所指结点之后
- pc = pb; //pc 指向 pb
- pb = pb->next; //pb 指向下一结点
- }
- }
-
- pc->next = pa?pa:pb; //将非空表的剩余段插入到 pc 所指结点之后
- delete LB; //释放 LB 的头结点
- }
(1)时间复杂度:O(m + n)
(2)空间复杂度:O(1)
有两个带头节点的降序单链表 L1、L2,较长的链表元素个数为 n。链表结点定义如下所示。请设计一个时间上尽可能高效算法,按升序输出两个链表中所有元素的权值。
- typedef struct LinkNode {
- double data; //权值
- struct LinkNode *next; //指向单链表的下一个结点
- } LinkNode, *Linklist;
将两个链表存入数组A中,通过对数组A进行快速排序,再从后到前升序输出数组。
时间复杂度为
,空间复杂度为 O(n)。
- void Qsort(double A[], int L, int R){ //a 数组保存数据,L 和 R 是边界
- if (L >= R) return; //当前区间元素个数 <= 1 ,则退出
- int pivot, i = L, j = R; //i 和 j 是左右两个数组下标移动
- swap(A[L], A[m]); //A[m]是在 A[L~R]之间随机选取的一个元素(快排优化)
- pivot = A[L]; //pivot 作为枢值参与比较
-
- while (i
- while (i < j && A[j] > pivot) j--;
- while (i < j && A[i] <= pivot) i++;
- if (i < j) swap(A[i], A[j]); //交换 A[i] 和 A[j]
- }
- swap(A[L], A[i]);
- Qsort(A, L, i-1); //递归处理左区间
- Qsort(A, i+1, R); //递归处理右区间
- }
-
- void ans(Linklist L1, L2){
- double A[maxn];
- int t = 0; // t 是新数组中元素个数
-
- Linklist p = L1->next; //p 指向 L1 链表的第一个元素
- while (p != null){ //链表 L1 转数组保存
- A[t++] = p->data;
- p = p->next;
- }
-
- Linklist q = L2->next; //q 指向 L2 链表的第一个元素
- while (q != null){ //链表 L2 转数组保存
- A[t++] = q->data;
- q = q->next;
- }
-
- Qsort(A, 0, t-1);
- for(int i = t - 1; i >= 0; i--){ //数组从后到前升序输出
- cout<", ";
- }
- }
(二)解法二:较优解(数组保存 + 指针后移归并)
1、算法思想
将两链表归并存入数组得到降序数组,然后从后到前升序输出数组。
2、算法分析
时间复杂度为 O(n),空间复杂度为 O(n)。
3、算法代码
- void ans(Linklist L1, L2){
- double A[maxn];
- int t = 0; //t 是新数组中元素个数
- Linklist p = L1->next; //p 指向 L1 链表的第一个元素
- Linklist q = L2->next; //q 指向 L2 链表的第一个元素
-
- while (p!= null && q != null) //归并两个降序链表,每次选较大元素
- if (p->data > q->data){
- A[t++] = p->data;
- p = p->next;
- }
- else{
- A[t++]=q->data;
- q=q->next;
- }
-
- while (p != null){ //如果 L1 链表中有剩余元素则放在数组末尾
- A[t++] = p->data;
- p = p->next;
- }
- while (q != null){ //如果 L2 链表中有剩余元素则放在数组末尾
- A[t++] = q->data;
- q = q->next;
- }
- for (int i = t-1; i >= 0; i--)
- cout << A[i] << ","; //数组从后到前升序输出
- }
(三)解法三:最优解(指针后移归并 + 头插法逆置)
1、算法思想
将两链表归并到链表 L3 中,归并时采用头插法(输入顺序与线性表中的逻辑顺序是相反的),得到的降序链表 L3,依次输 出链表中的元素。
2、算法分析
时间复杂度为 O(n),空间复杂度为 O(1)。
3、算法代码
- typedef struct LinkNode {
- double data; //权值
- struct LinkNode *next; //指向单链表的下一个结点
- } LinkNode, *Linklist;
-
-
- LinkNode L3; //定义全局变量 L3
- Linklist Head_Insert(Linklist p){ //头插法插入 L3 中
- Linklist p1 = p->next;
- p->next = L3->next;
- L3->next = p;
- p = p1;
- return p;
- }
-
- void ans(Linklist L1, L2){
- int t = 0; //t 是新数组中元素个数
- Linklist p = L1->next; //p 指向 L1 链表的第一个元素
- Linklist q = L2->next; //q 指向 L2 链表的第一个元素
- L3->next = null; //初始链表 L3 为空
-
- while (p != null && q != null){ //归并两个降序链表,每次选较大元素
- if (p->data > q->data)
- p = Head_Insert(p); //将 p 头插法插入 L3 中,p 指向下一个结点
- else
- q = Head_Insert(q); //将 q 头插法插入 L3 中,q 指向下一个结点
- }
-
- while (p != null){ //如果 L1 链表中有剩余元素则头插法插入 L3
- p = Head_Insert(p); //将 p 头插法插入 L3 中,p 指向下一个结点
-
- while (q != null) //如果 L2 链表中有剩余元素则头插法插入 L3
- q = Head_Insert(q); //将 q 头插法插入 L3 中,q 指向下一个结点
-
- p = L3->next;
-
- while (p != null){ //依次输出链表 L3 中的元素
- cout << p->data;
- p=p->next;
- }
- }