排序学习总结

本文详细介绍了七种基于比较的排序算法:直接插入排序、希尔排序、选择排序、堆排序、冒泡排序(包括优化版本)、快速排序(包括多种实现方式和优化策略)以及归并排序,并探讨了它们的时间复杂度、空间复杂度和稳定性。同时,还提到了非基于比较的计数排序,以及快速排序的三数取中法优化。内容涵盖了排序算法的基本概念、适用场景和性能分析。
摘要由CSDN通过智能技术生成

—、基本的定义

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
稳定性简单理解:例如1,5,8,6,5,若排序后的结果是1,5,5,6,8,则是稳定的。若是1,5,5,6,8就是不稳定的,原来在前面的排序完成还得在前面,是不能改变的,两个5某种意义上来说并不是相同的。
注意事项:以下描述的logn都是指以2为底的

二、常见的七种基于比较的排序算法

1.直接插入排序(对数据敏感)

private static void insertSort(int[] arr) {
        for (int i = 1; i <arr.length ; i++) {
            int tmp = arr[i];//插入的牌
            int j = i-1;
            for (; j >=0 ; j--) {
              if(arr[j]>tmp){//插入的牌与手上的牌比大小
                  arr[j+1]=arr[j];
              }else{
                  break;
              }
            }
            arr[j+1]=tmp;
        }
    }

这种插入排序就相当于玩扑克牌一样,摸一张牌后要插入到自己手中有序的牌中,保持一个有序的状态。
时间复杂度:最坏情况下(逆序)是O(n^2).最好情况(有序)是O(n)。
(故当数据量趋近于有序,直接插入排序的效率更高)
空间复杂度:O(1)
使用场景:数据量少,趋近于有序时
稳定性:稳定

2、希尔排序(缩小增量排序)

希尔排序是对于直接插入排序的一种优化,是将所有的数据统一分组,例如第一次分组每组5个数据,第二次分组每组2个数据,第三次分组每组一个数据。每一次分组以后都将每组的数据进行直接插入排序,由于数据量少,效率就高,每次分组的组数因越来越少,直到1为止,这样最后一次分组会将所有数据都排序好。这样做的好处就是每一次排序都将数据趋近于有序,直到最后一次,数据已经接近有序,这样排序就会很快。整体而言达到优化的效果。由于取组的可能性无穷,所以时间复杂度并不好计算。时间复杂度参考为O(n^1.3)
到O(n^1.5)

private static void shellSort(int[] arr) {
    int gap = arr.length;
    while(gap>1){//分组
        shell(arr,gap);
        gap=gap/2;
    }
    shell(arr,1);
}

private static void shell(int[] arr, int gap) {//每组的直接插入排序
    for (int i =gap ; i <arr.length ; i++) {
        int tmp = arr[i];
        int j = i-gap;
        for (; j >=0 ; j-=gap) {
            if(arr[j]>tmp){
                arr[j+gap]=arr[j];
            }else{
                break;
            }

        }
        arr[j+gap]=tmp;
    }
}

稳定性:不稳定

3、选择排序

  private static void selectSort(int[] arr) {


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

            for ( int j = i+1; j <arr.length ; j++) {
                if(arr[j]<arr[count]){
                  count = j;
                }
            }
            int tmp = arr[i];
            arr[i]=arr[count];
            arr[count]=tmp;
        }
    }

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。就是遍历所有的数字把最小的放到第一个位置,然后遍历剩下的数字,把最小的放到第二个位置,以此类推。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
使用场景:使用效率不是很好,实际上很少使用

4、堆排序

堆排序分为两步
1.建堆,若是升序则建立大堆,若是降序则建小堆
2.利用堆删除思想进行排序

 /**
     * 建堆
     * @param arr
     */
    private static void createHeap(int[] arr) {
        for (int i = (arr.length-2)/2; i >=0 ; i--) {
            shiftDown(i,arr.length,arr);
        }
    }
//向下调整
    private static void shiftDown(int root,int len,int[]arr) {
        int child = root*2+1;
        while(child<len){
            if(child+1<len&&arr[child]<arr[child+1]){
                          child++;
            }
            if(arr[child]>arr[root]){
                int tmp = arr[child];
                arr[child]=arr[root];
                arr[root] = tmp;
                root = child;
                child=root*2+1;
            }else{
                break;
            }
        }
    }
