diff --git a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md b/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md index f8270202..69d6d200 100644 --- a/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md +++ b/Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md @@ -1,15 +1,19 @@ ## 1. 计数排序算法思想 -> 计数排序(Counting Sort)基本思想: +> **计数排序(Counting Sort)基本思想**: > -> 使用一个额外的数组 `counts`,其中第 `i` 个元素 `counts[i]` 是待排序数组 `arr` 中值等于 `i` 的元素个数。然后根据数组 `counts` 来将 `arr` 中的元素排到正确的位置。 +> 使用一个额外的数组 `counts`,其中 `counts[i]` 表示原数组 `arr` 中值等于 `i` 的元素个数。然后根据数组 `counts` 来将 `arr` 中的元素排到正确的位置。 ## 2. 计数排序算法步骤 -- 找出待排序数组中最大值元素和最小值元素。 -- 统计数组中每个值为 `i` 的元素出现的次数,存入数组的第 `i` 项。 -- 对所有的计数累加(从 `counts` 中的第一个元素开始,每一项和前一项累加)。 -- 反向填充目标数组:将每个元素 `i` 放在新数组的第 `counts[i]` 项,每放一个元素就要将 `counts[i] -= 1`。 +1. 找出待排序序列中最大值元素 `arr_max` 和最小值元素 `arr_min`。 +2. 定义大小为 `arr_max - arr_min + 1` 的数组 `counts`,初始时,`counts` 中元素值全为 `0`。 +3. 遍历数组 `arr`,统计值为 `num` 的元素出现的次数。将其次数存入 `counts` 数组的第 `num - arr_min` 项(`counts[num - arr_min]` 表示元素值 `num` 出现的次数)。 +4. 对所有的计数累加,从 `counts` 中的第一个元素开始,每一项和前一项相加。此时 `counts[i]` 表示值为 `i` 的元素排名。 +5. 反向填充目标数组: + 1. 逆序遍历数组 `arr`。对于每个元素值 `arr[i]`,其对应排名为 `counts[arr[i] - arr_min]`。 + 2. 根据排名,将 `arr[i]` 放在数组对应位置(因为数组下标是从 `0` 开始的,所以对应位置为排名减 `1`)。即 `res[counts[arr[i] - arr_min] - 1] = arr[i]`。 + 3. 放入之后, 将 `arr[i]` 的对应排名减 `1`,即 `counts[arr[i] - arr_min] -= 1`。 ## 3. 计数排序动画演示 @@ -17,28 +21,36 @@ ## 4. 计数排序算法分析 -- 当输入元素是 `n` 个 `0 ~ k` 之间的整数时,计数排序的时间复杂度为 $O(n + k)$。 -- 由于用于计数的数组 `counts` 的长度取决于待排序数组中数据的范围(等于待排序数组最大值减去最小值再加 `1`)。所以计数排序对于数据范围很大的数组,需要大量的时间和内存。 -- 计数排序一般用于排序整数,不适用于按字母顺序排序人名。 -- 计数排序是 **稳定排序算法**。 +- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序序列的值域。 +- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。由于用于计数的数组 `counts` 的长度取决于待排序数组中数据的范围(大小等于待排序数组最大值减去最小值再加 `1`)。所以计数排序算法对于数据范围很大的数组,需要大量的内存。 +- **计数排序适用情况**:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。 +- **排序稳定性**:计数排序是一种 **稳定排序算法**。 ## 5. 计数排序代码实现 ```Python class Solution: def countingSort(self, arr): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1 size = arr_max - arr_min + 1 counts = [0 for _ in range(size)] - + + # 统计值为 num 的元素出现的次数 for num in arr: counts[num - arr_min] += 1 + + # 计算元素排名 for j in range(1, size): counts[j] += counts[j - 1] + # 反向填充目标数组 res = [0 for _ in range(len(arr))] for i in range(len(arr) - 1, -1, -1): + # 根据排名,将 arr[i] 放在数组对应位置 res[counts[arr[i] - arr_min] - 1] = arr[i] + # 将 arr[i] 的对应排名减 1 counts[arr[i] - arr_min] -= 1 return res diff --git a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md b/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md index 947e7793..97af6393 100644 --- a/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md +++ b/Contents/01.Array/02.Array-Sort/09.Array-Bucket-Sort.md @@ -1,15 +1,15 @@ ## 1. 桶排序算法思想 -> 桶排序(Bucket Sort)基本思想: +> **桶排序(Bucket Sort)基本思想**: > -> 将未排序的数组分到若干个「桶」中,每个桶的元素再进行单独排序。 +> 将未排序数组分到若干个「桶」中,每个桶的元素再进行单独排序。 ## 2. 桶排序算法步骤 -- 将区间划分为 `n` 个相同大小的子区间,每个区间称为一个桶。 -- 遍历数组,将每个元素装入对应的桶中。 -- 对每个桶内的元素单独排序(使用插入、归并、快排等算法)。 -- 最后按照顺序将桶内的元素合并起来。 +1. 根据原始数组的值域范围,将数组划分为 `k` 个相同大小的子区间,每个区间称为一个桶。 +2. 遍历原始数组元素,将每个元素装入对应区间的桶中。 +3. 对每个桶内的元素单独排序(使用插入、归并、快排等算法)。 +4. 最后按照区间顺序将桶内的元素合并起来,完成排序。 ## 3. 桶排序图解演示 @@ -27,33 +27,42 @@ ## 4. 桶排序算法分析 -- 桶排序可以在线性时间内完成排序,当输入元素个数为 `n`,桶的个数是 `m` 时,每个桶里的数据就是 `k = n / m` 个。每个桶内排序的时间复杂度为 $O(k * log_2k)$。`m` 个桶就是 $m * O(k * log_2k) = m * O((n/m)*log_2(n/m)) = O(n*log_2(n/m))$。当桶的个数 `m` 接近于数据个数 `n` 时,$log_2(n/m)$ 就是一个较小的常数,所以排序桶排序时间复杂度接近于 $O(n)$。 -- 由于桶排序使用了辅助空间,所以桶排序的空间复杂度是 $o(n + m)$。 -- 如果桶内使用插入排序算法等稳定排序算法,则桶排序也是 **稳定排序算法**。 +- **时间复杂度**:$O(n)$。当输入元素个数为 $n$,桶的个数是 $m$ 时,每个桶里的数据就是 $k = n / m$ 个。每个桶内排序的时间复杂度为 $O(k \times \log_2 k)$。$m$ 个桶就是 $m * O(k * log_2k) = m \times O((n / m) \times \log_2(n/m)) = O(n*log_2(n/m))$。当桶的个数 $m$ 接近于数据个数 $n$ 时,$log_2(n/m)$ 就是一个较小的常数,所以排序桶排序时间复杂度接近于 $O(n)$。 +- **空间复杂度**:$O(n + m)$。由于桶排序使用了辅助空间,所以桶排序的空间复杂度是 $O(n + m)$。 +- **排序稳定性**:如果桶内使用插入排序算法等稳定排序算法,则桶排序也是一种 **稳定排序算法**。 ## 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 def bucketSort(self, arr, bucket_size=5): + # 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min arr_min, arr_max = min(arr), max(arr) + # 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 + 1 bucket_count = (arr_max - arr_min) // bucket_size + 1 + # 定义桶数组 buckets buckets = [[] for _ in range(bucket_count)] + # 遍历原始数组元素,将每个元素装入对应区间的桶中 for num in arr: buckets[(num - arr_min) // bucket_size].append(num) + # 对每个桶内的元素单独排序,并合并到 res 数组中 res = [] for bucket in buckets: self.insertionSort(bucket) diff --git a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md index d6867e9f..e82fadd1 100644 --- a/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md +++ b/Contents/01.Array/02.Array-Sort/10.Array-Radix-Sort.md @@ -1,6 +1,6 @@ ## 1. 基数排序算法思想 -> 基数排序(Radix Sort)基本思想: +> **基数排序(Radix Sort)基本思想**: > > 将整数按位数切割成不同的数字,然后按每个位数分别比较进行排序。 @@ -10,10 +10,10 @@ 下面我们以最低位优先法为例,讲解一下算法步骤。 -- 遍历数组元素,获取数组最大值元素,并取得位数。 -- 以个位元素为索引,对数组元素排序。 -- 合并数组。 -- 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,进行排序,并合并数组,最终完成排序。 +1. 遍历数组元素,获取数组最大值元素,并取得位数。 +2. 以个位元素为索引,对数组元素排序。 +3. 合并数组。 +4. 之后依次以十位,百位,…,最大值元素的最高位 为索引,进行排序,并合并数组,最终完成排序。 ## 3. 基数排序动画演示 @@ -21,22 +21,28 @@ ## 4. 基数排序算法分析 -- 基数排序的时间复杂度是 $O(k * n)$。其中 `n` 是待排序元素的个数,`k` 是数字位数。`k` 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 -- 基数排序的空间复杂度是 $O(n + k)$。 -- 基数排序是 **稳定排序算法**。 +- **时间复杂度**:$O(k * n)$。其中 $n$ 是待排序元素的个数,$k$ 是数字位数。$k$ 的大小取决于数字位的选择(十进制位、二进制位)和待排序元素所属数据类型全集的大小。 +- **空间复杂度**:$O(n + k)$。 +- **排序稳定性**:基数排序是一种 **稳定排序算法**。 ## 5. 基数排序代码实现 ```Python class Solution: def radixSort(self, arr): + # 桶的大小为所有元素的最大位数 size = len(str(max(arr))) + # 从低位到高位依次遍历每一位,以各个数位值为索引,对数组进行按数位排序 for i in range(size): + # 使用一个长度为 10 的桶来存放各个位上的元素 buckets = [[] for _ in range(10)] + # 遍历数组元素,根据元素对应位上的值,将其存入对应位的桶中 for num in arr: buckets[num // (10 ** i) % 10].append(num) + # 清空原始数组 arr.clear() + # 从桶中依次取出对应元素,并重新加入到原始数组 for bucket in buckets: for num in bucket: arr.append(num)