算法定义
快速排序是一种排序算法,由C. A. R. Hoare所发展的,以平均效能来说,排序 n 个项目要Θ(n log n)次比较。然而,在最坏的效能下,它需要Θ(n2)次比较。一般来说,快速排序实际上明显地比其他Θ(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来,且在大部分真实世界的数据,可以决定设计的选择,减少所需时间的二次方项之可能性。
算法描述
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
步骤为:
- 从数列中挑出一个元素,称为 "基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
源码描述
function
QuickSort_Once(_pnArray, _pnLow, _pnHigh) {
// 首先将首元素做为枢轴
var nPivot = _pnArray[_pnLow];
var i = _pnLow, j = _pnHigh; // 设置两个指针,分别指向首尾处
while (i < j) { // 直到两个指针重合
// 从右向左,寻找首个小于枢轴的元素
while (_pnArray[j] >= nPivot && i < j) j -- ;
_pnArray[i] = _pnArray[j]; // 找到后 执行替换
// 从左到右,寻找首个大于枢轴的元素
while (_pnArray[i] <= nPivot && i < j) i ++ ;
_pnArray[j] = _pnArray[i]; // 找到后 执行替换
}
// 至此i,j两指针重合执行同一位置,i即是新的枢轴位置
_pnArray[i] = nPivot;
return i;
}
function QuickSort(_pnArray, _pnLow, _pnHigh) {
if (_pnLow >= _pnHigh) return ;
// 获取枢轴
var _nPivotIndex = QuickSort_Once(_pnArray, _pnLow, _pnHigh);
// 然后对被枢轴分开的两侧进行分别递归
QuickSort(_pnArray, _pnLow, _nPivotIndex - 1 );
QuickSort(_pnArray, _nPivotIndex + 1 , _pnHigh);
}
/* 排序测试 */
( function () {
// 10个随机数
var arr = [];
for ( var i = 0 ; i < 10 ; i ++ ) {
arr[i] = Math.floor(Math.random() * 60 );
}
document.write( ' 初始值: ' + arr + ' <br/> ' );
QuickSort(arr, 0 , arr.length - 1 );
document.write( ' 排序值: ' + arr);
// 首先将首元素做为枢轴
var nPivot = _pnArray[_pnLow];
var i = _pnLow, j = _pnHigh; // 设置两个指针,分别指向首尾处
while (i < j) { // 直到两个指针重合
// 从右向左,寻找首个小于枢轴的元素
while (_pnArray[j] >= nPivot && i < j) j -- ;
_pnArray[i] = _pnArray[j]; // 找到后 执行替换
// 从左到右,寻找首个大于枢轴的元素
while (_pnArray[i] <= nPivot && i < j) i ++ ;
_pnArray[j] = _pnArray[i]; // 找到后 执行替换
}
// 至此i,j两指针重合执行同一位置,i即是新的枢轴位置
_pnArray[i] = nPivot;
return i;
}
function QuickSort(_pnArray, _pnLow, _pnHigh) {
if (_pnLow >= _pnHigh) return ;
// 获取枢轴
var _nPivotIndex = QuickSort_Once(_pnArray, _pnLow, _pnHigh);
// 然后对被枢轴分开的两侧进行分别递归
QuickSort(_pnArray, _pnLow, _nPivotIndex - 1 );
QuickSort(_pnArray, _nPivotIndex + 1 , _pnHigh);
}
/* 排序测试 */
( function () {
// 10个随机数
var arr = [];
for ( var i = 0 ; i < 10 ; i ++ ) {
arr[i] = Math.floor(Math.random() * 60 );
}
document.write( ' 初始值: ' + arr + ' <br/> ' );
QuickSort(arr, 0 , arr.length - 1 );
document.write( ' 排序值: ' + arr);
})();
另一种实现
function
QuickSort(arr, start, end) {
if (start < end) {
var q = Partition(arr, start, end); // 找出分割基准点
QuickSort(arr, start, q - 1 );
QuickSort(arr, q + 1 , end);
}
}
// 元素交换
function swap(arr, a, b) {
if (a == b) return ;
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 数组分割
function Partition(arr, start, end) {
var pivot = arr[end]; // 将数组最后一个元素作为主元
var i = start - 1 ; // 指定一个指针
for ( var j = start; j < end; j ++ ) {
if (arr[j] <= pivot) { // 如果当前元素小于主元
i = i + 1 ;
swap(arr, i, j);
}
}
swap(arr, i + 1 , end);
return (i + 1 );
}
var arr = [ 2 , 8 , 7 , 1 , 3 , 5 , 6 , 4 ];
QuickSort(arr, 0 , 7 );
alert(arr);
if (start < end) {
var q = Partition(arr, start, end); // 找出分割基准点
QuickSort(arr, start, q - 1 );
QuickSort(arr, q + 1 , end);
}
}
// 元素交换
function swap(arr, a, b) {
if (a == b) return ;
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 数组分割
function Partition(arr, start, end) {
var pivot = arr[end]; // 将数组最后一个元素作为主元
var i = start - 1 ; // 指定一个指针
for ( var j = start; j < end; j ++ ) {
if (arr[j] <= pivot) { // 如果当前元素小于主元
i = i + 1 ;
swap(arr, i, j);
}
}
swap(arr, i + 1 , end);
return (i + 1 );
}
var arr = [ 2 , 8 , 7 , 1 , 3 , 5 , 6 , 4 ];
QuickSort(arr, 0 , 7 );
alert(arr);
因为如果是已经排好序的话。那么用快速选择还需要O(n^2)的时间复杂度。这样是不优的。为了防止这种情况。所以在数据量大的时候可以使用随机化版本。随机化主元就避免最坏情况发生。
随机选择排序
function
QuickSort(arr, start, end) {
if (start < end) {
var q = RandomPartition(arr, start, end); // 找出分割基准点
QuickSort(arr, start, q - 1 );
QuickSort(arr, q + 1 , end);
}
}
// 元素交换
function swap(arr, a, b) {
if (a == b) return ;
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 数组分割
function Partition(arr, start, end) {
var pivot = arr[end]; // 将数组最后一个元素作为主元
var i = start - 1 ; // 指定一个指针
for ( var j = start; j < end; j ++ ) {
if (arr[j] <= pivot) { // 如果当前元素小于主元
i = i + 1 ;
swap(arr, i, j);
}
}
swap(arr, i + 1 , end);
return (i + 1 );
}
// 随机交换主元后再Partition
function RandomPartition(arr, start, end) {
var i = Math.floor(Math.random() * (end - start + 1 ) + start);
swap(arr, end, i);
return Partition(arr, start, end);
}
var arr = [ 2 , 8 , 7 , 1 , 3 , 5 , 6 , 4 ];
QuickSort(arr, 0 , 7 );
if (start < end) {
var q = RandomPartition(arr, start, end); // 找出分割基准点
QuickSort(arr, start, q - 1 );
QuickSort(arr, q + 1 , end);
}
}
// 元素交换
function swap(arr, a, b) {
if (a == b) return ;
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 数组分割
function Partition(arr, start, end) {
var pivot = arr[end]; // 将数组最后一个元素作为主元
var i = start - 1 ; // 指定一个指针
for ( var j = start; j < end; j ++ ) {
if (arr[j] <= pivot) { // 如果当前元素小于主元
i = i + 1 ;
swap(arr, i, j);
}
}
swap(arr, i + 1 , end);
return (i + 1 );
}
// 随机交换主元后再Partition
function RandomPartition(arr, start, end) {
var i = Math.floor(Math.random() * (end - start + 1 ) + start);
swap(arr, end, i);
return Partition(arr, start, end);
}
var arr = [ 2 , 8 , 7 , 1 , 3 , 5 , 6 , 4 ];
QuickSort(arr, 0 , 7 );
alert(arr);
其余参考:
http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html?20110404221633