クイックソートは分割統治法に基づいたソートアルゴリズムです。これは、配列を小さなサブ配列に継続的に分割し、各サブ配列を再帰的に並べ替えることで機能し、最終的には配列全体を並べ替えます。
クイックソートアルゴリズムの原理は次のとおりです。
- ピボット要素として並べ替える要素を配列から選択します。
- ピボット要素より小さいすべての要素をピボット要素の左側に移動し、ピボット要素より大きい要素をすべてピボット要素の右側に移動します。
- クイック ソート アルゴリズムは、サブ配列の要素が 1 つだけ残るか空になるまで、左側のサブ配列と右側のサブ配列に対して再帰的に呼び出されます。
- 再帰が終了すると、配列全体がソートされます。
ピボット要素を継続的に選択して配列を分割することにより、クイックソート アルゴリズムは、配列全体が最終的にソートされるまで、配列をますます小さなサブ配列に分割します。このアルゴリズムの時間計算量は ですO(nlogn)
。
通常、ダブル ポインター方法は、ベース要素の正しい位置を見つけるために使用されます。まず、左ポインタで配列の先頭を指し、右ポインタで配列の末尾を指します。次に、左ポインタが左から右に移動し、基本要素よりも大きい要素が見つかって停止します。右ポインタは、基本要素よりも小さい要素が見つかるまで右から左に移動し、停止します。次に、左ポインタと右ポインタが指す要素を交換します。左右のポインタが一致するまで上記の手順を繰り返します。最後に、ベース要素が正しい位置になるように、ベース要素を左ポインタが指す要素と交換します。
具体的な実装は以下の通りです。
// 定义一个交换函数,用于交换数组中的两个元素
function swap(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// 定义一个分区函数,用于对数组进行分区操作
function partition(arr, left, right) {
const pivot = arr[right - 1]; // 将分区点设置为最右边的元素
let i = left,
j = right - 1; // 初始化指针i和j
while (i !== j) {
// 当i和j指针不相遇时,进行循环
arr[i] <= pivot ? i++ : swap(arr, i, --j); // 如果arr[i]小于等于分区点,则i指针向右移动;否则进行交换,并将j指针向左移动
}
swap(arr, j, right - 1); // 将分区点放置在正确的位置
return j; // 返回分区点的索引
}
// 定义一个快速排序函数
function qsort(arr, left = 0, right = arr.length) {
if (right - left <= 1) return; // 当要排序的元素个数小于等于1时,直接返回
const p = partition(arr, left, right); // 进行分区操作,并获取分区点的索引p
qsort(arr, left, p); // 对分区点左侧的子数组进行快速排序
qsort(arr, p + 1, right); // 对分区点右侧的子数组进行快速排序
}
const arr = [5, 3, 8, 4, 2, 1, 10, 11, -4, 55];
qsort(arr); // [ -4, 1, 2, 3, 4, 5, 8, 10, 11, 55 ]
console.log(arr);
その他の実装方法:
/**
1. 第一段程序使用的是递归的方式实现快速排序。它选择数组中间的元素作为基准元素,然后将数组分割为左右两个子数组,将比基准元素小的元素放在左子数组,将比基准元素大的元素放在右子数组,然后递归地对左右两个子数组进行快速排序,最后将左右两个子数组和基准元素拼接起来作为排序结果。
2. 第二段程序使用的是基于指针的方式实现快速排序。它通过定义一个分区函数来选择基准元素,并将数组分割为左右两个子数组,然后再递归地对左右两个子数组进行快速排序。分区函数中使用了双指针的方法,从左到右找到第一个大于基准元素的元素,从右到左找到第一个小于基准元素的元素,然后交换它们的位置。这样,最终基准元素的位置就确定下来了,并且左边的元素都小于等于基准元素,右边的元素都大于基准元素。
总的来说,这两段程序的思路是相同的,都是通过不断地将数组分割为更小的子数组并排序,最后再将子数组合并成排序后的结果。它们的实现细节略有不同,但最终的结果是相同的。
*/
//---------------------------------1------------------------------------
function quickSort(arr) {
// 如果数组长度小于等于1,则直接返回
if (arr.length <= 1) {
return arr;
}
// 选择一个基准元素
const pivot = arr[Math.floor(arr.length / 2)];
// 定义左右两个子数组
const left = [];
const right = [];
// 将元素分割到左右两个子数组
for (let i = 0; i < arr.length; i++) {
if (i === Math.floor(arr.length / 2)) {
continue; // 跳过基准元素
}
if (arr[i] <= pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归地对左右两个子数组进行快速排序
return quickSort(left).concat([pivot], quickSort(right));
}
// 测试
const arr = [5, 3, 8, 4, 2, 1, 10];
const sortedArr = quickSort(arr);
console.log(sortedArr); // 输出 [1, 2, 3, 4, 5, 8, 10]
//---------------------------------2------------------------------------
// 快速排序函数
function quickSort1(arr, left, right) {
// 递归结束条件
if (left >= right) {
return;
}
// 设置左右指针及基准值
let pivot = arr[left];
let i = left;
let j = right;
// 开始一轮排序
while (i < j) {
// 从右侧找到比基准值小的元素
while (i < j && arr[j] >= pivot) {
j--;
}
// 将该元素放到左侧
arr[i] = arr[j];
// 从左侧找到比基准值大的元素
while (i < j && arr[i] <= pivot) {
i++;
}
// 将该元素放到右侧
arr[j] = arr[i];
}
// 将基准值放回数组
arr[i] = pivot;
// 递归调用快速排序函数对左右两个子数组进行排序
quickSort1(arr, left, i - 1);
quickSort1(arr, i + 1, right);
}
// 测试
let arr2 = [5, 8, 2, 6, 3, 9, 1, 7, 4];
quickSort1(arr2, 0, arr2.length - 1);
console.log(arr2);