排序算法总结

排序算法分类

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


相关推荐
曦月逸霜30 分钟前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法
开开心心就好1 小时前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
海的诗篇_2 小时前
移除元素-JavaScript【算法学习day.04】
javascript·学习·算法
自动驾驶小卡2 小时前
A*算法实现原理以及实现步骤(C++)
算法
Unpredictable2222 小时前
【VINS-Mono算法深度解析:边缘化策略、初始化与关键技术】
c++·笔记·算法·ubuntu·计算机视觉
编程绿豆侠2 小时前
力扣HOT100之多维动态规划:1143. 最长公共子序列
算法·leetcode·动态规划
珂朵莉MM2 小时前
2021 RoboCom 世界机器人开发者大赛-高职组(初赛)解题报告 | 珂学家
java·开发语言·人工智能·算法·职场和发展·机器人
fail_to_code3 小时前
递归法的递归函数何时需要返回值
算法
C137的本贾尼3 小时前
(每日一道算法题)二叉树剪枝
算法·机器学习·剪枝