排序算法分类
- 基于比较的排序
- 通过比较大小来决定元素间的相对次序。
- 可以证明时间复杂度下界为 O ( n l o g n ) O(nlogn) O(nlogn),不可能突破这个复杂度达到更快。
- 非比较类排序
- 不通过比较大小来决定元素间的相对次序。
- 时间复杂度受元素的范围以及分布等多种因素影响,不单纯取决于元素数量 N。
比较类排序
-
选择排序
-
简单选择排序
- 每次从未排序数据中找最小值,放到已排序序列的末尾。
- 时间复杂度 O ( n 2 ) O(n^2) O(n2)。
cppfor(int i = 0;i < nums.size();i++) { int min_index = i; for(int j = i + 1;j < nums.size();i++) { if(nums[j] < nums[min_index]) { min_index = j; } int temp = nums[i]; nums[i] = nums[min_index]; nums[min_index] = temp; } }
-
堆排序
- 利用二叉堆高效地选出最小值,建立一个包含所有 n n n 个元素的二叉堆,重复 n n n 次从堆中取出最小值,即可得到有序序列。
- 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
cppvoid heap_sort(int a[], int n) { priority_queue<int> q; for(int i = 0;i < n;i++) { q.push(-a[i]); } for(int i = 0;i < n;i++) { a[i] = -q.top(); q.pop(); } }
-
-
插入排序
-
简单插入排序
- 从前到后依次考虑每个未排序数据,在已排序序列中找到合适位置插入。
- 时间复杂度 O ( n 2 ) O(n^2) O(n2)。
cppfor(int i = 1;i < nums.size();i++) { int pre_index = i - 1; int current = nums[i]; while(pre_index >= 0 && nums[pre_index] > current) { nums[pre_index + 1] = nums[pre_index]; pre_index--; } nums[pre_index + 1] = current; }
-
希尔排序
- 希尔排序是对插入排序的优化 -- 增量分组插入排序。
- 时间复杂度取决于增量序列(步长序列)的选取,最好序列可以做到 N 4 3 N^\frac43 N34 或 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
-
-
交换排序
-
冒泡排序
- 不断循环扫描,每次查看相邻的元素,如何逆序,则交换。
- 时间复杂度 O ( n 2 ) O(n^2) O(n2)。
cppfor(int i = 0;i < nums.size() - 1;i++) { for(int j = 0;j < nums.size() - 1 - i;j++) { if(nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } }
-
快速排序
- 快速排序也是一个基于分治的算法。
- 从数组中选取中轴元素 pivot。
- 将小元素放在 pivot 左边,大元素放在右边。
- 然后分别对左边和右边的子数组进行快排。
- 快速排序和归并排序具有相似性,但步骤顺序相反。
- 归并排序:先排序左右子数组,然后合并两个有序数组。
- 快速排序:先调配出左右子数组,然后对左右子数组分别进行排序。
- 随机选取 pivot,期望时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),最坏 O ( n 2 ) O(n^2) O(n2)
cppvoid quickSort(vector<int>& arr, int left, int right) { if (left >= right) return; int pivot = partition(arr, left, right); quickSort(arr, left, pivot); quickSort(arr, pivot + 1, right); } int partition(vector<int>& arr, int left, int right) { int pivot = left + rand() % (right - left + 1); int pivotVal = arr[pivot]; while(left <= right) { while(arr[left] < pivotVal) left++; while(arr[right] > pivotVal) right--; if (left == right) break; if (left < right) { int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } return right; }
-
-
归并排序
-
归并排序是一个基于分治的算法。
-
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn) 。
-
原问题:把数组排序。
-
子问题:把数组前一半、后一半分别排序,然后再合并左右两半(两个有序数组)就可以了。
cppvoid mergeSort(vector<int>& arr, int l, int r) { if (l >= r) return; int mid = (l + r) >> 1; mergeSort(arr, l, mid); mergeSort(arr, mid + 1, r); return merge(arr, l, mid, r); } void merge(vector<int>& arr, int left, int mid, int right) { vector<int> tmp = vector<int>(right - left + 1); // 临时数组 int i = left, j = mid + 1; for(int k = 0;k < tmp.size();k++) { if (j > right || (i <= mid && arr[i] <= arr[j])) { // 合并两个有序数组 tmp[k] = arr[i++]; } else { tmp[k] = arr[j++]; } } for (int k = 0;k < tmp.size();k++) { // 拷贝回原数组 arr[left + k] = tmp[k]; } }
-
非比较排序
- 计数排序
- 要求输入的数据必须是有确定范围的整数。
- 将输入的数据作为 key 存储在额外的数组中,然后依次把计数大于 1 的填充回原数组。
- 时间复杂度 O ( n + m ) O(n + m) O(n+m),n 为元素个数,m 为数值范围。
- 桶排序
- 假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能使用别的排序算法,或是以递归方式继续使用桶排序)。
- 时间复杂度 O ( n ) O(n) O(n) ~ O ( n 2 ) O(n^2) O(n2)
- 基数排序
- 把数据切割成一位位数字( 0 0 0 -- 9 9 9),从低位到高位对每一位分别进行计数排序。
- 时间复杂度 O ( n k ) O(nk) O(nk),k 为数字位数。
排序的稳定性
- 对于序列中存在的若干个关键字相等的元素,如果排序前后它们的相对次序保持不变,就称排序算法是稳定的,否则就称排序算法是不稳定的。
- 插入、冒泡、归并、计数、基数和桶排序是稳定的。
- 选择、希尔、快速、堆排序是不稳定的。
LeetCode 练习题