[数据结构习题]线性表——合并数组的中位数
👉知识点导航💎:【数据结构】线性表——顺序存储
👉知识点导航💎:[王道数据结构习题]线性表——数组合并中的时间复杂度
👉[王道数据结构]习题导航💎: p a g e 18.11 page 18.11 page18.11
本节为线性表的习题 |
题目描述:
🎇思路①:双指针
🔱思路分析:
1. 用两个指针i,j分别指向两个升序数组的a[0],b[0]
2. 若
a
[
i
]
<
b
[
j
]
a[i]< b[j]
a[i]<b[j] ,则说明排序后
a
[
i
]
a[i]
a[i]在当前
b
[
j
]
b[j]
b[j]之前,a需要更大的数才能与b匹配,则a向后移动一个元素,继续比较当前b[j]
3. 若
a
[
i
]
>
=
b
[
j
]
a[i]>=b[j]
a[i]>=b[j],则
a
[
i
]
a[i]
a[i]排在
b
[
j
]
b[j]
b[j]之后,此时将b向后移动一个元素,再与当前a[i]比较
4. 最后,将指针i,j指向的数a[i],b[j]进行比较,较小者即为answer
因为要求中位数,所以比较需要限定次数k
那么,这里k是多少呢?
由于这里数组a,b是长度相同且已经排好序的,所以,我们进行3次比较后,可以使指针指向处于合并后数组的中间位置处
💫也就是指针移动3次,即可得到中间两数,k<3(length-1)
如果不好理解,我们可以举例来得出结论:
-
对于上述给出的数组:
合并后数组 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] [1,2,3,4,5,6,7,8] [1,2,3,4,5,6,7,8]
移动三次后
-
再看特殊的数组:
a = [ 1 , 2 , 3 , 4 ] a= [1, 2, 3, 4] a=[1,2,3,4]
b = [ 5 , 6 , 7 , 8 ] b=[5,6,7,8] b=[5,6,7,8]
合并后数组 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] [1,2,3,4,5,6,7,8] [1,2,3,4,5,6,7,8]
所以,对于长度为4的两个数组,无论如何,双指针总共移动三次后,都会指向中间两个数
因此,我们可以定义双指针函数:
int medium(int* a, int* b, int length) {
int i = 0, j = 0, k = 0; //用双指针i,j,分别指向两个数组,k用于统计次数( < length-1 )
for (; k < length - 1; k++) {
if (a[i] < b[j])
i++;
else
j++;
}
return (a[i] < b[j]) ? a[i]:b[j];
}
完整代码实现:
#include<iostream>
#define Maxsize 100
using namespace std;
int medium(int* a, int* b, int length) {
int i = 0, j = 0, k = 0;
for (; k < length - 1; k++) {
if (a[i] < b[j])
i++;
else
j++;
}
return (a[i] < b[j]) ? a[i]:b[j];
}
//输入流
void Initlist(int *L) {
int x;
int i = 0;
while (cin >> x) {
L[i++] = x;
if (cin.get() == '\n')
break;
}
}
int main() {
int a[Maxsize];
int b[Maxsize];
int length;
cout << "请输入数组长度:" << endl;
cin >> length;
cout << "请输入数组L1:" << endl;
Initlist(a);
cout << "请输入数组L2:" << endl;
Initlist(b);
int res=medium(a, b, length);
cout <<"中位数为:" << res << endl;
system("pause");
return 0;
}
vs输出结果:
但是,我们易分析知,当a,b数组长度为n时,我们需要循环 n − 1 n-1 n−1次,时间复杂度为 O ( n ) O(n) O(n)
那么,有没有更好的方法呢?
🎇思路①:二分法
🔱思路分析:
分别求两个升序序列的 A、B 的中位数 a 和 b:
- 若
a
=
b
a = b
a=b, 则已找到两个序列的中位数,返回
a
a
a
(两个数组中位数一样,则合并后中位数也一样) - 若 a < b a < b a<b, 则舍弃序列 A 中较小的一半, 舍弃序列 B 中较大的一半
- 若 a > b a > b a>b, 则舍弃序列 A 中较大的一半, 舍弃序列 B 中较小的一半
- 重复过程 ① 到 ③ 直到两个序列均只含一个元素为止,返回较小者
隐含关系:A,B数组长度始终保持相同 |
注意:我们每次在去掉一半时,要判断奇偶性,如果为奇数,则保留中间点;如果为偶数,则去掉中间点,否则,将会陷入死循环
如图:
可以看到,每次比较中位数之后,都会将两个数组同时砍掉一半,即查找范围缩小一半,时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)
二分函数实现:
定义左右指针,这里是"假删除",实际上只是指针的移动
非结构体实现法:
int medium(int A[], int B[], int n) {
int l1 = 0, r1 = n - 1, m1, l2 = 0, r2 = n - 1, m2;//分别代表A,B左右指针和中位数
while (l1 != r1 || l2 != r2) {
m1 = l1 + (r1 - l1) / 2; //分别取A,B数组当前的中位数位置
m2 = l2 + (r2 - l2) / 2;
if (A[m1] == B[m2])
return A[m1];
if (A[m1] < B[m2]) {
if ((r1 - l1 + 1) % 2 == 0) { //若当前数组长度为偶数
l1 = m1 + 1; //小的数组,舍弃中位数
r2 = m2; //大的数组,保留中位数
}
else { //为奇数
l1 = m1; //小的和大的数组均保留中位数
r2 = m2;
}
}
else {
if ((r1 - l1 + 1) % 2 == 0) { //若当前数组长度为偶数
l2 = m2 + 1;
r1 = m1;
}
else { //为奇数
l2 = m2;
r1 = m1;
}
}
}
return (A[l1] < B[l2]) ? A[l1] : B[l2];
}
完整代码实现:
标准结构体实现法:
#include<iostream>
#define Maxsize 100
using namespace std;
typedef struct {
int data[Maxsize];
int length;
}Sqlist;
//初始化
void Initlist(Sqlist& L) {
L.length = 0;
int x;
while (cin >> x) {
L.data[L.length++] = x;
if (cin.get() == '\n')
break;
}
}
//功能函数
int medium(Sqlist &A,Sqlist &B, int n) {
int l1 = 0, r1 = n - 1, m1, l2 = 0, r2 = n - 1, m2;//分别代表A,B左右指针和中位数
while (l1 != r1 || l2 != r2) {
m1 = l1 + (r1 - l1) / 2; //分别取A,B数组当前的中位数位置
m2 = l2 + (r2 - l2) / 2;
if (A.data[m1] == B.data[m2])
return A.data[m1];
if (A.data[m1] < B.data[m2]) {
if ((r1 - l1 + 1) % 2 == 0) { //若当前数组长度为偶数
l1 = m1 + 1; //小的数组,舍弃中位数
r2 = m2; //大的数组,保留中位数
}
else { //为奇数
l1 = m1; //小的和大的数组均保留中位数
r2 = m2;
}
}
else {
if ((r1 - l1 + 1) % 2 == 0) { //若当前数组长度为偶数
l2 = m2 + 1;
r1 = m1;
}
else { //为奇数
l2 = m2;
r1 = m1;
}
}
}
return (A.data[r1] < B.data[r2]) ? A.data[r1] : B.data[r2];
}
//主函数
int main() {
Sqlist L1;
Sqlist L2;
int len;
cout << "请输入数组的长度:" << endl;
cin >> len;
cout << "请输入升序数组1:" << endl;
Initlist(L1);
cout << "请输入升序数组2:" << endl;
Initlist(L2);
cout << medium(L1, L2,len) << endl;
system("pause");
return 0;
}
vs输出结果: