数据结构有序表的合并
数据结构:有序表的合并
,本文介绍有序表的合并。这两种对有序表的操作,是数据结构中常考的内容,特别是在 408 考卷中,在算法设计的题目中,有可能会考查对有序表的操作。那么,这两篇文章中的方法就是能够拿到基本分数的方法。
例题 3.10.3 假设有两个非递减有序表 LA 和 LB,设计一个算法,将它们合并成一个非递减有序表 LC(假设每个有序表中和两个有序表间均不存在重复元素)。
【解】
采用二路归并算法将两个有序表合并成一个有序表,其过程是:创建空表 LC,且长度是 LA 和 LB 长度之和。分别扫描 LA 和 LB 两个有序表:
- 当两个有序表都没有扫描完成时,循环执行:比较 LA 和 LB 的当前元素,将其中较小的元素插入到 LC 中,再从较小元素所在的有序表中取下一个元素。
- 重复此过程,直到 LA 或 LB 比较完毕,最后将未比较的有序表中余下的元素插入到 LC 中。
举例:
LA = (1, 3, 5), LB = (2, 4, 8, 10)
,按照上述算法,过程如图 3.10.1 所示。
图 3.10.1 合并有序表
(1)采用顺序表存放有序表
【算法描述】
//顺序表存储结构
typedef struct{
ElemType *elem; //存储空间的基地址
int length; //当前长度
}SqList; //顺序表的结构类型为 SqList
void MergeList(SqList LA, SqList LB, SqList &LC){
//i,j 是 LA,LB 的下标,k 是 LC 中的元素个数
int i = 0, j = 0, k = 0;
LC = (SqList *)malloc(sizeof(SqList));
//LA 和 LB 均未到达表尾
while(i < LA.length && j < LB.length){
if (LA.elem[i] < LB.elem[j]){
LC.elem[k] = LA.elem[i];
i++;
k++;
} else {
LC.elem[k] = LB.elem[j];
j++;
k++;
}
}
//LA尚未扫描完毕,将其余元素插入 LC 中
while(i < LA.length){
LC.elem[k] = LA.elem[i];
i++;
k++;
}
//LB尚未扫描完毕,将其余元素插入 LC 中
while(j < LB.length){
LC.elem[k] = LB.elem[j];
j++;
k++;
}
LC.length = k;
}
上述写法比较容易理解,但写法不紧凑,可以对上述写法进行改进。
void MergeList(SqList LA, SqList LB, SqList &LC){
LC.length = LA.length + LB.length; //新表的长度
LC.elem = new ElemType[LC.length]; //为新表分配数组空间
pc = LC.elem; //指针 pc 指向新表的第一个元素
pa = LA.elem;
pb = LB.elem;
//指针 pa_last 指向 LA 的最后一个元素
pa_last = LA.elem + LA.length - 1;
pb_last = LB.elem + LB.length - 1;
//LA, LB 均未达到表尾
while((pa <= pa_last) && pb <= pb_last){
if(*pa <= *pb)
*pc++ = *pa++;
else
*pc++ = *pb++;
}
//LB已到表尾,依次将 LA 剩余元素插入 LC 的最后
while(pa <= pa_last) *pc++ = *pa++;
//LA已到表尾,依次将 LB 剩余元素插入 LC 的最后
while(pb <= pb_last) *pc++ = *pb++;
}
【算法分析】
假设 LA 和 LB 的长度分别为
m m
m 和
n n
n ,元素比较次数:
最好情况下的比较次数是
min ( m , n ) \min(m,n)
min
(
m
,
n
) 。时间复杂度是
O ( min ( m , n ) ) O(\min(m,n))
O
(
min
(
m
,
n
))
最坏情况下的比较次数是
m + n − 1 m+n-1
m
n
−
1 。时间复杂度是
O ( m + n ) O(m+n)
O
(
m
n
)
空间复杂度为
O ( m + n ) O(m+n)
O
(
m
n
) 。
(2)采用单链表存放有序表
假设头指针为
LA
和
LB
的单链表分别为线性表 LA 和 LB 的存储结构,现要归并 LA 和 LB 得到单链表 LC。因为链表结点之间的关系是通过指针指向建立起来的,所以用链表进行合并不需要另外开辟存储空间,可以直接利用原来两个表的存储空间,合并过程中只需把 LA 和 LB 两个表中的结点重新进行链接即可。
按照二路归并的思想,需设立 3 个指针
pa
、
pb
和
pc
:
pa
和pb
分别指向 LA 和LB 中当前待比较插入的结点,pc
指向 LC 中当前最后一个结点(LC 的表头结点设为 LA 的表头结点)。- 指针的初值:
pa
和pb
分别指向 LA 和 LB 表中的第一个结点,pc 指向空表 LC 中的头结点。通过比较指针pa
和pb
所指向的元素的值,依次从 LA 或 LB 中“摘取”元素值较小的结点插入到 LC 的最后,当其中一个表变空时,只要将另一个表的剩余段链接在pc
所指结点之后即可。
【算法描述】
//单链表存储结构
typedef struct LNode{
ElemType data; //结点的数据域
struct LNode * next; //结点的指针域
}LNode, *LinkList; //LinkList 为指向结构体 LNode 的指针类型
void MergeList(LinkList &LA, LinkList &LB, LinkList &LC){
//LA, LB 是带头结点的单链表
pa = LA->next;
pb = LA->next;
LC = LA; //用 LA 的头结点作为 LC 的头结点
pc = LC; //pc 的初始值指向 LC 的头结点
// LA, LB 均未到达表尾,依次“摘取”其中较小的结点插入到 LC 最后
while(pa && pb){
if(pa->data < pb->data){
pc->next = pa; //将 pa 所指结点作为到 pc 所指结点的后继
pc = pa; //pc 指向 pa,即为 LC 的尾结点指针
pa = pa->next; //pa 指向下一个结点
} else {
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa ? pa: pb; //将非空表的剩余段插入到 pc 所指结点之后
delete LB; //释放 LB 的头结点
}
【算法分析】
时间复杂度
O ( m + n ) O(m+n)
O
(
m
n
)
空间复杂度
O ( 1 ) O(1)
O
(
1
)