十种基础排序 java

前言

1、前7种排序,除了冒泡排序,都来自【算法4】,冒泡排序因为书里没有所以是从网上找的,

2、前7种排序的入参数组都是Comparable[];后三种计数、基数、桶排序,的入参数组,都是【非负整数】数组。

 

零、排序的util方法

public class SortUtil {
    public static boolean less(Comparable v,Comparable w){
        return v.compareTo(w)<0;
    }
    public static void exch(Comparable v,Comparable w){
        Comparable tmp = v;
        v = w;
        w = tmp;
    }
    // 以下两个方法用于堆排序
    public static boolean less(Comparable[] a,int i,int j){
        return a[i-1].compareTo(a[j-1])<0;
    }
    public static void exch(Comparable[] a,int i,int j){
        Comparable tmp = a[i-1];
        a[i-1] = a[j-1];
        a[j-1] = tmp;
    }
    // 用于基数、计数、桶排序
    public static int getMaxVal(int[] arr){
        int max = 0;
        for(int i=0;i<arr.length;i++){
            if(arr[i]>arr[max])
                max = i;
        }
        return arr[max];
    }
}

  

  

一、选择排序

从0开始遍历到数组末尾,找出当前最小的元素,交换

  • 运行时间和输入无关
  • 数据移动最少
  • 选择排序用了N次交换,交换次数和数组大小是线性关系
public static void sort(Comparable[] a){
    int N = a.lenght;
    for(int i=0; i<N; i++){
        int min = i;
        for(int j=i+1; j<N; j++)
            if(less(a[j], a[min]) min = j;
        exch(a[i],a[min]);
    }
}

  

 

二、插入排序

从1开始遍历数组到数组末尾,从当前元素开始往前移动,比较相邻的元素,若大小倒置则交换,保证当前元素之前的数组有序

插入排序对【部分有序数组】比较有效

部分有序数组:

  • 数组中每个元素距离它的最终位置都不远
  • 一个有序大数组,接一个小数组
  • 数组中只有几个元素的位置不正确
public static void sort(Comparable[] a){
    int N = a.lenght;
    for(int i=1; i<N; i++){
        for(int j=i; j>0 && less(a[j],a[j-1]); j--){
            exch(a[j],a[j-1]);
        }
    }
}

 

 

三、冒泡排序

外部遍历i : 0~N-1,

内部遍历j : 0~N-i-1,

找到当前一轮遍历的最大元素,放到尾部,下一轮遍历去除这个尾部,循环

 

public void bubbleSort(Comparable[] a){
    int N = a.length;
    // i 从0开始
    for(int i=0;i<N-1;i++){
        // 选取从0到N-i-1之间,最大的元素,放到末尾。j 从0开始 到N-i-1
        for(int j=0; j<N-i-1; j++){
            if(less(a[j+1],a[j]))
                exch(a[j],a[j+1]);
        }
    }
} 

  

 

四、希尔排序

 

public static void sort(Comparable[] a){
    int N = a.length;
    int h = 1;
    // 确定最大的h
    while(h < N/3) h = 3h + 1;
    while(h >= 1){
        // 从i=h(第h+1个)元素开始,使用插入排序
        for(int i = h; i< N ; i++){
            // 插入排序的间隔为h,必须保证j-h>=0
            for(int j=i; j>=h && less(a[j],a[j-h]); j-=h )
                exch(a[j],a[j-h]);
        }
        h = h/3;
    }
}

  

五、归并排序

1、进入merge之前,lo到mid,mid+1到hi必然有序,但是lo到hi整个区间有序

2、merge使得数组的lo到hi位置有序

3、【注意】,切分不是指将数组切分成三部分,lo ~ mid-1,mid,mid+1 ~ hi,而是lo ~ mid,mid+1 ~ hi

public static void merge(Comparable[] a,int lo,int mid,int hi){
    int i = lo, j = mid+1;
    // 将lo至hi的元素复制到临时数组aux中
    for(int k=lo;k<=hi;k++){
        aux[k]=a[k];
    }
    // 1、i从lo位置开始,到mid位置结束
    // 2、j从mid+1位置开始,到hi位置结束
    // 3、取a[i],a[j]中最小的一个,放到k位置,取哪个,哪个自增
    // 4、当i>mid时,若j有剩余,则继续使用aux[j],aux[j]肯定大于aux[i],因为这是上次比较的结果;i>hi同理
    // 5、最后一个else,隐含了a[j]==a[i]的情况,这种情况下,a[k]=aux[j++]或a[k]==aux[i++]完全一样任意选择一个左侧,或者右侧元素即可
    for(int k=lo;k<=hi;k++){
        // i 结束条件
        if      (i>mid)                 a[k]=aux[j++];
        // j 结束条件
        else if (j>hi)                 a[k]=aux[i++];
        else if (less(aux[j],aux[i]))   a[k]=aux[j++];
        else                           a[k]=aux[i++];
    }
}

 

自顶向下的归并

private static Comparable[] aux;

public static void sort(Comparable[] a){
// 排序之前先创建和原数组大小一样的空数组 aux = new int[a.length]; sort(a,0,a.lenght-1); } private 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); }

  

 

六、快速排序   

 

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

//将子数组切分,使得切点位置左侧都小于切点,切点右侧都大于切点,最后把切点位置return
private static int partition(Comparable[] a, int lo, int hi){
    int i = lo,j =hi+1;
    Comparable v = a[lo];
    while(true){
        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],a[j]);
    }
    // 边界情况,进入最后一次while(true)时, 
    //    A)此时全部交换已经完成,(exch不会再执行,若它会执行,那当前一定不是最后一次while(true))
    //    B)由上一次while(true)中的if(i>=j)break; 可知,必有i < j,
    //    C)此时,i和j之间可能还有很多元素,但必然存在一个点X,X位于[i,j]区间,使得i~X位置的元素都小于v,X+1~j之间的元素都大与v
    //    D) 当i=X时,while(less(a[++i],v))为false,i=X+1;j=X+1时,while(less(v,a[--j]))为false,j=X;
    //    因此当两个内部while结束时,必然有 i = j+1,此时从lo+1~j,都小于v==a[lo],从i~hi,都大于v,
    //    因此exch的元素,取j而不是i
    exch(a[lo],a[j]);
    return j;
}

  

 

 

