mirror of
https://github.com/krahets/hello-algo.git
synced 2025-07-12 18:10:42 +08:00
Bug fixes and improvements. (#1780)
* Fix the "尾递归优化" to "递归深度优化" in quick_sort. * Update landing pages. * Sync zh and zh-hant versions. * Sync zh and zh-hant versions.
This commit is contained in:
@ -89,9 +89,9 @@ void quickSortMedian(int nums[], int left, int right) {
|
||||
quickSortMedian(nums, pivot + 1, right);
|
||||
}
|
||||
|
||||
// 以下为尾递归优化的快速排序
|
||||
// 以下为递归深度优化的快速排序
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
void quickSortTailCall(int nums[], int left, int right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -127,10 +127,10 @@ int main() {
|
||||
printf("快速排序(中位基准数优化)完成后 nums = ");
|
||||
printArray(nums1, size);
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
int nums2[] = {2, 4, 1, 0, 3, 5};
|
||||
quickSortTailCall(nums2, 0, size - 1);
|
||||
printf("快速排序(尾递归优化)完成后 nums = ");
|
||||
printf("快速排序(递归深度优化)完成后 nums = ");
|
||||
printArray(nums1, size);
|
||||
|
||||
return 0;
|
||||
|
@ -84,7 +84,7 @@ class QuickSortMedian {
|
||||
}
|
||||
};
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
private:
|
||||
/* 哨兵划分 */
|
||||
@ -103,7 +103,7 @@ class QuickSortTailCall {
|
||||
}
|
||||
|
||||
public:
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
static void quickSort(vector<int> &nums, int left, int right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -135,10 +135,10 @@ int main() {
|
||||
cout << "快速排序(中位基准数优化)完成后 nums = ";
|
||||
printVector(nums1);
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
vector<int> nums2 = {2, 4, 1, 0, 3, 5};
|
||||
QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1);
|
||||
cout << "快速排序(尾递归优化)完成后 nums = ";
|
||||
cout << "快速排序(递归深度优化)完成后 nums = ";
|
||||
printVector(nums2);
|
||||
|
||||
return 0;
|
||||
|
@ -89,7 +89,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交换 */
|
||||
static void Swap(int[] nums, int i, int j) {
|
||||
@ -111,7 +111,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
public static void QuickSort(int[] nums, int left, int right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -142,9 +142,9 @@ public class quick_sort {
|
||||
QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1);
|
||||
Console.WriteLine("快速排序(中位基准数优化)完成后 nums1 = " + string.Join(",", nums1));
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
int[] nums2 = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1);
|
||||
Console.WriteLine("快速排序(尾递归优化)完成后 nums2 = " + string.Join(",", nums2));
|
||||
Console.WriteLine("快速排序(递归深度优化)完成后 nums2 = " + string.Join(",", nums2));
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交换 */
|
||||
static void _swap(List<int> nums, int i, int j) {
|
||||
@ -108,7 +108,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
static void quickSort(List<int> nums, int left, int right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -138,8 +138,8 @@ void main() {
|
||||
QuickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
print("快速排序(中位基准数优化)完成后 nums1 = $nums1");
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
List<int> nums2 = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
print("快速排序(尾递归优化)完成后 nums2 = $nums2");
|
||||
print("快速排序(递归深度优化)完成后 nums2 = $nums2");
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type quickSort struct{}
|
||||
// 快速排序(中位基准数优化)
|
||||
type quickSortMedian struct{}
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
// 快速排序(递归深度优化)
|
||||
type quickSortTailCall struct{}
|
||||
|
||||
/* 哨兵划分 */
|
||||
@ -112,7 +112,7 @@ func (q *quickSortTailCall) partition(nums []int, left, right int) int {
|
||||
return i // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化)*/
|
||||
/* 快速排序(递归深度优化)*/
|
||||
func (q *quickSortTailCall) quickSort(nums []int, left, right int) {
|
||||
// 子数组长度为 1 时终止
|
||||
for left < right {
|
||||
|
@ -25,10 +25,10 @@ func TestQuickSortMedian(t *testing.T) {
|
||||
fmt.Println("快速排序(中位基准数优化)完成后 nums = ", nums)
|
||||
}
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
// 快速排序(递归深度优化)
|
||||
func TestQuickSortTailCall(t *testing.T) {
|
||||
q := quickSortTailCall{}
|
||||
nums := []int{4, 1, 3, 1, 5, 2}
|
||||
q.quickSort(nums, 0, len(nums)-1)
|
||||
fmt.Println("快速排序(尾递归优化)完成后 nums = ", nums)
|
||||
fmt.Println("快速排序(递归深度优化)完成后 nums = ", nums)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交换 */
|
||||
static void swap(int[] nums, int i, int j) {
|
||||
@ -120,7 +120,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
public static void quickSort(int[] nums, int left, int right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -150,9 +150,9 @@ public class quick_sort {
|
||||
QuickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
System.out.println("快速排序(中位基准数优化)完成后 nums1 = " + Arrays.toString(nums1));
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
int[] nums2 = { 2, 4, 1, 0, 3, 5 };
|
||||
QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
System.out.println("快速排序(尾递归优化)完成后 nums2 = " + Arrays.toString(nums2));
|
||||
System.out.println("快速排序(递归深度优化)完成后 nums2 = " + Arrays.toString(nums2));
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交换 */
|
||||
swap(nums, i, j) {
|
||||
@ -123,7 +123,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
quickSort(nums, left, right) {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -154,8 +154,8 @@ const quickSortMedian = new QuickSortMedian();
|
||||
quickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
console.log('快速排序(中位基准数优化)完成后 nums =', nums1);
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
const nums2 = [2, 4, 1, 0, 3, 5];
|
||||
const quickSortTailCall = new QuickSortTailCall();
|
||||
quickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
console.log('快速排序(尾递归优化)完成后 nums =', nums2);
|
||||
console.log('快速排序(递归深度优化)完成后 nums =', nums2);
|
||||
|
@ -83,7 +83,7 @@ fun quickSortMedian(nums: IntArray, left: Int, right: Int) {
|
||||
quickSort(nums, pivot + 1, right)
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
fun quickSortTailCall(nums: IntArray, left: Int, right: Int) {
|
||||
// 子数组长度为 1 时终止
|
||||
var l = left
|
||||
@ -114,8 +114,8 @@ fun main() {
|
||||
quickSortMedian(nums1, 0, nums1.size - 1)
|
||||
println("快速排序(中位基准数优化)完成后 nums1 = ${nums1.contentToString()}")
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
val nums2 = intArrayOf(2, 4, 1, 0, 3, 5)
|
||||
quickSortTailCall(nums2, 0, nums2.size - 1)
|
||||
println("快速排序(尾递归优化)完成后 nums2 = ${nums2.contentToString()}")
|
||||
println("快速排序(递归深度优化)完成后 nums2 = ${nums2.contentToString()}")
|
||||
}
|
@ -79,7 +79,7 @@ class QuickSortMedian:
|
||||
|
||||
|
||||
class QuickSortTailCall:
|
||||
"""快速排序类(尾递归优化)"""
|
||||
"""快速排序类(递归深度优化)"""
|
||||
|
||||
def partition(self, nums: list[int], left: int, right: int) -> int:
|
||||
"""哨兵划分"""
|
||||
@ -97,7 +97,7 @@ class QuickSortTailCall:
|
||||
return i # 返回基准数的索引
|
||||
|
||||
def quick_sort(self, nums: list[int], left: int, right: int):
|
||||
"""快速排序(尾递归优化)"""
|
||||
"""快速排序(递归深度优化)"""
|
||||
# 子数组长度为 1 时终止
|
||||
while left < right:
|
||||
# 哨兵划分操作
|
||||
@ -123,7 +123,7 @@ if __name__ == "__main__":
|
||||
QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1)
|
||||
print("快速排序(中位基准数优化)完成后 nums =", nums1)
|
||||
|
||||
# 快速排序(尾递归优化)
|
||||
# 快速排序(递归深度优化)
|
||||
nums2 = [2, 4, 1, 0, 3, 5]
|
||||
QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1)
|
||||
print("快速排序(尾递归优化)完成后 nums =", nums2)
|
||||
print("快速排序(递归深度优化)完成后 nums =", nums2)
|
||||
|
@ -92,7 +92,7 @@ class QuickSortMedian
|
||||
end
|
||||
end
|
||||
|
||||
### 快速排序类(尾递归优化)###
|
||||
### 快速排序类(递归深度优化)###
|
||||
class QuickSortTailCall
|
||||
class << self
|
||||
### 哨兵划分 ###
|
||||
@ -115,7 +115,7 @@ class QuickSortTailCall
|
||||
i # 返回基准数的索引
|
||||
end
|
||||
|
||||
### 快速排序(尾递归优化)###
|
||||
### 快速排序(递归深度优化)###
|
||||
def quick_sort(nums, left, right)
|
||||
# 子数组长度不为 1 时递归
|
||||
while left < right
|
||||
@ -146,8 +146,8 @@ if __FILE__ == $0
|
||||
QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1)
|
||||
puts "快速排序(中位基准数优化)完成后 nums1 = #{nums1}"
|
||||
|
||||
# 快速排序(尾递归优化)
|
||||
# 快速排序(递归深度优化)
|
||||
nums2 = [2, 4, 1, 0, 3, 5]
|
||||
QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1)
|
||||
puts "快速排序(尾递归优化)完成后 nums2 = #{nums2}"
|
||||
puts "快速排序(递归深度优化)完成后 nums2 = #{nums2}"
|
||||
end
|
||||
|
@ -90,7 +90,7 @@ impl QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
struct QuickSortTailCall;
|
||||
|
||||
impl QuickSortTailCall {
|
||||
@ -111,7 +111,7 @@ impl QuickSortTailCall {
|
||||
i // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {
|
||||
// 子数组长度为 1 时终止
|
||||
while left < right {
|
||||
@ -141,8 +141,8 @@ fn main() {
|
||||
QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums);
|
||||
println!("快速排序(中位基准数优化)完成后 nums = {:?}", nums);
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
let mut nums = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums);
|
||||
println!("快速排序(尾递归优化)完成后 nums = {:?}", nums);
|
||||
println!("快速排序(递归深度优化)完成后 nums = {:?}", nums);
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func quickSortMedian(nums: inout [Int], left: Int, right: Int) {
|
||||
quickSortMedian(nums: &nums, left: pivot + 1, right: right)
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
func quickSortTailCall(nums: inout [Int], left: Int, right: Int) {
|
||||
var left = left
|
||||
var right = right
|
||||
@ -106,9 +106,9 @@ enum QuickSort {
|
||||
quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1)
|
||||
print("快速排序(中位基准数优化)完成后 nums1 = \(nums1)")
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
var nums2 = [2, 4, 1, 0, 3, 5]
|
||||
quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1)
|
||||
print("快速排序(尾递归优化)完成后 nums2 = \(nums2)")
|
||||
print("快速排序(递归深度优化)完成后 nums2 = \(nums2)")
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序类(尾递归优化) */
|
||||
/* 快速排序类(递归深度优化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交换 */
|
||||
swap(nums: number[], i: number, j: number): void {
|
||||
@ -140,7 +140,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
quickSort(nums: number[], left: number, right: number): void {
|
||||
// 子数组长度为 1 时终止
|
||||
while (left < right) {
|
||||
@ -171,10 +171,10 @@ const quickSortMedian = new QuickSortMedian();
|
||||
quickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
console.log('快速排序(中位基准数优化)完成后 nums =', nums1);
|
||||
|
||||
/* 快速排序(尾递归优化) */
|
||||
/* 快速排序(递归深度优化) */
|
||||
const nums2 = [2, 4, 1, 0, 3, 5];
|
||||
const quickSortTailCall = new QuickSortTailCall();
|
||||
quickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
console.log('快速排序(尾递归优化)完成后 nums =', nums2);
|
||||
console.log('快速排序(递归深度优化)完成后 nums =', nums2);
|
||||
|
||||
export {};
|
||||
|
@ -94,7 +94,7 @@ const QuickSortMedian = struct {
|
||||
}
|
||||
};
|
||||
|
||||
// 快速排序类(尾递归优化)
|
||||
// 快速排序类(递归深度优化)
|
||||
const QuickSortTailCall = struct {
|
||||
|
||||
// 元素交换
|
||||
@ -118,7 +118,7 @@ const QuickSortTailCall = struct {
|
||||
return i; // 返回基准数的索引
|
||||
}
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
// 快速排序(递归深度优化)
|
||||
pub fn quickSort(nums: []i32, left_: usize, right_: usize) void {
|
||||
var left = left_;
|
||||
var right = right_;
|
||||
@ -152,10 +152,10 @@ pub fn main() !void {
|
||||
std.debug.print("\n快速排序(中位基准数优化)完成后 nums = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &nums1);
|
||||
|
||||
// 快速排序(尾递归优化)
|
||||
// 快速排序(递归深度优化)
|
||||
var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 };
|
||||
QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1);
|
||||
std.debug.print("\n快速排序(尾递归优化)完成后 nums = ", .{});
|
||||
std.debug.print("\n快速排序(递归深度优化)完成后 nums = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &nums2);
|
||||
|
||||
_ = try std.io.getStdIn().reader().readByte();
|
||||
|
@ -89,7 +89,7 @@
|
||||
[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition}
|
||||
```
|
||||
|
||||
## 尾递归优化
|
||||
## 递归深度优化
|
||||
|
||||
**在某些输入下,快速排序可能占用空间较多**。以完全有序的输入数组为例,设递归中的子数组长度为 $m$ ,每轮哨兵划分操作都将产生长度为 $0$ 的左子数组和长度为 $m - 1$ 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
- 冒泡排序通过交换相邻元素来实现排序。通过添加一个标志位来实现提前返回,我们可以将冒泡排序的最佳时间复杂度优化到 $O(n)$ 。
|
||||
- 插入排序每轮将未排序区间内的元素插入到已排序区间的正确位置,从而完成排序。虽然插入排序的时间复杂度为 $O(n^2)$ ,但由于单元操作相对较少,因此在小数据量的排序任务中非常受欢迎。
|
||||
- 快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,导致时间复杂度劣化至 $O(n^2)$ 。引入中位数基准数或随机基准数可以降低这种劣化的概率。尾递归方法可以有效地减少递归深度,将空间复杂度优化到 $O(\log n)$ 。
|
||||
- 快速排序基于哨兵划分操作实现排序。在哨兵划分中,有可能每次都选取到最差的基准数,导致时间复杂度劣化至 $O(n^2)$ 。引入中位数基准数或随机基准数可以降低这种劣化的概率。通过优先递归较短子区间,可有效减小递归深度,将空间复杂度优化到 $O(\log n)$ 。
|
||||
- 归并排序包括划分和合并两个阶段,典型地体现了分治策略。在归并排序中,排序数组需要创建辅助数组,空间复杂度为 $O(n)$ ;然而排序链表的空间复杂度可以优化至 $O(1)$ 。
|
||||
- 桶排序包含三个步骤:数据分桶、桶内排序和合并结果。它同样体现了分治策略,适用于数据体量很大的情况。桶排序的关键在于对数据进行平均分配。
|
||||
- 计数排序是桶排序的一个特例,它通过统计数据出现的次数来实现排序。计数排序适用于数据量大但数据范围有限的情况,并且要求数据能够转换为正整数。
|
||||
@ -32,11 +32,11 @@
|
||||
|
||||
再深入思考一下,如果我们选择 `nums[right]` 为基准数,那么正好反过来,必须先“从左往右查找”。
|
||||
|
||||
**Q**:关于尾递归优化,为什么选短的数组能保证递归深度不超过 $\log n$ ?
|
||||
**Q**:关于快速排序的递归深度优化,为什么选短的数组能保证递归深度不超过 $\log n$ ?
|
||||
|
||||
递归深度就是当前未返回的递归方法的数量。每轮哨兵划分我们将原数组划分为两个子数组。在尾递归优化后,向下递归的子数组长度最大为原数组长度的一半。假设最差情况,一直为一半长度,那么最终的递归深度就是 $\log n$ 。
|
||||
递归深度就是当前未返回的递归方法的数量。每轮哨兵划分我们将原数组划分为两个子数组。在递归深度优化后,向下递归的子数组长度最大为原数组长度的一半。假设最差情况,一直为一半长度,那么最终的递归深度就是 $\log n$ 。
|
||||
|
||||
回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 $n$、$n - 1$、$\dots$、$2$、$1$ ,递归深度为 $n$ 。尾递归优化可以避免这种情况出现。
|
||||
回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 $n$、$n - 1$、$\dots$、$2$、$1$ ,递归深度为 $n$ 。递归深度优化可以避免这种情况出现。
|
||||
|
||||
**Q**:当数组中所有元素都相等时,快速排序的时间复杂度是 $O(n^2)$ 吗?该如何处理这种退化情况?
|
||||
|
||||
|
@ -347,7 +347,7 @@
|
||||
<!-- contributors -->
|
||||
<div style="margin: 2em auto;">
|
||||
<h3>贡献者</h3>
|
||||
<p>本书在开源社区一百多位贡献者的共同努力下不断完善,感谢他们付出的时间与精力!</p>
|
||||
<p>本书在开源社区 200 多位贡献者的共同努力下不断完善,感谢他们付出的时间与精力!</p>
|
||||
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=krahets/hello-algo&max=300&columns=12" alt="Contributors" style="width: 100%; max-width: 38.5em;">
|
||||
</a>
|
||||
|
@ -414,7 +414,7 @@
|
||||
<!-- contributors -->
|
||||
<div style="margin: 2em auto;">
|
||||
<h3>Contributors</h3>
|
||||
<p>This book has been refined by the efforts of over 180 contributors. We sincerely thank them for their invaluable time and contributions!</p>
|
||||
<p>This book has been refined by the efforts of over 200 contributors. We sincerely thank them for their invaluable time and contributions!</p>
|
||||
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=krahets/hello-algo&max=300&columns=12" alt="Contributors" style="width: 100%; max-width: 38.5em;">
|
||||
</a>
|
||||
|
@ -89,9 +89,9 @@ void quickSortMedian(int nums[], int left, int right) {
|
||||
quickSortMedian(nums, pivot + 1, right);
|
||||
}
|
||||
|
||||
// 以下為尾遞迴最佳化的快速排序
|
||||
// 以下為遞迴深度最佳化的快速排序
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
void quickSortTailCall(int nums[], int left, int right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -127,10 +127,10 @@ int main() {
|
||||
printf("快速排序(中位基準數最佳化)完成後 nums = ");
|
||||
printArray(nums1, size);
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
int nums2[] = {2, 4, 1, 0, 3, 5};
|
||||
quickSortTailCall(nums2, 0, size - 1);
|
||||
printf("快速排序(尾遞迴最佳化)完成後 nums = ");
|
||||
printf("快速排序(遞迴深度最佳化)完成後 nums = ");
|
||||
printArray(nums1, size);
|
||||
|
||||
return 0;
|
||||
|
@ -84,7 +84,7 @@ class QuickSortMedian {
|
||||
}
|
||||
};
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
private:
|
||||
/* 哨兵劃分 */
|
||||
@ -103,7 +103,7 @@ class QuickSortTailCall {
|
||||
}
|
||||
|
||||
public:
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
static void quickSort(vector<int> &nums, int left, int right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -135,10 +135,10 @@ int main() {
|
||||
cout << "快速排序(中位基準數最佳化)完成後 nums = ";
|
||||
printVector(nums1);
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
vector<int> nums2 = {2, 4, 1, 0, 3, 5};
|
||||
QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1);
|
||||
cout << "快速排序(尾遞迴最佳化)完成後 nums = ";
|
||||
cout << "快速排序(遞迴深度最佳化)完成後 nums = ";
|
||||
printVector(nums2);
|
||||
|
||||
return 0;
|
||||
|
@ -89,7 +89,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交換 */
|
||||
static void Swap(int[] nums, int i, int j) {
|
||||
@ -111,7 +111,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
public static void QuickSort(int[] nums, int left, int right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -142,9 +142,9 @@ public class quick_sort {
|
||||
QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1);
|
||||
Console.WriteLine("快速排序(中位基準數最佳化)完成後 nums1 = " + string.Join(",", nums1));
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
int[] nums2 = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1);
|
||||
Console.WriteLine("快速排序(尾遞迴最佳化)完成後 nums2 = " + string.Join(",", nums2));
|
||||
Console.WriteLine("快速排序(遞迴深度最佳化)完成後 nums2 = " + string.Join(",", nums2));
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交換 */
|
||||
static void _swap(List<int> nums, int i, int j) {
|
||||
@ -108,7 +108,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
static void quickSort(List<int> nums, int left, int right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -138,8 +138,8 @@ void main() {
|
||||
QuickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
print("快速排序(中位基準數最佳化)完成後 nums1 = $nums1");
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
List<int> nums2 = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
print("快速排序(尾遞迴最佳化)完成後 nums2 = $nums2");
|
||||
print("快速排序(遞迴深度最佳化)完成後 nums2 = $nums2");
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type quickSort struct{}
|
||||
// 快速排序(中位基準數最佳化)
|
||||
type quickSortMedian struct{}
|
||||
|
||||
// 快速排序(尾遞迴最佳化)
|
||||
// 快速排序(遞迴深度最佳化)
|
||||
type quickSortTailCall struct{}
|
||||
|
||||
/* 哨兵劃分 */
|
||||
@ -112,7 +112,7 @@ func (q *quickSortTailCall) partition(nums []int, left, right int) int {
|
||||
return i // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化)*/
|
||||
/* 快速排序(遞迴深度最佳化)*/
|
||||
func (q *quickSortTailCall) quickSort(nums []int, left, right int) {
|
||||
// 子陣列長度為 1 時終止
|
||||
for left < right {
|
||||
|
@ -25,10 +25,10 @@ func TestQuickSortMedian(t *testing.T) {
|
||||
fmt.Println("快速排序(中位基準數最佳化)完成後 nums = ", nums)
|
||||
}
|
||||
|
||||
// 快速排序(尾遞迴最佳化)
|
||||
// 快速排序(遞迴深度最佳化)
|
||||
func TestQuickSortTailCall(t *testing.T) {
|
||||
q := quickSortTailCall{}
|
||||
nums := []int{4, 1, 3, 1, 5, 2}
|
||||
q.quickSort(nums, 0, len(nums)-1)
|
||||
fmt.Println("快速排序(尾遞迴最佳化)完成後 nums = ", nums)
|
||||
fmt.Println("快速排序(遞迴深度最佳化)完成後 nums = ", nums)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交換 */
|
||||
static void swap(int[] nums, int i, int j) {
|
||||
@ -120,7 +120,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
public static void quickSort(int[] nums, int left, int right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -150,9 +150,9 @@ public class quick_sort {
|
||||
QuickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
System.out.println("快速排序(中位基準數最佳化)完成後 nums1 = " + Arrays.toString(nums1));
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
int[] nums2 = { 2, 4, 1, 0, 3, 5 };
|
||||
QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
System.out.println("快速排序(尾遞迴最佳化)完成後 nums2 = " + Arrays.toString(nums2));
|
||||
System.out.println("快速排序(遞迴深度最佳化)完成後 nums2 = " + Arrays.toString(nums2));
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交換 */
|
||||
swap(nums, i, j) {
|
||||
@ -123,7 +123,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
quickSort(nums, left, right) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -154,8 +154,8 @@ const quickSortMedian = new QuickSortMedian();
|
||||
quickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1);
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
const nums2 = [2, 4, 1, 0, 3, 5];
|
||||
const quickSortTailCall = new QuickSortTailCall();
|
||||
quickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2);
|
||||
console.log('快速排序(遞迴深度最佳化)完成後 nums =', nums2);
|
||||
|
@ -83,7 +83,7 @@ fun quickSortMedian(nums: IntArray, left: Int, right: Int) {
|
||||
quickSort(nums, pivot + 1, right)
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
fun quickSortTailCall(nums: IntArray, left: Int, right: Int) {
|
||||
// 子陣列長度為 1 時終止
|
||||
var l = left
|
||||
@ -114,8 +114,8 @@ fun main() {
|
||||
quickSortMedian(nums1, 0, nums1.size - 1)
|
||||
println("快速排序(中位基準數最佳化)完成後 nums1 = ${nums1.contentToString()}")
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
val nums2 = intArrayOf(2, 4, 1, 0, 3, 5)
|
||||
quickSortTailCall(nums2, 0, nums2.size - 1)
|
||||
println("快速排序(尾遞迴最佳化)完成後 nums2 = ${nums2.contentToString()}")
|
||||
println("快速排序(遞迴深度最佳化)完成後 nums2 = ${nums2.contentToString()}")
|
||||
}
|
@ -79,7 +79,7 @@ class QuickSortMedian:
|
||||
|
||||
|
||||
class QuickSortTailCall:
|
||||
"""快速排序類別(尾遞迴最佳化)"""
|
||||
"""快速排序類別(遞迴深度最佳化)"""
|
||||
|
||||
def partition(self, nums: list[int], left: int, right: int) -> int:
|
||||
"""哨兵劃分"""
|
||||
@ -97,7 +97,7 @@ class QuickSortTailCall:
|
||||
return i # 返回基準數的索引
|
||||
|
||||
def quick_sort(self, nums: list[int], left: int, right: int):
|
||||
"""快速排序(尾遞迴最佳化)"""
|
||||
"""快速排序(遞迴深度最佳化)"""
|
||||
# 子陣列長度為 1 時終止
|
||||
while left < right:
|
||||
# 哨兵劃分操作
|
||||
@ -123,7 +123,7 @@ if __name__ == "__main__":
|
||||
QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1)
|
||||
print("快速排序(中位基準數最佳化)完成後 nums =", nums1)
|
||||
|
||||
# 快速排序(尾遞迴最佳化)
|
||||
# 快速排序(遞迴深度最佳化)
|
||||
nums2 = [2, 4, 1, 0, 3, 5]
|
||||
QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1)
|
||||
print("快速排序(尾遞迴最佳化)完成後 nums =", nums2)
|
||||
print("快速排序(遞迴深度最佳化)完成後 nums =", nums2)
|
||||
|
@ -92,7 +92,7 @@ class QuickSortMedian
|
||||
end
|
||||
end
|
||||
|
||||
### 快速排序類別(尾遞迴最佳化)###
|
||||
### 快速排序類別(遞迴深度最佳化)###
|
||||
class QuickSortTailCall
|
||||
class << self
|
||||
### 哨兵劃分 ###
|
||||
@ -115,7 +115,7 @@ class QuickSortTailCall
|
||||
i # 返回基準數的索引
|
||||
end
|
||||
|
||||
### 快速排序(尾遞迴最佳化)###
|
||||
### 快速排序(遞迴深度最佳化)###
|
||||
def quick_sort(nums, left, right)
|
||||
# 子陣列長度不為 1 時遞迴
|
||||
while left < right
|
||||
@ -146,8 +146,8 @@ if __FILE__ == $0
|
||||
QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1)
|
||||
puts "快速排序(中位基準數最佳化)完成後 nums1 = #{nums1}"
|
||||
|
||||
# 快速排序(尾遞迴最佳化)
|
||||
# 快速排序(遞迴深度最佳化)
|
||||
nums2 = [2, 4, 1, 0, 3, 5]
|
||||
QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1)
|
||||
puts "快速排序(尾遞迴最佳化)完成後 nums2 = #{nums2}"
|
||||
puts "快速排序(遞迴深度最佳化)完成後 nums2 = #{nums2}"
|
||||
end
|
||||
|
@ -6,18 +6,28 @@
|
||||
|
||||
use hello_algo_rust::include::print_util;
|
||||
|
||||
use std::collections::BinaryHeap;
|
||||
use std::{cmp::Reverse, collections::BinaryHeap};
|
||||
|
||||
fn test_push(heap: &mut BinaryHeap<i32>, val: i32, flag: i32) {
|
||||
heap.push(flag * val); // 元素入堆積
|
||||
fn test_push_max(heap: &mut BinaryHeap<i32>, val: i32) {
|
||||
heap.push(val); // 元素入堆積
|
||||
println!("\n元素 {} 入堆積後", val);
|
||||
print_util::print_heap(heap.iter().map(|&val| flag * val).collect());
|
||||
print_util::print_heap(heap.iter().map(|&val| val).collect());
|
||||
}
|
||||
fn test_push_min(heap: &mut BinaryHeap<Reverse<i32>>, val: i32) {
|
||||
heap.push(Reverse(val)); // 元素入堆積
|
||||
println!("\n元素 {} 入堆積後", val);
|
||||
print_util::print_heap(heap.iter().map(|&val| val.0).collect());
|
||||
}
|
||||
|
||||
fn test_pop(heap: &mut BinaryHeap<i32>, flag: i32) {
|
||||
fn test_pop_max(heap: &mut BinaryHeap<i32>) {
|
||||
let val = heap.pop().unwrap();
|
||||
println!("\n堆積頂元素 {} 出堆積後", flag * val);
|
||||
print_util::print_heap(heap.iter().map(|&val| flag * val).collect());
|
||||
println!("\n堆積頂元素 {} 出堆積後", val);
|
||||
print_util::print_heap(heap.iter().map(|&val| val).collect());
|
||||
}
|
||||
fn test_pop_min(heap: &mut BinaryHeap<Reverse<i32>>) {
|
||||
let val = heap.pop().unwrap().0;
|
||||
println!("\n堆積頂元素 {} 出堆積後", val);
|
||||
print_util::print_heap(heap.iter().map(|&val| val.0).collect());
|
||||
}
|
||||
|
||||
/* Driver Code */
|
||||
@ -26,31 +36,29 @@ fn main() {
|
||||
// 初始化小頂堆積
|
||||
#[allow(unused_assignments)]
|
||||
let mut min_heap = BinaryHeap::new();
|
||||
// Rust 的 BinaryHeap 是大頂堆積,當入列時將元素值乘以 -1 將其反轉,當出列時將元素值乘以 -1 將其還原
|
||||
let min_heap_flag = -1;
|
||||
// Rust 的 BinaryHeap 是大頂堆積,小頂堆積一般會“套上”Reverse
|
||||
// 初始化大頂堆積
|
||||
let mut max_heap = BinaryHeap::new();
|
||||
let max_heap_flag = 1;
|
||||
|
||||
println!("\n以下測試樣例為大頂堆積");
|
||||
|
||||
/* 元素入堆積 */
|
||||
test_push(&mut max_heap, 1, max_heap_flag);
|
||||
test_push(&mut max_heap, 3, max_heap_flag);
|
||||
test_push(&mut max_heap, 2, max_heap_flag);
|
||||
test_push(&mut max_heap, 5, max_heap_flag);
|
||||
test_push(&mut max_heap, 4, max_heap_flag);
|
||||
test_push_max(&mut max_heap, 1);
|
||||
test_push_max(&mut max_heap, 3);
|
||||
test_push_max(&mut max_heap, 2);
|
||||
test_push_max(&mut max_heap, 5);
|
||||
test_push_max(&mut max_heap, 4);
|
||||
|
||||
/* 獲取堆積頂元素 */
|
||||
let peek = max_heap.peek().unwrap() * max_heap_flag;
|
||||
let peek = max_heap.peek().unwrap();
|
||||
println!("\n堆積頂元素為 {}", peek);
|
||||
|
||||
/* 堆積頂元素出堆積 */
|
||||
test_pop(&mut max_heap, max_heap_flag);
|
||||
test_pop(&mut max_heap, max_heap_flag);
|
||||
test_pop(&mut max_heap, max_heap_flag);
|
||||
test_pop(&mut max_heap, max_heap_flag);
|
||||
test_pop(&mut max_heap, max_heap_flag);
|
||||
test_pop_max(&mut max_heap);
|
||||
test_pop_max(&mut max_heap);
|
||||
test_pop_max(&mut max_heap);
|
||||
test_pop_max(&mut max_heap);
|
||||
test_pop_max(&mut max_heap);
|
||||
|
||||
/* 獲取堆積大小 */
|
||||
let size = max_heap.len();
|
||||
@ -65,9 +73,9 @@ fn main() {
|
||||
min_heap = BinaryHeap::from(
|
||||
vec![1, 3, 2, 5, 4]
|
||||
.into_iter()
|
||||
.map(|val| min_heap_flag * val)
|
||||
.collect::<Vec<i32>>(),
|
||||
.map(|val| Reverse(val))
|
||||
.collect::<Vec<Reverse<i32>>>(),
|
||||
);
|
||||
println!("\n輸入串列並建立小頂堆積後");
|
||||
print_util::print_heap(min_heap.iter().map(|&val| min_heap_flag * val).collect());
|
||||
print_util::print_heap(min_heap.iter().map(|&val| val.0).collect());
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ impl QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
struct QuickSortTailCall;
|
||||
|
||||
impl QuickSortTailCall {
|
||||
@ -111,7 +111,7 @@ impl QuickSortTailCall {
|
||||
i // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {
|
||||
// 子陣列長度為 1 時終止
|
||||
while left < right {
|
||||
@ -141,8 +141,8 @@ fn main() {
|
||||
QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums);
|
||||
println!("快速排序(中位基準數最佳化)完成後 nums = {:?}", nums);
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
let mut nums = [2, 4, 1, 0, 3, 5];
|
||||
QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums);
|
||||
println!("快速排序(尾遞迴最佳化)完成後 nums = {:?}", nums);
|
||||
println!("快速排序(遞迴深度最佳化)完成後 nums = {:?}", nums);
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func quickSortMedian(nums: inout [Int], left: Int, right: Int) {
|
||||
quickSortMedian(nums: &nums, left: pivot + 1, right: right)
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
func quickSortTailCall(nums: inout [Int], left: Int, right: Int) {
|
||||
var left = left
|
||||
var right = right
|
||||
@ -106,9 +106,9 @@ enum QuickSort {
|
||||
quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1)
|
||||
print("快速排序(中位基準數最佳化)完成後 nums1 = \(nums1)")
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
var nums2 = [2, 4, 1, 0, 3, 5]
|
||||
quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1)
|
||||
print("快速排序(尾遞迴最佳化)完成後 nums2 = \(nums2)")
|
||||
print("快速排序(遞迴深度最佳化)完成後 nums2 = \(nums2)")
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class QuickSortMedian {
|
||||
}
|
||||
}
|
||||
|
||||
/* 快速排序類別(尾遞迴最佳化) */
|
||||
/* 快速排序類別(遞迴深度最佳化) */
|
||||
class QuickSortTailCall {
|
||||
/* 元素交換 */
|
||||
swap(nums: number[], i: number, j: number): void {
|
||||
@ -140,7 +140,7 @@ class QuickSortTailCall {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
quickSort(nums: number[], left: number, right: number): void {
|
||||
// 子陣列長度為 1 時終止
|
||||
while (left < right) {
|
||||
@ -171,10 +171,10 @@ const quickSortMedian = new QuickSortMedian();
|
||||
quickSortMedian.quickSort(nums1, 0, nums1.length - 1);
|
||||
console.log('快速排序(中位基準數最佳化)完成後 nums =', nums1);
|
||||
|
||||
/* 快速排序(尾遞迴最佳化) */
|
||||
/* 快速排序(遞迴深度最佳化) */
|
||||
const nums2 = [2, 4, 1, 0, 3, 5];
|
||||
const quickSortTailCall = new QuickSortTailCall();
|
||||
quickSortTailCall.quickSort(nums2, 0, nums2.length - 1);
|
||||
console.log('快速排序(尾遞迴最佳化)完成後 nums =', nums2);
|
||||
console.log('快速排序(遞迴深度最佳化)完成後 nums =', nums2);
|
||||
|
||||
export {};
|
||||
|
@ -94,7 +94,7 @@ const QuickSortMedian = struct {
|
||||
}
|
||||
};
|
||||
|
||||
// 快速排序類別(尾遞迴最佳化)
|
||||
// 快速排序類別(遞迴深度最佳化)
|
||||
const QuickSortTailCall = struct {
|
||||
|
||||
// 元素交換
|
||||
@ -118,7 +118,7 @@ const QuickSortTailCall = struct {
|
||||
return i; // 返回基準數的索引
|
||||
}
|
||||
|
||||
// 快速排序(尾遞迴最佳化)
|
||||
// 快速排序(遞迴深度最佳化)
|
||||
pub fn quickSort(nums: []i32, left_: usize, right_: usize) void {
|
||||
var left = left_;
|
||||
var right = right_;
|
||||
@ -152,10 +152,10 @@ pub fn main() !void {
|
||||
std.debug.print("\n快速排序(中位基準數最佳化)完成後 nums = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &nums1);
|
||||
|
||||
// 快速排序(尾遞迴最佳化)
|
||||
// 快速排序(遞迴深度最佳化)
|
||||
var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 };
|
||||
QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1);
|
||||
std.debug.print("\n快速排序(尾遞迴最佳化)完成後 nums = ", .{});
|
||||
std.debug.print("\n快速排序(遞迴深度最佳化)完成後 nums = ", .{});
|
||||
inc.PrintUtil.printArray(i32, &nums2);
|
||||
|
||||
_ = try std.io.getStdIn().reader().readByte();
|
||||
|
@ -1026,7 +1026,7 @@
|
||||
為了加深對串列工作原理的理解,我們嘗試實現一個簡易版串列,包括以下三個重點設計。
|
||||
|
||||
- **初始容量**:選取一個合理的陣列初始容量。在本示例中,我們選擇 10 作為初始容量。
|
||||
- **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除實時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。
|
||||
- **數量記錄**:宣告一個變數 `size` ,用於記錄串列當前元素數量,並隨著元素插入和刪除即時更新。根據此變數,我們可以定位串列尾部,以及判斷是否需要擴容。
|
||||
- **擴容機制**:若插入元素時串列容量已滿,則需要進行擴容。先根據擴容倍數建立一個更大的陣列,再將當前陣列的所有元素依次移動至新陣列。在本示例中,我們規定每次將陣列擴容至之前的 2 倍。
|
||||
|
||||
```src
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
### 參考全排列解法
|
||||
|
||||
類似於全排列問題,我們可以把子集的生成過程想象成一系列選擇的結果,並在選擇過程中實時更新“元素和”,當元素和等於 `target` 時,就將子集記錄至結果串列。
|
||||
類似於全排列問題,我們可以把子集的生成過程想象成一系列選擇的結果,並在選擇過程中即時更新“元素和”,當元素和等於 `target` 時,就將子集記錄至結果串列。
|
||||
|
||||
而與全排列問題不同的是,**本題集合中的元素可以被無限次選取**,因此無須藉助 `selected` 布林串列來記錄元素是否已被選擇。我們可以對全排列程式碼進行小幅修改,初步得到解題程式碼:
|
||||
|
||||
|
@ -1,34 +1,34 @@
|
||||
# 字元編碼 *
|
||||
|
||||
在計算機中,所有資料都是以二進位制數的形式儲存的,字元 `char` 也不例外。為了表示字元,我們需要建立一套“字符集”,規定每個字元和二進位制數之間的一一對應關係。有了字符集之後,計算機就可以透過查表完成二進位制數到字元的轉換。
|
||||
在計算機中,所有資料都是以二進位制數的形式儲存的,字元 `char` 也不例外。為了表示字元,我們需要建立一套“字元集”,規定每個字元和二進位制數之間的一一對應關係。有了字元集之後,計算機就可以透過查表完成二進位制數到字元的轉換。
|
||||
|
||||
## ASCII 字符集
|
||||
## ASCII 字元集
|
||||
|
||||
<u>ASCII 碼</u>是最早出現的字符集,其全稱為 American Standard Code for Information Interchange(美國標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個不同的字元。如下圖所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字元(如換行符和製表符)。
|
||||
<u>ASCII 碼</u>是最早出現的字元集,其全稱為 American Standard Code for Information Interchange(美國標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個不同的字元。如下圖所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字元(如換行符和製表符)。
|
||||
|
||||

|
||||
|
||||
然而,**ASCII 碼僅能夠表示英文**。隨著計算機的全球化,誕生了一種能夠表示更多語言的 <u>EASCII</u> 字符集。它在 ASCII 的 7 位基礎上擴展到 8 位,能夠表示 256 個不同的字元。
|
||||
然而,**ASCII 碼僅能夠表示英文**。隨著計算機的全球化,誕生了一種能夠表示更多語言的 <u>EASCII</u> 字元集。它在 ASCII 的 7 位基礎上擴展到 8 位,能夠表示 256 個不同的字元。
|
||||
|
||||
在世界範圍內,陸續出現了一批適用於不同地區的 EASCII 字符集。這些字符集的前 128 個字元統一為 ASCII 碼,後 128 個字元定義不同,以適應不同語言的需求。
|
||||
在世界範圍內,陸續出現了一批適用於不同地區的 EASCII 字元集。這些字元集的前 128 個字元統一為 ASCII 碼,後 128 個字元定義不同,以適應不同語言的需求。
|
||||
|
||||
## GBK 字符集
|
||||
## GBK 字元集
|
||||
|
||||
後來人們發現,**EASCII 碼仍然無法滿足許多語言的字元數量要求**。比如漢字有近十萬個,光日常使用的就有幾千個。中國國家標準總局於 1980 年釋出了 <u>GB2312</u> 字符集,其收錄了 6763 個漢字,基本滿足了漢字的計算機處理需要。
|
||||
後來人們發現,**EASCII 碼仍然無法滿足許多語言的字元數量要求**。比如漢字有近十萬個,光日常使用的就有幾千個。中國國家標準總局於 1980 年釋出了 <u>GB2312</u> 字元集,其收錄了 6763 個漢字,基本滿足了漢字的計算機處理需要。
|
||||
|
||||
然而,GB2312 無法處理部分罕見字和繁體字。<u>GBK</u> 字符集是在 GB2312 的基礎上擴展得到的,它共收錄了 21886 個漢字。在 GBK 的編碼方案中,ASCII 字元使用一個位元組表示,漢字使用兩個位元組表示。
|
||||
然而,GB2312 無法處理部分罕見字和繁體字。<u>GBK</u> 字元集是在 GB2312 的基礎上擴展得到的,它共收錄了 21886 個漢字。在 GBK 的編碼方案中,ASCII 字元使用一個位元組表示,漢字使用兩個位元組表示。
|
||||
|
||||
## Unicode 字符集
|
||||
## Unicode 字元集
|
||||
|
||||
隨著計算機技術的蓬勃發展,字符集與編碼標準百花齊放,而這帶來了許多問題。一方面,這些字符集一般只定義了特定語言的字元,無法在多語言環境下正常工作。另一方面,同一種語言存在多種字符集標準,如果兩臺計算機使用的是不同的編碼標準,則在資訊傳遞時就會出現亂碼。
|
||||
隨著計算機技術的蓬勃發展,字元集與編碼標準百花齊放,而這帶來了許多問題。一方面,這些字元集一般只定義了特定語言的字元,無法在多語言環境下正常工作。另一方面,同一種語言存在多種字元集標準,如果兩臺計算機使用的是不同的編碼標準,則在資訊傳遞時就會出現亂碼。
|
||||
|
||||
那個時代的研究人員就在想:**如果推出一個足夠完整的字符集,將世界範圍內的所有語言和符號都收錄其中,不就可以解決跨語言環境和亂碼問題了嗎**?在這種想法的驅動下,一個大而全的字符集 Unicode 應運而生。
|
||||
那個時代的研究人員就在想:**如果推出一個足夠完整的字元集,將世界範圍內的所有語言和符號都收錄其中,不就可以解決跨語言環境和亂碼問題了嗎**?在這種想法的驅動下,一個大而全的字元集 Unicode 應運而生。
|
||||
|
||||
<u>Unicode</u> 的中文名稱為“統一碼”,理論上能容納 100 多萬個字元。它致力於將全球範圍內的字元納入統一的字符集之中,提供一種通用的字符集來處理和顯示各種語言文字,減少因為編碼標準不同而產生的亂碼問題。
|
||||
<u>Unicode</u> 的中文名稱為“統一碼”,理論上能容納 100 多萬個字元。它致力於將全球範圍內的字元納入統一的字元集之中,提供一種通用的字元集來處理和顯示各種語言文字,減少因為編碼標準不同而產生的亂碼問題。
|
||||
|
||||
自 1991 年釋出以來,Unicode 不斷擴充新的語言與字元。截至 2022 年 9 月,Unicode 已經包含 149186 個字元,包括各種語言的字元、符號甚至表情符號等。在龐大的 Unicode 字符集中,常用的字元佔用 2 位元組,有些生僻的字元佔用 3 位元組甚至 4 位元組。
|
||||
自 1991 年釋出以來,Unicode 不斷擴充新的語言與字元。截至 2022 年 9 月,Unicode 已經包含 149186 個字元,包括各種語言的字元、符號甚至表情符號等。在龐大的 Unicode 字元集中,常用的字元佔用 2 位元組,有些生僻的字元佔用 3 位元組甚至 4 位元組。
|
||||
|
||||
Unicode 是一種通用字符集,本質上是給每個字元分配一個編號(稱為“碼點”),**但它並沒有規定在計算機中如何儲存這些字元碼點**。我們不禁會問:當多種長度的 Unicode 碼點同時出現在一個文字中時,系統如何解析字元?例如給定一個長度為 2 位元組的編碼,系統如何確認它是一個 2 位元組的字元還是兩個 1 位元組的字元?
|
||||
Unicode 是一種通用字元集,本質上是給每個字元分配一個編號(稱為“碼點”),**但它並沒有規定在計算機中如何儲存這些字元碼點**。我們不禁會問:當多種長度的 Unicode 碼點同時出現在一個文字中時,系統如何解析字元?例如給定一個長度為 2 位元組的編碼,系統如何確認它是一個 2 位元組的字元還是兩個 1 位元組的字元?
|
||||
|
||||
對於以上問題,**一種直接的解決方案是將所有字元儲存為等長的編碼**。如下圖所示,“Hello”中的每個字元佔用 1 位元組,“演算法”中的每個字元佔用 2 位元組。我們可以透過高位填 0 將“Hello 演算法”中的所有字元都編碼為 2 位元組長度。這樣系統就可以每隔 2 位元組解析一個字元,恢復這個短語的內容了。
|
||||
|
||||
@ -42,7 +42,7 @@ Unicode 是一種通用字符集,本質上是給每個字元分配一個編號
|
||||
|
||||
UTF-8 的編碼規則並不複雜,分為以下兩種情況。
|
||||
|
||||
- 對於長度為 1 位元組的字元,將最高位設定為 $0$ ,其餘 7 位設定為 Unicode 碼點。值得注意的是,ASCII 字元在 Unicode 字符集中佔據了前 128 個碼點。也就是說,**UTF-8 編碼可以向下相容 ASCII 碼**。這意味著我們可以使用 UTF-8 來解析年代久遠的 ASCII 碼文字。
|
||||
- 對於長度為 1 位元組的字元,將最高位設定為 $0$ ,其餘 7 位設定為 Unicode 碼點。值得注意的是,ASCII 字元在 Unicode 字元集中佔據了前 128 個碼點。也就是說,**UTF-8 編碼可以向下相容 ASCII 碼**。這意味著我們可以使用 UTF-8 來解析年代久遠的 ASCII 碼文字。
|
||||
- 對於長度為 $n$ 位元組的字元(其中 $n > 1$),將首個位元組的高 $n$ 位都設定為 $1$ ,第 $n + 1$ 位設定為 $0$ ;從第二個位元組開始,將每個位元組的高 2 位都設定為 $10$ ;其餘所有位用於填充字元的 Unicode 碼點。
|
||||
|
||||
下圖展示了“Hello演算法”對應的 UTF-8 編碼。觀察發現,由於最高 $n$ 位都設定為 $1$ ,因此系統可以透過讀取最高位 $1$ 的個數來解析出字元的長度為 $n$ 。
|
||||
|
@ -10,7 +10,7 @@
|
||||
- 原碼、一補數和二補數是在計算機中編碼數字的三種方法,它們之間可以相互轉換。整數的原碼的最高位是符號位,其餘位是數字的值。
|
||||
- 整數在計算機中是以二補數的形式儲存的。在二補數表示下,計算機可以對正數和負數的加法一視同仁,不需要為減法操作單獨設計特殊的硬體電路,並且不存在正負零歧義的問題。
|
||||
- 浮點數的編碼由 1 位符號位、8 位指數位和 23 位分數位構成。由於存在指數位,因此浮點數的取值範圍遠大於整數,代價是犧牲了精度。
|
||||
- ASCII 碼是最早出現的英文字符集,長度為 1 位元組,共收錄 127 個字元。GBK 字符集是常用的中文字符集,共收錄兩萬多個漢字。Unicode 致力於提供一個完整的字符集標準,收錄世界上各種語言的字元,從而解決由於字元編碼方法不一致而導致的亂碼問題。
|
||||
- ASCII 碼是最早出現的英文字元集,長度為 1 位元組,共收錄 127 個字元。GBK 字元集是常用的中文字元集,共收錄兩萬多個漢字。Unicode 致力於提供一個完整的字元集標準,收錄世界上各種語言的字元,從而解決由於字元編碼方法不一致而導致的亂碼問題。
|
||||
- UTF-8 是最受歡迎的 Unicode 編碼方法,通用性非常好。它是一種變長的編碼方法,具有很好的擴展性,有效提升了儲存空間的使用效率。UTF-16 和 UTF-32 是等長的編碼方法。在編碼中文時,UTF-16 佔用的空間比 UTF-8 更小。Java 和 C# 等程式語言預設使用 UTF-16 編碼。
|
||||
|
||||
### Q & A
|
||||
|
@ -78,8 +78,8 @@ $$
|
||||
|
||||
### 正確性證明
|
||||
|
||||
使用反證法,只分析 $n \geq 3$ 的情況。
|
||||
使用反證法,只分析 $n \geq 4$ 的情況。
|
||||
|
||||
1. **所有因子 $\leq 3$** :假設最優切分方案中存在 $\geq 4$ 的因子 $x$ ,那麼一定可以將其繼續劃分為 $2(x-2)$ ,從而獲得更大的乘積。這與假設矛盾。
|
||||
1. **所有因子 $\leq 3$** :假設最優切分方案中存在 $\geq 4$ 的因子 $x$ ,那麼一定可以將其繼續劃分為 $2(x-2)$ ,從而獲得更大(或相等)的乘積。這與假設矛盾。
|
||||
2. **切分方案不包含 $1$** :假設最優切分方案中存在一個因子 $1$ ,那麼它一定可以合併入另外一個因子中,以獲得更大的乘積。這與假設矛盾。
|
||||
3. **切分方案最多包含兩個 $2$** :假設最優切分方案中包含三個 $2$ ,那麼一定可以替換為兩個 $3$ ,乘積更大。這與假設矛盾。
|
||||
|
@ -89,7 +89,7 @@
|
||||
[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition}
|
||||
```
|
||||
|
||||
## 尾遞迴最佳化
|
||||
## 遞迴深度最佳化
|
||||
|
||||
**在某些輸入下,快速排序可能佔用空間較多**。以完全有序的輸入陣列為例,設遞迴中的子陣列長度為 $m$ ,每輪哨兵劃分操作都將產生長度為 $0$ 的左子陣列和長度為 $m - 1$ 的右子陣列,這意味著每一層遞迴呼叫減少的問題規模非常小(只減少一個元素),遞迴樹的高度會達到 $n - 1$ ,此時需要佔用 $O(n)$ 大小的堆疊幀空間。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
- 泡沫排序透過交換相鄰元素來實現排序。透過新增一個標誌位來實現提前返回,我們可以將泡沫排序的最佳時間複雜度最佳化到 $O(n)$ 。
|
||||
- 插入排序每輪將未排序區間內的元素插入到已排序區間的正確位置,從而完成排序。雖然插入排序的時間複雜度為 $O(n^2)$ ,但由於單元操作相對較少,因此在小資料量的排序任務中非常受歡迎。
|
||||
- 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。尾遞迴方法可以有效地減少遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。
|
||||
- 快速排序基於哨兵劃分操作實現排序。在哨兵劃分中,有可能每次都選取到最差的基準數,導致時間複雜度劣化至 $O(n^2)$ 。引入中位數基準數或隨機基準數可以降低這種劣化的機率。透過優先遞迴較短子區間,可有效減小遞迴深度,將空間複雜度最佳化到 $O(\log n)$ 。
|
||||
- 合併排序包括劃分和合並兩個階段,典型地體現了分治策略。在合併排序中,排序陣列需要建立輔助陣列,空間複雜度為 $O(n)$ ;然而排序鏈結串列的空間複雜度可以最佳化至 $O(1)$ 。
|
||||
- 桶排序包含三個步驟:資料分桶、桶內排序和合並結果。它同樣體現了分治策略,適用於資料體量很大的情況。桶排序的關鍵在於對資料進行平均分配。
|
||||
- 計數排序是桶排序的一個特例,它透過統計資料出現的次數來實現排序。計數排序適用於資料量大但資料範圍有限的情況,並且要求資料能夠轉換為正整數。
|
||||
@ -32,11 +32,11 @@
|
||||
|
||||
再深入思考一下,如果我們選擇 `nums[right]` 為基準數,那麼正好反過來,必須先“從左往右查詢”。
|
||||
|
||||
**Q**:關於尾遞迴最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ?
|
||||
**Q**:關於快速排序的遞迴深度最佳化,為什麼選短的陣列能保證遞迴深度不超過 $\log n$ ?
|
||||
|
||||
遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在尾遞迴最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。
|
||||
遞迴深度就是當前未返回的遞迴方法的數量。每輪哨兵劃分我們將原陣列劃分為兩個子陣列。在遞迴深度最佳化後,向下遞迴的子陣列長度最大為原陣列長度的一半。假設最差情況,一直為一半長度,那麼最終的遞迴深度就是 $\log n$ 。
|
||||
|
||||
回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。尾遞迴最佳化可以避免這種情況出現。
|
||||
回顧原始的快速排序,我們有可能會連續地遞迴長度較大的陣列,最差情況下為 $n$、$n - 1$、$\dots$、$2$、$1$ ,遞迴深度為 $n$ 。遞迴深度最佳化可以避免這種情況出現。
|
||||
|
||||
**Q**:當陣列中所有元素都相等時,快速排序的時間複雜度是 $O(n^2)$ 嗎?該如何處理這種退化情況?
|
||||
|
||||
|
@ -366,7 +366,7 @@
|
||||
<!-- contributors -->
|
||||
<div style="margin: 2em auto;">
|
||||
<h3>貢獻者</h3>
|
||||
<p>本書在開源社群一百多位貢獻者的共同努力下不斷完善,感謝他們付出的時間與精力!</p>
|
||||
<p>本書在開源社群 200 多位貢獻者的共同努力下不斷完善,感謝他們付出的時間與精力!</p>
|
||||
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=krahets/hello-algo&max=300&columns=12" alt="Contributors" style="width: 100%; max-width: 38.5em;">
|
||||
</a>
|
||||
|
Reference in New Issue
Block a user