总结七大排序!

排序总览

外部排序:依赖硬盘(外部存储器)进行的排序。对于数据集合的要求特别高,只能在特定场合下使用(比如一个省的高考成绩排序)。包括桶排序,基数排序,计数排序,都是o(n)

1.什么是稳定性?

待排序的元素中,有两个相同的数据,如果排序后,它们的相对位置,与排序前一致,就称为稳定

例子:taobao商城,有两个用户下单的金额都是8元, 

  1    2023/8/5   15:00    8rmb

   2   2023/8/5   15:06    8rmb

要求先按照时间排序,然后按照金额排序,那么排序后,顺序仍然是1 2 ,1先下单就先发货。

1.选择排序   不稳定

每次从无序区间中,选择一个最小(或者最大值),放在无序区间的最前(或者最后位置),此位置的元素已经有序,直到所有的数据都排序结束。

 双向选择排序:

 2.插入排序    稳定

将集合分为两个区间,已经排序的区间[0..i)  未排序的区间[i...n]  i是当前遍历元素
每次从待排序区间的第一个元素插入到已排序区间的合适位置,直到整个数组有序

 

 选择排序和希尔排序的代码非常相似,选择排序每次走1步,希尔排序每次走gap步。

插入排序和选择排序最大的不同?

插入排序当前遍历的元素  > 前驱元素,此时可以提前结束内存循环。

极端情况下,当集合是一个完全有序的集合,插入排序的内层循环,一次都不走,插入排序变成o(n).

插入排序经常用作高级排序算法的优化手段之一。

3.折半插入排序

 4.希尔排序

 先选定一个整数gap,一般选取一个数组长度的一半,将待排序的数组先按照gap分组,不同组之间内部用插入排序,排序之后,在将gap/=2,不断缩小gap,重复上述流程,直到gap=1。

当gap=1,整个数组已经近乎有序。在整个数组上进行一次插入排序,就排序完成了。

此时数组已经近乎有序,gap=1,进行一次插入排序,数组就完全有序了。

代码如下:

希尔排序与选择排序的区别是:希尔排序每次走gap步,选择排序每次走1步  

 5.归并排序    稳定   

将原数组不断拆分,一直拆到每个子数组只有一个元素(归)。

将相邻的两个数组合并为一个有序的数组,直到整个数组有序(并)。典型的分治

public  static  void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length -1); //递归去拆
    }
    private static void mergeSortInternal(int[] arr, int l, int r) {
        if(l >= r)  return;   // 说明当前区间仅剩一个元素,归过程已经结束
        int mid = (l+r) >>1;
        //将原数组拆分为两个区间,分别递归的进行归并排序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        merge(arr,l,mid,r);  //开始 并
    }

    private static void merge(int[] arr, int l, int mid, int r) {
        //合并两个子数组 [l,mid] [mid+1,r]为一个大的有序数组
        int[] aux =new int[r-l+1]; //首先创建一个新的临时数组
        //将arr的值拷贝到aux上
        for (int i = 0; i <aux.length; i++) {
            aux[i] = arr[i+l]; //aux下标从0开始,保存arr[l]的值,差了l个偏差
        }
        int i =l;//左侧小数组的开始索引
        int j = mid+1;//右侧小数组的开始索引
        for (int k = l; k <=r; k++) {    //k表示当前正在合并的原数组的下标
            if(i >mid){
                //左侧区间已经被合处理完毕,只需要将右边数组的值拷贝到原数组
                arr[k] = aux[j-l];
                j++;
            }else if(j >r){
                arr[k] = aux[i-l];
                i++;
            }else if(aux[i - l]<= aux[j-l]){
                //此时左侧区间的元素值较小  相等元素放在左区间,来保证稳定性
                arr[k] = aux[i -l];
                i++;
            }else{
                arr[k] = aux[j-l];
                j++;
            }
        }
    }

为什么合并过程中需要创建一个临时数组?

防止在合并过程中,小元素覆盖大元素,会丢失大元素。

无论集合中的元素如何变化,归并排序的时间复杂度都是稳定的(Nlogn)。

归并排序的两个优化:

1. 当左右两个区间走完子函数之后,左右两个区间已经有序了,如果此时arr[mid] <arr[mid +1],说明整个数组已经有序,就没必要再执行merge

2.在小区间上,可以直接使用插入排序来优化,没必要一直拆分到剩一个元素。r -l <=15,插入排序大的性能是非常好的,可以减少归的递归次数。

归并排序的核心 merge

merge操作可以对物理不连续的元素集合进行排序

(链表排序  leetcode148)

(数组逆序对    leetcode51)

海量数据排序处理

假设现在待排序数据有100G,但内存只有1G,如何排序?(本质仍然是使用归并排序)

1.先将这100G的数据存储在200个文件中(文件保存在硬盘中),这是每个文件的大小为0.5G

2.分别将这200个文件督导内存中,任意使用一个内部排序对其排序,此时就可以得到200个已经有序的文件

3.分别对这200个文件进行merge操作

   那具体怎么操作捏?

这200个小文件已经有序,每次都取出这200个文件的第一个元素放到内存中,内部排序这个200个小值,写回大文件的第一行,重复上述流程,直到200个文件的内容全部写回大文件即可。

6.冒泡排序    稳定   

  public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            boolean isSwaped = false;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    isSwaped = true;
                }
            }
            if (!isSwaped) {
                break;//已经彻底有序了
            }
        }
    }

  • 24
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值