《算法》(第四版)------------排序

《算法》(第四版)------------排序

1、初级排序

基础API

public static void sort(Comparable[] a); //排序方法
public static boolean less(Comparable v,Comparable w);//比较,v小于w
public static void exch(Comparabl[] a, int i, int j);//交换元素

选择排序

思路

​ 将数组分为已排序(前)、(后)未排序两段。后段找到数组中最小元素,使之与后段第一个元素交换位置(交换后该位置也会成为前段中的一员)。然后后段按此重复。当全部元素都成为已排序段成员则循环结束,排序结束。

成本

​ N2。时间成本与输入情况无关,无论输入的数组是随机排序还是已排序或者全为相同元素,所需时间一样。

代码

public static void sort(Comparable a[]){
    int N = a.length;
    for(int i = 0;i < N;i++){
        int min = i;
        for(int j = i+1;j < N;j++){
            if(less(j,min) min = j;
        }
		exch(a,i,min);
    }
    
}

插入排序

思路

​ 将数组分为已排序(前)、(后)未排序两段。后段选取第一个元素加入到前段,然后该元素寻找自己能插入的位置,进行比较发现比前一个元素小则向前移动,找到后则会进入下一个循环。当全部元素都成为已排序段成员则循环结束,排序结束。

成本

​ N2。若是输入为已排序或者全为相同元素,则经过线性时间(N次比较,0次交换)就能运算完毕。

代码

public static void sort(Comparable[] a){
    //将每一个元素插入到合适的位置,位置不合适则前面的元素向后推
    int N = a.length;
    for(int i = 0;i < N;i++){
        int now = i,num = a[i];
        while(now>0&&less(a,now,now-1)){
            now--;
            a[now] = a[now-1];
        }
        a[now] = num;
    }
}

优化

​ 提高插入排序的方法很简单。从交换这个步骤入手,在对某元素选择需要插入的位置时,记录下该元素,然后再比较时将较大的元素右移即可,这样访问数组的次数能减半。

希尔排序

概念

​ 基于插入排序的一种快速排序算法。思想是使数组中任意间隔为h的元素都有序。这样的数组称为h有序数组。下图为示意图。
在这里插入图片描述

思路

​ 1、选取一个合适的步长h

​ 2、外循环:逐渐减小h到1

​ 3、内循环:没个h都进行从a[h]开始,往后遍历,每个元素都插入到a[i-h],a[i-2*h],a[i-3*h]中。

2、归并排序

概念

​ 分治的典型算法。将数组分成两半,分别排序,然后再将两个数组归并起来。

​ 归并:左右分组各维护一个索引,不断向右遍历,取两边索引的较小值线性排序。

自顶向下归并排序

​ 递归排序,从大数组不断递归到左右数组的排序。从树底下开始往上归并。
在这里插入图片描述

private static Comparable aux[]; //归并需要使用的辅助数组

public static void sort(Comparable a[], int lo, int hi){
    if(hi<=lo){
        return;
    }
    int mid = (lo+hi)/2;
    sort(a,lo,mid);
    sort(a,mid+1,hi);
    merge(a,lo,mid,hi);
}

public static void merge(Comparable a[], int lo, int mid, int hi){
    //维护左右数组下标
    int i = lo,j = mid+1;
    //得到辅助数组
     for(int k=0;k<=hi;k++)
    	aux[k]=a[k];
    
     for(int k=0;k<=hi;k++)
         //左右边界检测
        if (i>mid)	a[k]=aux[j++];
    	else if(j>hi) a[k]=aux[i++];
    	//选取
    	else if(less(aux[i],aux[j]))	a[k]=aux[i++];
    	else	a[k]=aux[j++];
}

成本

​ 对于长度为N的任意数组。树有n层,对于0<=k<=n-1,第k层有2k个子数组,每个数组长度为2n-k,最多需要2n-k次比较。因此每层比较次数都为2k*2n-k=2n,n层则一共为n2n=NlgN。归并排序最多需要访问数组6NlgN次,每次归并最多6N次访问数组(2N次用来复制,2N次用来将排序好的元素移回去,比较时最多2N次)。时间成本:NlgN。

自底向上归并排序

​ 与自顶向下不同,自底向上归并排序维护一个sz值(当前要归并分组的大小)。sz从1开始组件翻倍递增,数组中的子数组两两配对归并,最终归并成完全排序数组。

public static void sort(Comparable a[], int lo, int hi){
    int N = a.length;
    int sz = 1;
    //外循环增大每次需要归并长度
    for(sz = 1;sz<N;sz*=2)
        //内循环将数组中各个子数组两两配对
        for(int i=0;i<N-sz;i+=2*sz)
            //最后一组若落单则渠道下一轮等待归并,最后一组可能长度小于sz,此时会出现N-1<i+2*sz-1
            merge(a,i,i+sz-1,Math.min(i+2*sz-1,N-1));
}

概念

​ 快排也是一种分治算法。数组被切分成两个子数组,切分位置(k)左边的子数组均<=k,右边的子数组均>=k。当左右子数组均有序,此时整个数组有序。

快排思路

​ 切分:数组的两端维护各维护一个索引,从两头往中间靠拢,当左边遇到>v,右边遇到<v,则可以退出循环进行一次交换两个索引对应的元素。

private static void sort(Comparable a[], int lo, int hi){
    if(hi<=lo) return;
    int j = partition(a,lo,hi);
    sort(a,lo,j-1);
    sort(a,j+1,hi);
}

private static int partition(Comparable a[], int lo, int hi){
    Comparable v = a[lo];
    int i=lo,j=hi+1;
    //i,j向中间靠拢,要注意边界检测
    while(less(a[++i],v)) if(i==hi) break;
    while(less(v,a[--j])) if(j==lo) break;
    //数组遍历完
    if(i>=j)	break;
    exch(a,i,j);
    //将切分位置元素换到适合的位置(j必定走到左子数组的最后一个元素:a[j]<v且j<=i)
    exch(a,lo,j);
    return j;
}

算法改进

切换到插入排序

​ 对于小数组插入排序比较快。

//将sort()中
if(hi<=lo) return;
//替换为
if(hi<= lo+M){
    //插入排序
    Insertion.sort(a,lo,hi);
    return;
}
三取样切分

​ 取中位数来做切分对象,但是需要额外的计算中位数的成本。

三项切分

​ 分成<v,=v,>v三部分。

/**
	维护指针,lt,gt,i
	[lo..lt-1]:<v
	[lt..i-1]:=v
	[i..gt]:?未知部分
	[gt+1..hi]:>v
**/
/**
	i从lo+1开始向前遍历:
	a[i]<v:交换(lt,i),lt++,i++
	a[i]>v:交换(i,gt),gt--
	a[i]=v:i++
**/
private static void sort(Comparable a[], int lo, int hi){
    if(lo<=hi) return;
    Comparable v = a[lo];
    int lt=lo,gt=hi,i=lo+1;
    //i向前直到遇上gt(仍然有未知的部分,级没有排序好的元素)
    while(i<=gt){
        int cmp = a[i].compareTo(v);
        if(cmp<0) exch(a,lt,i);i++,lt++;
        else if(cmp>0) exch(a,gt,i);gt--;
        else i++;
    }
    
    sort(a,lo,lt-1);//<v部分排序
    sort(a,gt+1,hi);//>v部分排序
    
}

3、堆排序

概念

​ 堆有序:二叉树每个结点都>=它的两个子结点。

​ 二叉堆:一组能用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用第一个位置0)
在这里插入图片描述

