1. 内部排序和外部排序
排序算法可以分为内部排序和外部排序两种。内部排序指的是排序的数据量很小,全部可以加载到内存里面进行排序。外部排序则指的是排序的数据量太大,无法一次性全部加载到内存中进行排序,需要借助外部存储器(如硬盘)来排序。
1.1 内部排序
内部排序算法基于数组的随机访问特性,按顺序处理排序数据的元素,实现排序。常见的内部排序算法包括:
冒泡排序(Bubble Sort)
选择排序(Selection Sort)
插入排序(Insertion Sort)
希尔排序(Shell Sort)
归并排序(Merge Sort)
快速排序(Quick Sort)
堆排序(Heap Sort)
计数排序(Counting Sort)
桶排序(Bucket Sort)
基数排序(Radix Sort)
1.2 外部排序
外部排序算法基于磁盘文件的顺序读写特性,将大量数据分成若干块,按顺序处理每个块,再对块间进行归并排序,最终实现排序。常见的外部排序算法包括:
多路归并排序(Multiway Merge Sort)
置换选择排序(Replacement Selection Sort)
2. 常用排序算法
以下列举了一些常用的排序算法,按照算法复杂度递增的顺序,依次为:
2.1 冒泡排序(Bubble Sort)
冒泡排序的基本思想是,从头到尾依次比较相邻两个元素的大小,若前者大于后者,则交换其位置,直到序列排好为止。
def bubble_sort(arr):
n = len(arr)
for i in range(n - 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
冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1),是一种稳定的排序算法。
2.2 选择排序(Selection Sort)
选择排序的基本思想是,从未排序的序列中选择最小的元素,将其放在已排序序列的末尾。不断重复该操作,直到整个序列排好为止。
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
选择排序的时间复杂度为O(n^2),空间复杂度为O(1),是一种不稳定的排序算法。
2.3 插入排序(Insertion Sort)
插入排序的基本思想是,将待排序元素插入到已排序序列的恰当位置(从后往前依次比较和交换),不断重复该操作,直到整个序列排好为止。
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
j = i
while j > 0 and arr[j] < arr[j - 1]:
arr[j], arr[j - 1] = arr[j - 1], arr[j]
j -= 1
return arr
插入排序的时间复杂度为O(n^2),空间复杂度为O(1),是一种稳定的排序算法。
2.4 希尔排序(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^1.3),空间复杂度为O(1),是一种不稳定的排序算法。
2.5 归并排序(Merge Sort)
归并排序的基本思想是,将待排序序列分为若干个子序列,对每个子序列进行归并排序,将已排序的子序列合并成完整的有序序列。
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
left_arr = arr[:mid]
right_arr = arr[mid:]
merge_sort(left_arr)
merge_sort(right_arr)
i = j = k = 0
while i < len(left_arr) and j < len(right_arr):
if left_arr[i] < right_arr[j]:
arr[k] = left_arr[i]
i += 1
else:
arr[k] = right_arr[j]
j += 1
k += 1
while i < len(left_arr):
arr[k] = left_arr[i]
i += 1
k += 1
while j < len(right_arr):
arr[k] = right_arr[j]
j += 1
k += 1
return arr
归并排序的时间复杂度为O(nlogn),空间复杂度为O(n),是一种稳定的排序算法。
2.6 快速排序(Quick Sort)
快速排序的基本思想是,首先选择一个元素作为基准值,然后将比基准值小的元素移动到基准值左边,比基准值大的元素移动到基准值右边,最后以基准值为分界线将数组分成两半,不断重复该过程,最终实现排序。
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr.pop()
left_arr = []
right_arr = []
for x in arr:
if x <= pivot:
left_arr.append(x)
else:
right_arr.append(x)
return quick_sort(left_arr) + [pivot] + quick_sort(right_arr)
快速排序的时间复杂度为O(nlogn),空间复杂度为O(logn),是一种不稳定的排序算法。
2.7 堆排序(Heap Sort)
堆排序的基本思想是,构建一个大根堆或小根堆,每次取出堆顶元素(即最大值或最小值),再重新调整堆的结构,直到整个序列排好为止。
def heap_sort(arr):
def build_max_heap(arr):
for i in range(len(arr) // 2, -1, -1):
heapify(arr, i, len(arr))
def heapify(arr, i, size):
left = i * 2 + 1
right = i * 2 + 2
max_index = i
if left < size and arr[left] > arr[max_index]:
max_index = left
if right < size and arr[right] > arr[max_index]:
max_index = right
if max_index != i:
arr[i], arr[max_index] = arr[max_index], arr[i]
heapify(arr, max_index, size)
build_max_heap(arr)
for i in range(len(arr) - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, 0, i)
return arr
堆排序的时间复杂度为O(nlogn),空间复杂度为O(1),是一种不稳定的排序算法。
2.8 计数排序(Counting Sort)
计数排序的基本思想是,统计序列中每个元素出现的次数,然后从小到大依次输出每个元素。
def counting_sort(arr):
n = len(arr)
max_val = max(arr)
count = [0] * (max_val + 1)
for i in range(n):
count[arr[i]] += 1
for i in range(1, len(count)):
count[i] += count[i - 1]
res = [0] * n
for i in range(n - 1, -1, -1):
res[count[arr[i]] - 1] = arr[i]
count[arr[i]] -= 1
return res
计数排序的时间复杂度为O(n + k),空间复杂度为O(k),是一种稳定的排序算法,其中k为桶的数量,取决于序列中数据的范围。
2.9 桶排序(Bucket Sort)
桶排序的基本思想是,将序列中的元素划分到不同的桶中,对每个桶中的元素进行排序,最后将所有桶中的元素依次输出。
def bucket_sort(arr):
n = len(arr)
max_val, min_val = max(arr), min(arr)
bucket_size = (max_val - min_val) // n + 1
bucket = [[] for _ in range(bucket_size)]
for i in range(n):
idx = (arr[i] - min_val) // bucket_size
bucket[idx].append(arr[i])
res = []
for i in range(bucket_size):
if bucket[i]:
bucket[i].sort()
res += bucket[i]
return res
桶排序的时间复杂度为O(n + k),空间复杂度为O(n + k),是一种稳定的排序算法。
2.10 基数排序(Radix Sort)
基数排序的基本思想是,从低位到高位逐个排序,根据每个元素对于某一位的数字进行分类和排序,最终得到一个有序序列。
def radix_sort(arr):
def counting_sort_for_radix(arr, radix):
n = len(arr)
count = [0] * 10
for i in range(n):
idx = arr[i] // radix % 10
count[idx] += 1
for i in range(1, len(count)):
count[i] += count[i - 1]
res = [0] * n
for i in range(n - 1, -1, -1):
idx = arr[i] // radix % 10
res[count[idx] - 1] = arr[i]
count[idx] -= 1
return res
max_val = max(arr)
radix = 1
while radix <= max_val:
arr = counting_sort_for_radix(arr, radix)
radix *= 10
return arr
基数排序的时间复杂度为O(d * (n + k)),空间复杂度为O(n + k),是一种稳定的排序算法,其中d为数字位数,k为桶的数量。
3. 总结
排序算法是算法中最基础、最基本的算法之一,也是每个程序员必须掌握的核心算法之一。不同的排序算法有着不同的优劣势,需要根据具体情况灵活选择。在实际应用中,我们有时需要根据数据规模、数据分布等不同情况进行选择。比如,对于小数据集,可以选择冒泡排序、插入排序等交换排序或插入排序算法。对于大数据集,可以选择快速排序、堆排序等分治排序或选择排序算法。对于数据分布均匀的情况,可以选择计数排序、桶排序等线性排序算法。对于数字较大的情况,可以选择基数排序等排序算法。
要想成为一名优秀的程序员,首先需要掌握算法和数据结构,其次需要不断练习和思考。在实际应用中,我们需要深入了解各种排序算法的原理、特点和应用场景,了解算法复杂度的概念,并在实际实现中注意一些常见的问题,如空间复杂度、递归调用等。