排序算法总结

排序算法分类

  • 基于比较的排序
    • 通过比较大小来决定元素间的相对次序。
    • 可以证明时间复杂度下界为 O ( n l o g n ) O(nlogn) O(nlogn),不可能突破这个复杂度达到更快。
  • 非比较类排序
    • 不通过比较大小来决定元素间的相对次序。
    • 时间复杂度受元素的范围以及分布等多种因素影响,不单纯取决于元素数量 N。

比较类排序

  • 选择排序

    • 简单选择排序

      • 每次从未排序数据中找最小值,放到已排序序列的末尾。
      • 时间复杂度 O ( n 2 ) O(n^2) O(n2)。
      cpp 复制代码
      for(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)。
      cpp 复制代码
      void 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)。
      cpp 复制代码
      for(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)。
      cpp 复制代码
      for(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)
      cpp 复制代码
      void 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) 。

    • 原问题:把数组排序。

    • 子问题:把数组前一半、后一半分别排序,然后再合并左右两半(两个有序数组)就可以了。

      cpp 复制代码
      void 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 练习题


相关推荐
海石16 分钟前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石16 分钟前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry2 小时前
LeetCode 151. 反转字符串中的单词
算法
gugucoding3 小时前
31. 【C语言】堆栈与队列的实现
c语言·开发语言·数据结构·链表
ChaoZiLL4 小时前
我的数据结构3——链表(link list)
数据结构·链表
a1117765 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜5 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry5 小时前
LeetCode 541. 反转字符串 II
算法
Jerry5 小时前
LeetCode 344. 反转字符串
算法
搞科研的小刘选手6 小时前
【香港大学主办&IEEE出版】第六届计算机视觉、应用与算法国际学术会议(CVAA 2026)
算法·计算机视觉·应用·学术会议