数据结构学习(基础)——简单排序法——Day03

1.10堆排序(时间复杂度O(nlogn),空间复杂度O(1))

堆在逻辑概念上是一颗完全二叉树结构,分为大根堆和小根堆

完全二叉树:要么是满二叉树,要么从左往右依次变满的二叉树

子树:从一个头节点出发,下面东西都要

大根堆:每个结点的值都大于其左孩子和右孩子结点的值。(每一颗子树的最大值就是头节点的值)

小根堆:每个结点的值都小于其左孩子和右孩子结点的值。(每一颗子树的最小值就是头节点的值)

节点i的左孩子:2i+1;右孩子:2i+2;父:(i-1)/2

heapinsert过程:新插入的数在树中跟自己的父节点去比较,如果比父节点大,就跟父节点交换位置,一直进行下去,直到整颗树变成大根数为止(变到根或者是父节点比他大,0位置的父就是自己)。判断某个数现在处在index位置,是否往上移动,如果要交换位置,index变到换完位置的地方(父节点)因为还要继续往上去比较。

之后我们将index=0位置的数(大根堆的头节点)和index=heapSize位置的数进行交换,并且heapSize-1。当heapSize-1后,原先heapSzie(现在放着之前大根堆的数也就是数组最大值)就会脱离堆。

heapify过程:之后让现在头节点的数(之前交换过来的)与自己左孩子和右孩子之间的最大值进行比较,如果比孩子小就交换位置,如果大就不变。直到不变为止,新的大根堆就会出现。不用从0位置进行heapify,可以从任何位置进行heapify,heapSize管着左右两个孩子是否越界,不是说数组的范围就是整个堆,而是heapSize管着堆的大小,一旦发现左孩子或者右孩子越界,就认为没孩子了。

堆排序思路:

heapSize从0-length-1依次让他成为堆,相当于一个空数组,用户一个一个给我数,我让这些数成为堆,最后扩充到整个数组整体。heapSize不断++变成length,整个数组就已经变成大根堆了。再把0位置上的数(数组最大值)和最后一个位置上的数进行交换。之后让heapSize--,是最大的数来到最后的位置之后让最后的位置和堆断开联系。之后让剩下的数字从0位置开始进行heapify成为大根堆,之后继续让0位置的数和最后一个数进行交换。以此类推知道heapSize为0为止。

优先级队列结构就是堆结构。

堆排序扩展问题:已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。

解法:对数组k个元素进行构造小根堆(PriorityQueue),保证最大移动不超过k。每次把小根堆的最小值放在这k个元素的首位,然后弹出首位元素,再向下进行k个元素构造小根堆,如此反复。

1.11比较器

我们可以自己定义一个比较器,让我们要排的对象按照我们的比较器进行比较后排序即可,例如学生先按年龄由小到大,年龄相同时在按照班级由大到小排。例如return o1.age - o2.age;改成return o2.age - o1.age;

1>比较器的实质就是重载比较运算符

2>比较器可以很好的应用在特殊标准的排序上

3>比较器可以很好的应用在根据特殊标准排序的结构上

默认比较器方法compare的性质:返回负数,第一个参数排在前面;返回正数,第二个参数排在前面;返回0,谁放前面都可以。

1.12桶排序

之前的排序无论是哪种,都是基于两个数比较的排序,而桶排序是不基于比较的排序 。不急于比较的排序都是根据数据状况做的排序,没有基于比较的排序有那么广的排序范围,因为需要申请额外空间。是根据一定数据状况去定制的不急于比较的排序。

桶可以是队列可以是栈可以是堆,我们在这里认为桶就是一个队列。先进桶就先出桶。先根据个位数进行排序再根据十位数进行排序,因为高位数字是晚排序的所以优先级最高。随着高位的进桶和出桶,低位排序的结果也会留存下来。

public class RadixSort {
    /**
     * 桶排序之基数排序
     * @param arr
     */
    public static void radixSort(int[] arr){
        radixSort(arr,0,arr.length-1,getMaxBits(arr));
    }

    //获得数组中最大元素的位数(1000->3位,100->2位)
    public static int getMaxBits(int[] arr){
        int max = arr[0];
        for(int i = 0;i < arr.length;i++){
            max = max < arr[i] ? arr[i] : max;
        }
        int bits = 0; //记录最大数是几位的
        while(max != 0){
            bits++;
            max /= 10;
        }
        return bits;//返回的值就是有几个十进制位,就是有多少个位
    }

    //获得数字x对应d为的值。 (117,2) -> x/10%10=1;(12345,3)->x/100%10
    public static int getDigit(int x,int d){
        return (x / ((int)Math.pow(10,d-1)) % 10);
    }

    //digit表示元素的最大位数 --> 将根据这个判断需要进行几次的桶排序
    public static void radixSort(int[] arr,int left,int right,int digit){//left和right表示要在数组的哪个部分进行桶排序(适用面广一些),并不是要递归,digtit表示这些数里面最大的值有几个十进制位
        final int radix = 10;//桶的个数,基数排序桶的个数为10,是确定的,每个桶都是一个队列
        int i = 0,j = 0;
        int[] help = new int[right - left + 1];//准备和要排序数一样大的数组
        for(int d = 1;d <= digit;d++){//记录入桶出桶的次数,比如最大的数是100,就要出入三次
            int[] count = new int[radix];//利用count进行分片,之后和help完成入桶和出桶   i的意思和i坐标上的值就是个位数小于等于i的个数有几个比如i位置上是7,那就是个位数小于等于i的有7个
            //记录相同位数的个数
            for(i = left;i <= right;i++){
                j = getDigit(arr[i],d);
                count[j]++;
            }
            //将count变形,每个元素为该元素和前面所有元素的和,用于后续的原数组的倒叙遍历
            for(i = 1;i < radix;i++){//从左到右遍历进桶,所以最右侧是最后一个进桶
                count[i] += count[i - 1];
            }
            //将原数组从右向左遍历,通过count对应位上的值-1找到在辅助数组的位置
            //为什么要从右往左,通过词频的--就代表先入桶的先出桶,保证了排序过程的稳定性
            //我们每次桶排序都是根据某一位从0-9进行排序,0放进临时数组前面,9放进临时数组后面,这个顺序要记录下去从个位一直传递到最上面
            //所以放进桶里的时候和取出来就要先进先出,保证这个顺序,那么在临时数组中表现形式就是先放进桶里的,我们放在临时数组的前面(由于从右往左遍历,同时也放的是help[i]-1的位置,所以可以看做后进后出)
            //为了这样错才要从右往左遍历,能保证这样,从左往右的话就会很麻烦
            for(i = right;i >= left;i--){
                j = getDigit(arr[i],d);
                help[count[j] - 1] = arr[i];
                count[j]--;
            }
            //将排序完成的数组重新赋给原数组
            for(i = left,j=0;i <= right;i++,j++){
                arr[i] = help[j];
            }
        }
    }
}


1.13排序算法的稳定性及其汇总

同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有。

不具备稳定性的排序:选择排序、快速排序、堆排序

具备稳定性的排序:冒泡排序、插入排序、归并排序、一切桶排序思想下的排序

目前没有找到时间复杂度O(nlogn),额外空间复杂度O(1),又稳定的排序。

转载于华哥

常见的坑:

1.归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,可以搜索“归幷排序,内部缓存法”

2.“原地归并排序”的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^2)

3.快排可以做到稳定性问题,但是非常难,不需要掌握,搜索“01stable sort”

4.所有的改进都不重要,因为目前没有找到时间复杂度O(nlogn),额外空间复杂度O(1),又稳定的排序。

5.有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个问题可以怼面试官

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值