This commit is contained in:
krahets
2024-05-06 14:40:36 +08:00
parent 7e7eb6047a
commit 5c7d2c7f17
54 changed files with 3456 additions and 215 deletions
+32 -2
View File
@@ -64,7 +64,20 @@ Example code is as follows:
=== "C++"
```cpp title="bubble_sort.cpp"
[class]{}-[func]{bubbleSort}
/* Bubble sort */
void bubbleSort(vector<int> &nums) {
// Outer loop: unsorted range is [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// Swap nums[j] and nums[j + 1]
// Here, the std
swap(nums[j], nums[j + 1]);
}
}
}
}
```
=== "Java"
@@ -181,7 +194,24 @@ Even after optimization, the worst-case time complexity and average time complex
=== "C++"
```cpp title="bubble_sort.cpp"
[class]{}-[func]{bubbleSortWithFlag}
/* Bubble sort (optimized with flag)*/
void bubbleSortWithFlag(vector<int> &nums) {
// Outer loop: unsorted range is [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
bool flag = false; // Initialize flag
// Inner loop: swap the largest element in the unsorted range [0, i] to the right end of the range
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// Swap nums[j] and nums[j + 1]
// Here, the std
swap(nums[j], nums[j + 1]);
flag = true; // Record swapped elements
}
}
if (!flag)
break; // If no elements were swapped in this round of "bubbling", exit
}
}
```
=== "Java"
+25 -1
View File
@@ -51,7 +51,31 @@ The code is shown as follows:
=== "C++"
```cpp title="bucket_sort.cpp"
[class]{}-[func]{bucketSort}
/* Bucket sort */
void bucketSort(vector<float> &nums) {
// Initialize k = n/2 buckets, expected to allocate 2 elements per bucket
int k = nums.size() / 2;
vector<vector<float>> buckets(k);
// 1. Distribute array elements into various buckets
for (float num : nums) {
// Input data range is [0, 1), use num * k to map to index range [0, k-1]
int i = num * k;
// Add number to bucket_idx
buckets[i].push_back(num);
}
// 2. Sort each bucket
for (vector<float> &bucket : buckets) {
// Use built-in sorting function, can also replace with other sorting algorithms
sort(bucket.begin(), bucket.end());
}
// 3. Traverse buckets to merge results
int i = 0;
for (vector<float> &bucket : buckets) {
for (float num : bucket) {
nums[i++] = num;
}
}
}
```
=== "Java"
+55 -4
View File
@@ -8,7 +8,7 @@ comments: true
## 11.9.1 &nbsp; Simple implementation
Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is illustrated in the following diagram.
Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is illustrated in Figure 11-16.
1. Traverse the array to find the maximum number, denoted as $m$, then create an auxiliary array `counter` of length $m + 1$.
2. **Use `counter` to count the occurrence of each number in `nums`**, where `counter[num]` corresponds to the occurrence of the number `num`. The counting method is simple, just traverse `nums` (suppose the current number is `num`), and increase `counter[num]` by $1$ each round.
@@ -46,7 +46,28 @@ The code is shown below:
=== "C++"
```cpp title="counting_sort.cpp"
[class]{}-[func]{countingSortNaive}
/* Counting sort */
// Simple implementation, cannot be used for sorting objects
void countingSortNaive(vector<int> &nums) {
// 1. Count the maximum element m in the array
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. Count the occurrence of each digit
// counter[num] represents the occurrence of num
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. Traverse counter, filling each element back into the original array nums
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
```
=== "Java"
@@ -161,7 +182,7 @@ $$
1. Fill `num` into the array `res` at the index `prefix[num] - 1`.
2. Reduce the prefix sum `prefix[num]` by $1$, thus obtaining the next index to place `num`.
After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in the figures below.
After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in Figure 11-17.
=== "<1>"
![Counting sort process](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
@@ -224,7 +245,37 @@ The implementation code of counting sort is shown below:
=== "C++"
```cpp title="counting_sort.cpp"
[class]{}-[func]{countingSort}
/* Counting sort */
// Complete implementation, can sort objects and is a stable sort
void countingSort(vector<int> &nums) {
// 1. Count the maximum element m in the array
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. Count the occurrence of each digit
// counter[num] represents the occurrence of num
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. Calculate the prefix sum of counter, converting "occurrence count" to "tail index"
// counter[num]-1 is the last index where num appears in res
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. Traverse nums in reverse order, placing each element into the result array res
// Initialize the array res to record results
int n = nums.size();
vector<int> res(n);
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // Place num at the corresponding index
counter[num]--; // Decrement the prefix sum by 1, getting the next index to place num
}
// Use result array res to overwrite the original array nums
nums = res;
}
```
=== "Java"
+35 -2
View File
@@ -106,9 +106,42 @@ In the code implementation, we used the sift-down function `sift_down()` from th
=== "C++"
```cpp title="heap_sort.cpp"
[class]{}-[func]{siftDown}
/* Heap length is n, start heapifying node i, from top to bottom */
void siftDown(vector<int> &nums, int n, int i) {
while (true) {
// Determine the largest node among i, l, r, noted as ma
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// If node i is the largest or indices l, r are out of bounds, no further heapification needed, break
if (ma == i) {
break;
}
// Swap two nodes
swap(nums[i], nums[ma]);
// Loop downwards heapification
i = ma;
}
}
[class]{}-[func]{heapSort}
/* Heap sort */
void heapSort(vector<int> &nums) {
// Build heap operation: heapify all nodes except leaves
for (int i = nums.size() / 2 - 1; i >= 0; --i) {
siftDown(nums, nums.size(), i);
}
// Extract the largest element from the heap and repeat for n-1 rounds
for (int i = nums.size() - 1; i > 0; --i) {
// Swap the root node with the rightmost leaf node (swap the first element with the last element)
swap(nums[0], nums[i]);
// Start heapifying the root node, from top to bottom
siftDown(nums, i, 0);
}
}
```
=== "Java"
+13 -1
View File
@@ -48,7 +48,19 @@ Example code is as follows:
=== "C++"
```cpp title="insertion_sort.cpp"
[class]{}-[func]{insertionSort}
/* Insertion sort */
void insertionSort(vector<int> &nums) {
// Outer loop: sorted range is [0, i-1]
for (int i = 1; i < nums.size(); i++) {
int base = nums[i], j = i - 1;
// Inner loop: insert base into the correct position within the sorted range [0, i-1]
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // Move nums[j] to the right by one position
j--;
}
nums[j + 1] = base; // Assign base to the correct position
}
}
```
=== "Java"
+38 -2
View File
@@ -109,9 +109,45 @@ The implementation of merge sort is shown in the following code. Note that the i
=== "C++"
```cpp title="merge_sort.cpp"
[class]{}-[func]{merge}
/* Merge left subarray and right subarray */
void merge(vector<int> &nums, int left, int mid, int right) {
// Left subarray interval is [left, mid], right subarray interval is [mid+1, right]
// Create a temporary array tmp to store the merged results
vector<int> tmp(right - left + 1);
// Initialize the start indices of the left and right subarrays
int i = left, j = mid + 1, k = 0;
// While both subarrays still have elements, compare and copy the smaller element into the temporary array
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// Copy the remaining elements of the left and right subarrays into the temporary array
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// Copy the elements from the temporary array tmp back to the original array nums at the corresponding interval
for (k = 0; k < tmp.size(); k++) {
nums[left + k] = tmp[k];
}
}
[class]{}-[func]{mergeSort}
/* Merge sort */
void mergeSort(vector<int> &nums, int left, int right) {
// Termination condition
if (left >= right)
return; // Terminate recursion when subarray length is 1
// Partition stage
int mid = (left + right) / 2; // Calculate midpoint
mergeSort(nums, left, mid); // Recursively process the left subarray
mergeSort(nums, mid + 1, right); // Recursively process the right subarray
// Merge stage
merge(nums, left, mid, right);
}
```
=== "Java"
+75 -7
View File
@@ -6,7 +6,7 @@ comments: true
<u>Quick sort</u> is a sorting algorithm based on the divide and conquer strategy, known for its efficiency and wide application.
The core operation of quick sort is "pivot partitioning," aiming to: select an element from the array as the "pivot," move all elements smaller than the pivot to its left, and move elements greater than the pivot to its right. Specifically, the pivot partitioning process is illustrated as follows.
The core operation of quick sort is "pivot partitioning," aiming to: select an element from the array as the "pivot," move all elements smaller than the pivot to its left, and move elements greater than the pivot to its right. Specifically, the pivot partitioning process is illustrated in Figure 11-8.
1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` at both ends of the array.
2. Set up a loop where each round uses `i` (`j`) to find the first element larger (smaller) than the pivot, then swap these two elements.
@@ -69,9 +69,27 @@ After the pivot partitioning, the original array is divided into three parts: le
=== "C++"
```cpp title="quick_sort.cpp"
[class]{QuickSort}-[func]{swap}
/* Swap elements */
void swap(vector<int> &nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
[class]{QuickSort}-[func]{partition}
/* Partition */
int partition(vector<int> &nums, int left, int right) {
// Use nums[left] as the pivot
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // Search from right to left for the first element smaller than the pivot
while (i < j && nums[i] <= nums[left])
i++; // Search from left to right for the first element greater than the pivot
swap(nums, i, j); // Swap these two elements
}
swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays
return i; // Return the index of the pivot
}
```
=== "Java"
@@ -210,7 +228,17 @@ The overall process of quick sort is shown in Figure 11-9.
=== "C++"
```cpp title="quick_sort.cpp"
[class]{QuickSort}-[func]{quickSort}
/* Quick sort */
void quickSort(vector<int> &nums, int left, int right) {
// Terminate recursion when subarray length is 1
if (left >= right)
return;
// Partition
int pivot = partition(nums, left, right);
// Recursively process the left subarray and right subarray
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}
```
=== "Java"
@@ -356,9 +384,34 @@ Sample code is as follows:
=== "C++"
```cpp title="quick_sort.cpp"
[class]{QuickSortMedian}-[func]{medianThree}
/* Select the median of three candidate elements */
int medianThree(vector<int> &nums, int left, int mid, int right) {
int l = nums[left], m = nums[mid], r = nums[right];
if ((l <= m && m <= r) || (r <= m && m <= l))
return mid; // m is between l and r
if ((m <= l && l <= r) || (r <= l && l <= m))
return left; // l is between m and r
return right;
}
[class]{QuickSortMedian}-[func]{partition}
/* Partition (median of three) */
int partition(vector<int> &nums, int left, int right) {
// Select the median of three candidate elements
int med = medianThree(nums, left, (left + right) / 2, right);
// Swap the median to the array's leftmost position
swap(nums, left, med);
// Use nums[left] as the pivot
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // Search from right to left for the first element smaller than the pivot
while (i < j && nums[i] <= nums[left])
i++; // Search from left to right for the first element greater than the pivot
swap(nums, i, j); // Swap these two elements
}
swap(nums, i, left); // Swap the pivot to the boundary between the two subarrays
return i; // Return the index of the pivot
}
```
=== "Java"
@@ -509,7 +562,22 @@ To prevent the accumulation of stack frame space, we can compare the lengths of
=== "C++"
```cpp title="quick_sort.cpp"
[class]{QuickSortTailCall}-[func]{quickSort}
/* Quick sort (tail recursion optimization) */
void quickSort(vector<int> &nums, int left, int right) {
// Terminate when subarray length is 1
while (left < right) {
// Partition operation
int pivot = partition(nums, left, right);
// Perform quick sort on the shorter of the two subarrays
if (pivot - left < right - pivot) {
quickSort(nums, left, pivot - 1); // Recursively sort the left subarray
left = pivot + 1; // Remaining unsorted interval is [pivot + 1, right]
} else {
quickSort(nums, pivot + 1, right); // Recursively sort the right subarray
right = pivot - 1; // Remaining unsorted interval is [left, pivot - 1]
}
}
}
```
=== "Java"
+44 -4
View File
@@ -10,7 +10,7 @@ The previous section introduced counting sort, which is suitable for scenarios w
## 11.10.1 &nbsp; Algorithm process
Taking the student ID data as an example, assuming the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in the following diagram.
Taking the student ID data as an example, assuming the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in Figure 11-18.
1. Initialize digit $k = 1$.
2. Perform "counting sort" on the $k^{th}$ digit of the student IDs. After completion, the data will be sorted from smallest to largest based on the $k^{th}$ digit.
@@ -79,11 +79,51 @@ Additionally, we need to slightly modify the counting sort code to allow sorting
=== "C++"
```cpp title="radix_sort.cpp"
[class]{}-[func]{digit}
/* Get the k-th digit of element num, where exp = 10^(k-1) */
int digit(int num, int exp) {
// Passing exp instead of k can avoid repeated expensive exponentiation here
return (num / exp) % 10;
}
[class]{}-[func]{countingSortDigit}
/* Counting sort (based on nums k-th digit) */
void countingSortDigit(vector<int> &nums, int exp) {
// Decimal digit range is 0~9, therefore need a bucket array of length 10
vector<int> counter(10, 0);
int n = nums.size();
// Count the occurrence of digits 0~9
for (int i = 0; i < n; i++) {
int d = digit(nums[i], exp); // Get the k-th digit of nums[i], noted as d
counter[d]++; // Count the occurrence of digit d
}
// Calculate prefix sum, converting "occurrence count" into "array index"
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// Traverse in reverse, based on bucket statistics, place each element into res
vector<int> res(n, 0);
for (int i = n - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // Get the index j for d in the array
res[j] = nums[i]; // Place the current element at index j
counter[d]--; // Decrease the count of d by 1
}
// Use result to overwrite the original array nums
for (int i = 0; i < n; i++)
nums[i] = res[i];
}
[class]{}-[func]{radixSort}
/* Radix sort */
void radixSort(vector<int> &nums) {
// Get the maximum element of the array, used to determine the maximum number of digits
int m = *max_element(nums.begin(), nums.end());
// Traverse from the lowest to the highest digit
for (int exp = 1; exp <= m; exp *= 10)
// Perform counting sort on the k-th digit of array elements
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// i.e., exp = 10^(k-1)
countingSortDigit(nums, exp);
}
```
=== "Java"
+15 -1
View File
@@ -71,7 +71,21 @@ In the code, we use $k$ to record the smallest element within the unsorted inter
=== "C++"
```cpp title="selection_sort.cpp"
[class]{}-[func]{selectionSort}
/* Selection sort */
void selectionSort(vector<int> &nums) {
int n = nums.size();
// Outer loop: unsorted range is [i, n-1]
for (int i = 0; i < n - 1; i++) {
// Inner loop: find the smallest element within the unsorted range
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // Record the index of the smallest element
}
// Swap the smallest element with the first element of the unsorted range
swap(nums[i], nums[k]);
}
}
```
=== "Java"