//堆排序
    private static void heapSort(int[] arr) {
        createHeap(arr);
        for (int i = 0; i <arr.length-1 ; i++) {
            int tmp = arr[0];
            arr[0]=arr[arr.length-i-1];
            arr[arr.length-i-1]=tmp;
            shiftDown(0,arr.length-i-1,arr);
        }
    }

堆排序使用堆来选数,效率就高了很多。
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定

5、冒泡排序

冒泡排序是通过交换相邻的两个数字,如果是升序,则是前一个数字大,则交换,反之不换,最大的数字会到最后一位,以此类推,排完所有数据。

1.优化前版本

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

冒泡排序是一种非常容易理解的排序
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
冒泡排序还有优化的版本,即如果已经排序完成就不用继续再交换了。故这样对有序的数据的排序十分的快。

优化后版本

 private static void bubbleSort2(int[] arr) {
        for (int i = 0; i <arr.length-1 ; i++) {
            boolean flag = false;
            for (int j = 0; j < arr.length-i-1; j++) {
                if(arr[j]>arr[j+1]){
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1]=tmp;
                    flag = true;
                }
            }
            if(!flag){
                break;
            }
        }
    }

6.快速排序

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

1.Hoare法

 /**
     * 快速排序
     * @param arr 传进来的数组
     * @param low 起始位置
     * @param high 终止位置
     */
    private static void quickSort(int[] arr,int low,int high) {
                if(low>=high){           //递归结束条件
                    return;
                }
                int pivot = partition(arr,low,high);//获取基准值坐标
                 quickSort(arr,low,pivot-1);//递归基准值左边的数据
                 quickSort(arr,pivot+1,high);//递归基准值右边的数据
    }

    private static int partition(int[] arr, int low, int high) {
        int tmp = arr[low];
        int i = low;
        while(low<high) {
            while (low < high && arr[high] >= tmp) {
                high--;
            }
            while (low < high && arr[low] <= tmp) {
                low++;
            }
            int tmp1 = arr[low];
            arr[low]=arr[high];
            arr[high]=tmp1;
        }
        int tmp1 = arr[i];
        arr[i]=arr[high];
        arr[high]=tmp1;
        return high;
    }

上面是 Hoare版的快排,基准值是采取最左端为基准值,下面类似
时间复杂度:好的情况O(n*logn),坏的情况下O(n^2)
空间复杂度:好的情况下:O(logn),坏的情况下O(n)
稳定性:不稳定

2.挖坑法

private static int partition2(int[] arr, int low, int high) {
        int tmp = arr[low];
        while(low<high){
            while(low<high&&arr[high]>=tmp){
                high--;
            }
            arr[low]=arr[high];
            while(low<high&&arr[low]<=tmp){
                low++;
            }
            arr[high]=arr[low];
        }
        arr[high]=tmp;
        return high;
    }

以上是挖坑法的快排,与Hoare版的区别就是获得基准值坐标的方法不同。

3.前后指针法

//前后指针法
    private static int partition3(int[] arr, int low, int high) {
        int tmp = arr[low];
        int left = low+1;
        for (int i = low+1; i <=high ; i++) {
            if(arr[i]<tmp){
                int tmp1= arr[i];
                arr[i]=arr[left];
                arr[left]=tmp1;
                left++;
            }
        }
        int tmp1= arr[low];
        arr[low]=arr[left-1];
        arr[left-1]=tmp1;

        return left-1;
    }

以上是前后指针法,与前两种类似,只是求基准坐标的方法不同。

优化方法

1.将以上三种方法和直接插入排序混用,当递归的数据量小于一个值时(high-low+1<number),进行直接插入排序,会减少很多一部分递归所需要的时间与空间,这里的直接插入排序是有边界的,不是整个数据,是从low到high的数据.
2.快速排序是一种分治的思想,要想算法效率高,就得使得选出的基准值是尽可能可以均匀分数据的。

三数取中法

就是在三个数据中选出大小在中间的数据,尽量可能满足选出的基准值是尽可能可以均匀分数据的。减少递归的深度

   private static void quickSort(int[] arr,int low,int high) {
                if(low>=high){
                    return;
                }
               int mid =  getmid(arr,low,high);
                int tmp = arr[low];
                arr[low]=arr[mid];
                arr[mid] = tmp;
                int pivot = partition3(arr,low,high);
                 quickSort(arr,low,pivot-1);
                 quickSort(arr,pivot+1,high);
    }
