七大基于比较的排序

七大基于比较的排序



一、插入排序

1.直接插入排序-原理

整个区间被分为 :有序区间  无序区间

每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入

七大基于比较的排序

2.代码实现

    //直接插入排序
    public static void insertSort(int[] array){
        for(int i=1;i<array.length;i++){
            int tmp=array[i];
            int j=i-1;
            for(;j>=0;j--){
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else{
                    break;//前面都是有序的
                }
            }
            array[j+1]=tmp;
        }
    }

3.性能分析

时间复杂度

最坏情况:O(n^2)----数组逆序的情况下

最好情况:O(n)----数组有序的情况下

特点:越有序越快

空间复杂度:O(1)

稳定性:稳定的

(一个稳定的排序可以实现为不稳定的排序,但是一个不稳定的排序是不能实现为稳定的排序的)

如何快速判断一个排序是否稳定?

就看该排序是否发生跳跃式的交换(技巧,不是概念)

判断稳定性(概念):看两个相同数的排序前和排序后的位置是否发生了结构顺序的改变

4.折半插入排序

在有序区间选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想。
public static void bsInsertSort(int[] array) {
    for (int i = 1; i < array.length; i++) {
        int v = array[i];
        int left = 0;
        int right = i;
        // [left, right)
        // 需要考虑稳定性
        while (left < right) {
            int m = (left + right) / 2;
            if (v >= array[m]) {
                left = m + 1;
           } else {
                right = m;
           }
       }
        // 搬移
        for (int j = i; j > left; j--) {
            array[j] = array[j - 1];
       }
        array[left] = v;
   }
}




二、希尔排序

1.原理

希尔排序本身是直接插入排序的一种优化

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1 时,所有记录在统一组内排好序。 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。  

七大基于比较的排序

2.代码实现

    //希尔排序
    public static void shellSort1(int[] array){
        int[] drr={5,3,1};//增量的序列,这个不好求。假如有10000个序列的话,很麻烦
        for(int i=0;i<drr.length;i++ ){
            shell(array,drr[i]);
        }

    }
    public static void shellSort(int[] array) {
        int gap = array.length;
        while (gap > 1) {
            shell(array, gap);
            gap = (gap / 3) + 1; // OR gap = gap / 2;
            }
        shell(array, 1);
    }
    public static void shell(int[] array,int gap){
        for(int i=gap;i<array.length;i++){//i++每一组都能排序
            int tmp=array[i];
            int j=i-gap;
            for(;j>=0;j-=gap){
                if(array[j]>tmp){
                    array[j+gap]=array[j];
                }else{
                    break;
                }
            }
            array[j+gap]=tmp;
        }
    }

3.性能分析

时间复杂度:O(n^1.3) ~ O(n^1.5),希尔排序的时间复杂度与所取的增量有关

空间复杂度:O(1)

稳定性:不稳定




三、选择排序

1.直接选择排序-原理

每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素 排完 。

七大基于比较的排序

2.代码实现

    //选择排序
    public static void selectSort(int[] array){
        for(int i=0;i<array.length-1;i++){
            for(int j=i+1;j<array.length;j++){
                if(array[i]>array[j]){
                    swap(array,i,j);
                }
            }
        }
    }
    //交换数组中的两个元素
    public static void swap(int[] array,int i,int j){
        int tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }

3.性能分析

时间复杂度

最坏情况:O(n^2)

最好情况:O(n^2)

空间复杂度:O(1)

稳定性:不稳定

4.双向选择排序

每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完
public static void selectSortOP(int[] array) {
    int low = 0;
    int high = array.length - 1;
    // [low, high] 表示整个无序区间
    // 无序区间内只有一个数也可以停止排序了
    while (low <= high) {
        int min = low;
        int max = low;
        for (int i = low + 1; i <= max; i++) {
            if (array[i] < array[min]) {
                min = i;
           }
            if (array[i] > array[max]) {
                max = i;
           }
       }        
        swap(array, min, low);
        if (max == low) {
            max = min;
       }
        swap(array, max, high);
   }
}
private void swap(int[] array, int i, int j) {
    int t = array[i];
    array[i] = array[j];
    array[j] = t;
}




四、堆排序

1.原理

基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数(大堆堆顶元素就是最大元素)。
注意:升序建大堆,降序建小堆 

