十种基础排序 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;
}
十、桶排序
思路:桶排序的思路和计数排序很类似
- 计算数组中每个元素出现的次数,放入辅助数组,和计数排序第一步完全一样;
- 从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]--;
}
}
}

浙公网安备 33010602011771号