快速排序
说到快速排序算法,可能我们会了解到其最坏的运行时间为O(n*n),但是对于其平均性能为O(nlgn),正是这一点使得其被广泛的使用,另一个重要的特点就是,其能够进行原地排序,能够尽可能的减少空间的占用,尽可能的减小算法使用的空间。说了这么多理由,那么到底是怎么一回事,或者到底是怎么样的呢?
其核心部分就是选取一个元素,将大于它的放置到右边,将小于它的放置到左边,然后可以将其中间的位置与选取的元素进行交换,得到排序后的结果,是的,快速排序,归并排序,堆排序等都是基于交换的算法,故其算法一般的运行时间为O(nlgn),所以在这里其是最核心的东西还是交换,只不过交换的条件或者是形式改变了。
这么空泛的说可能比较蒙,结合代码来讨论一下:
public class QuickSort {
public static void QuickSort(int[] array, int left, int right){
int par_value;
//限定条件,left 要小于right,不要越界
if (left < right){
//寻找划分位置
par_value = Partition(array,left,right);
//分别递归进行左半部分数组和右半部分数组进行排序
QuickSort(array,left,par_value - 1);
QuickSort(array,par_value + 1, right);
}
}
private static int Partition(int[] array, int left, int right) {
int x, i , j , temp;
//选择一个待比较的数字,也就是说一直以这个为核心
// 将其与数组中其它的数字进行比较,根据比较条件确定交换方式
x = array[right];
//将i 移动到数组的外面,如果又符合下面的判断条件的情况则+1
i = left - 1;
//我们假定选取了数组的最后一个元素,将其从第一个位置进行比较
//i 表示的为交换的靠左边的为止,j 为交换两者靠右边的位置
//判断条件为 array[i] < x,x也即array[right]。
for(j = left; j <= right - 1; j ++){
if(array[j] <= x){
//
i += 1;
//交换i 位置和 j 位置元素
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//将最后元素array[right]和循环结束时i+1 的位置进行交换,因为此时
//数组中的array[right]所在的正确位置即为i + 1的位置,故进行交换
temp = array[i + 1];
array[i + 1] = array[right];
array[right] = temp;
//返回的是位置 i+1,即被比较元素应该在的地方
return i + 1;
}
}
其实对于运行时间的影响来说,Parition方法影响最大,因为其划分的结果直接影响了排序算法的性能,方法划分的时候有时候是好的,有时候是坏的,这个不好说,上面写的并不是原版的,原版的Hoare所提出的快速排序算法为:
public class QuickSort {
public static void QuickSort(int[] array, int left, int right){
int par_value;
//限定条件,left 要小于right,不要越界
if(left < right){
//寻找划分位置
par_value = Parition1(array,left,right);
//分别递归进行左半部分数组和右半部分数组进行排序
QuickSort(array,left,par_value - 1);
QuickSort(array,par_value + 1, right);
}
}
//T.Hoare提出的快速排序的版本
public static int Parition1(int[] array, int left, int right){
int basic_value, i , j, temp;
basic_value = array[left];
i = left ;
j = right;
while (i != j){
while (i < j && array[j] >= basic_value)
j--;
while(i < j && array[i] <= basic_value)
i++;
if(i < j){
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//此时i 和j应该时相当的,即i = j
array[left] = array[i];
array[i] = basic_value;
return i;
}
}
然而分析快速排序算法的代码可以发现有些时候对于某些递归调用是浪费的,而对于这种情况,了解到一种优化方式叫做尾递归,这个最终还是要看编译器是否支持的,对于使用尾递归,我们可以认为第二次递归调用是没意义的,可以使用循环结构来进行控制,对此我们可以修改一下代码:
public class QuickSort {
public static void QuickSort(int[] array, int left, int right){
int par_value;
//限定条件,left 要小于right,不要越界
while (left < right){
//寻找划分位置
par_value = Partition(array,left,right);
//尾递归调用,将第一次得到的为止传到第二次递归,第二次递归则相当于对右边
//子数组进行递归
QuickSort(array,left,par_value - 1);
left = par_value + 1;
}
}
private static int Partition(int[] array, int left, int right) {
int x, i , j , temp;
//选择一个待比较的数字,也就是说一直以这个为核心
// 将其与数组中其它的数字进行比较,根据比较条件确定交换方式
x = array[right];
//将i 移动到数组的外面,如果又符合下面的判断条件的情况则+1
i = left - 1;
//我们假定选取了数组的最后一个元素,将其从第一个位置进行比较
//i 表示的为交换的靠左边的为止,j 为交换两者靠右边的位置
//判断条件为 array[i] < x,x也即array[right]。
for(j = left; j <= right - 1; j ++){
if(array[j] <= x){
//
i += 1;
//交换i 位置和 j 位置元素
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//将最后元素array[right]和循环结束时i+1 的位置进行交换,因为此时
//数组中的array[right]所在的正确位置即为i + 1的位置,故进行交换
temp = array[i + 1];
array[i + 1] = array[right];
array[right] = temp;
//返回的是位置 i+1,即被比较元素应该在的地方
return i + 1;
}
}
此时,我们完成了尾递归对原来快速排序算法的改进,如果了解一下尾递归,参考下列博客:
https://blog.youkuaiyun.com/zcyzsy/article/details/77151709
http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html
最后我们了解一下尾递归的栈深度,了解一下尾递归的另一种写法:
public class TailRecuriveQuickSort {
private static int stack_depth = 0;
private static int max_stack_depth = 0;
public static void tail_recuive_quicksort(int[] array, int left, int right){
increment_stack_depth();
while(left < right){
int par_value = partition(array,left,right);
if(par_value < (left + right ) / 2){
tail_recuive_quicksort(array,left,par_value - 1);
//把当前路径传递给下次使用的位置
left = par_value + 1;
}else {
tail_recuive_quicksort(array,par_value + 1 , right );
right = par_value - 1;
}
}
decrement_stack_depth();
}
public static int partition(int[] array, int left, int right){
int x, i , j , temp;
x = array[right];
i = left - 1;
for(j = left; j <= right - 1; j++){
if(array[j] <= x){
i += 1;
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
temp = array[i + 1];
array[i + 1] = array[right];
array[right] = temp;
return i + 1;
}
private static void increment_stack_depth(){
stack_depth++;
if(max_stack_depth < stack_depth){
max_stack_depth = stack_depth;
}
}
private static void decrement_stack_depth(){
stack_depth --;
}
private static void reset_depth_counter(){
max_stack_depth = 0;
stack_depth = 0;
}
}