Lesson2:常用的排序算法

目录

一、概念

二、直接插入排序

2.1 原理

2.2 实现

2.3 性能分析

三、希尔排序

3.1 原理

3.2 实现

3.3 性能分析

四、选择排序

4.1 原理

4.2 实现

         4.3 性能分析

五、堆排序

5.1 原理

5.2 实现

         5.3 性能分析

六、冒泡排序

6.1 原理

6.2 实现

6.3 性能分析

七、快速排序

7.1 快速排序的递归实现

7.1.1 原理

7.1.2  挖坑法

7.1.3  hoare法

7.1.4 优化

7.2 非递归实现

7.3 性能分析

 7.4 总结

八、归并排序

8.1 原理

8.2 归并排序的递归实现

8.3 归并排序的非递归实现

8.4 性能分析

 九、排序总结


一、概念

时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。

空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。

稳定性:两个相等的数据,如果经过排序后,排序算法能够保证其相对位置不发生变化,则称该算法稳定。

一个稳定的算法,可以被实现为不稳定的排序。

但一个不稳定的算法,无法被实现为稳定的算法。

常见的排序算法:

二、直接插入排序

2.1 原理

插入:前提是[0,i-1]下标的数组都有序,将array[i]放在合适的位置,使得[0,i]之间的元素都有序。 

具体操作:遍历array[i-1]到array[0]之间的元素,用array[j]来表示,如果比array[j]比array[i]大,array[j]就往后挪动,即array[j+1] = array[j]。j--,当遇见array[j]<array[i]或者j<0(遍历结束都还没找到比array[i]小的),就让array[j+1]= tmp。

2.2 实现

public static void insertSort(int[] array){
        if(array == null){
            return;
        }
        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;
        }
    }

2.3 性能分析

时间复杂度

空间复杂度
稳定性

稳定

插入排序,数据越接近有序,时间效率越高。一般插入排序会用到一些需要优化的地方。

三、希尔排序

3.1 原理

希尔排序是对直接插入排序的一个优化。在排序之前,先分组,组内进行直接插入排序。

分组采用的缩小增量法,最后的增量必须为1.

直接插入排序就是希尔排序gap=1的情况。

格局小了,快来康康大佬是如何分组的。

3.2 实现

