算法第二章上机实践报告:张三木教你如何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),不满足题意。于是使用二分搜索的算法,分别比较两个数列的中位数。

  1. 当a[mid]和b[mid]相等时,中位数即为该数,直接返回;
  2. 当a[mid]大于b[mid]时,中位数一定在a[mid]前的数列或b[mid]后的数列中,此时我们只取这两部分,继续进行二分比较。
  3. 当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

posted @ 2019-09-21 17:16  MarcusJr19  阅读(247)  评论(0编辑  收藏  举报