排序算法
1. 比较排序算法
这些排序算法基于元素之间的比较进行排序,最常见的几种有:
1.1. 冒泡排序(Bubble Sort)
- 工作原理:相邻的元素两两比较,较大的元素冒泡到最后,重复这一过程,直到整个数组有序。
- 时间复杂度:最优 O(n),最差 O(n²),平均 O(n²)。
- 特点:简单但效率较低,适合小规模数据。
1.2. 选择排序(Selection Sort)
- 工作原理:每次从未排序的部分中选择最小(或最大)的元素,将其放到已排序部分的末尾,重复这一过程。
- 时间复杂度:最优 O(n²),最差 O(n²),平均 O(n²)。
- 特点:相比冒泡排序,减少了交换次数,但仍然效率不高。
1.3. 插入排序(Insertion Sort)
- 工作原理:每次将一个未排序的元素插入到已排序的部分中,直到整个数组有序。
- 时间复杂度:最优 O(n),最差 O(n²),平均 O(n²)。
- 特点:在数据基本有序时表现优异,适合小规模数据。
1.4. 希尔排序(Shell Sort)
- 工作原理:基于插入排序的改进,通过比较相隔一定间隔的元素(逐步缩小间隔)来进行多次插入排序。
- 时间复杂度:平均 O(n log n) 到 O(n²),最差 O(n²)。
- 特点:改进了插入排序的效率,适合中等规模的数据。
1.5. 归并排序(Merge Sort)
- 工作原理:采用分治法,将数组递归地分成两部分,分别排序后合并。
- 时间复杂度:最优、最差和平均都是 O(n log n)。
- 特点:稳定排序,适合大规模数据排序,但需要 O(n) 的额外空间。
1.6. 快速排序(Quick Sort)
- 工作原理:选择一个基准元素,将数组分成两部分,一部分小于基准,一部分大于基准,递归地对两部分进行排序。
- 时间复杂度:最优 O(n log n),最差 O(n²),平均 O(n log n)。
- 特点:效率高,尤其在数据量大时,最常用的排序算法之一,但不稳定。
1.7. 堆排序(Heap Sort)
- 工作原理:将数组看作完全二叉树,调整为最大(最小)堆,然后依次取出堆顶元素。
- 时间复杂度:最优、最差和平均都是 O(n log n)。
- 特点:不需要额外空间,适合需要较高空间效率的场景。
2. 非比较排序算法
这些排序算法并不依赖元素之间的比较来确定顺序,因此可以突破 O(n log n) 的时间复杂度。
2.1. 计数排序(Counting Sort)
- 工作原理:适用于已知范围的整数,计算每个数出现的次数,然后按次数重构数组。
- 时间复杂度 :最优、最差和平均都是 O(n + k),其中
k
是数值范围。 - 特点:非常快,但受限于数据的取值范围,适合整数排序。
2.2. 基数排序(Radix Sort)
- 工作原理:将数按位数进行排序(从低位到高位或反过来),多次排序使用稳定的排序算法(如计数排序或桶排序)。
- 时间复杂度 :最优、最差和平均都是 O(n * d),其中
d
是数字的位数。 - 特点:适合对整数或字符串等有多位数值的数据进行排序。
2.3. 桶排序(Bucket Sort)
- 工作原理:将数组分成多个桶,每个桶内分别排序(通常使用插入排序或其他排序算法),然后合并。
- 时间复杂度:最优 O(n),最差 O(n²),平均 O(n + k)。
- 特点:适合数据比较均匀分布的情况,适用于浮点数排序。
3. 排序算法总结
排序算法 | 时间复杂度(平均) | 时间复杂度(最优) | 时间复杂度(最差) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
希尔排序 | O(n log n) | O(n log² n) | O(n²) | O(1) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
桶排序 | O(n + k) | O(n) | O(n²) | O(n + k) | 稳定 |
基数排序 | O(n * d) | O(n * d) | O(n * d) | O(n + k) | 稳定 |
4. 选择排序算法的依据
不同的排序算法在不同的场景下有各自的优缺点,选择时可以考虑以下因素:
- 数据规模:数据量大时,时间复杂度低的算法(如快速排序、归并排序、堆排序)更为适合。
- 数据特点:对于整数、小范围数据,计数排序和基数排序有很好的表现;数据接近有序时,插入排序的表现优异。
- 稳定性需求:如果排序后需要保持相同值的元素的相对顺序,必须选择稳定的排序算法,如归并排序、插入排序等。
1. 冒泡排序(Bubble Sort)
cpp
void bubbleSort(std::vector<int>& arr) {
int n = arr.size();
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]) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
// 如果没有交换,说明数组已排序
if (!swapped) break;
}
}
2. 选择排序(Selection Sort)
cpp
void selectionSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
std::swap(arr[i], arr[minIndex]);
}
}
3. 插入排序(Insertion Sort)
- 通用
cpp
void insertionSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
// 将元素逐个比较并插入到合适位置
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
- 链表
链表中的插入排序实现步骤:
- 创建一个新的已排序链表。
- 逐个遍历原链表,将每个节点插入到新链表的正确位置。
- 保持新链表始终是有序的。
插入排序的时间复杂度为 O(n²),适合小规模链表或基本有序的链表。
cpp
4. 希尔排序(Shell Sort)
cpp
void shellSort(std::vector<int>& arr) {
int n = arr.size();
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
5. 归并排序(Merge Sort)
- 通用
cpp
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
std::vector<int> L(n1), R(n2);
for (int i = 0; i < n1; i++) L[i] = arr[left + i];
for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(std::vector<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);
}
}
- 链表中的归并排序
链表中的归并排序实现步骤:
涉及到链表元素变动的一般都有哨兵节点;
- 使用快慢指针(
slow
和fast
)找到链表的中间节点,将链表分成两半。 - 递归地对两部分进行排序。
- 合并两个已排序的链表。
注意:
查找中心点的while循环,得根据fast的初始值来进行设计,如果fast=head,则可以用fast→next&&fast→next→next
cpp
struct ListNode{
int val;
ListNode* next;
ListNode(int x): val(x),next(nullptr){}
};
class Solution{
public:
ListNode* mergeSort(ListNode* head){
if(!head || !head->next) return head;
ListNode* slow = head;
ListNode* fast = head->next;
//找中心点
while(fast&&fast->next){
slow = slow->next;
fast = fast->next->next;
}
// 分左右
ListNode* mid = slow->next;
//断开左右
slow->next = nullptr;
//递归左右,得到左右子链表
ListNode* rightHead = mergeSort(mid);
ListNode* leftHead = mergeSort(head);
//合并两个排序的子链表
return merge(leftHead,rightHead);
}
private:
ListNode* merge(ListNode* l,ListNode* r){
ListNode dummy(0);
ListNode* cur = &dummy;
while(l&&r){
if(l->val<r-val){
cur->next = l;
l = l->next;
}else{
cur->next = r;
r = r->next;
}
cur = cur->next;
}
cur->next = l1?l1:l2;
return dummy.next;
}
}
6. 快速排序(Quick Sort)
- 通用
cpp
int partition(std::vector<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++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort(std::vector<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);
}
}
- 链表
链表中的快速排序实现步骤:
- 选择一个基准元素(通常选择链表的头节点)。
- 将链表分为三个部分:小于基准的元素,等于基准的元素,大于基准的元素。
- 递归地对"小于基准"的部分和"大于基准"的部分进行排序。
- 最后将三部分合并。
注意:
递归的开头一定是判断,如果递归的是快慢那么一定是!head||!head→next
cpp
class Solution {
public:
ListNode* quickSort(ListNode* head) {
if (!head || !head->next) return head;
// Partition the list into three parts
ListNode* pivot = head;
ListNode* lessHead = new ListNode(0), *greaterHead = new ListNode(0);
ListNode* less = lessHead, *greater = greaterHead, *equal = pivot;
ListNode* curr = head->next;
while (curr) {
if (curr->val < pivot->val) {
less->next = curr;
less = less->next;
} else if (curr->val > pivot->val) {
greater->next = curr;
greater = greater->next;
} else {
equal->next = curr;
equal = equal->next;
}
curr = curr->next;
}
//断开链表
less->next = nullptr;
greater->next = nullptr;
equal->next = nullptr;
// Recursively sort the less and greater partitions
ListNode* sortedLess = quickSort(lessHead->next);
ListNode* sortedGreater = quickSort(greaterHead->next);
// Combine all parts together
return combine(sortedLess, pivot, sortedGreater);
}
private:
ListNode* combine(ListNode* less, ListNode* pivot, ListNode* greater) {
ListNode dummy(0), *tail = &dummy;
if (less) {
tail->next = less;
//将tail排到尾巴
while (tail->next) tail = tail->next;
}
tail->next = pivot;
//将tail放到尾巴
while (tail->next) tail = tail->next;
tail->next = greater;
return dummy.next;
}
};
7. 堆排序(Heap Sort)
cpp
void heapify(std::vector<int>& arr, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
if (largest != i) {
std::swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(std::vector<int>& arr) {
int n = arr.size();
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 依次从堆中取出元素
for (int i = n - 1; i > 0; i--) {
std::swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
8. 计数排序(Counting Sort)
cpp
void countingSort(std::vector<int>& arr) {
if (arr.empty()) return;
int maxVal = *max_element(arr.begin(), arr.end());
int minVal = *min_element(arr.begin(), arr.end());
int range = maxVal - minVal + 1;
std::vector<int> count(range, 0), output(arr.size());
// 计数
for (int i = 0; i < arr.size(); i++)
count[arr[i] - minVal]++;
// 累积计数
for (int i = 1; i < count.size(); i++)
count[i] += count[i - 1];
// 构建输出数组
for (int i = arr.size() - 1; i >= 0; i--) {
output[count[arr[i] - minVal] - 1] = arr[i];
count[arr[i] - minVal]--;
}
// 将排序后的结果复制回原数组
for (int i = 0; i < arr.size(); i++)
arr[i] = output[i];
}
9. 基数排序(Radix Sort)
cpp
void countingSortForRadix(std::vector<int>& arr, int exp) {
int n = arr.size();
std::vector<int> output(n), count(10, 0);
// 计数
for (int i = 0; i < n; i++)
count[(arr[i] / exp) % 10]++;
// 累积计数
for (int i = 1; i < 10; i++)
count[i] += count[i - 1];
// 构建输出数组
for (int i = n - 1; i >= 0; i--) {
output[count[(arr[i] / exp) % 10] - 1] = arr[i];
count[(arr[i] / exp) % 10]--;
}
// 复制回原数组
for (int i = 0; i < n; i++)
arr[i] = output[i];
}
void radixSort(std::vector<int>& arr) {
int maxVal = *max_element(arr.begin(), arr.end());
// 基数排序
for (int exp = 1; maxVal / exp > 0; exp *= 10)
countingSortForRadix(arr, exp);
}
10. 桶排序(Bucket Sort)
cpp
void bucketSort(std::vector<float>& arr) {
int n = arr.size();
std::vector<std::vector<float>> buckets(n);
// 将元素分配到桶
for (int i = 0; i < n; i++) {
int bucketIndex = n * arr[i];
buckets[bucketIndex].push_back(arr[i]);
}
// 对每个桶内的元素进行排序
for (int i = 0; i < n; i++) {
std::sort(buckets[i].begin(), buckets[i].end());
}
// 合并所有桶中的元素
int index = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < buckets[i].size(); j++) {
arr[index++] = buckets[i][j];
}
}
}