JavaScript实现原地快速排序:原理与代码解析

作者:青柠

时间:2026-10-30

浏览:105

本文介绍如何使用JavaScript实现基于分治法思想的快速排序。重点解析原地排序(in-place)策略,通过Partition函数将数组划分为小于和大于基准值的两部分,避免额外空间开销。文章对比了非原地排序的缺陷,详细阐述了递归实现步骤、时间复杂度分析及代码示例,帮助开发者理解高效排序算法的核心逻辑。

文章目录

快速排序与分治法概述

快速排序,也被称为划分交换排序,是一种基于分治法策略实现的排序算法。

本文主要探讨如何利用JavaScript实现基于原地(in-place)思想的快速排序算法。

分治法:

在计算机科学领域,分治法是一种基于多项分支递归的重要算法范式。其核心思想是“分而治之”,即将一个复杂的大问题分解为两个或多个相同或相似的子问题,直到子问题可以直接简单求解,最终原问题的解即为所有子问题解的合并(摘自维基百科)。

快速排序的核心思想:

在数组中指定一个元素作为基准(pivot),将比它大的元素放到该元素后面,比它小的元素放到前面,重复此过程直至整个数组有序排列。

快速排序主要分为三个步骤:

1. 选基准:在数据结构中选择一个元素作为基准(pivot)。

2. 划分区:参照基准元素值的大小划分无序区,所有小于基准元素的数据放入一个区间,所有大于基准元素的数据放入另一个区间。分区操作结束后,基准元素所处的位置即为最终排序后它应该所在的位置。

3. 递归:对初次划分出来的两个无序区间,递归调用第1步和第2步的算法,直到所有无序区间都只剩下一个元素为止。

非原地排序实现及其缺陷

首先介绍一种普遍的实现方法(未使用原地算法):


function quickSort(arr) {
if (arr.length <= 1) return ;
//取数组最接近中间的数位基准,奇数与偶数取值不同,但不印象,当然,你可以选取第一个,或者最后一个数为基准,这里不作过多描述
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
//左右区间,用于存放排序后的数
var left = [];
var right = [];
console.log('基准为:' + pivot + ' 时');
for (var i = 0; i < arr.length; i++) {
console.log('分区操作的第 ' + (i + 1) + ' 次循环:');
//小于基准,放于左区间,大于基准,放于右区间
if (arr[i] < pivot) {
left.push(arr[i]);
console.log('左边:' + (arr[i]))
} else {
right.push(arr[i]);
console.log('右边:' + (arr[i]))
}
}
//这里使用concat操作符,将左区间,基准,右区间拼接为一个新数组
//然后递归1,2步骤,直至所有无序区间都 只剩下一个元素 ,递归结束
return quickSort(left).concat([pivot], quickSort(right));
}
var arr = [14, 3, 15, 7, 2, 76, 11];
console.log(quickSort(arr));
/*
* 基准为7时,第一次分区得到左右两个子集[ 3, 2,] 7 [14, 15, 76, 11];
* 以基准为2,对左边的子集[3,2]进行划分区排序,得到[2] 3。左子集排序全部结束
* 以基准为76,对右边的子集进行划分区排序,得到[14, 15, 11] 76
* 此时对上面的[14, 15, 11]以基准为15再进行划分区排序, [14, 11] 15
* 此时对上面的[14, 11]以基准为11再进行划分区排序, 11 [14]
* 所有无序区间都只剩下一个元素,递归结束
*
*/

通过断点调试可以验证上述逻辑的执行结果。

这种方法的弊端:

它需要Ω(n)的额外存储空间,这一点与归并排序类似,并不理想。在生产环境中,额外的内存空间占用会显著影响性能。

此外,许多人误以为上述方法即为真正的快速排序。因此,下文有必要推荐基于原地(in-place)算法的快速排序实现。

关于原地算法的更多细节可参考维基百科,若访问受限,也可通过百度等搜索引擎获取相关信息。

in-place(原地):

快速排序通常使用递归实现,其核心在于partition(分割)函数。该函数将数组划分为两部分:一部分小于pivot,另一部分大于pivot。具体原理前文已提及。

原地快速排序(in-place)实现


function quickSort(arr) {
// 交换
function swap(arr, a, b) {
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 分区
function partition(arr, left, right) {
/**
* 开始时不知最终pivot的存放位置,可以先将pivot交换到后面去
* 这里直接定义最右边的元素为基准
*/
var pivot = arr[right];
/**
* 存放小于pivot的元素时,是紧挨着上一元素的,否则空隙里存放的可能是大于pivot的元素,
* 故声明一个storeIndex变量,并初始化为left来依次紧挨着存放小于pivot的元素。
*/
var storeIndex = left;
for (var i = left; i < right; i++) {
if (arr[i] < pivot) {
/**
* 遍历数组,找到小于的pivot的元素,(大于pivot的元素会跳过)
* 将循环i次时得到的元素,通过swap交换放到storeIndex处,
* 并对storeIndex递增1,表示下一个可能要交换的位置
*/
swap(arr, storeIndex, i);
storeIndex++;
}
}
// 最后: 将pivot交换到storeIndex处,基准元素放置到最终正确位置上
swap(arr, right, storeIndex);
return storeIndex;
}
function sort(arr, left, right) {
if (left > right) return;
var storeIndex = partition(arr, left, right);
sort(arr, left, storeIndex - 1);
sort(arr, storeIndex + 1, right);
}
sort(arr, 0, arr.length - 1);
return arr;
}
console.log(quickSort([8, 4, 90, 8, 34, 67, 1, 26, 17]));

分区优化与复杂度分析

分区的优化:

细心的读者可能会提出,选取不同的基准是否会影响性能表现?答案是肯定的。但由于本文侧重于前端开发视角,对算法底层优化了解有限,这一优化空间留给更专业的算法研究者去填补。

复杂度分析:

快速排序通常被认为是速度最快的排序算法之一,其平均时间复杂度为O(log n)(注:此处原文表述有误,标准复杂度见下文)。

在平均情况下,排序n个项目需要Ο(n log n)次比较。而在最坏状况下,则需要Ο(n^2)次比较。

参考代码仓库:https://github.com/LYZ0106/

以上介绍了JavaScript实现基于原地思想的快速排序方法,希望对大家有所帮助。如有任何疑问,欢迎留言,小编会及时回复。感谢大家对本站的支持!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章