mirror of
				https://github.com/krahets/hello-algo.git
				synced 2025-10-30 18:05:58 +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 | ||||
|  | ||||
| @ -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
	 Yudong Jin
					Yudong Jin