diff --git a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md b/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md index 72d6971c..fadff237 100644 --- a/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md +++ b/Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md @@ -11,11 +11,11 @@ 1. 第 `1` 趟排序: 1. 第 `1` 个元素为有序序列,后面第 `2` ~ `n `个元素(总共 `n - 1` 个元素)为无序序列。 2. 从右至左遍历有序序列中的元素,如果遇到「有序序列的元素> 无序序列的第 `1` 个元素」的情况时,则将向有序序列的元素后移动一位。 - 3. 如果遇到「有序序列的元素 <= 无序序列的第 `1` 个元素」的情况时,则说明找到了插入位置。将「无序序列的第 `1` 个元素」插入该位置。 + 3. 如果遇到「有序序列的元素 <= 无序序列的第 `1` 个元素」的情况或者「到达数组开始位置」时,则说明找到了插入位置。将「无序序列的第 `1` 个元素」插入该位置。 2. 第 `2` 趟排序: 1. 第 `1` ~ `2` 个元素为有序序列,后面第 `3` ~ `n` 个元素(总共 `n - 2` 个元素)为无序序列。 2. 从右至左遍历有序序列中的元素,如果遇到「有序序列的元素> 无序序列的第 `1` 个元素」的情况时,则将向有序序列的元素后移动一位。 - 3. 如果遇到「有序序列的元素 <= 无序序列的第 `1` 个元素」的情况时,则说明找到了插入位置。将「无序序列的第 `1` 个元素」插入该位置。 + 3. 如果遇到「有序序列的元素 <= 无序序列的第 `1` 个元素」的情况或者「到达数组开始位置」时,则说明找到了插入位置。将「无序序列的第 `1` 个元素」插入该位置。 3. 依次类推,对剩余 `n - 3` 个元素重复上述排序过程,直到所有元素都变为有序序列,则排序结束。 @@ -29,26 +29,39 @@ ## 3. 插入排序动画演示 -![img](https://www.runoob.com/wp-content/uploads/2019/03/insertionSort.gif) +![](https://qcdn.itcharge.cn/images/20220816143518.gif) + +1. 初始序列为:`[6, 2, 3, 5, 1, 4]`。 +2. 第 `1` 趟排序,将 `[6]` 作为有序序列,把 `[2, 3, 5, 1, 4]` 作为无序序列。无序序列第 `1` 个元素为 `2`。 + 1. 从右向左遍历有序序列 `[6]`,遇到 `6> 2`,则将 `6` 向右移动 `1` 位,到达数组开始位置,则找到了合适的插入位置,将 `2` 插入该位置。 + 2. 此时序列变为 `[2, 6, 3, 5, 1, 4]`。 +3. 第 `2` 趟排序,把 `[2, 6]` 作为有序序列, `[3, 5, 1, 4]` 为无序序列。无序序列第 `1` 个元素为 `3`。 + 1. 从右向左遍历有序序列 `[6]`,遇到 `6> 3`,则将 `6` 向右移动 `1` 位,继续遍历,遇到 `2 < 3`,则找到了合适的插入位置,将 `3` 插入该位置。 + 2. 此时序列变为 `[2, 3, 6, 5, 1, 4]`。 +4. 依次类推,对无序序列中剩余 `3` 个元素重复上述排序过程,直到无序序列中所有元素都插入到有序序列,则排序结束。此时,序列变为 `[1, 2, 3, 4, 5, 6]`。 ## 4. 插入排序算法分析 - **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),对应的每个 `i` 值只进行一次元素之间的比较,因而总的比较次数最少,为 $∑^n_{i = 2}1 = n − 1,ドル并不需要移动元素(记录),这是最好的情况。 - **最差时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列),对应的每个 `i` 值都要进行 `i - 1` 次元素之间的比较,总的元素之间的比较次数达到最大值,为 $∑^n_{i=2}(i − 1) = \frac{n(n−1)}{2}$。 - **平均时间复杂度**:$O(n^2)$。如果序列的初始情况是随机的,即参加排序的序列中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $n^2/4$。由此得知,插入排序算法的时间复杂度 $O(n^2)$。 -- **排序稳定性**:插入排序方法属于 **稳定排序算法**。 +- **排序稳定性**:插入排序方法是一种 **稳定排序算法**。 ## 5. 插入排序代码实现 ```Python class Solution: def insertionSort(self, arr): + # 遍历无序序列 for i in range(1, len(arr)): temp = arr[i] j = i + # 从右至左遍历有序序列 while j> 0 and arr[j - 1]> temp: + # 将有序序列中插入位置右侧的元素依次右移一位 arr[j] = arr[j - 1] j -= 1 + # 将该元素插入到适当位置 arr[j] = temp return arr @@ -56,6 +69,3 @@ class Solution: def sortArray(self, nums: List[int]) -> List[int]: return self.insertionSort(nums) ``` - - - diff --git a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md b/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md index 3fcd9b24..0493e4be 100644 --- a/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md +++ b/Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md @@ -1,14 +1,16 @@ ## 1. 希尔排序算法思想 -> 希尔排序(Shell Sort)基本思想: +> **希尔排序(Shell Sort)基本思想**: > > 将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和插入排序。直至最后一轮排序间隔为 `1`,对整个序列进行插入排序。 > ## 2. 希尔排序算法步骤 -- 首先确定一个元素间隔数 `gap`,然后将参加排序的序列按此间隔数从第 `1` 个元素开始一次分成若干个子序列,即分别将所有位置相隔为 `gap` 的元素视为一个子序列,在各个子序列中采用某种排序方法进行插入排序。 -- 然后减少间隔数,并重新将整个序列按新的间隔数分成若干个子序列,再分别对各个子序列进行排序,如此下去,直到间隔数 `gap = 1`。 +1. 确定一个元素间隔数 `gap`。 +2. 将参加排序的序列按此间隔数从第 `1` 个元素开始一次分成若干个子序列,即分别将所有位置相隔为 `gap` 的元素视为一个子序列。 +3. 在各个子序列中采用某种排序算法(例如插入排序算法)进行排序。 +4. 减少间隔数,并重新将整个序列按新的间隔数分成若干个子序列,再分别对各个子序列进行排序。依次类推,直到间隔数 `gap = 1`,排序结束。 ## 3. 希尔排序图解演示 @@ -16,10 +18,12 @@ ## 4. 希尔排序算法分析 -- 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,不太容易弄清楚比较次数与 `gap` 之间的依赖关系,并给出完整的数学分析。 -- 上面算法中,由于采用 $gap_i = \lfloor gap_{i-1}/2 \rfloor$ 的方法缩小间隔数,对于具有 `n` 个元素的序列,若 $gap_1 = \lfloor n/2 \rfloor,ドル则经过 $p = \lfloor log_2 n \rfloor$ 趟排序后就有 $gap_p = 1,ドル因此,希尔排序方法的排序总躺数为 $\lfloor log_2 n \rfloor$。 -- 从算法中也可以看到,最外层的 while 循环为 $log_2 n$ 数量级,中间层 do-while 循环为 `n` 数量级。当子序列分得越多时,子序列内的元素就越少,最内层的 for 循环的次数也就越少;反之,当所分的子序列个数减少时,子序列内的元素也随之增多,但整个序列也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n log_2 n)$ 与 $O(n^2)$ 之间。 -- 希尔排序方法是一种不稳定排序算法。 +- **时间复杂度**:介于 $O(n \times \log_2 n)$ 与 $O(n^2)$ 之间。 + - 希尔排序方法的速度是一系列间隔数 $gap_i$ 的函数,不太容易弄清楚比较次数与 $gap_i$ 之间的依赖关系,并给出完整的数学分析。 + - 由于采用 $gap_i = \lfloor gap_{i-1}/2 \rfloor$ 的方法缩小间隔数,对于具有 $n$ 个元素的序列,若 $gap_1 = \lfloor n/2 \rfloor,ドル则经过 $p = \lfloor \log_2 n \rfloor$ 趟排序后就有 $gap_p = 1,ドル因此,希尔排序方法的排序总躺数为 $\lfloor \log_2 n \rfloor$。 + - 从算法中也可以看到,最外层的 `while` 循环为 $\log_2 n$ 数量级,中间层 `do-while` 循环为 `n` 数量级。当子序列分得越多时,子序列内的元素就越少,最内层的 `for` 循环的次数也就越少;反之,当所分的子序列个数减少时,子序列内的元素也随之增多,但整个序列也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n \times \log_2 n)$ 与 $O(n^2)$ 之间。 + +- **排序稳定性**:希尔排序方法是一种 **不稳定排序算法**。 ## 5. 希尔排序代码实现 @@ -28,15 +32,21 @@ class Solution: def shellSort(self, arr): size = len(arr) gap = size // 2 - + # 按照 gap 分组 while gap> 0: + # 对每组元素进行插入排序 for i in range(gap, size): + # temp 为每组中无序序列第 1 个元素 temp = arr[i] j = i + # 从右至左遍历每组中的有序序列元素 while j>= gap and arr[j - gap]> temp: + # 将每组有序序列中插入位置右侧的元素依次在组中右移一位 arr[j] = arr[j - gap] j -= gap + # 将该元素插入到适当位置 arr[j] = temp + # 缩小 gap 间隔 gap = gap // 2 return arr diff --git a/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md b/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md index 67af15eb..7cbb9a8b 100644 --- a/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md +++ b/Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md @@ -1,49 +1,80 @@ ## 1. 归并排序算法思想 -> 归并排序(Merge Sort)基本思想: +> **归并排序(Merge Sort)基本思想**: > > 采用经典的分治策略,先递归地将当前序列平均分成两半。然后将有序序列两两合并,最终合并成一个有序序列。 ## 2. 归并排序算法步骤 -- 初始时,将待排序序列中的 `n` 个记录看成 `n` 个有序子序列(每个子序列总是有序的),每个子序列的长度均为 `1`。 -- 把当前序列组中有序子序列两两归并,完成一遍之后序列组里的排序序列个数减半,每个子序列的长度加倍。 -- 对长度加倍的有序子序列重复上面的操作,最终得到一个长度为 `n` 的有序序列。 +1. **分割过程**:先递归地将当前序列平均分成两半,直到子序列长度为 `1`。 + 1. 找到序列中心位置 `mid`,从中心位置将序列分成左右两个子序列 `left_arr`、`right_arr`。 + 2. 对左右两个子序列 `left_arr`、`right_arr` 分别进行递归分割。 + 3. 最终将数组分割为 `n` 个长度均为 `1` 的有序子序列。 +2. **归并过程**:从长度为 `1` 的有序子序列开始,依次进行两两归并,直到合并成一个长度为 `n` 的有序序列。 + 1. 使用数组变量 `arr` 存放归并后的有序数组。 + 2. 使用两个指针 `left_i`、`right_i` 分别指向两个有序子序列 `left_arr`、`right_arr` 的开始位置。 + 3. 比较两个指针指向的元素,将两个有序子序列中较小元素依次存入到结果数组 `arr` 中,并将指针移动到下一位置。 + 4. 重复步骤 `3`,直到某一指针到达子序列末尾。 + 5. 将另一个子序列中的剩余元素存入到结果数组 `arr` 中。 + 6. 返回归并后的有序数组 `arr`。 ## 3. 归并排序动画演示 -![](https://www.runoob.com/wp-content/uploads/2019/03/mergeSort.gif) +![](https://qcdn.itcharge.cn/images/20220816161220.gif) + +1. 初始序列为 `[6, 2, 1, 3, 7, 5, 4, 8]`。 +2. 将序列分解为 `[6, 2, 1, 3]`,`[7, 5, 4, 8]`。 +3. 将序列分解为 `[6, 2]`,`[1, 3]`,`[7, 5]`,`[4, 8]`。 +4. 将序列分为为 `[6]`,`[2]`,`[1]`,`[3]`,`[7]`,`[5]`,`[4]`,`[8]`。 +5. 将序列看做是 `8` 个长度为 `1` 的子序列,即 `[6]`,`[2]`,`[1]`,`[3]`,`[7]`,`[5]`,`[4]`,`[8]`。 +6. 第 `1` 趟排序:将子序列中的有序子序列两两归并,归并后的子序列为:`[2, 6]`,`[1, 3]`,`[5, 7]`,`[4, 8]`。 +7. 第 `2` 趟排序:将子序列中的有序子序列两两归并,归并后的子序列为:`[1, 2, 3, 6]`,`[4, 5, 7, 8]`。 +8. 第 `3` 趟排序:将子序列中的有序子序列两两归并,归并后的子序列为:`[1, 2, 3, 4, 5, 6, 7, 8]`。得到长度为 `n` 的有序序列,排序结束。 ## 4. 归并排序算法分析 -- 归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度成绩。子算法 `merge(left_arr, right_arr):` 的时间复杂度是 $O(n),ドル因此,归并排序算法总的时间复杂度为 $O(nlog_2n)$。 -- 归并排序方法需要用到与参加排序的序列同样大小的辅助空间。因此算法的空间复杂度为 $O(n)$。 -- 因为在两个有序子序列的归并过程中,如果两个有序序列中出现相同元素,`merge(left_arr, right_arr):` 算法能够使前一个序列中那个相同元素先被复制,从而确保这两个元素的相对次序不发生改变。所以归并排序算法是 **稳定排序算法**。 +- **时间复杂度**:$O(n \times \log_2n)$。归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度乘积。子算法 `merge(left_arr, right_arr):` 的时间复杂度是 $O(n),ドル因此,归并排序算法总的时间复杂度为 $O(n \times \log_2 n)$。 +- **空间复杂度**:$O(n)$。归并排序方法需要用到与参加排序的序列同样大小的辅助空间。因此算法的空间复杂度为 $O(n)$。 +- **排序稳定性**:归并排序算法是一种 **稳定排序算法**。 + - 因为在两个有序子序列的归并过程中,如果两个有序序列中出现相同元素,`merge(left_arr, right_arr):` 算法能够使前一个序列中那个相同元素先被复制,从而确保这两个元素的相对次序不发生改变。 + ## 5. 归并排序代码实现 ```Python class Solution: - def merge(self, left_arr, right_arr): + def merge(self, left_arr, right_arr): # 归并过程 arr = [] - while left_arr and right_arr: - if left_arr[0] <= right_arr[0]: - arr.append(left_arr.pop(0)) + left_i, right_i = 0, 0 + while left_i < len(left_arr) and right_i < len(right_arr): + # 将两个有序子序列中较小元素依次插入到结果数组中 + if left_arr[left_i] < right_arr[right_i]: + arr.append(left_arr[left_i]) + left_i += 1 else: - arr.append(right_arr.pop(0)) - while left_arr: - arr.append(left_arr.pop(0)) - while right_arr: - arr.append(right_arr.pop(0)) - return arr - - def mergeSort(self, arr): - size = len(arr) - if size < 2: + arr.append(right_arr[right_i]) + right_i += 1 + + while left_i < len(left_arr): + # 如果左子序列有剩余元素,则将其插入到结果数组中 + arr.append(left_arr[left_i]) + left_i += 1 + + while right_i < len(right_arr): + # 如果右子序列有剩余元素,则将其插入到结果数组中 + arr.append(right_arr[right_i]) + right_i += 1 + + return arr # 返回排好序的结果数组 + + def mergeSort(self, arr): # 分割过程 + if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 return arr - mid = len(arr) // 2 - left_arr, right_arr = arr[0: mid], arr[mid:] - return self.merge(self.mergeSort(left_arr), self.mergeSort(right_arr)) + + mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 + left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序 + right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序 + return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 def sortArray(self, nums: List[int]) -> List[int]: return self.mergeSort(nums) diff --git a/Templates/01.Array/Array-MergeSort.py b/Templates/01.Array/Array-MergeSort.py index 19f89009..da0cfeda 100644 --- a/Templates/01.Array/Array-MergeSort.py +++ b/Templates/01.Array/Array-MergeSort.py @@ -1,25 +1,35 @@ class Solution: - def merge(self, left_arr, right_arr): # 合并 + def merge(self, left_arr, right_arr): # 归并 arr = [] - while left_arr and right_arr: # 将两个排序数组中较小元素依次插入到结果数组中 - if left_arr[0] <= right_arr[0]: - arr.append(left_arr.pop(0)) + left_i, right_i = 0, 0 + while left_i < len(left_arr) and right_i < len(right_arr): + # 将两个有序子序列中较小元素依次插入到结果数组中 + if left_arr[left_i] < right_arr[right_i]: + arr.append(left_arr[left_i]) + left_i += 1 else: - arr.append(right_arr.pop(0)) - - while left_arr: # 如果左子序列有剩余元素,则将其插入到结果数组中 - arr.append(left_arr.pop(0)) - while right_arr: # 如果右子序列有剩余元素,则将其插入到结果数组中 - arr.append(right_arr.pop(0)) + arr.append(right_arr[right_i]) + right_i += 1 + + while left_i < len(left_arr): + # 如果左子序列有剩余元素,则将其插入到结果数组中 + arr.append(left_arr[left_i]) + left_i += 1 + + while right_i < len(right_arr): + # 如果右子序列有剩余元素,则将其插入到结果数组中 + arr.append(right_arr[right_i]) + right_i += 1 + return arr # 返回排好序的结果数组 - def mergeSort(self, arr): # 分解 + def mergeSort(self, arr): # 分割 if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组 return arr mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。 - left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分解和排序 - right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分解和排序 + left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序 + right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序 return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。 def sortArray(self, nums: List[int]) -> List[int]:

AltStyle によって変換されたページ (->オリジナル) /