常见排序方法详解(图示+方法)

一、插入排序

1.1基本思想

把待排序的记录 按其关键码值的大小逐个插入到一个已经排好序的有序序列中 ,直到所有的记录插入完为止,得到 一个新的有序序列。

1.2直接插入排序

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

示例代码如下:

class Sort{
    public static void insertSort(int[] array){
        //排序完成条件
        for (int i = 1; i < array.length ; i++) {
            //用于比较大小
            int tmp = array[i];
            //j的起始位置
            int j =i-1;
            //单次比较的结束条件
            for(;j>=0;j--){
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else{
                    //由于前面的元素经排序后已经有序,故直接将tmp的值放入对应位置后,j无需在遍历前面的元素进行比较,
                    //直接跳出循环即可。
                    array[j+1]=tmp;
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }
}

直接插入排序的特性总结:
1. 元素集合 越接近有序 ,直接插入排序算法的时间 效率越高
2. 时间复杂度: O(N^2)
    最坏情况:数据是逆序的;
    最好情况:数据本身就是有序的;
    结论:当一组数据 越接近于有序 ,直接插入排序 越快
3. 空间复杂度: O(1) ,它是一种 稳定 的排序算法
4. 稳定性:稳定

1.3希尔排序(缩小增量排序)

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

思路有了,代码如何实现?如何分组? 

代码如下:

 public static void shellSort(int[] array){
        int gap=array.length;
        while(gap>1){
            gap/=2;
            shell(array,gap);
        }
    }

    private 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{
                    array[j+gap]=tmp;
                    break;
                }
            }
            array[j+gap]=tmp;
        }
    }
希尔排序的特性总结:
1. 希尔排序是 对直接插入排序的优化
2. gap > 1时都是预排序 ,目的是让数组更 接近于有序 。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定
4. 稳定性: 不稳定

二、选择排序

2.1直接选择排序

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


2.2.2代码实现

图示:

代码演示:

public static void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            //每轮比较默认i下标的值为最小值
            int minIndex=i;
            for (int j =i+1; j <array.length ; j++) {
                if(array[j]<array[minIndex]){
                    minIndex=j;
                }
            }
            int tmp=array[i];
            array[i]= array[minIndex];
            array[minIndex]=tmp;
        }
    }
直接选择排序的特性总结
1. 直接选择排序思考非常好理解,但是 效率不是很好 。实际中 很少使用
2. 时间复杂度: O(N^2)
3. 空间复杂度: O(1)
4. 稳定性: 不稳定

2.2堆排序

堆排序思路在前面已经了解到,详情见文章中“堆的应用”部分。

https://blog.csdn.net/zhakakqns/article/details/141531282?spm=1001.2014.3001.5501icon-default.png?t=N7T8https://blog.csdn.net/zhakakqns/article/details/141531282?spm=1001.2014.3001.5501

 public static void heapSort(int[] array){
        createHeap(array);

        int end=array.length-1;
        while(end>0) {
            int tmp = array[0];
            array[0] = array[end];
            array[end] = tmp;
            siftDown(array, 0, end);
            end--;
        }
    }
   private static void createHeap(int[] array){
        //创建堆
       for (int parent = (array.length-1-1)/2; parent >=0 ; parent--) {
           siftDown(array,parent,array.length);
       }
   }
   private static void siftDown(int[] array,int parent,int length){
        //向下调整
        int child = parent+1;
        while(child<length){
            if(child+1<length && array[child]<array[child+1]){
                child++;
            }
            if(array[child]>array[parent]){
                int tmp=array[parent];
                array[parent]=array[child];
                array[child]=tmp;
                parent=child;
                child=2*parent+1;
            }else{
                break;
            }
        }
   }

直接选择排序的特性总结
1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(1)
4. 稳定性: 不稳定

 三、交换排序

3.1冒泡排序

3.1.1普通冒泡排序

public static void bubbleSort(int[] array){
       for (int i = 0; i <array.length-1 ; i++) {
           for (int j = 0; j < array.length-i-1; j++) {
               if(array[j]>array[j+1]){
                   int tmp=array[j];
                   array[j]=array[j+1];
                   array[j+1]=tmp;
               }
           }
       }
   }

 3.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-i-1; j++) {
               if(array[j]>array[j+1]){
                   int tmp=array[j];
                   array[j]=array[j+1];
                   array[j+1]=tmp;
                   flg=true;//发生交换,置为true
               }
           }
           if(!flg){
               break;
           }
       }
   }

!!!一般我们所说的冒泡排序的时间复杂度是指未优化过的冒泡排序。 

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

3.2快速排序

3.2.1快速排序

基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

3.2.1.1Hoare版

