分治法-合并排序和快速排序

分治法是按照以下方案工作的:

  • 将问题的实例划分为同一个问题的几个较小的实例,最好拥有同样的规模

  • 对这些较小的实例求解(一般使用递归方法,但在问题规模足够小的时候,有时会利用另一种算法以提高效率)
  • 如果必要的话,合并较小问题的解,以得到原始问题的解

分治法的流程:

4.1 合并排序

合并排序是成功应用分治技术的一个完美例子(书上说的)。

对于一个需要排序的数组,合并排序把它一分为二,并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组。

代码实现:

/**
     * 合并排序
     * @author xiaofeig
     * @since 2015.9.16
     * @param array 要排序的数组
     * @return 返回排好序的数组
     * */
    public static int[] mergeSort(int[] array){
        if(array.length>1){
            int[] subArray1=subArray(array,0,array.length/2);
            int[] subArray2=subArray(array,array.length/2,array.length);
            subArray1=mergeSort(subArray1);
            subArray2=mergeSort(subArray2);
            return merge(subArray1,subArray2);
        }
        return array;
    }
    /**
     * 返回指定数组的子数组
     * @author xiaofeig
     * @since 2015.9.16
     * @param array 指定的数组
     * @param beginIndex 子数组的开始下标
     * @param endIndex 子数组的结束位置(不包括该元素)
     * @return 返回子数组
     * */
    public static int[] subArray(int[] array,int beginIndex,int endIndex){
        int[] result=new int[endIndex-beginIndex];
        for(int i=beginIndex;i<endIndex;i++){
            result[i-beginIndex]=array[i];
        }
        return result;
    }
    /**
     * 根据数值大小合并两个数组
     * @author xiaofeig
     * @since 2015.9.16
     * @param subArray1 待合并的数组
     * @param subArray2 待合并的数组
     * @return 返回合并好的数组
     * */
    public static int[] merge(int[] subArray1,int[] subArray2){
        int[] result=new int[subArray1.length+subArray2.length];
        int i=0,j=0;
        while(i<subArray1.length&&j<subArray2.length){
            if(subArray1[i]>subArray2[j]){
                result[i+j]=subArray2[j];
                j++;
            }else{
                result[i+j]=subArray1[i];
                i++;
            }
        }
        if(i==subArray1.length){
            while(j<subArray2.length){
                result[i+j]=subArray2[j];
                j++;
            }
        }else{
            while(i<subArray1.length){
                result[i+j]=subArray1[i];
                i++;
            }
        }
        return result;
    }

算法分析:

当n>1时,C(n)=2C(n-2)+Cmerge(n),C(1)=0

Cmerge(n)表示合并阶段进行键值比较的次数。最坏的情况下(比如,最小的元素轮流来自不同的数组),Cmerge(n)=n-1。所以:Cworst(n)=2Cworst(n-1)+(n-1),Cworst(1)=0。

因此,Cworst(n)属于θ(n*log(n))。可以求得最差效率递推式的精确解:Cworst(n)=nlog2n-n+1。

合并排序在最坏情况下的键值比较次数十分接近于任何基于比较的排序算法在理论上能够达到的最少次数。合并排序的主要缺点就是该算法需要线性的额外空间。

4.2 快速排序

快速排序是另一种基于分治技术的重要排序算法。不像合并排序是按照元素在数组中的位置对它们进行划分,快速排序按照元素的值对它们进行划分。它对于给定数组中的元素进行重新排列,以得到一个快速排序的分区。在一个分区中,所有在s下标之前的元素都小于等于A[s],所有在s下标之后的元素都大于等于A[s]。

具体实现方式:在数组中选择一个元素(中轴),选择的策略有很多种,这里可选择数组的第一个元素。分别从左向右和从右向左的扫描数组:从左向右的扫描(i表示下标)从第二个元素开始,自动忽略小于中轴的元素,直到遇到大于等于中轴的元素才会停止;从右向左的扫描(j表示下标)从最后一个元素开始,扫描自动忽略大于中轴的元素,直到遇到小于等于中轴的元素才停止。当两端的扫描都停止后,会出现两种情况(i<j或i>=j),当i<j时,交换A[i]和A[j],并继续扫描;当i>=j时,返回j作为s的值,并继续递归位于s左右两边的子数组。

