排序专题


一. 快速排序

快排基于分治思想,几乎是最快的排序方式,被评为20世纪是十大算法之一。

1.1 快排的Java代码实现

    //快排
    public void quickSort(int data[],int start,int end){
        if(start<end){
            int partition=partition(data,start,end);
            quickSort(data,0, partition-1);
            quickSort(data,partition+1, end);
        }
    }
    //确定pivot
    public int partition(int data[],int start,int end){
        int pivot=data[start];
        int left=start+1;
        int right=end;
        while(left<=right){
            while(left<=right&&data[left]<=pivot){
                left++;
            }
            while(left<=right&&data[right]>pivot){
                right--;
            }
            if(left<right){
                swap(data,left,right);
            }
        }
        swap(data, start, right);
        return right;
    }
    //数据交换
    public void swap(int[] data, int x, int y) {
        int temp=data[x];
        data[x]=data[y];
        data[y]=temp;
    }

1.2 快排的复杂度分析

  • 平均时间– O(nlog2n)
    快排在平均情况下的时间复杂度比较难算,记住就行了。

  • 最好时间– O(nlog2n)
    最好情况下,每次找到的pivot点应该是 N2 的位置。
    所以:

    • 可以推测比较序列的次数为:
      f(1)0
      f(n)n1+2×f(n/2)
      n1+2×(n/21)+4×f(n/4)
      n1+2×(n/21)+4×(n/41)+8×f(n/8)
      nlog2nn
      O(nlog2n)
  • 最坏时间– O(n2)
    首先明确快排的最坏情况是:原序列有序。

    • 假设:数组为data[]={1,2,3,4,5,6}
    • 则分割序列为:
      {1},{2,3,4,5,6}
      {1},{2}{3,4,5,6}
      {1},{2}{3}{4,5,6}
      {1},{2}{3}{4}{5,6}
      {1},{2}{3}{4}{5}{6}
    • 很容易得到比较次数为:
      n1+n2++1= n2n2 =O(nlogn)
  • 空间复杂度–平均条件下– O(logn)
    同时间复杂度一样,求解过程较为麻烦,记住结论就好了

  • 空间复杂度–最好条件下– O(logn)
    最好条件下,每次取中值递归,递归树深度为 log2n 。其空间复杂度也就为 O(logn)

  • 空间复杂度–最坏条件下– O(n)
    最坏情况,需要进行n‐1递归调用,其递归树是一棵斜树,深度为n,其空间复杂度为 O(n)

1.3 快排的稳定性分析

  • 稳定性:不稳定
    由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。

二. 归并排序

归并排序也是基于分治法的思想,速度和快排差不多,不过空间占用稍大,具有稳定性。

1.1 归并排序的Java代码实现

    //归并排序
    public void mergeSort(int[] data,int start,int end){
        if(start<end){
            int mid=(start+end)/2;
            mergeSort(data, start, mid);
            mergeSort(data, mid+1, end);
            mergeChild(data, start, mid, end);
        }
    }
    //合并左右孩子
    public void mergeChild(int []data,int start,int mid,int end){
        int len=end-start+1;
        int []temp=new int[len];
        int left=start,right=mid+1;
        for(int i=0;i<len;i++){
            if(left>mid){
                temp[i]=data[right++];
                continue;
            }
            if(right>end){
                temp[i]=data[left++];
                continue;
            }
            if(data[left]<=data[right]){
                temp[i]=data[left++];
            }else{
                temp[i]=data[right++];
            }
        }
        for(int i=0;i<len;i++){
            data[start+i]=temp[i];
        }
    }

