0


七大排序算法—图文详解(插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)

ced485cbb11e458d81a746890b32cf3f.gif

作者:渴望力量的土狗

博客主页:渴望力量的土狗的博客主页

专栏:数据结构与算法

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册和我一起刷题吧

插入排序:

插入排序过程

基本思想:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。

直接插入排序:

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

插入排序

代码实现:

    //插入排序:
    public void insertSort(int[]arr){

        for (int i = 1; i < arr.length ; i++) {
            int j=i-1;
            int tmp=arr[i];
            while(j>=0){

                if(arr[j]>tmp){
                    arr[j+1]=arr[j];
                }
                if(arr[j]<tmp){
                    arr[j+1]=tmp;
                    break;
                }
                j--;
            }
            arr[j+1]=tmp;
        }
    }

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

希尔排序:

图片来源于网络

基本思想:

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

代码实现:

    public void shellSort(int[]arr){

        int gap= arr.length/2;

        while(gap>=1){

            for(int i=gap;i<arr.length;i++){
                int j=i-gap;
                int tmp=arr[i];
                while(j>=0){

                    if(arr[j]>tmp){
                        arr[j+gap]=arr[j];
                    }
                    if(arr[j]<tmp){
                        arr[j+gap]=tmp;
                        break;
                    }
                    j-=gap;
                }
                arr[j+gap]=tmp;

            }
            gap/=2;
        }
    }

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。

  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:

  4. 稳定性:不稳定

选择排序:

选择排序过程

基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

直接选择排序:

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

选择排序