2.代码实现

    //堆排序
    public static void heapSort(int[] array){
        //建立一个大根堆
        creatHeap(array);
        int end=array.length-1;
        while(end>0){ //O(n*logn)
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }

    }
    //建大根堆  O(n)
    public static void creatHeap(int[] array){
        for(int parent=(array.length-1-1)/2;parent>=0;parent--){
            shiftDown(array,parent,array.length);
        }
    }
    //向下调整  O(logn)
    public static void shiftDown(int[] array,int parent,int len){
        int child=parent*2+1;
        while(child<len){
            if(child+1<len && array[child]<array[child+1]){
                child++;
            }
            if(array[child]>array[parent]){
                swap(array,child,parent);
                parent=child;
                child=2*parent+1;
            }else{
                break;
            }
        }
    }
    //交换数组中的两个元素
    public static void swap(int[] array,int i,int j){
        int tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }

3.性能分析

时间复杂度:O(n*logn)

空间复杂度:O(1)

稳定性:不稳定


五、冒泡排序

1.原理

在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序

2.代码实现

    //冒泡排序
    public static void bubbleSort(int[] array){
        for(int i=0;i<array.length-1;i++){
            boolean flg=false;
            for(int j=0;j<array.length-1-i;j++){
                if(array[j]>array[j+1]){
                    swap(array,j,j+1);
                    flg=true;
                }
            }
            if(!flg){
                break;//判断数组是否已经有序
            }
        }
    }
    //交换数组中的两个元素
    public static void swap(int[] array,int i,int j){
        int tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }

3.性能分析

时间复杂度

最坏情况:O(n^2)-----不优化情况下不管有序无序都是

最好情况:O(n)-----优化之后

空间复杂度:O(1)

稳定性:稳定的


六、快速排序

1.原理

1. 从待排序区间选择一个数,作为基准值 (pivot) ; 2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可 以包含相等的)放到基准值的右边; 3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1 ,代表已经有序,或者小区间 的长度 == 0 ,代表没有数据。

