草木不争高,争的是生生不息
------ 25.5.26
选择排序回顾
① 遍历数组:从索引 0
到 n-1
(n
为数组长度)。
② 每轮确定最小值:假设当前索引 i
为最小值索引 min_index
。从 i+1
到 n-1
遍历,若找到更小元素,则更新 min_index
。
③ 交换元素:若 min_index ≠ i
,则交换 arr[i]
与 arr[min_index]
。
python
'''
① 遍历数组:从索引 0 到 n-1(n 为数组长度)。
② 每轮确定最小值:假设当前索引 i 为最小值索引 min_index。从 i+1 到 n-1 遍历,若找到更小元素,则更新 min_index。
③ 交换元素:若 min_index ≠ i,则交换 arr[i] 与 arr[min_index]。
'''
def selectionSort(arr: List[int]):
n = len(arr)
for i in range(n):
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
if min_index != i:
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
冒泡排序回顾
① 初始化:设数组长度为 n
。
② 外层循环:遍历 i
从 0
到 n-1
(共 n
轮)。
③ 内层循环:对于每轮 i
,遍历 j
从 0
到 n-i-2
。
④ 比较与交换:若 arr[j] > arr[j+1]
,则交换两者。
⑤ 结束条件:重复步骤 2-4,直到所有轮次完成。
python
'''
① 初始化:设数组长度为 n。
② 外层循环:遍历 i 从 0 到 n-1(共 n 轮)。
③ 内层循环:对于每轮 i,遍历 j 从 0 到 n-i-1。
④ 比较与交换:若 arr[j] > arr[j+1],则交换两者。
⑤ 结束条件:重复步骤 2-4,直到所有轮次完成。
'''
def bubbleSort(arr: List[int]):
n = len(arr)
for i in range(n):
for j in range(n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
插入排序回顾
① 遍历未排序元素:从索引 1
到 n-1
。
② 保存当前元素:将 arr[i]
存入 current
。
③ 元素后移:从已排序部分的末尾(索引 j = i-1
)向前扫描,将比 current
大的元素后移。直到找到第一个不大于 current
的位置或扫描完所有元素。
④ 插入元素:将 current
放入 j+1
位置。
python
'''
① 遍历未排序元素:从索引 1 到 n-1。
② 保存当前元素:将 arr[i] 存入 current。
③ 元素后移:从已排序部分的末尾(索引 j = i-1)向前扫描,将比 current 大的元素后移。直到找到第一个不大于 current 的位置或扫描完所有元素。
④ 插入元素:将 current 放入 j+1 位置。
'''
def insertSort(arr: List[int]):
n = len(arr)
for i in range(n):
current = arr[i]
j = i - 1
while current < arr[j] and j >0:
arr[j+1] = arr[j]
j -= 1
arr[j + 1] = current
return arr
计数排序回顾
① 初始化:设数组长度为 n
,元素最大值为 r
。创建长度为 r+1
的计数数组 count
,初始值全为 0。
② 统计元素频率:遍历原数组 arr
,对每个元素 x,
将 count[x]
加 1。
③ 重构有序数组:初始化索引 index = 0
。遍历计数数组 count
,索引 v
从 0 到 r,
若 count[v] > 0
,则将 v
填入原数组 arr[index]
,并将 index
加 1。count[v] - 1,重复此步骤直到 count[v]
为 0。
④ 结束条件:当计数数组遍历完成时,排序结束。
python
'''
输入全为非负整数,且所有元素 ≤ r
① 初始化:设数组长度为 n,元素最大值为 r。创建长度为 r+1 的计数数组 count,初始值全为 0。
② 统计元素频率:遍历原数组 arr,对每个元素 x,将 count[x] 加 1。
③ 重构有序数组:初始化索引 index = 0。遍历计数数组 count,索引 v 从 0 到 r,
若 count[v] > 0,则将 v 填入原数组 arr[index],并将 index 加 1。
count[v] - 1,重复此步骤直到 count[v] 为 0。
④ 结束条件:当计数数组遍历完成时,排序结束。
'''
def countingSort(arr: List[int], r: int):
# count = [0] * len(r + 1)
count = [0 for i in range(r + 1)]
for x in arr:
count[x] += 1
index = 0
for v in range(r + 1):
while count[v] > 0:
arr[index] = v
index += 1
count[v] -= 1
return arr
归并排序回顾
Ⅰ、递归分解列表
**① 终止条件:**若链表为空或只有一个节点(head is None
或 head.next is None
),直接返回头节点。
**② 快慢指针找中点:**初始化 slow
和 fast
指针,slow
指向头节点,fast
指向头节点的下一个节点。fast
每次移动两步,slow
每次移动一步。当 fast
到达末尾时,slow
恰好指向链表的中间节点。
**③ 分割链表:**将链表从中点断开,head2
指向 slow.next
(后半部分的头节点)。将 slow.next
置为 None
,切断前半部分与后半部分的连接。
**④ 递归排序子链表:**对前半部分(head
)和后半部分(head2
)分别递归调用 mergesort
函数。
Ⅱ、合并两个有序列表
① 创建虚拟头节点:创建一个值为 -1
的虚拟节点 zero
,用于简化边界处理。使用 current
指针指向 zero
,用于构建合并后的链表。
**② 比较并合并节点:**遍历两个子链表 head1
和 head2
,比较当前节点的值:若 head1.val <= head2.val
,将 head1
接入合并链表,并移动 head1
指针。否则,将 head2
接入合并链表,并移动 head2
指针。每次接入节点后,移动 current
指针到新接入的节点。
**③ 处理剩余节点:**当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。
**④ 返回合并后的链表:**虚拟节点 zero
的下一个节点即为合并后的有序链表的头节点。
python
'''
Ⅰ、递归分解列表
① 终止条件:若链表为空或只有一个节点(head is None 或 head.next is None),直接返回头节点。
② 快慢指针找中点:初始化 slow 和 fast 指针,slow 指向头节点,fast 指向头节点的下一个节点。fast 每次移动两步,slow 每次移动一步。当 fast 到达末尾时,slow 恰好指向链表的中间节点。
③ 分割链表:将链表从中点断开,head2 指向 slow.next(后半部分的头节点)。将 slow.next 置为 None,切断前半部分与后半部分的连接。
④ 递归排序子链表:对前半部分(head)和后半部分(head2)分别递归调用 mergesort 函数。
Ⅱ、合并两个有序列表
① 创建虚拟头节点:创建一个值为 -1 的虚拟节点 zero,用于简化边界处理。使用 current 指针指向 zero,用于构建合并后的链表。
② 比较并合并节点:遍历两个子链表 head1 和 head2,比较当前节点的值:若 head1.val <= head2.val,将 head1 接入合并链表,并移动 head1 指针。否则,将 head2 接入合并链表,并移动 head2 指针。每次接入节点后,移动 current 指针到新接入的节点。
③ 处理剩余节点:当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。
④ 返回合并后的链表:虚拟节点 zero 的下一个节点即为合并后的有序链表的头节点。
'''
def mergesort(self, head: ListNode):
if head is None or head.next is None:
return head
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
head2 = slow.next
slow.next = None
return self.merge(self.mergesort(head), self.mergesort(head2))
def merge(self, head1: ListNode, head2: ListNode):
zero = ListNode(-1)
current = zero
while head1 and head2:
if head1.val <= head2.val:
current.next = head1
head1 = head1.next
else:
current.next = head2
head2 = head2.next
current = current.next
current.next = head1 if head1 else head2
return zero.next
快速排序回顾
Ⅰ、分区函数 Partition
**① 随机选择基准元素:**根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较
**② 交换基准元素:**将基准元素移动到最左边,将基准元素存储在变量中,
**③ 分区操作:**对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边
**④ 返回基准元素的最终位置:**循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它
Ⅱ、递归排序函数
**① 定义递归终止条件:**当左索引小于右索引时,结束递归
② 分区操作: 执行第一次分区操作,找到基准元素
**③ 递归调用分区函数:**将基准元素的左边、右边部分分别传入递归函数进行排序
python
'''
Ⅰ、分区函数 Partition
① 随机选择基准元素:根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较
② 交换基准元素:将基准元素移动到最左边,将基准元素存储在变量中,
③ 分区操作:对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边
④ 返回基准元素的最终位置:循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它
Ⅱ、递归排序函数
① 定义递归终止条件:当左索引小于右索引时,结束递归
② 分区操作: 执行第一次分区操作,找到基准元素
③ 递归调用分区函数:将基准元素的左边、右边部分分别传入递归函数进行排序
'''
def Partition(arr, left, right):
idx = random.randint(left, right)
arr[left], arr[idx] = arr[idx], arr[left]
l = left
r = right
x = arr[l]
while l < r:
while l < r and x < arr[r]:
r -= 1
if l < r:
arr[l], arr[r] = arr[r], arr[l]
l += 1
while l < r and x > arr[l]:
l += 1
if l < r:
arr[l], arr[r] = arr[r], arr[l]
r -= 1
return l
def quickSort(arr, l, r):
if l >= r:
return
node = self.quickSort(l, r)
self.quickSort(arr, l, node-1)
self.quickSort(arr, node+1, r)
return arr
一、引言
桶排序(Bucket Sort)是一种基于计数的排序算法,工作原理是将数据分到有限数量的桶子里,然后每个桶再分别排序(可以使用别的排序算法或是以递归方式继续使用桶排序进行排序)
二、算法思想
**第一步:**设置固定数量的空桶。
**第二步:**把数据放到对应的桶中。
**第三步:**对每个不为空的桶中数据进行排序。
**第四步:**拼接不为空的桶中数据,得到结果。
三、算法分析
1.时间复杂度
桶排序属于一种组合排序,因为分入桶中的元素,可以采用任意一种排序,所以时间复杂度还是取决于每个桶中的元素,采用什么排序。
2.空间复杂度
由于采用了额外的桶空间,并且每个元素都需要放入桶中,最多存储的个数就是元素个数,所以空间复杂度为O(n)。
3.算法的优点
**① 排序速度快:**适用于一定范围内的整数或浮点数排序,不需要比较元素之间的大小,因此排序速度较快。
**② 稳定性:**桶排序是一种稳定的排序算法。
4.算法的缺点
**① 占用内存空间:**当数据范围较大时,需要的桶数量较多,会消耗较多的内存空间。
**② 不适合大规模数据:**桶排序是一种线性排序,不适合大规模数据排序。
四、实战练习
451. 根据字符出现频率排序
给定一个字符串
s
,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。返回 已排序的字符串。如果有多个答案,返回其中任何一个。
示例 1:
输入: s = "tree" 输出: "eert" 解释: 'e'出现两次,'r'和't'都只出现一次。 因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入: s = "cccaaa" 输出: "cccaaa" 解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。 注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入: s = "Aabb" 输出: "bbAa" 解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。 注意'A'和'a'被认为是两种不同的字符。
提示:
1 <= s.length <= 5 * 10^5
s
由大小写英文字母和数字组成
思路与算法
Ⅰ、桶排序
**① 初始化桶和频率数组:**创建字符长度+1的桶bucket,索引 i 表示频率为 i 的字符列表;长度为max的频率数组count,用于记录每个字符的出现次数
**② 统计字符频率:**通过 ord(char) 获取字符的ASCII码,作为频率数组的索引
**③ 将字符按照频率放入桶中:**遍历频率数组,将每个字符以频率作为索引放入数组中
**④ 返回桶数组:**返回桶数组,其中每个桶包含对应频率的字符列表
Ⅱ、频率排序
**① 调用桶排序生成桶数组:**桶数组 bucket
的索引表示频率,每个桶存储对应频率的字符列表
**② 逆序遍历桶数组生成结果字符串:**从最高频率(len(s)
)开始,确保先处理频率高的字符。对于频率为 i
的每个字符,重复 i
次并拼接到结果字符串。
python
class Solution:
def bucketSort(self, arr, max):
n = len(arr)
bucket = [ [] for i in range(n + 1)]
count = [0 for i in range(max)]
for i in range(n):
count[ord(arr[i])] += 1
for i in range(max):
temp = count[i]
bucket[temp].append(chr(i))
return bucket
def frequencySort(self, s: str) -> str:
bucket = self.bucketSort(s, 256)
ans = ''
# 逆序遍历桶
for i in range(len(s), 0, -1):
for j in bucket[i]:
for k in range(i):
ans += j
return ans
