mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-07 19:46:29 +08:00
build
This commit is contained in:
@@ -4,9 +4,9 @@ comments: true
|
||||
|
||||
# 11.3 Bubble sort
|
||||
|
||||
<u>Bubble sort</u> achieves sorting by continuously comparing and swapping adjacent elements. This process resembles bubbles rising from the bottom to the top, hence the name bubble sort.
|
||||
<u>Bubble sort</u> works by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name "bubble sort."
|
||||
|
||||
As shown in Figure 11-4, the bubbling process can be simulated using element swap operations: starting from the leftmost end of the array and moving right, sequentially compare the size of adjacent elements. If "left element > right element," then swap them. After the traversal, the largest element will be moved to the far right end of the array.
|
||||
As shown in Figure 11-4, the bubbling process can be simulated using element swaps: start from the leftmost end of the array and move right, comparing each pair of adjacent elements. If the left element is greater than the right element, swap them. After the traversal, the largest element will have bubbled up to the rightmost end of the array.
|
||||
|
||||
=== "<1>"
|
||||
{ class="animation-figure" }
|
||||
@@ -33,12 +33,12 @@ As shown in Figure 11-4, the bubbling process can be simulated using element swa
|
||||
|
||||
## 11.3.1 Algorithm process
|
||||
|
||||
Assuming the length of the array is $n$, the steps of bubble sort are shown in Figure 11-5.
|
||||
Assume the array has length $n$. The steps of bubble sort are shown in Figure 11-5:
|
||||
|
||||
1. First, perform a "bubble" on $n$ elements, **swapping the largest element to its correct position**.
|
||||
2. Next, perform a "bubble" on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**.
|
||||
3. Similarly, after $n - 1$ rounds of "bubbling," **the top $n - 1$ largest elements will be swapped to their correct positions**.
|
||||
4. The only remaining element is necessarily the smallest and does not require sorting, thus the array sorting is complete.
|
||||
1. First, perform one "bubble" pass on $n$ elements, **swapping the largest element to its correct position**.
|
||||
2. Next, perform a "bubble" pass on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**.
|
||||
3. Continue in this manner; after $n - 1$ such passes, **the largest $n - 1$ elements will have been moved to their correct positions**.
|
||||
4. The only remaining element **must** be the smallest, so **no** further sorting is required. At this point, the array is sorted.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -168,9 +168,9 @@ Example code is as follows:
|
||||
|
||||
## 11.3.2 Efficiency optimization
|
||||
|
||||
We find that if no swaps are performed in a round of "bubbling," the array is already sorted, and we can return the result immediately. Thus, we can add a flag `flag` to monitor this situation and return immediately when it occurs.
|
||||
If no swaps occur during a round of "bubbling," the array is already sorted, so we can return immediately. To detect this, we can add a `flag` variable; whenever no swaps are made in a pass, we set the flag and return early.
|
||||
|
||||
Even after optimization, the worst-case time complexity and average time complexity of bubble sort remain at $O(n^2)$; however, when the input array is completely ordered, it can achieve the best time complexity of $O(n)$.
|
||||
Even with this optimization, the worst time complexity and average time complexity of bubble sort remains $O(n^2)$. However, if the input array is already sorted, the best-case time complexity can be as low as $O(n)$.
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -306,6 +306,6 @@ Even after optimization, the worst-case time complexity and average time complex
|
||||
|
||||
## 11.3.3 Algorithm characteristics
|
||||
|
||||
- **Time complexity of $O(n^2)$, adaptive sorting**: The length of the array traversed in each round of "bubbling" decreases sequentially from $n - 1$, $n - 2$, $\dots$, $2$, $1$, totaling $(n - 1) n / 2$. With the introduction of `flag` optimization, the best time complexity can reach $O(n)$.
|
||||
- **Space complexity of $O(1)$, in-place sorting**: Only a constant amount of extra space is used by pointers $i$ and $j$.
|
||||
- **Stable sorting**: As equal elements are not swapped during the "bubbling".
|
||||
- **Time complexity of $O(n^2)$, adaptive sorting.** Each round of "bubbling" traverses array segments of length $n - 1$, $n - 2$, $\dots$, $2$, $1$, which sums to $(n - 1) n / 2$. With a `flag` optimization, the best-case time complexity can reach $O(n)$ when the array is already sorted.
|
||||
- **Space complexity of $O(1)$, in-place sorting.** Only a constant amount of extra space is used by pointers $i$ and $j$.
|
||||
- **Stable sorting.** Because equal elements are not swapped during "bubbling," their original order is preserved, making this a stable sort.
|
||||
|
||||
@@ -4,13 +4,13 @@ comments: true
|
||||
|
||||
# 11.8 Bucket sort
|
||||
|
||||
The previously mentioned sorting algorithms are all "comparison-based sorting algorithms," which sort by comparing the size of elements. Such sorting algorithms cannot surpass a time complexity of $O(n \log n)$. Next, we will discuss several "non-comparison sorting algorithms" that can achieve linear time complexity.
|
||||
The previously mentioned sorting algorithms are all "comparison-based sorting algorithms," which sort elements by comparing their values. Such sorting algorithms cannot have better time complexity of $O(n \log n)$. Next, we will discuss several "non-comparison sorting algorithms" that could achieve linear time complexity.
|
||||
|
||||
<u>Bucket sort</u> is a typical application of the divide-and-conquer strategy. It involves setting up a series of ordered buckets, each corresponding to a range of data, and then distributing the data evenly among these buckets; each bucket is then sorted individually; finally, all the data are merged in the order of the buckets.
|
||||
<u>Bucket sort</u> is a typical application of the divide-and-conquer strategy. It works by setting up a series of ordered buckets, each containing a range of data, and distributing the input data evenly across these buckets. And then, the data in each bucket is sorted individually. Finally, the sorted data from all the buckets is merged in sequence to produce the final result.
|
||||
|
||||
## 11.8.1 Algorithm process
|
||||
|
||||
Consider an array of length $n$, with elements in the range $[0, 1)$. The bucket sort process is illustrated in Figure 11-13.
|
||||
Consider an array of length $n$, with float numbers in the range $[0, 1)$. The bucket sort process is illustrated in Figure 11-13.
|
||||
|
||||
1. Initialize $k$ buckets and distribute $n$ elements into these $k$ buckets.
|
||||
2. Sort each bucket individually (using the built-in sorting function of the programming language).
|
||||
@@ -179,27 +179,27 @@ The code is shown as follows:
|
||||
|
||||
## 11.8.2 Algorithm characteristics
|
||||
|
||||
Bucket sort is suitable for handling very large data sets. For example, if the input data includes 1 million elements, and system memory limitations prevent loading all the data at once, you can divide the data into 1,000 buckets and sort each bucket separately before merging the results.
|
||||
Bucket sort is suitable for handling very large data sets. For example, if the input data includes 1 million elements, and system memory limitations prevent loading all the data at the same time, you can divide the data into 1,000 buckets and sort each bucket separately before merging the results.
|
||||
|
||||
- **Time complexity is $O(n + k)$**: Assuming the elements are evenly distributed across the buckets, the number of elements in each bucket is $n/k$. Assuming sorting a single bucket takes $O(n/k \log(n/k))$ time, sorting all buckets takes $O(n \log(n/k))$ time. **When the number of buckets $k$ is relatively large, the time complexity tends towards $O(n)$**. Merging the results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into a single bucket, and sorting that bucket takes $O(n^2)$ time.
|
||||
- **Time complexity is $O(n + k)$**: Assuming the elements are evenly distributed across the buckets, the number of elements in each bucket is $n/k$. Assuming sorting a single bucket takes $O(n/k \log(n/k))$ time, sorting all buckets takes $O(n \log(n/k))$ time. **When the number of buckets $k$ is relatively large, the time complexity approaches $O(n)$**. Merging the results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into a single bucket, and sorting that bucket takes $O(n^2)$ time.
|
||||
- **Space complexity is $O(n + k)$, non-in-place sorting**: It requires additional space for $k$ buckets and a total of $n$ elements.
|
||||
- Whether bucket sort is stable depends on whether the algorithm used to sort elements within the buckets is stable.
|
||||
- Whether bucket sort is stable depends on whether the sorting algorithm used within each bucket is stable.
|
||||
|
||||
## 11.8.3 How to achieve even distribution
|
||||
|
||||
The theoretical time complexity of bucket sort can reach $O(n)$, **the key is to evenly distribute the elements across all buckets**, as real data is often not uniformly distributed. For example, if we want to evenly distribute all products on Taobao by price range into 10 buckets, but the distribution of product prices is uneven, with many under 100 yuan and few over 1000 yuan. If the price range is evenly divided into 10, the difference in the number of products in each bucket will be very large.
|
||||
The theoretical time complexity of bucket sort can reach $O(n)$. **The key is to evenly distribute the elements across all buckets** as real-world data is often not uniformly distributed. For example, we may want to evenly distribute all products on eBay by price range into 10 buckets. However, the distribution of product prices may not be even, with many under $100 and few over $500. If the price range is evenly divided into 10, the difference in the number of products in each bucket will be significant.
|
||||
|
||||
To achieve even distribution, we can initially set a rough dividing line, roughly dividing the data into 3 buckets. **After the distribution is complete, the buckets with more products can be further divided into 3 buckets, until the number of elements in all buckets is roughly equal**.
|
||||
To achieve even distribution, we can initially set an approximate boundary to roughly divide the data into 3 buckets. **After the distribution is complete, the buckets with more items can be further divided into 3 buckets, until the number of elements in all buckets is roughly equal**.
|
||||
|
||||
As shown in Figure 11-14, this method essentially creates a recursive tree, aiming to make the leaf node values as even as possible. Of course, you don't have to divide the data into 3 buckets each round; the specific division method can be flexibly chosen based on data characteristics.
|
||||
As shown in Figure 11-14, this method essentially constructs a recursive tree, aiming to ensure the element counts in leaf nodes are as even as possible. Of course, you don't have to divide the data into 3 buckets each round - the partitioning strategy can be adaptively tailored to the data's unique characteristics.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
<p align="center"> Figure 11-14 Recursive division of buckets </p>
|
||||
|
||||
If we know the probability distribution of product prices in advance, **we can set the price dividing line for each bucket based on the data probability distribution**. It is worth noting that it is not necessarily required to specifically calculate the data distribution; it can also be approximated based on data characteristics using some probability model.
|
||||
If we know the probability distribution of product prices in advance, **we can set the price boundaries for each bucket based on the data probability distribution**. It is worth noting that it is not necessarily required to specifically calculate the data distribution; instead, it can be approximated based on data characteristics using a probability model.
|
||||
|
||||
As shown in Figure 11-15, we assume that product prices follow a normal distribution, allowing us to reasonably set the price intervals, thereby evenly distributing the products into the respective buckets.
|
||||
As shown in Figure 11-15, assuming that product prices follow a normal distribution, we can define reasonable price intervals to balance the distribution of items across the buckets.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ comments: true
|
||||
|
||||
# 11.9 Counting sort
|
||||
|
||||
<u>Counting sort</u> achieves sorting by counting the number of elements, typically applied to arrays of integers.
|
||||
<u>Counting sort</u> achieves sorting by counting the number of elements, usually applied to integer arrays.
|
||||
|
||||
## 11.9.1 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 Figure 11-16.
|
||||
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 shown 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.
|
||||
3. **Since the indices of `counter` are naturally ordered, all numbers are essentially sorted already**. Next, we traverse `counter`, filling `nums` in ascending order of occurrence.
|
||||
3. **Since the indices of `counter` are naturally ordered, all numbers are essentially sorted already**. Next, we traverse `counter`, and fill in `nums` in ascending order of occurrence.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -169,7 +169,7 @@ The code is shown below:
|
||||
|
||||
## 11.9.2 Complete implementation
|
||||
|
||||
Astute readers might have noticed, **if the input data is an object, the above step `3.` becomes ineffective**. Suppose the input data is a product object, we want to sort the products by their price (a class member variable), but the above algorithm can only provide the sorting result for the price.
|
||||
Observant readers might notice, **if the input data is an object, the above step `3.` is invalid**. Suppose the input data is a product object, we want to sort the products by the price (a class member variable), but the above algorithm can only give the sorted price as the result.
|
||||
|
||||
So how can we get the sorting result for the original data? First, we calculate the "prefix sum" of `counter`. As the name suggests, the prefix sum at index `i`, `prefix[i]`, equals the sum of the first `i` elements of the array:
|
||||
|
||||
@@ -177,10 +177,10 @@ $$
|
||||
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
|
||||
$$
|
||||
|
||||
**The prefix sum has a clear meaning, `prefix[num] - 1` represents the last occurrence index of element `num` in the result array `res`**. This information is crucial, as it tells us where each element should appear in the result array. Next, we traverse the original array `nums` for each element `num` in reverse order, performing the following two steps in each iteration.
|
||||
**The prefix sum has a clear meaning, `prefix[num] - 1` represents the index of the last occurrence of element `num` in the result array `res`**. This information is crucial, as it tells us where each element should appear in the result array. Next, we traverse each element `num` of the original array `nums` in reverse order, performing the following two steps in each iteration.
|
||||
|
||||
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`.
|
||||
2. Decrease the prefix sum `prefix[num]` by $1$ to obtain 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 Figure 11-17.
|
||||
|
||||
@@ -384,14 +384,14 @@ The implementation code of counting sort is shown below:
|
||||
|
||||
## 11.9.3 Algorithm characteristics
|
||||
|
||||
- **Time complexity is $O(n + m)$, non-adaptive sort**: Involves traversing `nums` and `counter`, both using linear time. Generally, $n \gg m$, and the time complexity tends towards $O(n)$.
|
||||
- **Space complexity is $O(n + m)$, non-in-place sort**: Utilizes arrays `res` and `counter` of lengths $n$ and $m$ respectively.
|
||||
- **Time complexity is $O(n + m)$, non-adaptive sort**: It involves traversing `nums` and `counter`, both using linear time. Generally, $n \gg m$, and the time complexity tends towards $O(n)$.
|
||||
- **Space complexity is $O(n + m)$, non-in-place sort**: It uses array `res` of lengths $n$ and array `counter` of length $m$ respectively.
|
||||
- **Stable sort**: Since elements are filled into `res` in a "right-to-left" order, reversing the traversal of `nums` can prevent changing the relative position between equal elements, thereby achieving a stable sort. Actually, traversing `nums` in order can also produce the correct sorting result, but the outcome is unstable.
|
||||
|
||||
## 11.9.4 Limitations
|
||||
|
||||
By now, you might find counting sort very clever, as it can achieve efficient sorting merely by counting quantities. However, the prerequisites for using counting sort are relatively strict.
|
||||
|
||||
**Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that these data can be converted to non-negative integers without changing the relative sizes of the elements. For example, for an array containing negative integers, you can first add a constant to all numbers, converting them all to positive numbers, and then convert them back after sorting is complete.
|
||||
**Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that these data can be converted to non-negative integers without changing the original order of the elements. For example, for an array containing negative integers, you can first add a constant to all numbers, converting them all to positive numbers, and then convert them back after sorting is complete.
|
||||
|
||||
**Counting sort is suitable for large data volumes but small data ranges**. For example, in the above example, $m$ should not be too large, otherwise, it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms.
|
||||
**Counting sort is suitable for large datasets with a small range of values**. For example, in the above example, $m$ should not be too large, otherwise, it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms.
|
||||
|
||||
@@ -6,27 +6,27 @@ comments: true
|
||||
|
||||
!!! tip
|
||||
|
||||
Before reading this section, please make sure you have completed the "Heap" chapter.
|
||||
Before reading this section, please ensure you have completed the "Heap" chapter.
|
||||
|
||||
<u>Heap sort</u> is an efficient sorting algorithm based on the heap data structure. We can implement heap sort using the "heap creation" and "element extraction" operations we have already learned.
|
||||
|
||||
1. Input the array and establish a min-heap, where the smallest element is at the heap's top.
|
||||
2. Continuously perform the extraction operation, recording the extracted elements in sequence to obtain a sorted list from smallest to largest.
|
||||
1. Input the array and construct a min-heap, where the smallest element is at the top of the heap.
|
||||
2. Continuously perform the extraction operation, record the extracted elements sequentially to obtain a sorted list from smallest to largest.
|
||||
|
||||
Although the above method is feasible, it requires an additional array to save the popped elements, which is somewhat space-consuming. In practice, we usually use a more elegant implementation.
|
||||
Although the above method is feasible, it requires an additional array to store the popped elements, which is somewhat space-consuming. In practice, we usually use a more elegant implementation.
|
||||
|
||||
## 11.7.1 Algorithm flow
|
||||
|
||||
Suppose the array length is $n$, the heap sort process is as follows.
|
||||
|
||||
1. Input the array and establish a max-heap. After completion, the largest element is at the heap's top.
|
||||
2. Swap the top element of the heap (the first element) with the heap's bottom element (the last element). After the swap, reduce the heap's length by $1$ and increase the sorted elements count by $1$.
|
||||
1. Input the array and establish a max-heap. After this step, the largest element is positioned at the top of the heap.
|
||||
2. Swap the top element of the heap (the first element) with the heap's bottom element (the last element). Following this swap, reduce the heap's length by $1$ and increase the sorted elements count by $1$.
|
||||
3. Starting from the heap top, perform the sift-down operation from top to bottom. After the sift-down, the heap's property is restored.
|
||||
4. Repeat steps `2.` and `3.` Loop for $n - 1$ rounds to complete the sorting of the array.
|
||||
|
||||
!!! tip
|
||||
|
||||
In fact, the element extraction operation also includes steps `2.` and `3.`, with the addition of a popping element step.
|
||||
In fact, the element extraction operation also includes steps `2.` and `3.`, with an additional step to pop (remove) the extracted element from the heap.
|
||||
|
||||
=== "<1>"
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: material/sort-ascending
|
||||
|
||||
!!! abstract
|
||||
|
||||
Sorting is like a magical key that turns chaos into order, enabling us to understand and handle data in a more efficient manner.
|
||||
Sorting is like a magical key that turns chaos into order, enabling us to understand and handle data more efficiently.
|
||||
|
||||
Whether it's simple ascending order or complex categorical arrangements, sorting reveals the harmonious beauty of data.
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ comments: true
|
||||
|
||||
<u>Insertion sort</u> is a simple sorting algorithm that works very much like the process of manually sorting a deck of cards.
|
||||
|
||||
Specifically, we select a pivot element from the unsorted interval, compare it with the elements in the sorted interval to its left, and insert the element into the correct position.
|
||||
Specifically, we select a base element from the unsorted interval, compare it with the elements in the sorted interval to its left, and insert the element into the correct position.
|
||||
|
||||
Figure 11-6 shows the process of inserting an element into an array. Assuming the pivot element is `base`, we need to move all elements between the target index and `base` one position to the right, then assign `base` to the target index.
|
||||
Figure 11-6 illustrates how an element is inserted into the array. Assuming the base element is `base`, we need to shift all elements from the target index up to `base` one position to the right, then assign `base` to the target index.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -18,10 +18,10 @@ Figure 11-6 shows the process of inserting an element into an array. Assuming th
|
||||
|
||||
The overall process of insertion sort is shown in Figure 11-7.
|
||||
|
||||
1. Initially, the first element of the array is sorted.
|
||||
2. The second element of the array is taken as `base`, and after inserting it into the correct position, **the first two elements of the array are sorted**.
|
||||
3. The third element is taken as `base`, and after inserting it into the correct position, **the first three elements of the array are sorted**.
|
||||
4. And so on, in the last round, the last element is taken as `base`, and after inserting it into the correct position, **all elements are sorted**.
|
||||
1. Consider the first element of the array as sorted.
|
||||
2. Select the second element as `base`, insert it into its correct position, **leaving the first two elements sorted**.
|
||||
3. Select the third element as `base`, insert it into its correct position, **leaving the first three elements sorted**.
|
||||
4. Continuing in this manner, in the final iteration, the last element is taken as `base`, and after inserting it into the correct position, **all elements are sorted**.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -155,13 +155,13 @@ Example code is as follows:
|
||||
|
||||
## 11.4.3 Advantages of insertion sort
|
||||
|
||||
The time complexity of insertion sort is $O(n^2)$, while the time complexity of quicksort, which we will study next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **it is usually faster in cases of small data volumes**.
|
||||
The time complexity of insertion sort is $O(n^2)$, while the time complexity of quicksort, which we will study next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **it is usually faster in small input sizes**.
|
||||
|
||||
This conclusion is similar to that for linear and binary search. Algorithms like quicksort that have a time complexity of $O(n \log n)$ and are based on the divide-and-conquer strategy often involve more unit operations. In cases of small data volumes, the numerical values of $n^2$ and $n \log n$ are close, and complexity does not dominate, with the number of unit operations per round playing a decisive role.
|
||||
This conclusion is similar to that for linear and binary search. Algorithms like quicksort that have a time complexity of $O(n \log n)$ and are based on the divide-and-conquer strategy often involve more unit operations. For small input sizes, the numerical values of $n^2$ and $n \log n$ are close, and complexity does not dominate, with the number of unit operations per round playing a decisive role.
|
||||
|
||||
In fact, many programming languages (such as Java) use insertion sort in their built-in sorting functions. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategies, such as quicksort; for short arrays, use insertion sort directly.
|
||||
In fact, many programming languages (such as Java) use insertion sort within their built-in sorting functions. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategies, such as quicksort; for short arrays, use insertion sort directly.
|
||||
|
||||
Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in practice, **insertion sort is used significantly more frequently than bubble sort and selection sort**, mainly for the following reasons.
|
||||
Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in practice, **insertion sort is commonly used than bubble sort and selection sort**, mainly for the following reasons.
|
||||
|
||||
- Bubble sort is based on element swapping, which requires the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, **the computational overhead of bubble sort is generally higher than that of insertion sort**.
|
||||
- The time complexity of selection sort is always $O(n^2)$. **Given a set of partially ordered data, insertion sort is usually more efficient than selection sort**.
|
||||
|
||||
@@ -6,8 +6,8 @@ comments: true
|
||||
|
||||
<u>Merge sort</u> is a sorting algorithm based on the divide-and-conquer strategy, involving the "divide" and "merge" phases shown in Figure 11-10.
|
||||
|
||||
1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into that of shorter arrays.
|
||||
2. **Merge phase**: Stop dividing when the length of the sub-array is 1, start merging, and continuously combine two shorter ordered arrays into one longer ordered array until the process is complete.
|
||||
1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into shorter arrays.
|
||||
2. **Merge phase**: Stop dividing when the length of the sub-array is 1, and then begin merging. The two shorter sorted arrays are continuously merged into a longer sorted array until the process is complete.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -18,9 +18,9 @@ comments: true
|
||||
As shown in Figure 11-11, the "divide phase" recursively splits the array from the midpoint into two sub-arrays from top to bottom.
|
||||
|
||||
1. Calculate the midpoint `mid`, recursively divide the left sub-array (interval `[left, mid]`) and the right sub-array (interval `[mid + 1, right]`).
|
||||
2. Continue with step `1.` recursively until the sub-array interval length is 1 to stop.
|
||||
2. Continue with step `1.` recursively until sub-array length becomes 1, then stops.
|
||||
|
||||
The "merge phase" combines the left and right sub-arrays into a single ordered array from bottom to top. Note that merging starts with sub-arrays of length 1, and each sub-array is ordered during the merge phase.
|
||||
The "merge phase" combines the left and right sub-arrays into a sorted array from bottom to top. It is important to note that, merging starts with sub-arrays of length 1, and each sub-array is sorted during the merge phase.
|
||||
|
||||
=== "<1>"
|
||||
{ class="animation-figure" }
|
||||
@@ -54,10 +54,10 @@ The "merge phase" combines the left and right sub-arrays into a single ordered a
|
||||
|
||||
<p align="center"> Figure 11-11 Merge sort process </p>
|
||||
|
||||
It is observed that the order of recursion in merge sort is consistent with the post-order traversal of a binary tree.
|
||||
It can be observed that the order of recursion in merge sort is consistent with the post-order traversal of a binary tree.
|
||||
|
||||
- **Post-order traversal**: First recursively traverse the left subtree, then the right subtree, and finally handle the root node.
|
||||
- **Merge sort**: First recursively handle the left sub-array, then the right sub-array, and finally perform the merge.
|
||||
- **Post-order traversal**: First recursively traverse the left subtree, then the right subtree, and finally process the root node.
|
||||
- **Merge sort**: First recursively process the left sub-array, then the right sub-array, and finally perform the merge.
|
||||
|
||||
The implementation of merge sort is shown in the following code. Note that the interval to be merged in `nums` is `[left, right]`, while the corresponding interval in `tmp` is `[0, right - left]`.
|
||||
|
||||
@@ -290,9 +290,9 @@ The implementation of merge sort is shown in the following code. Note that the i
|
||||
|
||||
## 11.6.3 Linked List sorting
|
||||
|
||||
For linked lists, merge sort has significant advantages over other sorting algorithms, **optimizing the space complexity of the linked list sorting task to $O(1)$**.
|
||||
For linked lists, merge sort has significant advantages over other sorting algorithms. **It can optimize the space complexity of the linked list sorting task to $O(1)$**.
|
||||
|
||||
- **Divide phase**: "Iteration" can be used instead of "recursion" to perform the linked list division work, thus saving the stack frame space used by recursion.
|
||||
- **Merge phase**: In linked lists, node addition and deletion operations can be achieved by changing references (pointers), so no extra lists need to be created during the merge phase (combining two short ordered lists into one long ordered list).
|
||||
- **Merge phase**: In linked lists, node insertion and deletion operations can be achieved by changing references (pointers), so no extra lists need to be created during the merge phase (combining two short ordered lists into one long ordered list).
|
||||
|
||||
Detailed implementation details are complex, and interested readers can consult related materials for learning.
|
||||
The implementation details are relatively complex, and interested readers can consult related materials for learning.
|
||||
|
||||
@@ -4,12 +4,12 @@ comments: true
|
||||
|
||||
# 11.5 Quick sort
|
||||
|
||||
<u>Quick sort</u> is a sorting algorithm based on the divide and conquer strategy, known for its efficiency and wide application.
|
||||
<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 in Figure 11-8.
|
||||
The core operation of quick sort is "pivot partitioning," which aims to select an element from the array as the "pivot" and move all elements less than the pivot to its left side, while moving all elements greater than the pivot to its right side. Specifically, the process of pivot partitioning 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.
|
||||
1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` to point to the two ends of the array respectively.
|
||||
2. Set up a loop where each round uses `i` (`j`) to search for the first element larger (smaller) than the pivot, then swap these two elements.
|
||||
3. Repeat step `2.` until `i` and `j` meet, finally swap the pivot to the boundary between the two sub-arrays.
|
||||
|
||||
=== "<1>"
|
||||
@@ -41,11 +41,11 @@ The core operation of quick sort is "pivot partitioning," aiming to: select an e
|
||||
|
||||
<p align="center"> Figure 11-8 Pivot division process </p>
|
||||
|
||||
After the pivot partitioning, the original array is divided into three parts: left sub-array, pivot, and right sub-array, satisfying "any element in the left sub-array $\leq$ pivot $\leq$ any element in the right sub-array." Therefore, we only need to sort these two sub-arrays next.
|
||||
After the pivot partitioning, the original array is divided into three parts: left sub-array, pivot, and right sub-array, satisfying "any element in the left sub-array $\leq$ pivot $\leq$ any element in the right sub-array." Therefore, we then only need to sort these two sub-arrays.
|
||||
|
||||
!!! note "Quick sort's divide and conquer strategy"
|
||||
!!! note "Divide-and-conquer strategy for quick sort"
|
||||
|
||||
The essence of pivot partitioning is to simplify a longer array's sorting problem into two shorter arrays' sorting problems.
|
||||
The essence of pivot partitioning is to simplify the sorting problem of a longer array into two shorter arrays.
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -196,8 +196,8 @@ After the pivot partitioning, the original array is divided into three parts: le
|
||||
The overall process of quick sort is shown in Figure 11-9.
|
||||
|
||||
1. First, perform a "pivot partitioning" on the original array to obtain the unsorted left and right sub-arrays.
|
||||
2. Then, recursively perform "pivot partitioning" on both the left and right sub-arrays.
|
||||
3. Continue recursively until the sub-array length reaches 1, thus completing the sorting of the entire array.
|
||||
2. Then, recursively perform "pivot partitioning" on the left and right sub-arrays separately.
|
||||
3. Continue recursively until the length of sub-array is 1, thus completing the sorting of the entire array.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -318,27 +318,27 @@ The overall process of quick sort is shown in Figure 11-9.
|
||||
|
||||
## 11.5.2 Algorithm features
|
||||
|
||||
- **Time complexity of $O(n \log n)$, non-adaptive sorting**: In average cases, the recursive levels of pivot partitioning are $\log n$, and the total number of loops per level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of pivot partitioning divides an array of length $n$ into two sub-arrays of lengths $0$ and $n - 1$, reaching $n$ recursive levels, and using $O(n^2)$ time overall.
|
||||
- **Space complexity of $O(n)$, in-place sorting**: In completely reversed input arrays, reaching the worst recursion depth of $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of additional arrays.
|
||||
- **Time complexity of $O(n \log n)$, non-adaptive sorting**: In average cases, the recursive levels of pivot partitioning are $\log n$, and the total number of loops per level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of pivot partitioning divides an array of length $n$ into two sub-arrays of lengths $0$ and $n - 1$, when the number of recursive levels reaches $n$, the number of loops in each level is $n$, and the total time used is $O(n^2)$.
|
||||
- **Space complexity of $O(n)$, in-place sorting**: In the case where the input array is completely reversed, the worst recursive depth reaches $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of additional arrays.
|
||||
- **Non-stable sorting**: In the final step of pivot partitioning, the pivot may be swapped to the right of equal elements.
|
||||
|
||||
## 11.5.3 Why is quick sort fast
|
||||
|
||||
From its name, it is apparent that quick sort should have certain efficiency advantages. Although the average time complexity of quick sort is the same as "merge sort" and "heap sort," quick sort is generally more efficient, mainly for the following reasons.
|
||||
As the name suggests, quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as that of "merge sort" and "heap sort," it is generally more efficient for the following reasons.
|
||||
|
||||
- **Low probability of worst-case scenarios**: Although the worst time complexity of quick sort is $O(n^2)$, less stable than merge sort, in most cases, quick sort can operate under a time complexity of $O(n \log n)$.
|
||||
- **High cache usage efficiency**: During the pivot partitioning operation, the system can load the entire sub-array into the cache, thus accessing elements more efficiently. In contrast, algorithms like "heap sort" need to access elements in a jumping manner, lacking this feature.
|
||||
- **Small constant coefficient of complexity**: Among the mentioned algorithms, quick sort has the fewest total number of comparisons, assignments, and swaps. This is similar to why "insertion sort" is faster than "bubble sort."
|
||||
- **High cache utilization**: During the pivot partitioning operation, the system can load the entire sub-array into the cache, thus accessing elements more efficiently. In contrast, algorithms like "heap sort" need to access elements in a jumping manner, lacking this feature.
|
||||
- **Small constant coefficient of complexity**: Among the three algorithms mentioned above, quick sort has the least total number of operations such as comparisons, assignments, and swaps. This is similar to why "insertion sort" is faster than "bubble sort."
|
||||
|
||||
## 11.5.4 Pivot optimization
|
||||
|
||||
**Quick sort's time efficiency may decrease under certain inputs**. For example, if the input array is completely reversed, since we select the leftmost element as the pivot, after the pivot partitioning, the pivot is swapped to the array's right end, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. If this recursion continues, each round of pivot partitioning will have a sub-array length of $0$, and the divide and conquer strategy fails, degrading quick sort to a form similar to "bubble sort."
|
||||
**Quick sort's time efficiency may degrade under certain inputs**. For example, if the input array is completely reversed, since we select the leftmost element as the pivot, after the pivot partitioning, the pivot is swapped to the array's right end, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. Continuing this way, each round of pivot partitioning will have a sub-array length of $0$, and the divide-and-conquer strategy fails, degrading quick sort to a form similar to "bubble sort."
|
||||
|
||||
To avoid this situation, **we can optimize the strategy for selecting the pivot in the pivot partitioning**. For instance, we can randomly select an element as the pivot. However, if luck is not on our side, and we keep selecting suboptimal pivots, the efficiency is still not satisfactory.
|
||||
To avoid this situation, **we can optimize the pivot selection strategy in the pivot partitioning**. For instance, we can randomly select an element as the pivot. However, if luck is not on our side, and we consistently select suboptimal pivots, the efficiency is still not satisfactory.
|
||||
|
||||
It's important to note that programming languages usually generate "pseudo-random numbers". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade.
|
||||
|
||||
For further improvement, we can select three candidate elements (usually the first, last, and midpoint elements of the array), **and use the median of these three candidate elements as the pivot**. This significantly increases the probability that the pivot is "neither too small nor too large". Of course, we can also select more candidate elements to further enhance the algorithm's robustness. Using this method significantly reduces the probability of time complexity degradation to $O(n^2)$.
|
||||
For further improvement, we can select three candidate elements (usually the first, last, and midpoint elements of the array), **and use the median of these three candidate elements as the pivot**. This way, the probability that the pivot is "neither too small nor too large" will be greatly increased. Of course, we can also select more candidate elements to further enhance robustness of the algorithm. With this method, the probability of the time complexity degrading to $O(n^2)$ is greatly reduced.
|
||||
|
||||
Sample code is as follows:
|
||||
|
||||
@@ -530,7 +530,8 @@ Sample code is as follows:
|
||||
|
||||
## 11.5.5 Tail recursion optimization
|
||||
|
||||
**Under certain inputs, quick sort may occupy more space**. For a completely ordered input array, assume the sub-array length in recursion is $m$, each round of pivot partitioning produces a left sub-array of length $0$ and a right sub-array of length $m - 1$, meaning the problem size reduced per recursive call is very small (only one element), and the height of the recursion tree can reach $n - 1$, requiring $O(n)$ stack frame space.
|
||||
**Under certain inputs, quick sort may occupy more space**. For example, consider a completely ordered input array. Let the length of the sub-array in the recursion be $m$. In each round of pivot partitioning, a left sub-array of length $0$ and a right sub-array of length $m - 1$ are produced. This means that the problem size is reduced by only one element per recursive call, resulting in a very small reduction at each level of recursion.
|
||||
As a result, the height of the recursion tree can reach $n − 1$ , which requires $O(n)$ of stack frame space.
|
||||
|
||||
To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of pivot sorting, **and only recursively sort the shorter sub-array**. Since the length of the shorter sub-array will not exceed $n / 2$, this method ensures that the recursion depth does not exceed $\log n$, thus optimizing the worst space complexity to $O(\log n)$. The code is as follows:
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@ comments: true
|
||||
|
||||
# 11.10 Radix sort
|
||||
|
||||
The previous section introduced counting sort, which is suitable for scenarios where the data volume $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, where each ID is an $8$-digit number. This means the data range $m = 10^8$ is very large, requiring a significant amount of memory space for counting sort, while radix sort can avoid this situation.
|
||||
The previous section introduced counting sort, which is suitable for scenarios where the data size $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, where each ID is an $8$-digit number. This means the data range $m = 10^8$ is very large. Using counting sort in this case would require significant memory space. Radix sort can avoid this situation.
|
||||
|
||||
<u>Radix sort</u> shares the core idea with counting sort, which also sorts by counting the frequency of elements. Building on this, radix sort utilizes the progressive relationship between the digits of numbers, sorting each digit in turn to achieve the final sorted order.
|
||||
<u>Radix sort</u> shares the same core concept as counting sort, which also sorts by counting the frequency of elements. Meanwhile, radix sort builds upon this by utilizing the progressive relationship between the digits of numbers. It processes and sorts the digits one at a time, achieving the final sorted order.
|
||||
|
||||
## 11.10.1 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 Figure 11-18.
|
||||
Taking the student ID data as an example, assume 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.
|
||||
3. Increment $k$ by $1$, then return to step `2.` and continue iterating until all digits have been sorted, then the process ends.
|
||||
3. Increment $k$ by $1$, then return to step `2.` and continue iterating until all digits have been sorted, at which point the process ends.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -292,12 +292,12 @@ Additionally, we need to slightly modify the counting sort code to allow sorting
|
||||
|
||||
!!! question "Why start sorting from the least significant digit?"
|
||||
|
||||
In consecutive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the result of the first round is $a < b$ and the result of the second round is $a > b$, the result of the second round will replace the first round's result. Since the significance of higher digits is greater than that of lower digits, it makes sense to sort lower digits before higher digits.
|
||||
In consecutive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the result of the first round is $a < b$ and the second round is $a > b$, the second round's result will replace the first round's result. Since higher-order digits take precedence over lower-order digits, it makes sense to sort the lower digits before the higher digits.
|
||||
|
||||
## 11.10.2 Algorithm characteristics
|
||||
|
||||
Compared to counting sort, radix sort is suitable for larger numerical ranges, **but it assumes that the data can be represented in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are not suitable for radix sort, as their digit count $k$ may be large, potentially leading to a time complexity $O(nk) \gg O(n^2)$.
|
||||
Compared to counting sort, radix sort is suitable for larger numerical ranges, **but it assumes that the data can be represented in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are unsuitable for radix sort, as their digit count $k$ may be large, potentially leading to a time complexity $O(nk) \gg O(n^2)$.
|
||||
|
||||
- **Time complexity is $O(nk)$, non-adaptive sorting**: Assuming the data size is $n$, the data is in base $d$, and the maximum number of digits is $k$, then sorting a single digit takes $O(n + d)$ time, and sorting all $k$ digits takes $O((n + d)k)$ time. Generally, both $d$ and $k$ are relatively small, leading to a time complexity approaching $O(n)$.
|
||||
- **Space complexity is $O(n + d)$, non-in-place sorting**: Like counting sort, radix sort relies on arrays `res` and `counter` of lengths $n$ and $d$ respectively.
|
||||
- **Stable sorting**: When counting sort is stable, radix sort is also stable; if counting sort is unstable, radix sort cannot guarantee a correct sorting outcome.
|
||||
- **Stable sorting**: When counting sort is stable, radix sort is also stable; if counting sort is unstable, radix sort cannot ensure a correct sorting order.
|
||||
|
||||
@@ -4,15 +4,15 @@ comments: true
|
||||
|
||||
# 11.2 Selection sort
|
||||
|
||||
<u>Selection sort</u> works on a very simple principle: it starts a loop where each iteration selects the smallest element from the unsorted interval and moves it to the end of the sorted interval.
|
||||
<u>Selection sort</u> works on a very simple principle: it uses a loop where each iteration selects the smallest element from the unsorted interval and moves it to the end of the sorted section.
|
||||
|
||||
Suppose the length of the array is $n$, the algorithm flow of selection sort is as shown in Figure 11-2.
|
||||
Suppose the length of the array is $n$, the steps of selection sort is shown in Figure 11-2.
|
||||
|
||||
1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is $[0, n-1]$.
|
||||
2. Select the smallest element in the interval $[0, n-1]$ and swap it with the element at index $0$. After this, the first element of the array is sorted.
|
||||
3. Select the smallest element in the interval $[1, n-1]$ and swap it with the element at index $1$. After this, the first two elements of the array are sorted.
|
||||
4. Continue in this manner. After $n - 1$ rounds of selection and swapping, the first $n - 1$ elements are sorted.
|
||||
5. The only remaining element is necessarily the largest element and does not need sorting, thus the array is sorted.
|
||||
5. The only remaining element is subsequently the largest element and does not need sorting, thus the array is sorted.
|
||||
|
||||
=== "<1>"
|
||||
{ class="animation-figure" }
|
||||
@@ -178,7 +178,7 @@ In the code, we use $k$ to record the smallest element within the unsorted inter
|
||||
|
||||
## 11.2.1 Algorithm characteristics
|
||||
|
||||
- **Time complexity of $O(n^2)$, non-adaptive sort**: There are $n - 1$ rounds in the outer loop, with the unsorted interval length starting at $n$ in the first round and decreasing to $2$ in the last round, i.e., the outer loops contain $n$, $n - 1$, $\dots$, $3$, $2$ inner loops respectively, summing up to $\frac{(n - 1)(n + 2)}{2}$.
|
||||
- **Time complexity of $O(n^2)$, non-adaptive sort**: There are $n - 1$ iterations in the outer loop, with the length of the unsorted section starting at $n$ in the first iteration and decreasing to $2$ in the last iteration, i.e., each outer loop iterations contain $n$, $n - 1$, $\dots$, $3$, $2$ inner loop iterations respectively, summing up to $\frac{(n - 1)(n + 2)}{2}$.
|
||||
- **Space complexity of $O(1)$, in-place sort**: Uses constant extra space with pointers $i$ and $j$.
|
||||
- **Non-stable sort**: As shown in Figure 11-3, an element `nums[i]` may be swapped to the right of an equal element, causing their relative order to change.
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ comments: true
|
||||
|
||||
- Bubble sort works by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to $O(n)$.
|
||||
- Insertion sort sorts each round by inserting elements from the unsorted interval into the correct position in the sorted interval. Although the time complexity of insertion sort is $O(n^2)$, it is very popular in sorting small amounts of data due to relatively fewer operations per unit.
|
||||
- Quick sort is based on sentinel partitioning operations. In sentinel partitioning, it's possible to always pick the worst pivot, leading to a time complexity degradation to $O(n^2)$. Introducing median or random pivots can reduce the probability of such degradation. Tail recursion can effectively reduce the recursion depth, optimizing the space complexity to $O(\log n)$.
|
||||
- Quick sort is based on sentinel partitioning operations. In sentinel partitioning, it's possible to always pick the worst pivot, leading to a time complexity degradation to $O(n^2)$. Introducing median or random pivots can reduce the probability of such degradation. Tail recursion effectively reduce the recursion depth, optimizing the space complexity to $O(\log n)$.
|
||||
- Merge sort includes dividing and merging two phases, typically embodying the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, resulting in a space complexity of $O(n)$; however, the space complexity for sorting a list can be optimized to $O(1)$.
|
||||
- Bucket sort consists of three steps: data bucketing, sorting within buckets, and merging results. It also embodies the divide-and-conquer strategy, suitable for very large datasets. The key to bucket sort is the even distribution of data.
|
||||
- Counting sort is a special case of bucket sort, which sorts by counting the occurrences of each data point. Counting sort is suitable for large datasets with a limited range of data and requires that data can be converted to positive integers.
|
||||
- Radix sort sorts data by sorting digit by digit, requiring data to be represented as fixed-length numbers.
|
||||
- Overall, we hope to find a sorting algorithm that has high efficiency, stability, in-place operation, and adaptability. However, like other data structures and algorithms, no sorting algorithm can meet all these conditions simultaneously. In practical applications, we need to choose the appropriate sorting algorithm based on the characteristics of the data.
|
||||
- Bucket sort consists of three steps: distributing data into buckets, sorting within each bucket, and merging results in bucket order. It also embodies the divide-and-conquer strategy, suitable for very large datasets. The key to bucket sort is the even distribution of data.
|
||||
- Counting sort is a variant of bucket sort, which sorts by counting the occurrences of each data point. Counting sort is suitable for large datasets with a limited range of data and requires data conversion to positive integers.
|
||||
- Radix sort processes data by sorting it digit by digit, requiring data to be represented as fixed-length numbers.
|
||||
- Overall, we seek sorting algorithm that has high efficiency, stability, in-place operation, and adaptability. However, like other data structures and algorithms, no sorting algorithm can meet all these conditions simultaneously. In practical applications, we need to choose the appropriate sorting algorithm based on the characteristics of the data.
|
||||
- Figure 11-19 compares mainstream sorting algorithms in terms of efficiency, stability, in-place nature, and adaptability.
|
||||
|
||||
{ class="animation-figure" }
|
||||
@@ -32,7 +32,7 @@ It can be seen that the positions of students D and C have been swapped, disrupt
|
||||
|
||||
No, when using the leftmost element as the pivot, we must first "search from right to left" then "search from left to right". This conclusion is somewhat counterintuitive, so let's analyze the reason.
|
||||
|
||||
The last step of the sentinel partition `partition()` is to swap `nums[left]` and `nums[i]`. After the swap, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we "search from left to right" first, then if no element larger than the pivot is found, **we will exit the loop when `i == j`, possibly with `nums[j] == nums[i] > nums[left]`**. In other words, the final swap operation will exchange an element larger than the pivot to the left end of the array, causing the sentinel partition to fail.
|
||||
The last step of the sentinel partition `partition()` is to swap `nums[left]` and `nums[i]`. After the swap, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we "search from left to right" first, and if no element larger than the pivot is found, **we will exit the loop when `i == j`, possibly with `nums[j] == nums[i] > nums[left]`**. In other words, the final swap operation will exchange an element larger than the pivot to the left end of the array, causing the sentinel partition to fail.
|
||||
|
||||
For example, given the array `[0, 0, 0, 0, 1]`, if we first "search from left to right", the array after the sentinel partition is `[1, 0, 0, 0, 0]`, which is incorrect.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user