2.代码实现

    //快速排序
    public static void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int low,int high){

        //结束条件
        if(low>=high){
            return;
        }
        //优化
        //1.三数取中法----避免出现单分支情况(单分支可能会导致栈溢出错误)
        mid_three(array,low,high);

        //2.如果排序基本有序,可以采用直接插入排序
        if((high-low+1)<=100){
            //直接插入排序
            insertSort1(array,low,high);
        }
        //随机选取基准法

        int pivot=partition(array,low,high);
        quick(array,low,pivot-1);
        quick(array,pivot+1,high);
    }

    //固定位置选取基准
    //找基准(挖坑法)
    public static int partition(int[] array,int start,int end){
        int tmp=array[start];
        while(start<end){
            while(start<end&&array[end]>=tmp){//必须加“=”,不然会死循环
                end--;
            }
            array[start]=array[end];
            while(start<end&&array[start]<=tmp){
                start++;
            }
            array[end]=array[start];
        }
        array[start]=tmp;
        return start;
    }
    //Hoare法
    public static int partition1(int[] array,int start,int end){
        int i=start;
        int j=end;
        int tmp=array[start];
        while(i<j){
            while(i<j&&array[j]>=tmp){
                j--;
            }
            while(i<j&&array[i]<=tmp){
                i++;
            }
            swap(array,i,j);
        }
        swap(array,i,start);
        return i;
    }
    //几数取中法(三数取中法)
    public static void mid_three(int[] array,int left,int right){

        int mid=(left+right)/2;//left+(right-left)/2

        if(array[left]>array[right]){
            swap(array,left,right);
        }//array[left]<=array[right]
        if(array[mid]>array[left]){
            swap(array,mid,left);
        }//array[mid]<=array[left]
        if(array[mid]>array[right]){
            swap(array,mid,right);
        }//array[mid]<=array[right]
    }
    //在某个区间直接插入排序
    public static void insertSort1(int[] array,int low,int high){
        for(int i=low+1;i<=high;i++){
            int tmp=array[i];
            int j=i-1;
            for(;j>=low;j--){
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else{
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }

非递归快速排序(分治)

    //非递归快速排序
    public static void quickSort2(int[] array){
        quick2(array,0,array.length-1);
    }
    public static void quick2(int[] array,int low,int high){
        Stack<Integer> stack=new Stack<>();
        int pivot=partition(array,low,high);
        //入基准值左边首尾下标
        if(pivot>low+1){//基准值左边的数大于一个
            stack.push(low);
            stack.push(pivot-1);
        }
        //入基准值右边的首尾下标
        if(pivot<high-1){//基准值右边的数大于一个
            stack.push(pivot+1);
            stack.push(high);
        }
        while(!stack.isEmpty()){
            //弹出下标找基准值
            high=stack.pop();
            low=stack.pop();
            pivot=partition(array,low,high);
            //继续入基准值左边右边首尾下标
            if(pivot>low+1){
                stack.push(low);
                stack.push(pivot-1);
            }
            if(pivot<high-1){
                stack.push(pivot+1);
                stack.push(high);
            }
        }
    }
    //找基准(挖坑法)
    public static int partition(int[] array,int start,int end){
        int tmp=array[start];
        while(start<end){
            while(start<end&&array[end]>=tmp){//必须加“=”,不然会死循环
                end--;
            }
            array[start]=array[end];
            while(start<end&&array[start]<=tmp){
                start++;
            }
            array[end]=array[start];
        }
        array[start]=tmp;
        return start;
    }

3.性能分析

时间复杂度:O(n*logn)(比堆排序用时少-》系数小)

最坏情况:O(n*logn)-----每次能够均与地分割待排序序列

最好情况:O(n^2)----单分支情况(一直在一边递归)

空间复杂度:

最坏情况:O(n)---单分支情况

最好情况:O(logn)

稳定性:不稳定


七、归并排序

1.原理

归并排序 是建立在归并操作上的一种有效的排序算法 ,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子 序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

七大基于比较的排序

2.代码实现

    //递归排序
    public static void mergeSort(int[] array){
        mergeSortIn(array,0,array.length-1);
    }
    public static void mergeSortIn(int[] array,int low,int high){
        int mid=(low+high)/2;
        if(low>=high){
            return;
        }
        mergeSortIn(array,low,mid);
        mergeSortIn(array,mid+1,high);
        merge(array,low,mid,high);
    }
    public static void merge(int[] array,int low,int mid,int high){
        int s1=low;
        int e1=mid;
        int s2=mid+1;
        int e2=high;
        //申请一个临时数组存放比较之后的元素
        int[] arr=new int[high-low+1];
        int k=0;
        while(s1<=e1&&s2<=e2){//判断要合并的数组里是否有元素
            if(array[s1]<=array[s2]){
                arr[k++]=array[s1++];
            }else{
                arr[k++]=array[s2++];
            }
        }
        while(s1<=e1){//第二个数组为空时
            arr[k++]=array[s1++];
        }
        while (s2<=e2){//第一个数组为空时
            arr[k++]=array[s2++];
        }
        //写回数据到原来的数组
        for (int i = 0; i < k; i++) {
            array[i+low]=arr[i];//i+low保证右边数组放回时不会覆盖左边已经放好的数组
        }
    }

非递归的归并排序

    //非递归归并排序
    public static void mergeSortNor(int[] array){
        int gap=1;
        while(gap<=array.length){
            for(int i=0;i< array.length;i+=2*gap){
                int low=i;
                int mid=i+gap-1;
                int high=mid+gap;
                //防止mid越界
                if(mid>=array.length){
                    mid=array.length-1;
                }
                //防止high越界
                if(high>=array.length){
                    high=array.length-1;
                }
                merge(array,low,mid,high);
            }
            gap=gap*2;
        }
    }

3.性能分析

时间复杂度:   O(n*logn)

空间复杂度:O(n)

稳定性:稳定的排序


总结:

排序方法 最好 平均 最坏 空间复杂度 稳定性
冒泡排序 O(n) O(n^2) O(n^2) O(1) 稳定
插入排序 O(n) O(n^2) O(n^2) O(1) 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
希尔排序 O(n) O(n^1.3-1.5) O(n^2) O(1) 不稳定
堆排序 O(n*logn) O(n*logn) O(n*logn) O(1) 不稳定
快速排序 O(n*logn) O(n*logn) O(n^2) O(logn)-O(n) 不稳定
归并排序 O(n*logn) O(n*logn) O(n*logn) O(n) 稳定

七大基于比较的排序

上一篇:LeetCode-099-恢复二叉搜索树


下一篇:关于ESC的初次使用感受