排序算法
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)
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)
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)
- 通用
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²),适合小规模链表或基本有序的链表。
4. 希尔排序(Shell Sort)
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)
- 通用
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
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)
- 通用
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
class Solution {
public:ListNode* quickSort(ListNode* head) {if (!head || !head->next) return head;// Partition the list into three partsListNode* 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 partitionsListNode* sortedLess = quickSort(lessHead->next);ListNode* sortedGreater = quickSort(greaterHead->next);// Combine all parts togetherreturn 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)
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)
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)
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)
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];}}
}