详解桶排序以及排序内容大总结
文章目录
- 详解桶排序以及排序内容大总结
- 堆
- 堆的操作(大)
- heapinsert --- 调整成大根堆
- heapify --- 移除原根节点后,继续调整成大根堆
- 堆中某个位置的数值发生改变
- 堆排序
- 优化
- 堆练习
- 比较器
- 桶排序
- 基数排序
堆
注:堆是一种特殊的二叉树
堆分为大根堆(以某一节点为根节点的整棵树中最大值为该节点)和小根堆(以某一节点为根节点的整棵树中最小值为该节点)
堆的操作(大)
heapinsert — 调整成大根堆
假设一个用户不断地给出数,程序拿到数字并将在此之前的所有数字调整成大根堆
- 找父节点
(i-1)/2
进行比较,比父节点大则交换位置
代码实现:
/**
* 调整过程:某个数正处在index的位置,不断往上调整位置时
*
* 新节点比自己的父节点大,位置需要置换
* 置换以后新节点处于父节点位置,下标需要改变
* @param arr
* @param index
*/
public static void heapInsert(int[]arr,int index){
//while停止条件:
//1:来到了一个合适的位置,比自己的父亲节点小,不需要再调整
//2:来到了根节点,根节点下标为0,(0-1)/2==0,自己不会比自己大,while停止
while(arr[index] > arr[(index-1)/2]){
//置换
swap(arr,index,(index-1)/2);
//改变该节点位置
index = (index-1)/2;
}
}
heapify — 移除原根节点后,继续调整成大根堆
假设用户停止抛出数字,让程序返回在此之前的所有数中的最大值,并且将剩下的数再次调整成为大根堆
- 返回下标为0的数字,即为最大值
- 将堆中的最后一个数的位置调换到根节点的位置(用root标记),数组长度-1,开始调整
- 调整步骤:在root节点的左孩子和右孩子之中选择一个最大值,与root进行比较,root比较小的话则调换位置;继续上述调整步骤,知道root节点比自己的左右孩子都大,或者没有左右孩子时
代码实现:
/**
* 剔除最大值后,对剩下的节点调整成大根队
* @param arr
* @param index 初始index 可以从任何一个位置往下调整
* @param heapSize 堆的大小
*/
public static void heapIfy(int[] arr,int index,int heapSize){
//左孩子下标
int left = index*2+1;
//左孩子下标还没有越界,证明还有孩子
while(left<heapSize){
//左右孩子PK
int largest = left +1 <heapSize && arr[left+1]>arr[left]?
left+1: //如果右孩子的下标没有越界 且 右孩子比左孩子大
left; //反之,右孩子越界或左孩子比较大则都选左孩子
//父节点和较大的孩子节点PK
if (arr[largest] > arr[index]){ //孩子节点比较大
//孩子节点与父节点交换
swap(arr,largest,index);
index = largest; //此时的父节点处在孩子节点的位置
left = index *2 +1; //此时的父节点的左孩子位置
//继续循环换位置
}else { //孩子节点没有比父节点大,大根堆形成
break;
}
}
}
堆中某个位置的数值发生改变
- 变大:往上进行heapInsert
- 变小:往下惊醒heapIfy
堆排序
- 调整成大根堆,剔除最大值(根节点),将最后一个位置上的数放到根节点上,heapSize–
- 继续调整,剔除,更新位置,heapSize
- 直到排序完成
代码实现:
/**
* 堆排序代码实现
* @param arr
*/
public static void heapSort(int[] arr){
//arr为空或arr只有一个或零个数
if(arr==null || arr.length<2){
return;
}
//初始大根堆
for (int i = 0;i<arr.length;i++){ //O(N)
heapInsert(arr,i); //O(log N)
}
int heapSize = arr.length; //初始大根堆长度
swap(arr,0,-heapSize); //0位置上堆中最后的位置做交换
//当堆的长度不为0时,需要不断拿走根节点,再重新调整
while (heapSize > 0){ //O(N)
heapIfy(arr,0,heapSize); //O(log N)
swap(arr,0,--heapSize);
}
}
优化
完全二叉树的叶子节点:
- 如果是偶数个节点,叶子节点等于总节点除以2, 即 N % 2==0, n = N/2
- 如果是奇数个节点,叶子节点等于==(总节点+1)除以2==, 即 N % 2 == 1, n = (N+1)/2
时间复杂度:假设数组中有N个数,叶子节点为N/2个叶子节点
- 最底层的叶子节点的时间复杂度即为:
(N/2)*1
(1为只进行一次操作,因为叶子节点没有子节点,只遍历) - 倒数第二层的节点的时间复杂度:
(N/4)*2
(2为 遍历+往下移动一层) - 倒数第三层的节点的时间复杂度:
(N/8)*3
(3为 遍历+往下移动2层) - 以此类推
T ( N ) = N / 2 ∗ 1 + N / 4 ∗ 2 + N / 8 ∗ 3 + . . . + 1 ∗ l o g 2 N T(N)=N/2*1+N/4*2+N/8*3+...+1*log2 N T(N)=N/2∗1+N/4∗2+N/8∗3+...+1∗log2N
错位相减2T(N)-T(N)==T(N) 结果为O(N)
代码实现:
//更快的初始化堆的方法 时间复杂度O(N)
for (int i = arr.length-1;i>=0;i--){
heapIfy(arr,i, arr.length);
}
堆练习
堆排序扩展题目
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
题解:
每一个元素移动的距离都不超过K:意味着,在数组的 0~K 的范围内的最小值即为整个数组的最小值,K+1位置以后的数也全都不可能移动到 0 位置上。
所以,只需要 使用一个固定长度为 K+1的滑动窗口或双指针,不断的选出该范围内的最小值,然后不断地推后该滑动窗口。
Java中现成的堆结构:优先级队列:
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
默认小根堆,想要大根堆则传入比较器指定比较挥着
底层是数组:
扩容机制???
默认堆结构,只支持用户给出一个数,和系统弹出最值并移除(黑盒)
比较器
public static class myCompare implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
桶排序
之前所有的排序都是只和两个数之间的比较有关系。(基于比较的排序)
不基于比较的排序(根据数据状况定制):
例子:员工年龄排序,返回0-200
解题思路:申请一个长度为200的数组,下标 i 认为是年龄,i 位置的值为年龄为 i 的人数。
时间复杂度:O(N)
不基于比较的排序都是根据数据状况做的排序,应用范围比基于比较的排序小。
基数排序