1.2 归并排序的复杂度分析

  • 平均时间– O(nlog2n)
    归并排序中,每一次的mid点都是 endstart2 ,所以:

    • 可以推测比较序列的次数为:
      f(1)0
      f(n)2n+2×f(n/2)
      2n+2n+4×f(n/4)
      2n+2n+2n+8×f(n/8)
      2n×log2n
      O(nlog2n)
  • 最好时间– O(nlog2n)
    最好情况和平均情况一样,每次的mid点都是 endstart2 ,所以,最好情况和平均情况一样,都是 O(nlog2n)

  • 最坏时间– O(nlog2n)
    最坏情况和平均情况一样,每次的mid点都是 endstart2 ,所以,最坏情况和平均情况一样,都是 O(nlog2n)

  • 空间复杂度–平均条件下– O(n)
    关于平均条件下的空间复杂度,书上说的是 O(n) ,但是它把mergeChild函数里申请的临时变量int []temp也算进去了,应该是在递归外层申请了一个大小为n的数组,这样一来,空间复杂度就变成了 n+log2n ,也就是 O(n) 。但是我上面的代码,把这个临时变量写到了mergeChild中,函数执行完毕自动回收,也就不算是空间占用了,此时的空间复杂度和快排的一样,都是 O(logn) 。不过我看网上都是说的 O(n) ,我这里也写成 O(n) 了。也许我的理解有误,还请知道的指点一下,谢谢。

  • 空间复杂度–最好条件下– O(n)
    同上,是 O(n)

  • 空间复杂度–最坏条件下– O(n)
    同上,是 O(n)

1.3 归并排序的稳定性分析

  • 稳定性:稳定
    因为归并排序并没有涉及到跳跃着排序,因此,归并排序是一种稳定的排序方法。

三. 冒泡排序

冒泡排序理解起来较为简单。具有稳定性。时间复杂度较高,空间复杂度较低。了解一下吧。

1.1 冒泡排序的Java代码实现

    public void bubbing(int[] data){
        int len=data.length;
        for(int i=0;i<len;i++){
            int tag=0;
            for(int j=0;j<len-i-1;j++){
                if(data[j]>data[j+1]){
                    tag=1;
                    int temp=data[j];
                    data[j]=data[j+1];
                    data[j+1]=temp;
                }
            }
            if(tag==0)return;
        }
    }

1.2 冒泡排序的复杂度分析

  • 平均时间– O(n2)
    乱序排列的复杂度。

  • 最好时间– O(n)
    顺序排列的复杂度。

  • 最坏时间– O(n2)
    逆序排列的复杂度。
  • 空间复杂度–平均条件下– O(1)

  • 空间复杂度–最好条件下– O(1)

  • 空间复杂度–最坏条件下– O(1)

1.3 冒泡排序的稳定性分析

  • 稳定性:稳定
    因为冒泡排序并没有涉及到跳跃着排序,因此,冒泡排序是一种稳定的排序方法。

四. 堆排序

堆排序利用最大(小)堆的性质进行排序。使用最大堆时,先构建一棵最大堆,然后不断取顶部节点,得到的序列就是有序的。
最大堆是一棵所有根节点都大于子节点的完全二叉树;最大堆的最大元素是顶部根节点,第二大是根节点的两个子节点的其中的一个,第三大不确定,有可能是根节点的子节点的另外一个,也可能是子节点中较大的节点的子节点中的一个。最小堆同理。

1.1 堆排序的Java代码实现

    //堆排序
    public void heapSort(int[] data) {
        if(data==null||data.length==0)return;
        int n=data.length;
        buildMaxHeap(data);
        for(int i=0;i<n;i++){
            swap(data, 0, n-i-1);
            shiftDown(data, 0, n-i-1);
        }
    }
    //构建最大堆
    public void buildMaxHeap(int[] data){
        int n=data.length;
        int x=n/2-1;
        for(int i=x;i>=0;i--){
            shiftDown(data, i, n);
        }
    }
    //下筛
    public void shiftDown(int []data,int x,int n){
        int l=2*x+1,r=2*x+2;
        int max=x;
        if(l<n&&data[l]>data[max])max=l;
        if(r<n&&data[r]>data[max])max=r;
        if(max!=x){
            swap(data, max, x);
            shiftDown(data, max, n);
        }
    }
    //数据交换
    public void swap(int[] data,int x,int y){
        int temp=data[x];
        data[x]=data[y];
        data[y]=temp;
    }

1.2 堆排序的复杂度分析

  • 平均时间– O(nlogn)