七、堆排序

  1、堆是一个完全二叉树,即树的每一层都是从左向右排列,除最底层以外,其他层必须排满;用数组来表示堆,若堆元素个数为N,数组大小为N+1,将堆中元素,从顶至底,从左至右,放入数组1~N的位置中,则有如下关系,对于用堆构造数组arr中任意元素arr[k],其在堆中的左儿子为arr[2k],右儿子为arr[2k+1],如下图,

  2、优先队列的实现方式有多种,此处的优先队列是由数组形式的二叉堆实现的

  算法思路

  • 首先用sink方法,将数组转换为最大优先队列
  • 然后,依次把最大值挪动到数组尾部,再对尾部“去掉尾部”的数组做下沉,重新构造优先队列,循环
  • sink函数名,这里指的是小值的下沉
public void sort(Comparable[] a){
    int N = a.length;
    // 将数组转换为最大优先队列
    for(int i=N/2;i>1;i--)
        sink(a,i,N);
    // 排序
    while(N>1){
        exch(a,1,N--);
        sink(a,1,N);
    }
}

// 优先队列的下沉
void sink(Comparable[] a,int k,int N){
    //循环条件,若k存在孩子
    while(2*k <= N){
        //1、令j为k的左子
        int j = 2*k;
        //2、令j等于k两个孩子中较大的一个
        if(j < N && less(a,j,j+1)) j++;
        //3、若k大于最大的孩子,则结束
        if(!less(a,k,j)) break;
        //4、若k小于最大的孩子,将k下沉
        exch(a,j,k);
        //5、重置k为被交换的孩子
        k = j;
    }
}

   

