排序算法
Posted thetinkerj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法相关的知识,希望对你有一定的参考价值。
排序算法
排序算法两阶段
第一阶段(比较排序)
- 插入排序
- 合并排序
- 堆排序
- 快速排序
第二阶段(非比较排序)
- 计数排序
- 基数排序
- 桶排序
第一阶段:比较排序
插入排序
插入排序的主要思想:
将当前的元素放入前面合适的位置
插入排序的实现细节:
public int[] insert_sort(int[]a){
int len = a.length;
for(int i = 0;i<len;i++){
for(int j = 0;j<i;j++){
if(a[i]<a[j]){
int tmp = a[i];
a[i] = a[j];
a[j]=tmp;
}
}
}
return a;
}
插入排序的小结:
插入排序是原地排序
最多只需要一个额外空间,用于中间的交换
插入排序的时间复杂度是(Theta(n^2))
合并排序
合并排序的主要思想:
使用分而治之的思想,分治法的核心在合
分:将一个需要排序的数组,分成对称的左右两个
合:将两段升序数组合并
合并排序的实现细节:
public void merge(int[]a,int p,int q,int m){
// [p-----m][-----q]
int len_l = m-p+2;
int len_r = q-(m+1)+2;// ****容易出错的部分!***
int[] left = new int[len_l];
int[] right= new int[len_r];
for(int i=p;i<=m;i++) left[i-p] = a[i];
for(int i=m+1;i<=q;i++) right[i-(m+1)] = a[i];
left[len_l-1] = Integer.MAX_VALUE;
right[len_r-1] = Integer.MAX_VALUE;
//合并的思考角度有三个
//x,y,本身,这里采用的是本身策略
int i = p;
int idl = 0;
int idr = 0;
while(i<=q){
if(left[idl]<right[idr]){
a[i] = left[idl];
i++;idl++;
}else{
a[i] = right[idr];
i++;idr++;
}
}
//return a;此处的修改涉及到java引用传递的问题
}
public int[] merge_sort(int[]a,int p,int q) {
if(p<q){
int m = (p+q)/2;
merge_sort(a, p, m);
merge_sort(a, m+1, q);
merge(a, p, q, m);
}
return a;
}
合并排序的小结:
合并排序需要最多的额外空间
是数组一倍的空间
算法的复杂度是(Theta(nlogn))
个人认为:该算法设计的精妙之处在于每一次的修改都是直接作用在数组本身,这样可以是分合过程十分自然的连为一体!
堆排序
堆排序所使用到的数据结构:最大堆/最小堆
形式化的结构:
a[15] : {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
抽象式的结构:完全二叉树
0
1 2
3 4 5 6
7 8 9 10 11 12 13 14
注:在实现过程中发现诸多计算机数学中的细节问题
- 数组下标为(0)带来的问题:
最大(最小)堆的性质:
对于任意一个(node[i]),其父亲节点(node[frac{i-1}{2}]),孩子节点分别为(node[2 imes i])和(node[2 imes i+1]) 。(以最大堆为例)每个节点必然满足大于其左右任意孩子节点。
堆排序涉及到的三个过程以及具体实现
public int[] max_heapify(int[]a,int i,int size){
/*
一个基本假设:
a[i]是待插入元素,其左右两边的堆是最大堆
a[i]有可能比左右两个最大堆的子堆还要小
*/
//对输入参数的解释
// a 输入数组 i 需要调整的根节点
// 其左儿子 left = a[2×i]
// 其右儿子 right = a[2×i+1]
// 目标是使元素a[i]贪心下沉到一个合适的位置
// 使合并后的堆任然是一个 max_heap
// size参数的设计是为了方便配合heap_sort
// 由于heap_sort过程中需要在衰减的空间中进行原地排序
int max_idx = i;
int left = 2*(i+1)-1;
int right = 2*(i+1);
if(left<size){
if(a[left]>a[max_idx]){
max_idx = left;
}
}
if(right<size){
if(a[right]>a[max_idx]){
max_idx = right;
}
}
if(max_idx!=i){
int tmp = a[i];
a[i] = a[max_idx];
a[max_idx] = tmp;
max_heapify(a, max_idx,size);
}
return a;
}
public int[] build_max_heap(int[]a){
int len = a.length;
for(int i = (len-1)/2;i>=0;i--)
a = max_heapify(a, i,a.length);
return a;
}
public int[] heap_sort(int[]a){
a = build_max_heap(a);
int size = a.length;
for(int i = a.length-1;i>0;i--){
int tmp = a[i];
a[i] = a[0];
a[0]=tmp;
a =max_heapify(a, 0, size-(a.length-i)) ;
}
return a;
}
heap_sort小结:
heap_sort中最重要的过程是max_heapify过程,对该过程进行适当修改可以适应不同算法的要求。
build_max_heap算法采用自底向上
的策略,首先构造满足条件左右子树,最后在层层推进将待插入元素使用heap_sort方法,插入到合适的位置。
heap_sort方法:初始状态-过程状态-结束状态。
快速排序
快速排序的主要思想是分治法
将一个数放在合适的位置上,然后左右递归。
([ dots forall m<i , x_m le x_i dots][x_i][dots forall n>i , x_n ge x_i dots])
算法实现细节:
public int[] quick_sort(int[]a,int p,int q){
if(p<q){
int i = partition(a, p, q);
quick_sort(a, p, i-1);
quick_sort(a, i+1, q);
}
return a;
}
public int partition(int[]a,int p,int q){
int x = a[q];
int i = p-1;
for(int j=p;j<q;j++){
if(a[j]<=x){
i = i+1;
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
i++;
a[q] = a[i];
a[i] = x;
return i;
}
对递归过程的文字描述:
每次等待排序的是数组最后一个元素,从头到尾对数组进行一次遍历,再标记一个分割线,如果当前元素小于待安插元素,将分界线右移一位,将该元素放进左边,若该元素大于待安插元素,则进行下一次判断。
本算法的初始状态为在没有开始之前,没有元素在左边。
过程演示:
|]*****^**************$ // |分割线,*非末尾元素,$待安插元素
|*]****^**************$ // ]算法当前遍历位置
|**]***^**************$ // *表示大于$的数,^表示小于$的数
|***]**^**************$
|****]*^**************$
|*****]^**************$
|*****]^**************$
---------------------------过程当放大
*|****]^**************$
^|****]***************$
---------------------------
^|*****]**************$
...
^|*******************]$
^$|*******************]
个人认为本算法中唯一难点就在于当前元素小于待安插元素时,分割线右移,小于元素与分割线占位元素交换,当然将两个操作顺序交换也是完全没有问题的。
第二阶段:非比较排序
- 计数排序
- 基数排序
- 桶排序
首先,比较算法的最坏下界一定是(Theta (nlgn)),关于这点可以使用决策树来解释(此处不做解释)。所以非比较算法一定是在特定的问题条件下,牺牲空间或者其它非时间因素,完成对算法的优化。
计数排序
使用条件:待排序样本满足 (X = {x|ale x le b,a、bin Z })
使用思想: hash 映射
注意数组下标的调试
public int[] count_sort(int[]a,int min,int max){
int[]b = new int[a.length];
int[]count = new int[max-min+1];
/*
基数排序的主要流程
第一轮 计数
第二轮 累加
第三轮 排序
*/
for(int i = 0;i<count.length;i++){
count[i]=0;
}
for(int v : a){
count[v-min]++;
}
int cur_num = 0;
for (int i = 0;i<count.length;i++) {
cur_num = count[i]+cur_num;
count[i] = cur_num;
}
for(int i = a.length-1;i>=0;i--){
b[count[a[i]-min]-1] = a[i];
count[a[i]-min]--;
}
return b;
}
基数排序
基数排序还是非常有意思的过程下面模拟下:
原始 | 个位 | 十位 | 百位 |
---|---|---|---|
329 | 72 0 |
7 20 |
329 |
457 | 35 5 |
3 29 |
355 |
657 | 43 6 |
4 36 |
436 |
839 | 45 7 |
8 39 |
457 |
436 | 65 7 |
3 55 |
657 |
720 | 32 9 |
4 57 |
720 |
355 | 83 9 |
6 57 |
839 |
其伪代码
for i 最小比较位 to 最大比较位
使用合适稳定的比较排序对目标数组进行排序
桶排序
桶排序的使用场景比较特殊:
桶排序适用于均匀分布的数组,期望与最终待分类的样本会被均匀的分布到一个个小桶中(这貌似有点经典统计学派)。所以就认为事先将其划分出一个个大小相等的桶,然后将待分配元素放在合适桶中,最终整个数组就是一个天然有序!
以上是关于排序算法的主要内容,如果未能解决你的问题,请参考以下文章