算法第二章上机实践报告:张三木教你如何A过PTA第二章实验题
二分查找
题目:
代码如下:
#include<iostream>
using namespace std;
int counter = 0;
int binarySearch(int arr[], int x, int low, int high) {
if (low > high) {
return -1;
};
counter++;
int mid = (low + high) / 2;
if (arr[mid] == x) {
return mid;
};
if (arr[mid] > x) {
return binarySearch(arr, x, low, mid - 1);
}
else {
return binarySearch(arr, x, mid+1, high);
}
}
int main() {
int len;
int arr[100000];
int x;
cin >> len;
for (int i = 0; i < len; i++) {
cin >> arr[i];
};
cin >> x;
int index = binarySearch(arr, x, 0, len - 1);
cout << index << endl;
cout << counter << endl;
system("pause");
return 0;
}
解析:
这道题目很简单,只要用经典的二分查找算法写法,再输出比较次数即可。但是我们一直没有提交成功,主要是因为此处二分法分为左右两部分的时候排除了mid元素。
去掉mid元素划分之后就很成功的A了。
改写二分搜索算法
题目描述:
算法描述:
#include<iostream>
using namespace std;
int binarySearch(int arr[], int x, int low, int high) {
if (low > high) {
return low;
};
int mid = (low + high) / 2;
if (arr[mid] == x) {
return mid;
};
if (arr[mid] > x) {
return binarySearch(arr, x, low, mid - 1);
}
else {
return binarySearch(arr, x, mid + 1, high);
}
}
int main() {
int len;
int arr[100000];
int x;
cin >> len;
cin >> x;
for (int i = 0; i < len; i++) {
cin >> arr[i];
};
int index = binarySearch(arr, x, 0, len - 1);
if (arr[index] == x) {
cout << index << " " << index << endl;
}
else {
cout << index - 1 << " " << index << endl;
}
system("pause");
return 0;
}
问题解析:
这道题我们需要输出x不在数组中时,返回小于x的最大元素位置i和大于x的最小元素位置j。只要将上面binarySearch函数中,当元素不存在时,返回low即可。
(图用left和right更明显)
因为当low>high时即退出递归返回,所以此时low一定是刚好
大于x的最小元素位置的下标(即j)。由题目可以看出,i和j的坐标一定是连续的。所以只要输出low和low-1即可。当元素存在时,返回mid下标并输出。
两个有序序列的中位数
题目描述:
算法描述:
#include<iostream>
using namespace std;
int searchMid(int a[],int b[],int n) {
int low1 = 0, low2 = 0, high1 = n - 1, high2 = n - 1;
while (low1 < high1&&low2 < high2) {
int mid1 = (low1 + high1) / 2;
int mid2 = (low2 + high2) / 2;
if (a[mid1] == b[mid2]) {
return a[mid1];
};
if (a[mid1] < b[mid2]) {
if ((low1 + high1) % 2 == 0) {
low1 = mid1;
high2 = mid2;
}
else {
low1 = mid1 + 1;
high2 = mid2 ;
}
}
else {
if ((low1 + high1) % 2 == 0) {
high1 = mid1;
low2 = mid2;
}
else {
//加1,保证取得的数列长度一样
high1 = mid1;
low2 = mid2 +1;
}
}
};
if (a[low1] < b[low2]) {
cout << a[low1] << endl;
}
else {
cout << b[low2] << endl;
}
}
int main() {
int a[100000];
int b[100000];
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
cin >> b[i];
}
searchMid(a, b, n);
system("pause");
return 0;
}
问题解析:
这道题如果使用排序方法再求中位数,时间复杂度就会超过O(logn)(排序算法最低时间复杂度为nlogn),不满足题意。于是使用二分搜索的算法,分别比较两个数列的中位数。
- 当a[mid]和b[mid]相等时,中位数即为该数,直接返回;
- 当a[mid]大于b[mid]时,中位数一定在a[mid]前的数列或b[mid]后的数列中,此时我们只取这两部分,继续进行二分比较。
- 当a[mid]小于b[mid]是,中位数一定在a[mid]后的数列或b[mid]前的数列中,此时我们只取这两部分,继续进行二分比较。
Q:为什么这样划分数列(分别取前半段和后半段)?
A:因为要让剩下的数列的中位数相接近,这样就可以直接返回总的中位数。
当退出循环条件之后,直接比较两数中位数,小的那个就是最后的结果。
此时只剩下最后一个数,因为mid向下取整,所以较小的即为全局的中位数。
复杂度分析
时间复杂度:
因为采用二分查找算法来寻找中位数而不是排序,所以时间复杂度为O(logn)。
空间复杂度:
运行过程中并没有开辟额外的空间,所以空间复杂度为O(1)。
心得体会
二分搜索算法适用于存在某个数并且找到它的情况,带来更高效的查找速率。同时,如果看到题目要求时间复杂度为O(logn)的时候,应该想到用查找算法而不是排序算法。
这道题目也让我对分治法的作用有了新的体会。
参考资料:
https://blog.csdn.net/gdufsTFknight/article/details/78824552
https://blog.csdn.net/Scar_Halo/article/details/83691880