最大堆构建的时间复杂度为 O(n) ,下筛的时候,总共n-1次循环,一次下筛的复杂度为 O(log2n) (具有n个节点的二叉树最高为 log2n+1 )。所以总的复杂度为 O(nlogn)

  • 最好时间– O(nlogn)

  • 最坏时间– O(nlogn)

  • 空间复杂度–平均条件下– O(1)

只需要一个临时变量,所以为 O(1)

  • 空间复杂度–最好条件下– O(1)

  • 空间复杂度–最坏条件下– O(1)

1.3 堆排序的稳定性分析

  • 稳定性:不稳定
    因为堆排序涉及到跳跃着排序,因此,堆排序是一种不稳定的排序方法。

三. 希尔排序

希尔排序是直接插入排序的改进,复杂度可以降到O(n^1.3).
主要流程是:
1. 初始化增量d=n/2
2. 分别对d个数列进行插入排序
3. d=d/2,然后执行步骤2.

1.1 希尔排序的Java代码实现

public void shellSort(int[]data){
        if(data==null||data.length==0)return;
        int n=data.length;
        int d=n/2;
        while(d>=1){
            for(int k=0;k<d;k++){
                for(int i=k+d;i<n;i+=d){
                    int temp=data[i];
                    int j=i-d;
                    while(j>=k&&data[j]>temp){
                        data[j+d]=data[j];
                        j-=d;
                    }
                    data[j+d]=temp;
                }
            }
            d=d/2;
        }
    }

1.2 希尔排序的复杂度分析

  • 平均时间– O(n1.3)
  • 最好时间– O(n1.3)
  • 最坏时间– O(n1.3)

  • 空间复杂度–平均条件下– O(1)

  • 空间复杂度–最好条件下– O(1)

  • 空间复杂度–最坏条件下– O(1)

1.3 希尔排序的稳定性分析

  • 稳定性:不稳定
    希尔排序不稳定

最后,总结一下各个排序方法的复杂度和稳定性:

排序算法最好情形(时间 ~ 空间)平均情形(时间 ~ 空间)最坏情形(时间 ~ 空间)稳定性
快速排序 O(nlogn) ~ O(logn) O(nlogn) ~ O(logn) O(n2) ~ O(n) 不稳定
归并排序 O(nlogn) ~ O(logn) O(nlogn) ~ O(logn) O(nlogn) ~ O(n) 稳定
冒泡排序 O(n2) ~ O(1) O(n2) ~ O(1) O(n) ~ O(1) 稳定
堆排序 O(nlogn) ~ O(1) O(nlogn) ~ O(1) O(nlogn) ~ O(1) 稳定
希尔排序 O(n1.3) ~ O(1) O(n1.3) ~ O(1) O(n1.3) ~ O(1) 不稳定

四. KMP算法

KMP算法是字符串匹配的高效算法,朴素的匹配算法的时间复杂度为 O(nm) ,KMP匹配算法的时间复杂度可以达到 O(n+m) ,当m很小n很大时,这个复杂度可以逼近 O(n) ,是一个很巧妙的算法。

1.1 KMP算法的Java代码实现


//根据计算得到的next数组进行模式字符串的查找。
    public int matcher(String t,String p){
        if(t==null||p==null)return -1;
        int [] next = next(p);
        int tLen=t.length();
        int pLen=p.length();
        for(int i=0,j=0;i<tLen;i++){
            while (j>0&&t.charAt(i)!=p.charAt(j)){
                j=next[j-1];
            }
            if(t.charAt(i)==p.charAt(j)){
                j++;
            }
            if(j==pLen){
                return i-j+1;
            }
        }
        return -1;
    }
//计算next数组
    public int[] next(String p){
        int n=p.length();
        int [] next=new int[n];
        next[0]=0;
        for(int i=1;i<n;i++){
            int k=next[i-1];
            while (k>0&&p.charAt(k)!=p.charAt(i)){
                k=next[k-1];
            }
            if(p.charAt(k)==p.charAt(i)){
                k++;
            }
            next[i]=k;
        }
        return next;
    }

1.2 KMP算法时间复杂度

KMP匹配算法的时间复杂度可以达到 O(n+m) ,当m很小n很大时,这个复杂度可以逼近 O(n) ,是一个很巧妙的算法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值