排序算法-交换排序-快速排序
算法思想
快速排序是对冒泡排序的一种改进,其基本思想是基于分治算法的。
在待排序表
L
[
0...
n
]
L[0...n]
L[0...n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两个部分
L
[
0...
k
−
1
]
L[0...k-1]
L[0...k−1]和
L
[
k
+
1...
n
]
L[k+1...n]
L[k+1...n],使得
L
[
0...
k
−
1
]
L[0...k-1]
L[0...k−1]中的元素小于pivot,
L
[
k
+
1...
n
]
L[k+1...n]
L[k+1...n]中的元素大于或等于pivot,则pivot放在了最终的位置
L
(
k
)
L(k)
L(k)上,这个过程称为一趟快速排序。
每一次排序都会使得确定一个中间位置的元素,即本次排序的枢轴元素被放到了最终的位置上。
而后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或者元素为空为止,即所有元素放在了其最终位置上。
每一趟排序的代码如下,起到划分的作用。
public static int Partition(int[] nums, int low, int high){
// 以子表的第一个元素为枢轴进行划分,划分完即完成一次排序过程。
int pivot = nums[low]; // 设定枢轴值
while(low<high){ // 循环结束条件
while(low<high && nums[high]>=pivot) --high;
nums[low] = nums[high]; //将比枢轴值小的元素移动到左端
while(low<high && nums[low]<=pivot) ++low;
nums[high] = nums[low]; //将比枢轴值大的元素移动到右端
}
nums[low] = pivot; // 枢轴元素存放到最终位置
return low; // 返回枢轴元素的下标
}
最终图中小于下标小于low的元素的值小于等于25(pivot),下标大于low的元素值大于等于25.
经过一次partition之后,枢轴值已经在最终的位置上了,所以可以对枢轴值左边的子表和右边的子表进行同样的操作,达到排序的目的。代码如下:
public static void quicksort(int[] nums, int left, int right){
if(left<right){ //递归结束的条件
// partition将nums以枢轴划分为两个表,枢轴右边的元素大于枢轴,左边的元素小于枢轴。
int pivotpos = Partition(nums, left, right);
quicksort(nums, left, pivotpos-1); // 对左表进行递归排序
quicksort(nums, pivotpos+1, right); // 对右表进行递归排序
}
}
完整代码:
public class QuickSort {
public static void main(String[] args) {
int[] nums = {3,8,4,5,6,8,9,0};
quicksort(nums,0,nums.length-1);
for(int num:nums){
System.out.print(num);
}
}
public static void quicksort(int[] nums, int left, int right){
if(left<right){ //递归结束的条件
// partition将nums以枢轴划分为两个表,枢轴右边的元素大于枢轴,左边的元素小于枢轴。
int pivotpos = Partition(nums, left, right);
quicksort(nums, left, pivotpos-1); // 对左表进行递归排序
quicksort(nums, pivotpos+1, right); // 对右表进行递归排序
}
}
public static int Partition(int[] nums, int low, int high){
// 以子表的第一个元素为枢轴进行划分,划分完即完成一次排序过程。
int pivot = nums[low]; // 设定枢轴值
while(low<high){ // 循环结束条件
while(low<high && nums[high]>=pivot) --high;
nums[low] = nums[high]; //将比枢轴值小的元素移动到左端
while(low<high && nums[low]<=pivot) ++low;
nums[high] = nums[low]; //将比枢轴值大的元素移动到右端
}
nums[low] = pivot; // 枢轴元素存放到最终位置
return low; // 返回枢轴元素的下标
}
}
复杂度分析:
空间效率:由于快速排序是递归的,需要借助一个递归工作栈来保存每一层递归调用的必要信息,其容量应与递归调用的最大深度一致。最好情况下是 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_{2}(n+1) \rceil ⌈log2(n+1)⌉(或 ⌊ l o g 2 n ⌋ + 1 \lfloor log_{2}n\rfloor +1 ⌊log2n⌋+1),也就是有n个节点二叉树的最小高度;最坏情况下,因为要进行n-1次递归调用,所以栈的深度为 O ( n ) O(n) O(n),也就是有n个节点的二叉树的最大高度;平均情况下,栈的深度为 O ( l o g 2 n ) O(log_{2}n) O(log2n).
时间效率:
快速排序的运行时间与划分是否对称有关,而后者又与具体使用的划分算法有关,即取决于枢轴值的选取。每次partition的时间复杂度为
O
(
n
)
O(n)
O(n),整个排序算法的时间复杂度为
O
(
n
∗
递
归
深
度
)
O(n*递归深度)
O(n∗递归深度)。最坏情况发生在两个区域分别包含n-1和0个元素时,这种最大程度的不对称性发生在每一层递归上,即对应于初始排序基本有序或者基本逆序时,递归深度为n数量级,就得到最坏情况下的时间复杂度为
O
(
n
2
)
O(n^{2})
O(n2). 在最理想的情况下,也即Partition()可能做到的最平衡的划分中,得到的两个子问题的大小都不可能大于n/2,递归深度为logn数量级,此时,时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn).
快速排序平均情况下运行时间与其最佳情况下运行时间很接近,而不是接近其最坏情况下的运行时间。快速排序是内部排序算法中平均性能最优的排序算法。
稳定性:不稳定。在划分算法中,若右端区间存在两个关键字相同,且小于枢轴值的元素时,在交换到左端区间后,他们的位置会发生变化。