zoukankan      html  css  js  c++  java
  • 归并排序和快速排序的衍生问题

    前面两篇总结了常见的几种排序算法的主要思想以及C++与python两种方式的实现过程, 几种排序算法中比较重要的就是归并排序和快速排序,这两种方法的相同点就是都使用了分治的思想,现在用来解决两个具体问题。

    1.分治法

      分治法就是将原问题分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。 需要注意的是归并排序和快速排序虽然都使用了分治的思想,但它们分别代表了分治算法的两类基本思想。对于归并排序而言,它对“分”这个过程没有做太多操作,只是简单的将数组分为两部分然后递归的进行归并排序,而归并排序的关键是这样分完之后如何将它们归并起来,即merge()操作。 
      而对于快速排序来说,则是废了很大功夫放在了如何“分”这个问题上,我们是选取了一个标定点,然后使用partition()这个子过程将这个标定点移到了合适的位置,当它移到了合适的位置之后才将整个数组分成了两部分,而这样分完之后,在“合”的时候就不用做过多的考虑了,只需要一步一步递归下去就好了。 
      下面解决两个直接从归并排序和快速排序中衍生出来的具体问题的。

    2.求逆序对数量

    • 问题描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。给定一个数组,求出这个数组中的逆序对的总数。
    • 这个问题直接的做法是逐个统计,复杂度是O(n^2),但显然是很愚蠢的方法,这里可以利用归并排序的思想,在归并排序过程中统计逆序对的个数。
    • 实现思想: 
        归并排序是将数列a[l,r]分成两部分,a[l,mid]和a[mid+1,r]分别进行归并排序,然后再将这两半合并起来。在合并的过程中(设l<=i<=mid,mid+1<=j<=r),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并排序中的合并过程中计算逆序数。
    • C++实现:
    #include <iostream>
    using namespace std;
    // 计算逆序数对的结果以long long返回
    // 因为对于一个大小为n的数组, 其最大的逆序数对个数为 n*(n-1)/2, 非常容易产生整型溢出
    // __merge函数求出在arr[l,mid]和arr[mid+1,r]有序的基础上, arr[l,r]的逆序数对个数
    long long __merge(int arr[],int l,int mid,int r){
    long long res = 0;// 初始化逆序数对个数 res = 0
    int aux[r - l + 1];
    for(int i = l;i <= r;i++)
    aux[i - l] = arr[i];
    int i = l;
    int j = mid + 1;
    for(int k = l;k <= r;k++){
    if(i > mid){
    arr[k] = aux[j - l];
    j ++;
    }
    else if(j > r){
    arr[k] = aux[i - l];
    i ++;
    }
    else if(aux[i - l] < aux[j - l]){
    arr[k] = aux[i - l];
    i ++;
    }
    else{// 左半部分所指元素 > 右半部分所指元素
    arr[k] = aux[j - l];
    j ++;
    res += (long long) (mid - i + 1);
    // 此时, 因为右半部分所指的元素小
    // 这个元素和左半部分的所有未处理的元素都构成了逆序数对
    // 左半部分此时未处理的元素个数为 mid - j + 1
    }
    }
    return res;
    }
    // 求arr[l,r]范围的逆序数对个数
    long long __inversionCount(int arr[],int l,int r){
    if(l >= r)
    return 0;
    int mid = l + (r - l) / 2;
    long long res1 = __inversionCount(arr,l,mid);// 求出 arr[l,mid] 范围的逆序数
    long long res2 = __inversionCount(arr,mid + 1,r);// 求出 arr[mid+1,r] 范围的逆序数
    return res1 + res2 + __merge(arr,l,mid,r);
    }
    long long inversionCount(int arr[],int n){
    return __inversionCount(arr,0,n - 1);
    }
    int main(){
    int a[10] = {10,9,8,7,6,5,4,3,2,1};
    int n = 10;
    cout<< "逆序对数量为:" << inversionCount(a,n)<<endl;
    }
    • Python实现:
     
    def __merge(arr,l,mid,r):
    res = 0
    aux = []
    for x in range(l,r + 1):
    aux.append(arr[x])
    i = l
    j = mid + 1
    for k in range(l,r + 1):
    if i > mid:
    arr[k] = aux[j - l]
    j += 1
    elif j > r:
    arr[k] = aux[i - l]
    i += 1
    elif aux[i - l] < aux[j - l]:
    arr[k] = aux[i - l]
    i += 1
    else:
    arr[k] = aux[j - l]
    j += 1
    res += mid - i + 1
    return res
    def __inversionCount(arr,l,r):
    if l >= r:
    return 0
    mid = l + (r - l) // 2
    res1 = __inversionCount(arr,l,mid)
    res2 = __inversionCount(arr,mid + 1,r)
    return res1 + res2 + __merge(arr,l,mid,r)
    def inversionCount(arr):
    n = len(arr)
    return __inversionCount(arr,0,n - 1)
    arr = [10,9,8,7,6,5,4,3,2,1]
    res = inversionCount(arr)
    print('逆序对数量为:' + str(res))

    3.取数组中第k大的元素

    • 题目很简单,给定一个数组,求出该数组中第k大的元素。最简单的做法是直接进行排序,算法复杂度是O(nlogn),但是这么做很明显比较低效率,因为只要求出第k大的元素,并不需要别的信息。
    • 另一种方法是用快速排序的思想。快速排序每次把一个元素交换到正确的位置,同时把左边的都放上小的,右边都放上大的。这个算法每一次选取一个中心点,排序之后,查看中心点的位置。如果它的位置大于K,就说明,要在前面一个子序列中找出第K大的元素。反之,如果小于K,就说明要在后面一个序列中找出第k大的元素。
    • 复杂度分析: 
        这种算法的复杂度分析稍微复杂。第一次交换,算法复杂度为O(n),接下来的过程和快速排序不同,快速排序是要继续处理两边的数据,再合并,合并操作的算法复杂度是O(1),于是总的算法复杂度是O(nlogn)(可以这么理解,每次交换用了n,一共logn次)。但是这里在确定中心点的相对位置(在K的左边或者右边)之后不用再对剩下的一半进行处理。也就是说第二次插入的算法复杂度不再是O(n)而是O(n/2),然后接下来的过程是1+1/2+1/4+........<2,换句话说就是一共是O(2n)的算法复杂度也就是O(n)的算法复杂度。 
        这个算法是一种很经典的算法。原因是因为它通过努力把算法复杂度在每次递归中下降一些,最终让整个算法的复杂度下降极多,算是一种十分聪明的做法。
    • C++实现:
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    using namespace std;
    int __partition(int arr[],int l,int r){
    swap(arr[l],arr[rand()%(r - l + 1) + l]);
    int v = arr[l];
    int j = l;
    for(int i = l + 1;i <= r;i++){
    if(arr[i] > v){
    swap(arr[i],arr[j + 1]);
    j ++;
    }
    }
    swap(arr[l],arr[j]);
    return j;
    }
    int __selection(int arr[],int l,int r,int k){
    if(l == r)
    return arr[l];
    int p = __partition(arr,l,r);
    if(p == k)
    return arr[p];
    else if(p > k)
    return __selection(arr,l,p - 1,k);
    else
    return __selection(arr,p + 1,r,k);
    }
    int selection(int arr[],int n,int k){
    srand(time(NULL));
    return __selection(arr,0,n - 1,k - 1);//索引是从0开始的
    }
    int main(){
    int arr[10] = {10,9,8,7,6,5,4,3,2,1};
    int n = 10;
    cout<<"数组中第3大的元素为:"<<selection(arr,n,3)<<endl;
    }
    • Python实现:
    import random
    def __partition(arr,l,r):
    k = random.randint(l,r)
    arr[l],arr[k] = arr[k],arr[l]
    v= arr[l]
    j = l
    for i in range(l + 1,r + 1):
    if arr[i] < v:
    arr[i],arr[j + 1] = arr[j + 1],arr[i]
    j += 1
    arr[l],arr[j] = arr[j],arr[l]
    return j
    def __selection(arr,l,r,k):
    if l == r:
    return arr[l]
    p = __partition(arr,l,r)
    if p == k:
    return arr[p]
    elif p > k:
    return __selection(arr,l,p - 1,k)
    else:
    return __selection(arr,p + 1,r,k)
    def selection(arr,k):
    n = len(arr)
    return __selection(arr,0,n - 1,k - 1)
    arr = [10,9,8,7,6,5,4,3,2,1]
    print('数组中第2小的元素为'+ str(selection(arr,2)))

    推荐一个良心公众号【IT资源社】:

    本公众号致力于免费分享全网最优秀的视频资源,学习资料,面试经验等,前端,PHP,JAVA,算法,Python,大数据等等,你想要的这都有

    IT资源社-QQ交流群:625494093

    也可添加微信拉你进微信群: super1319164238

    微信搜索公众号:ITziyuanshe 或者扫描下方二维码直接关注,

     
  • 相关阅读:
    智器SmartQ T7实体店试用体验
    BI笔记之SSAS库Process的几种方案
    PowerTip of the Day from powershell.com上周汇总(八)
    PowerTip of the Day2010071420100716 summary
    PowerTip of the Day from powershell.com上周汇总(十)
    PowerTip of the Day from powershell.com上周汇总(六)
    重新整理Cellset转Datatable
    自动加密web.config配置节批处理
    与DotNet数据对象结合的自定义数据对象设计 (二) 数据集合与DataTable
    在VS2003中以ClassLibrary工程的方式管理Web工程.
  • 原文地址:https://www.cnblogs.com/Lovebugs/p/8778671.html
Copyright © 2011-2022 走看看