排序算法是计算机科学中的基础内容,用于将一组数据按特定顺序排列。以下是对常见排序算法的详细解析,包括实现代码(以 Python 为例)、时间复杂度分析以及特点。算法按复杂度递增顺序介绍,涵盖简单排序、进阶排序和特殊场景优化排序。
---
1. 冒泡排序 (Bubble Sort)
原理
冒泡排序通过重复比较相邻元素并交换位置,逐步将最大(或最小)元素“冒泡”到数组一端。
实现
```python
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, 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
```
时间复杂度
- 最优:O(n)(数组已排序,优化后提前退出)
- 平均:O(n²)
- 最坏:O(n²)
- 空间复杂度:O(1)(原地排序)
特点
- 简单易实现,适合小规模数据。
- 稳定性:稳定(相同元素相对顺序不变)。
- 缺点:效率低,比较和交换次数多。
---
2. 选择排序 (Selection Sort)
原理
选择排序每次从未排序部分选出最小(或最大)元素,放到已排序部分的末尾。
实现
```python
def selection_sort(arr):
n = len(arr)
for i in range(n):
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²)
- 平均:O(n²)
- 最坏:O(n²)
- 空间复杂度:O(1)
特点
- 交换次数少于冒泡排序(最多 n-1 次)。
- 稳定性:不稳定(可能改变相同元素的相对顺序)。
- 适用场景:数据量小或交换代价高。
---
3. 插入排序 (Insertion Sort)
原理
插入排序将数组分为已排序和未排序两部分,依次将未排序元素插入到已排序部分的正确位置。
实现
```python
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
```
时间复杂度
- 最优:O(n)(数组接近有序)
- 平均:O(n²)
- 最坏:O(n²)
- 空间复杂度:O(1)
特点
- 稳定性:稳定。
- 适合小规模数据或部分有序数据。
- 在线排序:可处理动态输入。
---
4. 希尔排序 (Shell Sort)
原理
希尔排序是插入排序的改进,通过分组(增量)进行粗略排序,逐步缩小增量,直至增量为 1。
实现
```python
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 log n)(取决于增量序列)
- 平均:O(n^1.3)(常用增量如 Hibbard)
- 最坏:O(n²)(最差增量)
- 空间复杂度:O(1)
特点
- 稳定性:不稳定。
- 比插入排序效率高,适合中等规模数据。
- 增量序列选择(如 Sedgewick、Hibbard)影响性能。
---
5. 归并排序 (Merge Sort)
原理
归并排序采用分治法,将数组递归分为两半,分别排序后合并。
实现
```python
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
```
时间复杂度
- 最优:O(n log n)
- 平均:O(n log n)
- 最坏:O(n log n)
- 空间复杂度:O(n)
特点
- 稳定性:稳定。
- 适合大规模数据和链表排序。
- 缺点:需要额外空间,递归开销。
---
6. 快速排序 (Quick Sort)
原理
快速排序通过选择一个“枢轴”(pivot),将数组分为小于和大于枢轴的两部分,递归排序子数组。
实现
```python
def quick_sort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
quick_sort(arr, low, pi - 1)
quick_sort(arr, pi + 1, high)
return arr
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
```
时间复杂度
- 最优:O(n log n)
- 平均:O(n log n)
- 最坏:O(n²)(枢轴选择不当,如已排序数组)
- 空间复杂度:O(log n)(递归栈)
特点
- 稳定性:不稳定。
- 原地排序,效率高,适合大多数场景。
- 优化:随机枢轴、三数取中可降低最坏情况概率。
---
7. 堆排序 (Heap Sort)
原理
堆排序利用最大堆(或最小堆)结构,先构建堆,然后反复将堆顶元素与末尾交换并调整堆。
实现
```python
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
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)
```
时间复杂度
- 最优:O(n log n)
- 平均:O(n log n)
- 最坏:O(n log n)
- 空间复杂度:O(1)
特点
- 稳定性:不稳定。
- 原地排序,适合大规模数据。
- 缺点:缓存不友好,实际性能不如快速排序。
---
8. 计数排序 (Counting Sort)
原理
计数排序适用于整数范围有限的数据,通过统计每个值的出现次数,直接构造排序结果。
实现
```python
def counting_sort(arr):
max_val = max(arr)
count = [0] * (max_val + 1)
for num in arr:
count[num] += 1
result = []
for i in range(len(count)):
result.extend([i] * count[i])
return result
```
时间复杂度
- 最优/平均/最坏:O(n + k)(k 为值范围)
- 空间复杂度:O(n + k)
特点
- 稳定性:稳定(需调整实现)。
- 非比较排序,适合小范围整数。
- 缺点:范围过大时空间效率低。
---
9. 基数排序 (Radix Sort)
原理
基数排序按位(从低位到高位)对数字排序,每位使用稳定排序(如计数排序)。
实现
```python
def radix_sort(arr):
max_val = max(arr)
exp = 1
while max_val // exp > 0:
counting_sort_by_digit(arr, exp)
exp *= 10
return arr
def counting_sort_by_digit(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for i in range(n):
index = (arr[i] // exp) % 10
count[index] += 1
for i in range(1, 10):
count[i] += count[i - 1]
for i in range(n - 1, -1, -1):
index = (arr[i] // exp) % 10
output[count[index] - 1] = arr[i]
count[index] -= 1
for i in range(n):
arr[i] = output[i]
```
时间复杂度
- 最优/平均/最坏:O(d(n + k))(d 为最大位数,k 为基数)
- 空间复杂度:O(n + k)
特点
- 稳定性:稳定。
- 适合大范围整数或字符串排序。
- 缺点:需要额外空间,依赖于位数。
---
10. 桶排序 (Bucket Sort)
原理
桶排序将数据分配到多个桶中,对每个桶进行排序(通常用插入排序),最后合并。
实现
```python
def bucket_sort(arr):
n = len(arr)
buckets = [[] for _ in range(n)]
for num in arr:
index = int(num * n) # 假设数据在 [0,1) 区间
buckets[index].append(num)
for bucket in buckets:
bucket.sort() # 使用插入排序
result = []
for bucket in buckets:
result.extend(bucket)
return result
```
时间复杂度
- 最优:O(n + k)(k 为桶数)
- 平均:O(n + n²/k + k)
- 最坏:O(n²)
- 空间复杂度:O(n + k)
特点
- 稳定性:稳定(依赖桶内排序)。
- 适合均匀分布的数据。
- 缺点:需要预知数据分布。
---
比较与选择
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|------------|----------------|----------------|------------|--------|------------------------------|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模数据 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 小规模,交换代价高 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模,部分有序 |
| 希尔排序 | O(n^1.3) | O(n²) | O(1) | 不稳定 | 中等规模数据 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 大规模数据,链表排序 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 | 通用,内存受限 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | 大规模数据,无额外空间 |
| 计数排序 | O(n + k) | O(n + k) | O(n + k) | 稳定 | 小范围整数 |
| 基数排序 | O(d(n + k)) | O(d(n + k)) | O(n + k) | 稳定 | 大范围整数,字符串 |
| 桶排序 | O(n + n²/k + k)| O(n²) | O(n + k) | 稳定 | 均匀分布数据 |
选择建议
- 小规模数据(n < 50):冒泡、选择、插入排序简单易用。
- 部分有序数据:插入排序或希尔排序。
- 大规模通用数据:快速排序(优化后)或归并排序。
- 内存受限:堆排序或快速排序。
- 整数/特定分布:计数排序、基数排序、桶排序。
- 需要稳定性:归并排序、计数排序、基数排序。
---
总结
排序算法的选择取决于数据规模、分布、内存限制和稳定性需求。比较排序(如快速排序、归并排序)适合通用场景,非比较排序(如计数排序、基数排序)在特定条件下效率更高。理解每种算法的原理和复杂度有助于在实际应用中做出最优选择。
以下为C#实现
using System;
using System.Diagnostics;
public class SortingAlgorithms
{
// 冒泡排序
public static void BubbleSort(int[] arr)
{
int n = arr.Length;
for (int i = 0; i < n - 1; i++)
{
bool swapped = false;
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果没有交换,数组已排序
if (!swapped)
break;
}
}
// 快速排序
public static void QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
int pi = Partition(arr, low, high);
QuickSort(arr, low, pi - 1);
QuickSort(arr, pi + 1, high);
}
}
private static int Partition(int[] arr, int low, int high)
{
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
if (arr[j] <= pivot)
{
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp1 = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp1;
return i + 1;
}
// 归并排序
public static void MergeSort(int[] arr, int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2;
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
Merge(arr, left, mid, right);
}
}
private static void Merge(int[] arr, int left, int mid, int right)
{
int n1 = mid - left + 1;
int n2 = right - mid;
int[] L = new int[n1];
int[] R = new int[n2];
Array.Copy(arr, left, L, 0, n1);
Array.Copy(arr, mid + 1, R, 0, n2);
int i = 0, j = 0, k = left;
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
arr[k++] = L[i++];
else
arr[k++] = R[j++];
}
while (i < n1)
arr[k++] = L[i++];
while (j < n2)
arr[k++] = R[j++];
}
// 测试程序
public static void Main()
{
int[] arr = { 64, 34, 25, 12, 22, 11, 90 };
Console.WriteLine("原始数组: " + string.Join(", ", arr));
// 测试冒泡排序
int[] arrBubble = (int[])arr.Clone();
Stopwatch sw = Stopwatch.StartNew();
BubbleSort(arrBubble);
sw.Stop();
Console.WriteLine($"冒泡排序结果: {string.Join(", ", arrBubble)}, 时间: {sw.ElapsedTicks} ticks");
// 测试快速排序
int[] arrQuick = (int[])arr.Clone();
sw = Stopwatch.StartNew();
QuickSort(arrQuick, 0, arrQuick.Length - 1);
sw.Stop();
Console.WriteLine($"快速排序结果: {string.Join(", ", arrQuick)}, 时间: {sw.ElapsedTicks} ticks");
// 测试归并排序
int[] arrMerge = (int[])arr.Clone();
sw = Stopwatch.StartNew();
MergeSort(arrMerge, 0, arrMerge.Length - 1);
sw.Stop();
Console.WriteLine($"归并排序结果: {string.Join(", ", arrMerge)}, 时间: {sw.ElapsedTicks} ticks");
}
}
using System;
using System.Diagnostics;
public class SortingAlgorithmsAnalysis
{
///
/// 冒泡排序:通过相邻元素交换将最大/最小值逐步"冒泡"到数组一端
/// 时间复杂度:
/// - 最好情况:O(n) - 数组已排序,仅需一轮扫描
/// - 平均情况:O(n²) - 需要多轮比较和交换
/// - 最坏情况:O(n²) - 数组逆序
/// 空间复杂度:O(1) - 原地排序
/// 稳定性:稳定
///
public static void BubbleSort(int[] arr)
{
int n = arr.Length;
for (int i = 0; i < n - 1; i++)
{
bool swapped = false;
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 优化:如果本轮无交换,说明已排序完成
if (!swapped)
break;
}
}
///
/// 快速排序:通过选取基准值将数组分为两部分,递归排序
/// 时间复杂度:
/// - 最好情况:O(n log n) - 每次基准值将数组均分
/// - 平均情况:O(n log n)
/// - 最坏情况:O(n²) - 数组已排序或逆序,基准选择不当
/// 空间复杂度:O(log n) - 递归调用栈
/// 稳定性:不稳定
///
public static void QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
int pi = Partition(arr, low, high);
QuickSort(arr, low, pi - 1);
QuickSort(arr, pi + 1, high);
}
}
private static int Partition(int[] arr, int low, int high)
{
// 选择最右元素作为基准
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
if (arr[j] <= pivot)
{
i++;
// 交换元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准放到正确位置
int temp1 = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp1;
return i + 1;
}
///
/// 归并排序:将数组递归分割为小块,排序后合并
/// 时间复杂度:
/// - 最好情况:O(n log n)
/// - 平均情况:O(n log n)
/// - 最坏情况:O(n log n)
/// 空间复杂度:O(n) - 需要辅助数组
/// 稳定性:稳定
///
public static void MergeSort(int[] arr, int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2;
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
Merge(arr, left, mid, right);
}
}
private static void Merge(int[] arr, int left, int mid, int right)
{
int n1 = mid - left + 1;
int n2 = right - mid;
// 创建临时数组
int[] L = new int[n1];
int[] R = new int[n2];
// 复制数据到临时数组
Array.Copy(arr, left, L, 0, n1);
Array.Copy(arr, mid + 1, R, 0, n2);
// 合并临时数组回原数组
int i = 0, j = 0, k = left;
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
arr[k++] = L[i++];
else
arr[k++] = R[j++];
}
// 复制剩余元素
while (i < n1)
arr[k++] = L[i++];
while (j < n2)
arr[k++] = R[j++];
}
///
/// 测试程序:比较不同排序算法的性能
///
public static void Main()
{
// 测试用例
int[] arr = { 64, 34, 25, 12, 22, 11, 90 };
Console.WriteLine("原始数组: " + string.Join(", ", arr));
// 测试冒泡排序
int[] arrBubble = (int[])arr.Clone();
Stopwatch sw = Stopwatch.StartNew();
BubbleSort(arrBubble);
sw.Stop();
Console.WriteLine($"\n冒泡排序结果: {string.Join(", ", arrBubble)}");
Console.WriteLine($"时间: {sw.ElapsedTicks} ticks");
Console.WriteLine("特点: 简单实现,适合小规模数据,性能较差");
// 测试快速排序
int[] arrQuick = (int[])arr.Clone();
sw = Stopwatch.StartNew();
QuickSort(arrQuick, 0, arrQuick.Length - 1);
sw.Stop();
Console.WriteLine($"\n快速排序结果: {string.Join(", ", arrQuick)}");
Console.WriteLine($"时间: {sw.ElapsedTicks} ticks");
Console.WriteLine("特点: 高效,适合大规模数据,基准选择影响性能");
// 测试归并排序
int[] arrMerge = (int[])arr.Clone();
sw = Stopwatch.StartNew();
MergeSort(arrMerge, 0, arrMerge.Length - 1);
sw.Stop();
Console.WriteLine($"\n归并排序结果: {string.Join(", ", arrMerge)}");
Console.WriteLine($"时间: {sw.ElapsedTicks} ticks");
Console.WriteLine("特点: 稳定,适合大数据,需额外空间");
// 算法比较
Console.WriteLine("\n算法比较:");
Console.WriteLine("1. 冒泡排序:简单直观,适合教学和小型数据");
Console.WriteLine("2. 快速排序:通常最快,但极端情况退化");
Console.WriteLine("3. 归并排序:稳定且性能可预测,适合链表排序");
}
}
如果需要进一步分析某算法的变种、优化或特定场景应用,请告诉我!