​ 上浮(swim):由下至上的堆有序化。将v上浮即,使v与父结点p比较,若是v>p则v与p交换位置,v层级上升。不断循环比较上升直至v<=p或者上升至根结点。

private void swim(int k){
    //	k/2为父结点
    while(k>1&&less(k/2,k)){
        exch(k/2,k);
        k/=2;
    }
}

​ 下沉(sink):由上至下的堆有序化。将v下沉即,使v与子结点c比较,若是v<c则v与c交换位置,v的层级下降。不断循环比较下降直至v>=c或者下降至叶子结点。

private void sink(int k){
    while(k<=N/2){
        int j = 2*k;
        //max(子结点1,子结点2)
        if(j<N&&less(j,j+1)) j+=1;
        if(!less(k,j)) break;
        exch(j,k);
        k=j;
    }
}

思路

public static void sort(Comparable[] a){
    int N = a.length();
	//构造二叉堆:从数组n-1层级开始下沉(最后一层无需下沉,则此操作的对象数量为N/2)
    for(int i = N/2;i>=1;i--) sink(a,i,N);
	//将根结点(最大元素)与最后一个元素交换,然后取出最大元素,并且sink(1):重新使堆有序化
	while(N>1){
        exch(a,1,N--);
        sink(a,1,N);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值