八、计数排序

算法导论P108

  • 排序数组中的元素,只能是[0,maxValue)范围之间的整数
  • 思路:对于排序数组中的每个元素x,只要确定比x小的元素个数,那么x就可以直接放在固定的位置上
  • 参考 https://www.cnblogs.com/developerY/p/3166462.html

 

public static int[] countingSort(int[] arr){
    int maxValue = getMaxVal(arr);
    // 辅助数组count,用来保存排序数组中每个元素x,小于等于x的元素的个数
    int[] count = new int[maxValue+1];
    int[] result = new int[arr.length];
    // 计算排序数组中,每个元素出现的次数
    for(int i=0;i<arr.length;i++)
        count[arr[i]]++;
    // 求得排序数组中,每个元素,小于等于该元素的个数
    // 小于等于当前元素个数,当前元素的个数 加上 小于等于上一个元素的个数
    for(int i=1;i<maxValue+1;i++){
        count[i] += count[i-1];
    }
    /**
     * 1、从后往前遍历arr
     * 2、取出arr[j],通过辅助数组count,得到小于等于arr[j]的元素个数count[arr[j]],将arr[j]放在B数组的count[arr[j]]-1位置
     * 3、辅助数组C,小于等于arr[j]的个数减一
     */
    for(int j=arr.length-1;j>=0;j--){
        result[count[arr[j]]-1] = arr[j];
        count[arr[j]]--;
    }
    return result;
}

  

 

九、基数排序

  • 采用LSD(least significant digital)方法
  • 主要参考https://www.cnblogs.com/developerY/p/3172379.html,次要参考https://www.cnblogs.com/skywang12345/p/3603669.html
public static void radixSort(int[] arr){
    int length = arr.length;
    // 创建10个桶,每个桶的容量为length
    int[][] buckets = new int[10][length];
    // 每个桶中,保存的元素个数
    int[] count = new int[10];

    // 取大于最大值的最小位数
    int digit = getDigit(getMaxVal(arr));;
    for(int d=0;d<digit;d++)
        int k = 0;
        // 将arr按照digit位数字取余,放入对应的桶中
        for(int i=0;i<length;i++){
            // 原始值去掉digit位,模10得到余数
            int remainder = (arr[i]/new Double(Math.pow(10,d)).intValue())%10;
            buckets[remainder][count[remainder]++] = arr[i];
        }
        // 从桶中取出元素,重新放入到arr中
        for(int bkt=0;bkt<10;i++){
            for(int j=0;j<count[bkt];j++){
                arr[k++] = buckets[bkt][j];
            }
            count[bkt] = 0;
        }
    }
}


public static int getDigit(int maxVal){
    int result = 0;
    while ((maxVal=maxVal/10)>0)
        result++;
    return ++result;
}

  

 

十、桶排序

 思路:桶排序的思路和计数排序很类似

  1. 计算数组中每个元素出现的次数,放入辅助数组,和计数排序第一步完全一样;
  2. 从0~max遍历辅助数组,若位置i上的计数大于0,则将其i放入结果集,令计数自减;
public static bucketSort(int arr[] arr){
    int N = arr.length;
    
    int max = getMaxVal(arr);
    
    int[] bucket = new int[max+1];
    
    for(int i=0;i<N;i++)
        bucket[arr[i]]++;
    
    int k = 0;
    //  从 0 开始遍历桶
    for(int i=0;i<max+1;i++){
        // 当某个桶的数字大于0时
        while( bucket[i] >0 ){
            arr[k++] = i;
            bucket[i]--;
        }
    }
}

  

 

posted @ 2019-02-14 15:30  leondryu  阅读(266)  评论(0)    收藏  举报