排序算法总结

排序算法分类

  • 基于比较的排序
    • 通过比较大小来决定元素间的相对次序。
    • 可以证明时间复杂度下界为 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 练习题


相关推荐
智者知已应修善业23 分钟前
【51单片机8位数码管同时倒计时从9999】2024-1-25
c++·经验分享·笔记·算法·51单片机
洛水水26 分钟前
【力扣100题】86.柱状图中最大的矩形
算法·leetcode·职场和发展
渡之33 分钟前
GRiM-Net 深度解析 | 无人机 GNSS 拒止场景下两阶段跨视角视觉定位框架
深度学习·算法·动态规划·无人机
测试仪器廖生135902563851 小时前
罗德与施瓦茨 FSP13频谱分析仪FSP30
网络·人工智能·算法
happymaker06261 小时前
LeetCodeHot100——560.和为K的子数组
算法
dtq04241 小时前
C语言刷题数组5,6(求平均值,求最大值)
c语言·数据结构·算法
郭梧悠1 小时前
Hash算法入门Hash冲突解决方案
算法·哈希算法
洛水水2 小时前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
happymaker06263 小时前
LeetCodeHot100——155.最小栈
算法
洛水水3 小时前
【力扣100题】85.每日温度
算法·leetcode·职场和发展