排序方法 | 最好情况 | 最坏情况 | 平均情况 | 辅助空间 | |
---|---|---|---|---|---|
插入 | 直接插入排序(稳定) | n | n^2 | n^2 | O(1) |
插入 | 希尔排序 | n | n^2 | n1/n3 | O(1) |
交换 | 冒泡排序(稳定) | n | n^2 | n^2 | O(1) |
交换 | 快速排序 | nlogn | n^2 | nlogn | O(nlogn) |
选择 | 直接选择排序 | n^2 | n^2 | n^2 | O(1) |
选择 | 堆排序 | nlogn | nlogn | nlogn | O(n^2) |
归并排序(稳定) | nlogn | nlogn | nlogn | O(n) |
七大排序算法总结
通用swap代码:
private static final ThreadLocalRandom random=ThreadLocalRandom.current();
private static void swap(int[] arr, int i, int min) {
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
/**
* 直接选择排序
*/
public static void selectionSort(int[] arr) {
//剩余的选出来与第i个交换 i=0
for (int i = 0; i < arr.length-1; i++) {
//已经有序的集合[0,i] 未排序的集合[i+1,n]
int min=i;
for (int j = i+1; j < arr.length; j++) {
if(arr[j]<arr[min]){
min=j;
}
}
//此时min存储了最小值元素的下标
swap(arr,i,min);
}
}
- 代码2:双向选择排序
/**
* 双向选择排序 每次选出最小的放前面 最大的放后面
* @param arr
*/
public static void selectionSortOP(int[] arr){
int low=0,high=arr.length-1;
//有序区间 [0,low+1]
while(low<high){
int min=low,max=low;
for (int i = low+1; i <= high; i++) {
if(arr[min]>arr[i]){
min=i;
}
if(arr[max]<arr[i]){
max=i;
}
}
//max存储无序区间最大值 min存储最小值
swap(arr,low,min);
if(low==max){
//max在首(low)位 交换最小之后会覆盖max的位置 所以使用刚好交换后min位置的最大值找到max
max=min;
}
swap(arr,max,high);
low++;
high--;
}
}
/**
* 直接插入排序
* 每次选择无序区间的第一个元素 插入到有序区间的合适位置
* @param arr
*/
public static void insertionSortBase(int[] arr){
//有序区间 [0,1) 默认第一个元素有序
for (int i = 1; i < arr.length ; i++) {
for (int j = i; j>0&&arr[j]<arr[j-1] ; j--) {
swap(arr,j,j-1);
// if(arr[j]<arr[j-1]){
// swap(arr,j,j-1);
// }
// else{
// //j位置对应元素已经到达相应位置
// break;
// }
}
}
}
- 代码2:折半插入排序
/**
* 插入数据时采用二分法查找插入位置
* @param arr
*/
public static void insertSortBS(int[] arr){
for (int i = 1; i < arr.length; i++) {
//无序区间第一个值
int val=arr[i];
//有序区间[0,i);
int low=0;
int high=i;
while(low<high){
int mid =(low+high)>>1;
if(val<=arr[mid]){
high=mid;
}else{
low=mid+1;
}
}
//数据后移
for(int j=i;j>low;j--){
arr[j]=arr[j-1];
}
arr[low]=val;
}
}
-
希尔排序:
-
思想:gap=length/2作为第一个增量 ,将待排序的数据按照gap分组,距离为gap的数组放在同一组,然后不断缩小增量gap=gap/2,当gap=1时,整个数组接近有序,调用普通插入方法同意排序。
-
代码:
-
/**
* 希尔排序
* @param arr
*/
public static void shellSort(int[] arr){
int gap=arr.length>>1;
while(gap>1){
insertSortGap(arr,gap);
gap/=2;
}
//整个数组的插入排序
insertSortGap(arr,1);
}
private static void insertSortGap(int[] arr, int gap) {
for(int i=gap;i<arr.length;i++){
for(int j=i;j-gap>=0&&arr[j-gap]>arr[j];j=j-gap){
swap(arr,j,j-gap);
}
}
}
-
归并排序:
-
思想:归并 -》先拆了才能并 所以归并排序在这里分为两步
- 将原数组不断拆分直至每个拆分后的数组只剩一个元素,拆分结束
- 将拆分后的数组不断合并 (2排序2->4 4排序4->8) 直至整个数组有序
-
辅助代码:当r-l<=15时,在数组arr[l…r]上使用插入排序
-
/**
* 在数组arr[l..r]上使用插入排序
* @param arr
* @param l
* @param r
*/
private static void insertBase(int[] arr, int l, int r) {
//有序区间[l..i)
//无序区间[i..r]
for (int i = l+1; i <=r ; i++) {
for (int j = i; j >l&&arr[j]<arr[j-1]; j--) {
swap(arr,j,j-1);
}
}
}
- 代码1:
/**
* 归并排序
* @param arr
*/
public static void mergeSort(int[] arr){
mergeSortInternal(arr,0,arr.length-1);
}
/**
* 在arr[l..r]进行归并排序
* @param arr
*/
private static void mergeSortInternal(int[] arr, int l, int r) {
if(r-l<=15){
insertBase(arr,l,r);
return;
}
int mid=l+((r-l)>>1);
//再拆分的两个数组上使用归并排序
//先排序左区间
mergeSortInternal(arr,l,mid);
//再排续又区间
mergeSortInternal(arr,mid+1,r);
//当两个小区间乱序才合并
if(arr[mid]>arr[mid+1]){
merge(arr,l,mid,r);
}
}
/**
* 将已经有序的两个小区间合并成一个大的有序数组
* @param arr
* @param l
* @param mid
* @param r
*/
private static void merge(int[] arr, int l, int mid, int r) {
//开辟一个数组
int[] tep=new int[r-l+1];
for (int i = l; i <=r ; i++) {
tep[i-l]=arr[i];
}
//遍历元素组,选择作曲兼和有趣间的最小值写入数组
//i左半有序区间的第一个索引
int i=l;
//j右半区间的第一个索引
int j=mid+1;
for (int k = l; k <=r; k++) {
if(i>mid){
//此时左半区间已经完全处理完毕,将有版区间所有制写入元素组
arr[k]=tep[j-l];
j++;
}else if(j>r){
//此时有伴区间已经处理完毕,作伴写入数组
arr[k]=tep[i-l];
i++;
}else if(tep[i-l]<=tep[j-l]){
arr[k]=tep[i-l];
i++;
}else{
arr[k]=tep[j-l];
j++;
}
}
}
- 代码2:非递归版本
/**
* 归并排序的非递归版本
* @param arr
*/
public static void mergeSortNonRecursion(int[] arr){
for(int a=1;a<=arr.length;a+=a+a){
//一个一个两个两个sigesige合并 所以是i+=a+a
for (int i = 0; i + a<arr.length ; i+=a+a) {
merge(arr,i,i+a-1,Math.min(i+2*a-1,arr.length-1));
}
}
}
/**
* 在数组arr[l..r]上使用插入排序
* @param arr
* @param l
* @param r
*/
private static void insertBase(int[] arr, int l, int r) {
//有序区间[l..i)
//无序区间[i..r]
for (int i = l+1; i <=r ; i++) {
for (int j = i; j >l&&arr[j]<arr[j-1]; j--) {
swap(arr,j,j-1);
}
}
}
- 代码1:
public static void quickSort1(int[] arr){
quickSortInternal(arr,0,arr.length-1);
}
/**
* 基础快速排序
* @param arr
* @param l
* @param r
*/
public static void quickSortInternal(int[] arr,int l,int r){
//递归终止时 小数组使用插入排序
if(r-l<=15){
insertBase(arr,l,r);
return;
}
int p=partition(arr,l,r);
//在小于基准的区间进行排序
quickSortInternal(arr,l,p-1);
quickSortInternal(arr,p+1,r);
}
/**
* 在arr[l..r]上选取基准值,将数组划分为<v,>=v的区间 返回基准值对应的下标
* @param arr
* @param l
* @param r
* @return
*/
private static int partition(int[] arr, int l, int r) {
int randomIndex=random.nextInt(l,r);
swap(arr,l,randomIndex);
int v=arr[l];
//[l+1...j]< [j+1...i)>=
int j=l;
//i是当前要处理元素的下标
for (int i = l+1; i <=r; i++) {
if(arr[i]<v){
swap(arr,j+1,i);
j++;
}
}
swap(arr,l,j);
return j;
}
- 代码2:双路快排
public static void quickSort2(int[] arr){
quickSortInternal2(arr,0,arr.length-1);
}
private static void quickSortInternal2(int[] arr, int l, int r) {
if(r-l<=15){
insertBase(arr,l,r);
return;
}
int p=partition2(arr,l,r);
//在小于基准的区间进行排序
quickSortInternal2(arr,l,p-1);
quickSortInternal2(arr,p+1,r);
}
private static int partition2(int[] arr, int l, int r) {
int randomIndex=random.nextInt(l,r);
swap(arr,l,randomIndex);
int v=arr[l];
int i=l+1;
int j=r;
while(true){
//j扫描第一个大于等于v的元素
while(i<=r&&arr[i]<v){
i++;
}
while(j>=l+1&&arr[j]>v){
j--;
}
if(i>j){
break;
}
swap(arr,i,j);
i++;
j--;
}
//j落在最后一个小于等于v的元素
swap(arr,l,j);
return j;
}
- 代码3:三路快排
public static void quickSort3(int[] arr){
quickSortInternal3(arr,0,arr.length-1);
}
private static void quickSortInternal3(int[] arr, int l, int r) {
if(r-l<=15){
insertBase(arr,l,r);
return;
}
int randomIndex=random.nextInt();
swap(arr,l,randomIndex);
int v=arr[l];
//arr[l+1...lt]<v;
int lt=l;
//arr[gt..r]>v
int gt=r+1;
//lt+1..gt ==
int i=l+1;
while(i<gt){
if(arr[i]<v){
swap(arr,lt+1,i);
i++;
lt++;
}else if(arr[i]>v){
swap(arr,gt-1,i);
gt--;
}else{
i++;
}
}
swap(arr,l,lt);
//[l,lt-1]<v
//[gt,r]>v
quickSortInternal3(arr,l,lt-1);
quickSortInternal3(arr,gt,r);
}
-
堆排序:
-
思想:利用堆进行排序的方法。其基本思想为:将待排序列构造成一个大顶堆(或小顶堆),整个序列的最大值(或最小值)就是堆顶的根结点,将根节点的值和堆数组的末尾元素交换,此时末尾元素就是最大值(或最小值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值(或次小值),如此反复执行,最终得到一个有序序列。
-
代码:
-
/**
* 堆排序
* @param arr
*/
public static void heapSort(int[] arr){
//将任意数组调整为最大堆
for (int i = (arr.length-1-1)/2; i >=0 ; i--) {
siftDown(arr,i,arr.length);
}
//拿走一个大的之后重新开始调整
for (int i = arr.length-1; i >0 ; i--) {
swap(arr,0,i);
siftDown(arr,0,i);
}
}
private static void siftDown(int[] arr, int i, int n) {
//仍存在子树
while(2*i+1<n){//存在左子树
int j=2*i+1;
if(j+1<n&&arr[j+1]>arr[j]){//存在右子树且右子树大于左子树
j=j+1;
}
if(arr[i]>arr[j]){
break;
}else{
swap(arr,i,j);
i=j;
}
}
}
/**
* 冒泡排序
* @param arr
*/
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
boolean b=false;
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j]>arr[j+1]){
b=true;
swap(arr,j,j+1);
}
}
if(!b){
break;
}
}
}
SortHelper
/**
* 排序的辅助类
*/
public class SortHelper {
//生成随机数的类(线程不安全)
//通过方法去得对象
private static final ThreadLocalRandom random=ThreadLocalRandom.current();
/**
* 生成一个具有n个数的随机数 取值范围 l r
* @return
*/
public static int[] generateRandomArray(int n,int L,int R){
int[] ret=new int[n];
for (int i = 0; i < n; i++) {
ret[i]=random.nextInt(L,R);
}
return ret;
}
public static int[] generateSortedArray(int n,int times){
//n个完全有序的数组
int[] ret=new int[n];
for (int i = 0; i < n; i++) {
ret[i]=i;
}
//交换任意位置的数字times次
for (int i = 0; i < times; i++) {
int index1=random.nextInt(0,n-1);
int index2=random.nextInt(0,n-1);
swap(ret,index1,index2);
}
return ret;
}
//如果排序后的数组不是有序
public static boolean isSorted(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
if (arr[i] > arr[i + 1]) {
System.err.println("排序算法有误");
return false;
}
}
return true;
}
/**
* 根据排序名称来调用相应的排序算法来对arr做排序处理
* 反射 在运行过程中动态获取方法或属性
* @param sortName
* @param arr
*/
public static void testSort(String sortName,int[] arr) throws Exception{
//获取class对象
Class<SevenSort> cls=SevenSort.class;
Method method=cls.getDeclaredMethod(sortName,int[].class);
long start=System.nanoTime();
//调用这个排序算法
method.invoke(null,arr);
long end=System.nanoTime();
if(isSorted(arr)){
System.out.println(sortName+"排序完成,共耗时:"+(end-start)/1000000+"ms");
}
}
public static int[] arrCopy(int[] arr){
return Arrays.copyOf(arr,arr.length);
}
private static void swap(int[] ret, int index1, int index2) {
int temp = ret[index1];
ret[index1] = ret[index2];
ret[index2] = temp;
}
}