代码实现:

/**
     * 合并排序
     * @author xiaofeig
     * @since 2015.9.17
     * @param array 要排序的数组
     * */
    public static void quickSort(int[] array){
        if(array.length>1){
            int s=partition(array);
            int[] leftPart=Arrays.copyOfRange(array, 0, s);
            int[] rightPart=Arrays.copyOfRange(array, s+1, array.length);
            quickSort(leftPart);
            quickSort(rightPart);
            //合并中轴元素的左右两部分,包括中轴元素
            for(int i=0;i<leftPart.length;i++){
                array[i]=leftPart[i];
            }
            for(int i=0;i<rightPart.length;i++){
                array[i+leftPart.length+1]=rightPart[i];
            }
        }
    }
    /**
     * 合并排序划分区
     * @author xiaofeig
     * @since 2015.9.17
     * @param array 要分区的数组
     * @return 返回中轴元素的最终下标
     * */
    public static int partition(int[] array){
        int i=0,j=array.length;
        do{
            do{
                i++;
            }while(i<array.length-1&&array[i]<array[0]);
            do{
                j--;
            }while(j>1&&array[j]>array[0]);
            int temp=array[i];
            array[i]=array[j];
            array[j]=temp;
        }while(i<j);
        int temp=array[i];
        array[i]=array[j];
        array[j]=temp;
        if(array[j]<=array[0]){//中轴右侧元素均大于中轴元素时无需交换
            temp=array[0];
            array[0]=array[j];
            array[j]=temp;
        }else{
            j--;//中轴右侧元素均大于中轴元素时须将j值降至0
        }
        return j;
    }

算法分析:

当n>1时,Cbest(n)=2Cbest(n/2)+n,Cbest(1)=0

所以,Cbest(n)属于θ(nlog2n);对于n=2k的情况求得Cbest(n)=nlog2n

最差的情况是输入的数组已经排过序了,如果A[0..n-1]是严格递增的数组,并且我们将A[0]作为中轴,从左到右的扫描会停在A[1]上,而从右往左的扫描会一直处理到A[0]为止(我上面的代码是扫描到A[1]),导致分列点出现在0位置上。这种情况下,键值比较的总次数应该等于:

Cworst(n)=(n+1)+n+…+3=(n+1)(n+2)/2-3,属于θ(n2)

对于大小为n的随机排列的数组,快速排序的平均键值比较次数记为Cavg(n)。假设分区的分裂点s(0<=s<=n-1)位于每个位置的概率都是1/n,得到下面的递推关系式:

当n>1时,

Cavg(0)=0,Cavg(1)=1,计算结果:Cavg(n)≈2n*ln(n)≈1.38nlog2n。

因此,快速排序在平均情况下,仅比最优情况多执行38%的比较操作。此外,它的最内层循环效率非常高,使得在处理随机排列的数组时,速度要比合并排序快

时间: 2024-08-11 01:45:55

分治法-合并排序和快速排序的相关文章

算法实验:分治法合并排序(C++)

这篇文章分两部分来写,第一部分写代码的实现过程,第二部分把实验报告从头到尾呈现出来. 我习惯调试使用的编译器是DEV C++,不是vs系列的,可能头文件上有点区别.但是下面的报告是我放到vs里面测试过的,可以直接用,不影响. 第一部分:(解析) 题目:随机产生一个整型数组,然后用合并排序将该数组做升序排列,要求输出排序前和排序后的数组. 题目分析: 需要随机产生一个整数数组: 采用的算法是合并排序,也就是用归并排序: 输出排序后的数组. 随机产生一个整数数组:这个问题首先想到的是用rand()函

合并排序和快速排序

