排序算法总结

排序算法分类

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


相关推荐
好奇龙猫1 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
sp_fyf_20241 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
ChoSeitaku2 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程2 小时前
双向链表专题
数据结构
香菜大丸2 小时前
链表的归并排序
数据结构·算法·链表
jrrz08282 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time2 小时前
golang学习2
算法
@小博的博客2 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
南宫生3 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步4 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