代码实现:

    public void selectSort(int[]arr){

        for(int i=0;i< arr.length;i++){
            int minIndex=i;

            int j=i+1;
            while(j< arr.length){

                if(arr[j]<arr[minIndex]){
                    minIndex=j;
                }

                j++;
            }
            if(minIndex!=i){
                swap(arr,i,minIndex);
            }

        }

    }
    public void swap(int[]arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

【直接选择排序的特性总结】

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

堆排序

堆排序过程

堆排序在博主的这一篇文章有详细解释:堆排序和Top-K问题https://blog.csdn.net/m0_67995737/article/details/127292116

交换排序

基本思想:

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

冒泡排序:

冒泡排序在博主的这一篇文章有详细解释:【Java SE】实现冒泡排序和数组逆序https://blog.csdn.net/m0_67995737/article/details/126131273

【冒泡排序的特性总结】

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

快速排序:

快速排序过程

基本思想:

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

快速排序

Hoare递归代码实现:

    /**
     * 快速排序(Hoare版)
     * 时间复杂度:O(n*logn)
     * 空间复杂度:O(logn)
     * 稳定性:不稳定
     * @param arr
     * 问题:当我们给定的数据是有序的时候其时间复杂度为O(n^2),空间复杂度达到了O(n)
     */
    public void quickSort(int[]arr){
        quick(arr,0,arr.length-1);
    }

    private void quick(int[]arr,int start,int end){

        if(start>=end){
            return;
        }

        int pivot=pivotPartationHoare(arr,start,end);
        quick(arr,start,pivot-1);
        quick(arr,pivot+1,end);
    }
    //Hoare法
    private int pivotPartationHoare(int[]arr,int left,int right){
        int i=left;
        int pivot=arr[left];

        while(left<right){

            //left<right不能少,否则会出现越界
            while (left<right&&arr[right]>=pivot){
                right--;
            }
            while (left<right&&arr[left]<=pivot){
                left++;
            }

            //都找到后交换
            swap(arr,left,right);

        }

        //一边找完之后和原来的left交换
        swap(arr,left,i);

        return left;//为新的基准

    }

    public void swap(int[]arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

挖坑法递归代码实现:

    public void quickSort(int[]arr){
        quick(arr,0,arr.length-1);
    }

    private void quick(int[]arr,int start,int end){

        if(start>=end){
            return;
        }

        int pivot=pivotPartationHoare(arr,start,end);
        quick(arr,start,pivot-1);
        quick(arr,pivot+1,end);
    }

        private int pivotPartation(int[]arr,int left,int right){
        int i=left;
        int pivot=arr[left];

        while(left<right){

            //left<right不能少,否则会出现越界
            while (left<right&&arr[right]>=pivot){
                right--;
            }
            arr[left]=arr[right];
            while (left<right&&arr[left]<=pivot){
                left++;
            }

            arr[right]=arr[left];

            //都找到后交换
            swap(arr,left,right);

        }

        //一边找完之后和原来的left交换
        arr[left]=pivot;
        //swap(arr,left,i);

        return left;//为新的基准

    }

        public void swap(int[]arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

快排优化方法:

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序
    private static int partition(int[] array, int left, int right) {
        int prev = left ;
        int cur = left+1;
        while (cur <= right) {
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }
            cur++;
        }
        swap(array,prev,left);
        return prev;
    }

    /**
     * 对指定区间的数据进行插入排序
     * @param array
     * @param left
     * @param right
     */
    private void insertSort(int[] array,int left,int right) {
        for (int i = left+1; i <= right; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= left;j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    //array[j+1] = tmp;
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
    private void quick(int[] array,int start,int end) {
 
        if(start >= end) {
            return;
        }

        if(end-start+1 <= 15) {
            //对 start 和 end区间 范围内 使用插入排序
            insertSort(array,start,end);
            return;
        }
        //System.out.println("start: " +start +" end: "+end);

        // 在执行 partition 找基准之前,解决划分不均匀的问题
        int index = findMidValOfIndex(array,start,end);
        swap(array,start,index);

        int pivot = partition(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

    private int findMidValOfIndex(int[] array,int start,int end) {
        int midIndex = (start+end) / 2;
        //    3                 9
        if(array[start] < array[end]) {
            if(array[midIndex] < array[start]) {
                return start;
            }else if(array[midIndex] > array[end]) {
                return end;
            }else {
                return midIndex;
            }
        }else {
            if(array[midIndex] > array[start]) {
                return start;
            }else if(array[midIndex] < array[end]) {
                return end;
            }else {
                return midIndex;
            }
        }
    }

    /**
     * 时间复杂度:O(N*logN)
     * 空间复杂度:O(logN)
     * 稳定性:不稳定的排序
     * @param array
     *
     * 问题:
     * 当我们给定的数据 是有序的时候,这个快排的时间复杂度是O(n^2)
     * 空间复杂度:O(n)
     */
    public void quickSort1(int[] array) {
        quick(array,0,array.length-1);
    }

非递归实现:

    public static void quickSort(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        int pivot = partition(array,start,end);
        //1.判断左边是不是有2个元素
        if(pivot > start+1) {
            stack.push(start);
            stack.push(pivot-1);
        }
        //2.判断右边是不是有2个元素
        if(pivot < end-1) {
            stack.push(pivot+1);
            stack.push(end);
        }
        while (!stack.isEmpty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //3.判断左边是不是有2个元素
            if(pivot > start+1) {
                stack.push(start);
                stack.push(pivot-1);
            }
            //4.判断右边是不是有2个元素
            if(pivot < end-1) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }

    }

快速排序总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

归并排序

归并排序过程

基本思想:

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

归并排序

递归实现代码:

   public void mergeSort1(int[] array) {
        mergeSortChild(array,0,array.length-1);
    }

    private void mergeSortChild(int[] array,int left,int right) {
        if(left == right) {
            return;
        }
        int mid = (left+right) / 2;
        mergeSortChild(array,left,mid);
        mergeSortChild(array,mid+1,right);
        //合并
        merge(array,left,mid,right);
    }

    private void merge(int[] array,int left,int mid,int right) {
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArr = new int[right-left+1];
        int k = 0;//表示tmpArr 的下标
        while (s1 <= e1  && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmpArr[k++] = array[s1++];
            }else{
                tmpArr[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmpArr[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmpArr[k++] = array[s2++];
        }
        //tmpArr当中 的数据 是right  left 之间有序的数据
        for (int i = 0; i < k; i++) {
            array[i+left] = tmpArr[i];
        }
    }

非递归实现代码:

 public static void mergeSort(int[] array) {
        int gap = 1;
        while (gap < array.length) {
            for (int i = 0; i < array.length; i += gap*2) {
                int left = i;
                int mid = left + gap -1;
                int right = mid+gap;
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                if(right >= array.length) {
                    right = array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap *= 2;
        }
    }

归并排序总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

海量数据的排序问题
外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

排序算法复杂度及稳定性分析


排序方法最好平均最坏空间复杂度稳定性冒泡排序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)O(n^2)O(1)不稳定堆排序O(n * log(n))O(n * log(n))O(n * log(n))O(1)不稳定快速排序O(n * log(n))O(n * log(n))O(n^2)O(log(n)) ~ O(n)不稳定归并排序O(n * log(n))O(n * log(n))O(n * log(n))O(n)稳定


本文转载自: https://blog.csdn.net/m0_67995737/article/details/127358244
版权归原作者 渴望力量的土狗 所有, 如有侵权,请联系我们删除。

“七大排序算法&mdash;图文详解(插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)”的评论:

还没有评论