冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历待排序的元素列表,一次比较相邻的两个元素,并按照顺序交换它们,直到整个列表排序完成。
基本步骤
下面是冒泡排序的基本步骤:
冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,其中n是待排序列表的长度。虽然冒泡排序的效率相对较低,但对于简单的排序任务或较小的数据集来说,它仍然是一个可行的选择。
代码实现
Python实现冒泡排序算法的示例代码
def bubble_sort(arr):
n = len(arr)
# 遍历数组元素
for i in range(n):
# 每次遍历都会将最大的元素放到末尾,所以只需遍历到 n-i-1
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
# 测试示例
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = bubble_sort(arr)
print("排序后的数组:", sorted_arr)
代码优化
下面是一个经过优化的冒泡排序算法的示例代码:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
# 初始化标志位,表示本次遍历是否发生了交换
swapped = False
for j in range(n-i-1):
if arr[j] > arr[j+1]:
# 交换元素位置
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
# 如果本次遍历没有发生交换,说明列表已经有序,提前结束排序
if not swapped:
break
return arr
# 测试示例
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = bubble_sort(arr)
print("排序后的数组:", sorted_arr)
在这个优化的代码中,添加了一个 swapped 标志位 来记录每次遍历是否发生了交换。如果某次遍历没有发生交换,说明列表已经有序,可以提前结束排序。这样可以减少不必要的比较次数,从而节约时间。
算法分析
最佳情况时间复杂度:
O
(
n
)
O(n)
O(n)
当待排序的列表已经是有序的情况下,冒泡排序只需进行一次遍历,就可以确定列表已经有序,时间复杂度为
O
(
n
)
O(n)
O(n) 。
最坏情况时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
当待排序的列表是逆序的情况下,冒泡排序需要进行
n
−
1
n-1
n−1 次遍历,每次遍历需要比较相邻元素并可能进行交换,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2) 。
平均情况时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
在平均情况下,冒泡排序需要进行
n
−
1
n-1
n−1 次遍历,每次遍历需要比较相邻元素并可能进行交换,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2) 。
空间复杂度:
O
(
1
)
O(1)
O(1)
冒泡排序是一种原地排序算法,不需要额外的空间来存储临时数据,所以空间复杂度为
O
(
1
)
O(1)
O(1) 。
稳定性:
冒泡排序是一种稳定的排序算法,相等元素的相对顺序在排序后不会改变。
选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是每次从待排序的数据中选择最小(或最大)的元素,放到已排序的序列的末尾,直到全部待排序的数据元素排完为止。
基本步骤
选择排序的步骤如下:
选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,其中 n n n 是待排序序列的长度。虽然选择排序的时间复杂度较高,但是它的实现简单,适合小规模数据。
代码实现
当然,以下是选择排序的Python代码实现:
def selection_sort(arr):
n = len(arr)
for i in range(n-1):
# 找到未排序部分的最小元素索引
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
# 将最小元素与当前位置交换
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
代码优化
def selection_sort(arr):
n = len(arr)
for i in range(n-1):
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
在优化后的代码中,加入了一个判断,只有当找到的最小元素索引不等于当前位置时,才进行交换。这样可以避免不必要的交换操作,减少了一些冗余操作。
算法分析
时间复杂度:选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,其中n是待排序序列的长度。在选择排序的过程中,需要进行 n − 1 n-1 n−1 次迭代,每次迭代都需要在未排序的部分中找到最小(或最大)的元素,这需要遍历未排序部分,时间复杂度为 O ( n ) O(n) O(n) 。因此,总的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。
空间复杂度:选择排序的空间复杂度为 O ( 1 ) O(1) O(1) ,因为它只需要使用常数级别的额外空间来存储临时变量和交换元素。
稳定性:选择排序是一种不稳定的排序算法,即相等元素的相对顺序可能会发生改变。
插入排序(Insertion Sort)是一种简单直观的排序算法。它的基本思想是将待排序序列分成已排序区间和未排序区间,初始时已排序区间只有一个元素。然后,依次将未排序区间的元素插入到已排序区间的合适位置,直到全部元素都插入完毕。
基本步骤
插入排序的步骤如下:
插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,其中 n n n 是待排序序列的长度。虽然插入排序的时间复杂度较高,但是它的实现简单,对于小规模的数据排序是一种较为合适的选择。此外,插入排序是稳定的排序算法,即相等元素的相对顺序不会发生改变。
代码实现
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
# 将比key大的元素后移一位
while j >= 0 and arr[j] > key:
arr[j+1] = arr[j]
j -= 1
# 插入key到合适位置
arr[j+1] = key
return arr
算法分析
时间复杂度:插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,其中n是待排序序列的长度。在插入排序的过程中,需要进行 n − 1 n-1 n−1 次迭代,每次迭代都需要将当前元素插入到已排序部分的合适位置。在最坏情况下,每次迭代都需要将当前元素与已排序部分的所有元素比较,因此内循环的时间复杂度为 O ( i ) O(i) O(i) ,其中i是当前迭代的索引。所以总的时间复杂度为 O ( 1 + 2 + 3 + . . . + n − 1 ) O(1+2+3+...+n-1) O(1+2+3+...+n−1) ,即 O ( n 2 ) O(n^2) O(n2) 。
空间复杂度:插入排序的空间复杂度为 O ( 1 ) O(1) O(1) ,因为它只需要使用常数级别的额外空间来存储临时变量和交换元素。
稳定性:插入排序是一种稳定的排序算法,即相等元素的相对顺序不会发生改变。
归并排序(Merge Sort)是一种常见的排序算法,采用分治法(Divide and Conquer)的思想。它将待排序的序列分成两个子序列,分别对两个子序列进行排序,然后将两个已排序的子序列合并成一个有序序列。
基本步骤
具体步骤如下:
归并排序的关键在于合并操作。合并操作的步骤如下:
代码实现
def merge_sort(arr):
if len(arr) <= 1:
return arr
# 将数组分成两半
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 递归地对左右两半进行排序
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
# 合并左右两半
return merge(left_half, right_half)
def merge(left, right):
merged = []
left_index = 0
right_index = 0
# 依次比较左右两个数组的元素,将较小的元素添加到合并数组中
while left_index < len(left) and right_index < len(right):
if left[left_index] < right[right_index]:
merged.append(left[left_index])
left_index += 1
else:
merged.append(right[right_index])
right_index += 1
# 将剩余的元素添加到合并数组中
while left_index < len(left):
merged.append(left[left_index])
left_index += 1
while right_index < len(right):
merged.append(right[right_index])
right_index += 1
return merged
# 测试代码
arr = [5, 2, 8, 6, 1, 9, 3, 7, 4]
sorted_arr = merge_sort(arr)
print(sorted_arr)
输出结果为:[1, 2, 3, 4, 5, 6, 7, 8, 9]
算法分析
希尔排序(Shell Sort)是一种基于插入排序的排序算法,它通过比较相距一定间隔的元素来对数据进行排序。希尔排序先将整个待排序的序列分割成若干个子序列,分别进行插入排序,然后逐步缩小间隔,最终使整个序列变为有序。
基本步骤
具体步骤如下:
代码实现
def shell_sort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap //= 2
return arr
算法分析
时间复杂度:希尔排序的时间复杂度是个开放问题,尚未得到完全确定的结论。这是因为希尔排序的性能取决于所选择的增量序列。一些增量序列可以使希尔排序的时间复杂度接近 O ( n ) O(n) O(n),而其他增量序列则可能导致时间复杂度接近 O ( n 2 ) O(n^2) O(n2)。最坏情况下,希尔排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。这种情况发生在增量序列不好的情况下,例如使用常见的Hibbard增量序列( 2 k − 1 2^k - 1 2k−1)。最好情况下,希尔排序的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn),这种情况发生在增量序列按照Knuth增量序列( 3 k − 1 3^k - 1 3k−1)选择时。
空间复杂度:希尔排序的空间复杂度为O(1),因为它只需要使用常数级别的额外空间来存储临时变量。
稳定性:希尔排序是一种不稳定的排序算法,即相等元素的相对顺序可能在排序过程中发生改变。
快速排序(Quick Sort)是一种常用的排序算法,它的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到整个数据变成有序序列。
基本步骤
代码实现
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# 测试代码
arr = [5, 2, 8, 3, 1, 6, 9, 4, 7]
sorted_arr = quick_sort(arr)
print(sorted_arr)
算法分析
时间复杂度:快速排序的平均时间复杂度为 O ( n log n ) O(n\log n) O(nlogn),其中 n n n 为待排序数组的长度。在最坏情况下,快速排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,但这种情况发生的概率较低。
空间复杂度:快速排序的空间复杂度为 O ( log n ) O(\log n) O(logn) ,因为需要使用递归来实现划分和排序操作,递归调用的栈空间会占用一定的额外空间。
稳定性:快速排序是一种不稳定的排序算法,即在排序过程中,相同元素的相对顺序可能会改变。这是因为在划分操作中,相同元素可能会被分到不同的子数组中。
堆排序(Heap Sort)是一种基于堆数据结构的排序算法。它的基本思想是将待排序的数据构建成一个最大堆(或最小堆),然后反复从堆顶取出最大(或最小)元素,将其与堆的最后一个元素交换位置,并调整堆,使剩余元素重新构成一个最大(或最小)堆。通过不断重复这个过程,最终得到一个有序的数组。
基本步骤
堆排序的步骤如下:
构建最大堆:将待排序的数组看作一个完全二叉树,从最后一个非叶子节点开始,依次向上调整每个子树,使其满足最大堆的性质。最大堆的性质是父节点的值大于等于其左右子节点的值。
取出最大元素:将堆顶元素(最大值)与堆的最后一个元素交换位置,并将堆的大小减1。
调整堆:对交换后的堆顶元素进行下沉操作,将其与左右子节点中较大的节点交换位置,直到满足最大堆的性质。
重复步骤2和步骤3,直到堆的大小为1,即所有元素都已排序完毕。
以下是一个示例,以数组 [5, 2, 8, 3, 1, 6, 9, 4, 7] 进行堆排序的步骤说明:
构建最大堆:将数组构建成最大堆,得到 [9, 7, 8, 4, 1, 6, 5, 3, 2] 。
取出最大元素:将堆顶元素 9 与堆的最后一个元素 2 交换位置,得到 [2, 7, 8, 4, 1, 6, 5, 3, 9] 。
调整堆:对交换后的堆顶元素 2 进行下沉操作,与左子节点 7 交换位置,得到 [7, 2, 8, 4, 1, 6, 5, 3, 9] 。
取出最大元素:将堆顶元素 7 与堆的最后一个元素 3 交换位置,得到 [3, 2, 8, 4, 1, 6, 5, 7, 9] 。
调整堆:对交换后的堆顶元素 3 进行下沉操作,与左子节点 8 交换位置,得到 [8, 2, 3, 4, 1, 6, 5, 7, 9] 。
重复步骤2和步骤3,直到堆的大小为1,即所有元素都已排序完毕。最终得到有序数组 [1, 2, 3, 4, 5, 6, 7, 8, 9] 。
代码实现
以下是使用Python语言实现堆排序的代码:
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
# 构建最大堆
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 逐个取出最大元素并调整堆
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, i, 0)
return arr
# 测试代码
arr = [5, 2, 8, 3, 1, 6, 9, 4, 7]
sorted_arr = heap_sort(arr)
print(sorted_arr)
算法分析
时间复杂度:堆排序的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn),其中n为待排序数组的长度。这是因为构建最大堆的过程需要 O ( n ) O(n) O(n) 的时间复杂度,而对于每个节点进行下沉操作的时间复杂度为 O ( log n ) O(\log n) O(logn) 。在最坏情况下,堆排序的时间复杂度也为 O ( n log n ) O(n \log n) O(nlogn)。
空间复杂度:堆排序的空间复杂度为 O ( 1 ) O(1) O(1) ,因为排序过程中只需要使用常数级别的额外空间。
稳定性:堆排序是一种不稳定的排序算法,即相同元素的相对顺序可能会改变。这是因为在构建最大堆和调整堆的过程中,元素的位置会发生改变。
计数排序(Counting Sort)是一种线性时间复杂度的排序算法,它适用于待排序元素的范围较小的情况。计数排序的基本思想是统计待排序数组中每个元素出现的次数,然后根据统计结果将元素放置到正确的位置上,从而得到有序的数组。
基本步骤
计数排序的步骤如下:
统计元素出现次数:遍历待排序数组,统计每个元素出现的次数,并将统计结果存储在一个辅助数组中。
累加统计次数:对辅助数组进行累加操作,使得辅助数组中的每个元素表示小于等于该元素的元素个数。
生成有序数组:遍历待排序数组,根据元素的值在辅助数组中找到对应的位置,将元素放置到正确的位置上,并更新辅助数组中的统计次数。
代码实现
def counting_sort(arr):
# 确定待排序数组的最大值和最小值
min_val = min(arr)
max_val = max(arr)
# 统计元素出现的次数
count = [0] * (max_val - min_val + 1)
for num in arr:
count[num - min_val] += 1
# 累加统计次数
for i in range(1, len(count)):
count[i] += count[i - 1]
# 生成有序数组
sorted_arr = [0] * len(arr)
for num in reversed(arr):
index = count[num - min_val] - 1
sorted_arr[index] = num
count[num - min_val] -= 1
return sorted_arr
算法分析
时间复杂度:计数排序的时间复杂度为 O ( n + k ) O(n+k) O(n+k),其中 n n n 为待排序数组的长度, k k k 为待排序元素的范围。在最坏情况下,即待排序元素的范围很大且与待排序数组长度接近时,计数排序的时间复杂度接近 O ( n ) O(n) O(n) 。
空间复杂度:计数排序的空间复杂度为 O ( n + k ) O(n+k) O(n+k) ,其中 n n n 为待排序数组的长度, k k k 为待排序元素的范围。这是因为需要使用一个辅助数组来存储统计结果和生成有序数组。
稳定性:计数排序是一种稳定的排序算法,即相同元素的相对顺序在排序后保持不变。
桶排序(Bucket Sort)是一种线性时间复杂度的排序算法,它适用于待排序元素服从均匀分布的情况。桶排序的基本思想是将待排序的元素分配到不同的桶中,每个桶内的元素进行排序,然后按照桶的顺序依次取出元素,得到有序的数组。
基本步骤
桶排序的步骤如下:
确定桶的数量和范围:根据待排序元素的范围和分布情况,确定桶的数量和每个桶的范围。
将元素分配到桶中:遍历待排序数组,将每个元素根据其值分配到对应的桶中。
桶内排序:对每个桶内的元素进行排序。可以使用其他排序算法,如插入排序、快速排序等。
合并结果:按照桶的顺序依次取出每个桶内的元素,将其合并到一个有序的数组中。
代码实现
def bucket_sort(arr):
# 确定桶的数量和范围
min_val = min(arr)
max_val = max(arr)
bucket_size = 5
# 计算桶的数量
bucket_count = (max_val - min_val) // bucket_size + 1
# 创建桶
buckets = [[] for _ in range(bucket_count)]
# 将元素分配到桶中
for num in arr:
index = (num - min_val) // bucket_size
buckets[index].append(num)
# 桶内排序
for bucket in buckets:
bucket.sort()
# 合并结果
sorted_arr = []
for bucket in buckets:
sorted_arr.extend(bucket)
return sorted_arr
算法分析
基数排序(Radix Sort)是一种非比较排序算法,它根据元素的每个位上的数值进行排序。基数排序的核心思想是将待排序的元素按照低位到高位的顺序依次进行排序,最终得到有序的结果。
基本步骤
基数排序的步骤如下:
代码实现
以下是基数排序的Python代码实现:
def radix_sort(arr):
# 获取数组中的最大值,确定位数
max_val = max(arr)
max_digits = len(str(max_val))
# 进行位数的循环排序
for digit in range(max_digits):
# 创建10个桶
buckets = [[] for _ in range(10)]
# 将元素按照当前位数的值放入对应的桶中
for num in arr:
digit_val = (num // (10 ** digit)) % 10
buckets[digit_val].append(num)
# 合并桶中的元素
arr = [num for bucket in buckets for num in bucket]
return arr
算法分析
思路
代码
import functools
class Solution:
def minNumber(self, nums: List[int]) -> str:
def cmp(a, b):
if a + b == b + a:
return 0
elif a + b > b + a:
return 1
else:
return -1
num_s = list(map(str, nums))
num_s.sort(key=functools.cmp_to_key(cmp))
return ''.join(num_s)
思路
快慢指针,上分~
代码
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
slow = 0 # 快慢指针
fast = 0
while fast < len(nums):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
fast += 1
思路
代码
class Solution:
def shellSort(self, nums: [int]) -> List[int]: # 希尔排序
size = len(nums)
gap = size // 2
while gap > 0:
for i in range(gap, size):
temp = nums[i]
j = i
while j >= gap and nums[j - gap] < temp:
nums[j] = nums[j - gap]
j -= gap
nums[j] = temp
gap = gap // 2
return nums
def findRelativeRanks(self, score: List[int]) -> List[str]:
res = []
nums = score.copy()
nums = self.shellSort(nums)
score_map = dict() # 新建字典,存储序号
for i in range(len(nums)):
score_map[nums[i]] = i + 1
for i in range(len(score)): # 遍历score,找出前三名
if score[i] == nums[0]:
res.append("Gold Medal")
elif score[i] == nums[1]:
res.append("Silver Medal")
elif score[i] == nums[2]:
res.append("Bronze Medal")
else:
res.append(str(score_map[score[i]]))
return res
参考文献
—— END ——
如果以上内容有任何错误或者不准确的地方,欢迎在下面 👇 留言。或者你有更好的想法,欢迎一起交流学习~~~
更多精彩内容请前往 AXYZdong的博客