public static void shellSort(int[] array){
        if(array == null){
            return;
        }
        int gap = array.length;
        while(gap > 1){
            gap = gap/2;
            for(int i =gap;i<array.length;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.3 性能分析

时间复杂度

空间复杂度
稳定性

不稳定

四、选择排序

4.1 原理

假设数组长度为elem,数组长度为len.

第一趟,假设第一个元素elem[0]是最小的,从elem[1]开始遍历数组,若是遇见的元素比elem[0]小,交换这两个元素。保证elem[0]存到元素是最小的。第一遍遍历结束后,最小的元素存在elem[0]当中。

第二趟,由于elem[0]已经存放最小的元素,不用再调整位置了,第二次假设elem[1]是elem[1]到elem[len-1]中最小的元素,从elem[2]开始遍历数组,若是遇见比elem[1]小的元素就交换位置,保证elelm[1]中存的是elem[1]-elem[len-1]中最小的元素。

即:第i (从i=0开始) 次遍历数组,假设elem[i]是最小的元素,从elem[i+1]开始遍历数组,遇见比elem[i]小的就交换位置。

基于上述思路,选择排序有两种思路:

第一种:假设elem[i]是最小的元素,从elem[i+1]开始遍历数组,遇见比elem[i]小的就交换位置。这种思路每次遇见比elem[i]小的都会交换。

第二种:假设min下标所指的元素最小,未比较之前假设min=i;从elem[i+1]开始遍历数组,遇见比elem[i]小的就跟新 i 的值,遍历结束后,若是 min != i,证明最小的值不是elem[i],交换elem[min]和elem[i]的位置。若min=i,证明elem[i]最小,不用交换。

第一种思路是每次遇见比elem[i]小就交换。第二种,先找出最小的再交换。

优化:每次遍历找到最小的元素和最大的元素,将最小和最大的分别放在数组的头和尾。

 

4.2 实现

 public static void selectSort(int[] array){
        if(array == null){
            return;
        }
        for(int i = 0;i<array.length;i++){
            for(int j = i+1;j<array.length;j++){
                if(array[i]>array[j]){  // 碰见比array[i]小的,就交换
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
            }
        }
    }
    public static void selectSort2(int[] array){
        if(array == null){
            return;
        }
        for(int i= 0;i<array.length;i++){
            int min = i;
            for(int j =i+1;j<array.length;j++){  // 找到最小的元素,再交换
                if(array[j]<array[min]){
                    min = j;
                }
            }
            if(min != i){
                int tmp = array[min];
                array[min] = array[i];
                array[i] = tmp;
            }
        }
    }
//每一趟遍历找出最大的最小的,将其放在队列的尾部和头部
public static void selelctSort(int[] array){
        if(array == null){
            return;
        }
        for(int i = 0;i<=array.length/2;i++){
            int min = i;
            int max = i;
            for(int j = i+1;j<=array.length-1-i;j++){//每次在array[i,array.length-1-i]中找出最大和最小
                if(array[j]<array[min]){
                    min = j;  //找最小
                }
                if(array[j]>array[max]){
                    max = j;  // 找最大
                }
            }
            if(max == i && min ==array.length-1-i){
                swap(array,min,max);  //恰好最大的元素在队头,最小的元素在队尾,则可以直接交换队头和队尾元素。
            }else {
                swap(array,i,min); // 最小的放队头
                swap(array,array.length-1-i,max); // 最大的放队尾
            }
        }

    }
    public static void swap(int[] array,int left,int right){
        int tmp = array[left];
        array[left] = array[right];
        array[right] = tmp;
    }

4.3 性能分析

时间复杂度

空间复杂度
稳定性不稳定

五、堆排序

5.1 原理

先建立一个大根堆,交换堆顶元素和最后一个元素的位置。交换完后,最大的元素已经在队尾,再次建立大根堆时不用再考虑最后一个元素。用 [0,end] 表示需要排序的元素,每把大根堆的堆顶元素和最后一个元素交换结束后,end减一,直到end=0,证明已经排序结束。

5.2 实现

public static void heapSort(int[] array){
        if(array == null){
            return;
        }
        int end = array.length-1; //标记结束位置的下标
        while(end>=0){
            int parent = (end-1)/2;
            while(parent>=0){
                shiftDown(array,parent,end);
                parent--;
            }
            int tmp = array[0];
            array[0] = array[end];
            array[end] = tmp;
            end--;
        }
    }
    public static void shiftDown(int[] array,int parent,int end){
        int child = parent*2+1;
        while(child <= end){
            if(child+1<=end && array[child]<array[child+1]){
                child++;
            }
            if(array[child]>array[parent]){
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }
            parent = child;
            child = 2*parent+1;
        }
    }

5.3 性能分析

时间复杂度

空间复杂度
稳定性不稳定

六、冒泡排序

6.1 原理

如果第一个元素比第二个大,就交换这两个的位置。依次比较相邻位置的元素,

6.2 实现

public static void bubble(int[] array){
        if(array == null){
            return;
        }
        for(int i = 0;i<array.length-1;i++){
            System.out.printf("第%d趟比较",i+1);
            for(int j = 0;j<array.length-1-i;j++){
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
            System.out.println(Arrays.toString(array));
        }
    }
// 优化后
public static void bubbleSort(int[] array){
        if(array == null){
            return;
        }
        for(int i = 0;i<array.length-1;i++){
            boolean flag = false;
            System.out.printf("第%d趟比较",i+1);
            for(int j = 0;j<array.length-1-i;j++){
                if(array[j]>array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flag = true;
                }
            }
            System.out.println(Arrays.toString(array));
            if(! flag){
                break;
            }
        }
    }

6.3 性能分析

时间复杂度

空间复杂度
稳定性稳定

七、快速排序

7.1 快速排序的递归实现

7.1.1 原理

step1:从待排序区间中选择一个数,作为基准值(pivot).

step2:Partiton:遍历整个待排序区间,将比基准值pivot小的放到基准值的左边,将比基准值pivot大的放在基准值的右边。

step3:采用分治思想,对左右两个区间按照同样的方式处理,直到区间的长度小于等于1.

7.1.2  挖坑法

    public static void quickSort(int[] array){
        if(array == null){
            return;
        }
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int left,int right){
        if(right-left<=0){
            return;
        }
        int privot = partition(array,left,right);
        quick(array,left,privot-1);
        quick(array,privot+1,right);

    }
    public static int partition(int[] array,int left,int right){
        int tmp = array[left];
        while(left<right){
            while (left<right && array[right]>= tmp){
                right--;
            }
            array[left] = array[right];
            while(left<right && array[left] <= tmp){
                left++;
            }
            array[right] = array[left];
            if(left == right){
                array[left] = tmp;
            }
        }
        return left;
    }

7.1.3  hoare法

    public static void quickSort(int[] array){
        if(array == null){
            return;
        }
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int left,int right){
        if(right-left<=0){
            return;
        }
        int privot = hoarePartition(array,left,right);
        quick(array,left,privot-1);
        quick(array,privot+1,right);

    }
    public static int hoarePartition(int[] array,int left,int right){
        int key = left;
        while(left<right){
            while(left<right && array[right]>=array[key]){
                right--;
            }
            while(left<right && array[left]<=array[key]){
                left++;
            }
            if(left != right){
                int tmp = array[left];
                array[left] = array[right];
                array[right] = tmp;
            }
            if(left == right){
                int tmp = array[left];
                array[left] = array[key];
                array[key] = tmp;
            }
        }
        return left;
    }

7.1.4 优化

优化分为三个方面:

①选择基准值的优化:主要是想将待排序序列均匀的分割,越均匀,速度越快。

     选择左边或者右边:若最小的数在最左边,将其作为基准值,第一趟排序中数组里的元素几乎没交换,还处于无序状态。

     随机选取:不是特别好,有可能产生的随机数是第二小的,第一趟排序下来就只交换了一下。

     几数取中法:以三数取中为例,先调换元素位置使其满足array[left]<=array[mid]<=array[right],然后调换array[mid]和array[left]的位置。

②partiton过程中把和基准值相等的数也选择出来。

③当递归执行到一个区间时,进行直接插入排序。

    public static void midtofirst(int[] array,int left,int right){
        int mid = (left+right)/2;
        while(true){
            if(array[mid]<array[left]){
                int tmp = array[mid];
                array[mid] = array[left];
                array[left] = tmp;
            }
            if(array[mid] > array[right]){
                int tmp = array[mid];
                array[mid] = array[right];
                array[right] = tmp;

            }
            if(array[left]>array[right]){
                int tmp =array[left];
                array[left] = array[right];
                array[right] = tmp;
            }
            if(array[left]<=array[mid] && array[mid]<=array[right]){
                break;
            }
           // 满足array[left]<= array[mid]<=array[right]
            // 交换array[left] 和array[mid]
            int tmp = array[left];
            array[left] = array[mid];
            array[mid] = tmp;
        }
  // 使用
     public static void quickdiedai(int[] array,int left,int right){
        if(right-left<=0){
            return;
        }
        midtofirst(array,left,right);
        int privot = hoarepartiton(array,left,right);
        quickdiedai(array,left,privot-1);
        quickdiedai(array,privot+1,right);

    }

7.2 非递归实现

采用栈将每次的left和right都存储起来。注意每次的pop和push时需要与left和right对应起来。

    public static void quickNull(int[] array){
        if(array == null){
            return;
        }
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        stack.push(array.length-1);
        while(! stack.isEmpty()){
            int right = stack.pop();
            int left = stack.pop();
            int privot = partition(array,left,right);
            if(privot-left>1){
                stack.push(left);
                stack.push(privot-1);
            }
            if(right-privot>1){
                stack.push(privot+1);
                stack.push(right);
            }
        }
    }

7.3 性能分析

时间复杂度

空间复杂度
稳定性不稳定

 7.4 总结

step1:在待排序区间选择一个基准值

           选择左边或右边、随机选取、几数取中法

step2:做partition,使得左边的数小于privot,右边的数大于privot。

             hoare、挖坑

step3:分治处理左右两个小区间,直到小区间数目小于一个阈值,使用插入排序。

八、归并排序

8.1 原理

建立在归并操作上的一种有效的排序算法。先将每个子序列排序,再将有序子序列合并成一个有序表。若将两个有序表合并成一个有序表,称为二路归并。

8.2 归并排序的递归实现

    public static void mergeSort(int[] array){
        if(array == null){
            return;
        }
        mergesortdigui(array,0,array.length-1);
    }
    public static void mergesortdigui(int[] array,int left,int right){
        if(right - left<=0){
            return;
        }
        int mid = (left+right)/2;
        mergesortdigui(array,left,mid);
        mergesortdigui(array,mid+1,right);
        merge(array,left,mid,right);
    }
    public static void merge(int[] array,int left,int mid,int right){
        int[] elem = new int[right-left+1];
        int k = 0;
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        // 这种情况是s1和s2没有越界的情况
        while(s1<=e1 && s2<=e2){
            if(array[s1]<array[s2]){
                elem[k++] = array[s1++];
            }else{
                elem[k++] = array[s2++];
            }
        }
        // s1没有越界了
        while(s1<=e1){
            elem[k++] = array[s1++];
        }
        // s2没有越界
        while(s2<=e2){
            elem[k++] = array[s2++];
        }
        for (int i = 0; i < elem.length; i++) {
            array[i+left] = elem[i];
        }
    }

8.3 归并排序的非递归实现

 public static void mergeNoRecurrence(int[] array){
        if(array == null){
            return;
        }
        for(int gap =1;gap<array.length;gap*=2){
            mergefeidiedai(array,gap);
        }
    }
    public static void mergefeidiedai(int[] array,int gap){
        int s1 = 0;
        int e1 = s1+gap-1;
        int s2 = e1+1;
        int e2 = s2+gap-1 <=array.length-1?(s2+gap-1):array.length-1;
        int[] elem = new int[array.length];
        int k = 0;
        while(s2 <= array.length-1){
            while(s1<=e1 && s2<=e2){
                if(array[s1]<=array[s2]){
                    elem[k++] = array[s1++];
                }else{
                    elem[k++] = array[s2++];
                }
            }
            while(s1<=e1){
                elem[k++] = array[s1++];
            }
            while(s2<=e2){
                elem[k++] = array[s2++];
            }
            // 结束后,s2和e2都指向s2+1,要不然都跳不出上面的循环
            s1 = s2;
            e1 = s1+gap-1;
            s2 = e1+1;
            e2 = s2+gap-1 <=array.length-1?(s2+gap-1):array.length-1;
        }
        // 第一段是肯定会有的,第二段有可能没有
        // 如果第二段没有,就会跳出循环
        // 此处也不能用e1,因为e1也有可能超过数组长度
        while(s1<=array.length-1){
            elem[k++] = elem[s1++];
        }
        for(int i =0;i<elem.length;i++){
            array[i] = elem[i];
        }
    }

8.4 性能分析

时间复杂度

空间复杂度
稳定性稳定

 九、排序总结

排序算法时间复杂度(最坏)空间复杂度稳定性
冒泡排序

  

稳定
插入排序

  

稳定
选择排序

  

不稳定
希尔排序

  

不稳定
堆排序不稳定
快速排序不稳定
归并排序

 

稳定

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘减减

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值