/* 合并排序 O(n*lgn) */ #include <iostream> using namespace std; #define MAXN 100 int a[MAXN]; void Merge(int a[MAXN],int left,int mid,int right) { int i,j,k; int n1=mid-left+1; int n2=right-mid; int L[MAXN],R[MAXN]; for(i=1;i<=n1;i++) L[i]=a[left+i-

算法有插入排序,堆排序,合并排序,快速排序和stooge排序

比较的算法有插入排序,堆排序,合并排序,快速排序和stooge排序, 先说一下比较结果 1,比较插入和stooge排序,stooge的表现如此之差,数组大小在2000时 InsertSort VS StoogeSort 's Running Time:     16ms:47672ms; Terribly! Isn't It? 所以在后面的比较中,没有带stooge这个垃圾算法 2,插入排序,堆排序,合并排序,快速排序运行时间对比 (网易博客的表格功能太差了,不爽,只好以文本对齐展现给大家了):

使用合并排序和快速排序对字符串按长度排序

前段时间要对字符串集合进行按长度排序,字符串长度长的排在队列前面,最短的排在最后,可以使用两种排序方法进行排序,其中快速排序的效能会好些,但快速排序在字符串的集合非常大的时候,有时会得不到正确的结果,具体原因还不清楚. 1.合拼排序的代码 using System; using System.Collections.Generic; using System.Text; namespace FtpproxyDownRule.DBUtility { public class sortbyStrin

递归与分治-合并排序、快速排序以及循环赛问题

合并排序 合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的.然后再把有序子序列合并为整体有序序列. 递归方法: 基本思想是:将待排序元素分成大小一致相同的2个子集和,分别对两个子集和进行排序,最终将排好序的子集合并成所需要的排好序的集合 package com.gqx.arithmetic.Recursion; public class Recursion_Merge2 { private static void mergeSort(

如何优化合并排序和快速排序

和并排序和快速排序在元素的重复率特别高的时候排序的时间变长.我们可以利用三向切分的办法来避免相同的元素进行交换,以减少交换次数. 具体如下图所示: 总共有3个指针,lt,i,和gt,这个三个指针分别指着队首,队首的下一位,队尾.以队首为参考点,设该数组为a.设中间变量temp. temp ← a[lt] //队首设为参考变量 if a[i] < temp:           swap(a,lt++,i++) else if a[i] > temp:           swap(a,i,j-

分治法(一)

这篇文章将讨论: 1) 分治策略的思想和理论 2) 几个分治策略的例子:合并排序,快速排序,折半查找,二叉遍历树及其相关特性. 说明:这几个例子在前面都写过了,这里又拿出来,从算法设计的策略的角度把它们放在一起来比较,看看分治是如何实现滴.由于内容太多,我将再花一篇文章来写4个之前没有写过的分治算法:1,大整数乘法   2,矩阵乘法的分治策略   3,最近点对  4,凸包问题,请见下一篇. 好了,切入正题. --------------------------------------------

005-算法-分治法

一.概念: 在计算机科学中,分治法是建基于多项分支递归的一种很重要的算法范式.字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以寄简单的直接求解,原问题的解即子问题的解的合并. 这个技巧是很多高校算法的基础,如排序算法(快速排序.归并排序).傅立叶转换(快速傅立叶变换) 思路: 1.分解:把原问题分解为若干个规模较小,相对独立,与原问题形式相同的子问题. 2. 解决:若子问题规模较小且易于解决时,则直接解.否则,递归地解决各子问题. 3.合并:

对第二章分治法的总结

分治法是将较大规模的问题划分为较小规模的问题提高解决问题的效率. 二分法是分治法的一个特殊方法,通常和递归算法结合使用. 个人认为这个算法提供给我们一个新的解决问题的思路. 使用二分法的关键在于找到分解为子问题的方式,分解方式不同也会影响算法的效率,比如说二分排序中有合并排序和快速排序两种.虽然这两种排序方法的时间复杂度都是nlog(n),但是对于不同的数据,两者排序的效率还是有一定差别的.对于基本排好序的数据集来说,快速排序会比较高效一些. 原文地址:https://www.cnblogs.c