//获取中间位置的坐标
    private static int getmid(int[] arr, int low, int high) {

        int mid = low+(high-low)/2;
        if(arr[low]<arr[high]){
            if(arr[low]>arr[mid]){
                return low;
            }else if(arr[high]<arr[mid]){
                return high;
            }else{
                return mid;
            }
        }else{
            if(arr[low]<arr[mid]){
                return low;
            }else if(arr[high]>arr[mid]){
                return high;
            }else{
                return mid;
            }
        }
    }

非递归快速排序

//非递归的快速排序
    private static void quickSort2(int[] arr, int left, int right) {
        int pivot = partition3(arr,left,right);
        Stack<Integer>stack = new Stack<>();

        if(left+1<pivot){
            stack.push(left);
            stack.push(pivot-1);
        }
        if(pivot+1<right){
            stack.push(pivot+1);
            stack.push(right);
        }
        while(!stack.empty()){
            right=stack.pop();
            left = stack.pop();
            pivot = partition3(arr,left,right);
            if(left+1<pivot){
                stack.push(left);
                stack.push(pivot-1);
            }
            if(pivot+1<right){
                stack.push(pivot+1);
                stack.push(right);
            }
        }

    }

7.归并排序

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

//归并排序
    private static void mergeSort(int[] arr, int low, int high) {
        if(low>=high){
            return;
        }
            int mid = (low+high)/2;
            mergeSort(arr,low,mid);
            mergeSort(arr,mid+1,high);
            merge(arr,low,mid,high);//给每一部分的数据进行合并
    }

    private static void merge(int[] arr, int low, int mid, int high) {
           int[]tmp = new int[high-low+1];
           int a = low;
           int a1 = mid;
           int b = mid+1;
           int b1 = high;
           int i = 0;
           while(a<=mid&&b<=b1){          //把小的给到中间数组,使得中间数组是有序的
               if(arr[a]<arr[b]){
                   tmp[i++] = arr[a++];
               }else{
                   tmp[i++] = arr[b++];
               }
           }
           while(a<=mid){
               tmp[i++] = arr[a++];

           }
           while(b<=b1){
               tmp[i++] = arr[b++];

           }
           //把中间数组重新赋值给原数组
        for (int j = 0; j <tmp.length ; j++) {
            arr[j+low]=tmp[j];
        }
    }

时间复杂度: O(logn*n)(和数据是否有序无关)
空间复杂度:O(n)
稳定性:稳定

非递归归并排序

//非递归归并排序
    private static void mergeSortNor(int[] arr, int low, int high) {
        int gap = 1;
        while(gap<arr.length){
            for (int i = 0; i <arr.length ; i+=2*gap) {
                int left = i;
                int mid =left +gap-1;
                if(mid>=arr.length){
                    mid=arr.length-1;
                }
                int right = mid +gap;
                if(right>=arr.length){
                    right=arr.length-1;
                }
                merge(arr,left,mid,right);
            }
            gap*=2;
        }
    }
  private static void merge(int[] arr, int low, int mid, int high) {
           int[]tmp = new int[high-low+1];
           int a = low;
           int a1 = mid;
           int b = mid+1;
           int b1 = high;
           int i = 0;
           while(a<=mid&&b<=b1){          //把小的给到中间数组,使得中间数组是有序的
               if(arr[a]<arr[b]){
                   tmp[i++] = arr[a++];
               }else{
                   tmp[i++] = arr[b++];
               }
           }
           while(a<=mid){
               tmp[i++] = arr[a++];

           }
           while(b<=b1){
               tmp[i++] = arr[b++];

           }
           //把中间数组重新赋值给原数组
        for (int j = 0; j <tmp.length ; j++) {
            arr[j+low]=tmp[j];
        }
    }

非基于比较排序

1.计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中
//计数排序
    private static void countSort(int[] arr) {
        //获取最大值和最小值
        int min = arr[0];
        int max = arr[0];
        for (int i = 1; i <arr.length ; i++) {
            if(arr[i]<min){
                min = arr[i];
            }
            if(arr[i]>max){
                max = arr[i];
            }
        }
        //开始计数
        int []count = new int[max-min+1];
        for (int i = 0; i <arr.length ; i++) {
            count[arr[i]-min]++;
        }
        int index = 0;
        for (int i = 0; i <count.length ; i++) {
            while(count[i]>0){
                arr[index] = min+i;
                count[i]--;
                index++;
            }
        }
    }

时间复杂度:O(MAX(N,范围)) 范围是指count数组的大小
空间复杂度:O(范围)
基数排序与桶排序不作详细介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值