代码实现:

 public static void quickSort(int[] array){
        quick(array,0,array.length-1);
   }
   private static void quick(int[] array,int start,int end){
        if(start>=end){
            return;
        }
           
        //划分区域
        int pivot = partition(array,start,end);
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);
   }

    private static int partition(int[] array, int left, int right){
        //以left下标的值作为基准
        int key = left;
        while(left<right) {
            //如果数据为3,4,5,6那么right一直--也找不到比3大的数据
            //,会导致越界,因此必须右left<right这个条件
            while (left<right && array[right] >= array[key]) {
                right--;
            }
            while (left<right && array[left] <= array[key]) {
                left++;
            }
            int tmp = array[right];
            array[right] = array[left];
            array[left] = tmp;
        }
        int tmp = array[left];
        array[left]=array[key];
        array[key]=tmp;
        return left;
    }
特性总结】
1. 时间复杂度: O(N^2)
当数据本身有序时,时间复杂度为n^2
最好情况 N*logN
2. 空间复杂度: O(N)
3. 稳定性: 稳定

 3.2.1.2挖坑法 

递归的思路和Hoare法一样,也就是说只需要修改Hoare法的partition方法即可。

代码如下:

private 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];
        }

        array[left] = tmp;
        return left;
    }

3.2.2快速排序优化

当一组数据有序时,快速排序的时间复杂度会达到O(N^2),并且当数据量过多时还会导致栈溢出,因此我们右以下几种优化方法:

(1)三数取中法选key 

下面是寻找中间大小数据的方法:

 private static int getMiddleNum(int[] array,int left,int right){
        int mid = (left+right)/2;
        if(array[left]<array[right]){
            if(array[mid]<array[left]){
                return left;
            }else if(array[mid]>array[right]){
                return right;
            }else{
                return mid;
            }
        }else{
           if(array[mid]>array[left]){
               return left;
           }else if(array[mid]<array[right]){
               return right;
           }else{
               return mid;
           }
        }

(2)递归到小的子区间时,可以考虑使用插入排序 ,但是在使用插入排序时,应注意排序的范围

i要从start+1开始,当j<start时结束该轮插入。

         代码如下:

 private static void quick(int[] array,int start,int end){
        if(start>=end){
            return;
        }
        //当数据小于10个时,使用直接插入排序
        if(end-start+1<10){
            insertSortRange(array,start,end);
            return;
        }
        int pivot = partition(array,start,end);
        
        quick(array,start,pivot-1);
        
        quick(array,pivot+1,end);
   }

    private static void insertSortRange(int[] array, int start, int end) {
        for (int i = start+1; i <=end ; i++) {
            int tmp=array[i];
            int j=i-1;
            for(;j>=start;j--){
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else{
                    array[j+1]=tmp;
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }

3.2.3快速排序非递归

 

代码如下: 

public static void quickNor(int[] array,int start,int end){
        //首先先用pivot将两个子数组的start,end放入栈中
        int pivot = partition(array,start,end);
        Stack<Integer> stack = new Stack<>();
        //如果pivot<=start+1,pivot>=end-1说明只剩下一个数据,则已经有序,不用再排序
          if(pivot>start+1){
              stack.push(start);
              stack.push(pivot-1);
          }
          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);
              if(pivot>start+1){
                  stack.push(start);
                  stack.push(pivot-1);
              }
              if(pivot<end-1){
                  stack.push(pivot+1);
                  stack.push(end);
              }
          }
    }

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

四、归并排序

归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用 分治法 (Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即 先使每个子序列有序,再使子序列段间有序 。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
public static void mergeSort(int[] array){
        mergeSortTmp(array,0,array.length-1);
    }


    private static void mergeSortTmp(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = (left+right)/2;
        mergeSortTmp(array,left,mid);
        mergeSortTmp(array,mid+1,right);
        //分解完毕,开始合并
        merge(array,left,mid,right);
    }

    private static void merge(int[] array, int left, int mid, int right) {
        //相当于合并连个有序数组
        int[] tmp=new int[right-left+1];
        int k=0;
        int s1=left;
        int e1=mid;
        int s2=mid+1;
        int e2=right;

        while(s1<=e1 && s2<=e2){
            if(array[s1]<=array[s2]){
                tmp[k++]=array[s1++];
            }else{
                tmp[k++]=array[s2++];
            }
        }

        while(s1<=e1){
            tmp[k++]=array[s1++];
        }
        while(s2<=e2){
            tmp[k++]=array[s2++];
        }
        for (int i = 0; i < k; i++) {
            array[i+left]=tmp[i];
        }
    }
1. 归并的缺点在于需要 O(N) 的空间复杂度,归并排序的思考更多的是 解决在磁盘中的外排序 问题。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(N)

4. 稳定性:稳定

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值