您现在的位置是:首页 >技术教程 >排序算法总结网站首页技术教程
排序算法总结
堆结构
完全二叉树
左孩子 2*i+1
右孩子2*i+2
父节点 i-1/2
大根堆
每棵子树最大值是头结点的值
小根堆
每棵子树最小值是头结点的值
heapInsert方法
O(log N)
与父节点比较(i-1)/2
public static void headInsert(int []arr,int index){
while(arr[index] > arr[(i-1)/2]){
swap(arr,index,(index-1)/2);
index = (index - 1)/2;
}
}
heapify方法
O(log N)
public static void heapify(int[] arr,int index,int heapSize){
int left = index * 2 +1;
while(left < heapSize){
int largest = left + 1 < heapSize && arr[left + 1] > arr[left]
?left + 1 : left;//比较左右孩子
largest = arr[largest] > arr[index] ? largest:index;//比较父亲和较大孩子之间
if(largest == index){
break;
}
swap(arr,largest,index);
index = largest;
left = index * 2 + 1;
}
}
heapSort方法
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i = 0;i < arr.length;i++){ //O(N)
heapInsert(arr,i); //O(logN)
}
int heapSize = arr.length;
swap(arr,0,--heapSize);
while(heapSize > 0){ //O(N)
heapify(arr,0,heapSize); //O(logN)
swap(arr,0,--heapSize); //O(1)
}
}
题目
一个几乎有序的数组,几乎有序是指,如果把数组排好序,每个元素移动的距离不超过k,并且k相对数组来说较小,选择一个合适的排序算法
public class Code04{
priorityQueue<Integer>heap = new PriorityQueue<>();
int index = 0;
for(;index <=
Math.min(arr.length,k);index++){
heap.add(arr[index]);
}
int i = 0;
for(;index < arr.length;i++,index++){
heap.add(arr[index]);
arr[i] = heap.poll();
}
while(!heap.isEmpty()){
arr[i++] = heap.poll();
}
}
系统自带的堆结构相当于一个黑盒,支持add或者poll的基础操作,不支持以很小的代价调整其中一个数还维持堆结构,手写的可以,所以某些情况要手写堆结构来提高效率
计数排序
不基于比较排序
对于特殊数据可以使用计数排序,例如针对员工年龄进行排序,假设员工年龄范围为0-100岁,那么准备一个0-100的数组,遍历一遍原数组,在计数数组中按顺序记录年龄出现的频次,再将词频还原成有序的数组,本质上是一种特殊的桶排序
时间复杂度为O(N)
基数排序
将数据从左往右依次按照个位数大小放桶,再按顺序倒出来,先进先出,再按照十位数依次进桶,按顺序倒出来,最后按照百位数进桶 ,倒出来排好序
public static void radixSort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
public static int maxbits(int[] arr)
{
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length - 1; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
public static void radixSort(int[] arr, int L, int R, int digit)
{
final int radix = 10;
int i, j = 0;
int[] bucket = new int[R - L + 1];
for (int d = 1; d <= digit; d++) {
int[] count = new int[radix];
for (i = L; i <= R; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
for (i = R; i >= L; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
for (i = L, j = 0; i <= R; i++, j++) {
arr[i] = bucket[j];
}
}
}
public static int getDigit(int x, int d)
{
return ((x / ((int)Math.pow(10, d - 1))) % 10);
}
排序算法的稳定性及其汇总
同样值的个体之间,如果不因为排序而改变相对次序,那么这个排序是有稳定性的,否则就没有
不具备稳定性的排序:选择,快速,堆
具备稳定性:冒泡(相等不交换),插入 (相等不交换),归并(左指针优先),一切桶排序思想下的排序
一般会选择使用快速排序,因为经过实验的结果,快排的常数项最低,实在是有空间的限制可以用堆排,或者对稳定性有要求的用归并排序
坑
1.归并排序的空间复杂度可以变成O(1),不需要掌握,但是损失稳定性
2.原地归并排序空间复杂度变成O(1),时间复杂度变成O(N^2);,不如快排
3.快速排序可以稳定性,但是空间复杂度变成O(N),不如归并
4.改进都不重要,目前没有时间复杂度为O(N*logN),额外空间复杂度O(1),还具有稳定性的排序
5.有一道题目,奇数在数组左边,偶数在数组右边,求原始相对次序不变,时间复杂度O(N),空间复杂度O(1)
经典快排的paration做不到稳定性,01标准和奇偶标准是同一个标准,所有做不到
工程程序上排序的改进
1.充分利用O(N*logN)和O(N^2)排序各自的优势
2.稳定性的考虑
.有一道题目,奇数在数组左边,偶数在数组右边,求原始相对次序不变,时间复杂度O(N),空间复杂度O(1)
经典快排的paration做不到稳定性,01标准和奇偶标准是同一个标准,所有做不到