当前位置: 首页 > news >正文

【杂乱算法】七种常见的排序

文章目录

        • 1、选择排序 O(n^2)
        • 2、插入排序 O(n^2)
        • 3、希尔排序 O(n log n)
        • 4、冒泡排序 O(n^2)
        • 5、快速排序 O(n log n)
        • 6、归并排序 O(n log n)
        • 7、基数排序 O(d(n+k))

1、选择排序 O(n^2)

每次从未排序部分选择最小元素放到已排序部分的末尾。

#include <iostream>
#include <vector>
#include <algorithm>void selection_sort(std::vector<int>& arr, int l, int r) {for (int i = l; i < r - 1; i++) {int ind = i;for (int j = i + 1; j < r; j++) {if (arr[j] < arr[ind]) ind = j;}std::swap(arr[i], arr[ind]);}
}// 打印向量的辅助函数
void printVector(const std::vector<int>& vec) {for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;
}int main() {std::vector<int> arr = {64, 25, 12, 22, 11, 35};std::cout << "原始数组: ";printVector(arr);selection_sort(arr, 0, arr.size());std::cout << "排序后数组: ";printVector(arr);return 0;
}

选择排序的特点:

  • 时间复杂度: O(n^2),无论输入如何都是这个复杂度
  • 空间复杂度: O(1),原地排序
  • 不稳定排序: 相等元素的相对顺序可能会改变
  • 交换次数少: 每次外循环最多交换一次
2、插入排序 O(n^2)

从第二个元素开始, 将每个元素插入到已排序的前面部分的合适位置, 通过不断交换相邻元素实现插入.(找到第一个小于它的数。)

#include <iostream>
#include <vector>void insert_sort(std::vector<int>& arr, int l, int r) {for (int i = l + 1; i < r; i++) {int j = i;while (j > l && arr[j] < arr[j - 1]) {swap(arr[j], arr[j - 1]);j -= 1;}}
}// 打印数组的辅助函数
void printArray(const std::vector<int>& arr) {for (int num : arr) {std::cout << num << " ";}std::cout << std::endl;
}int main() {std::vector<int> arr = {64, 34, 25, 12, 22, 11, 90};std::cout << "原始数组: ";printArray(arr);insert_sort(arr, 0, arr.size());std::cout << "排序后数组: ";printArray(arr);return 0;
}

插入排序的特点:

  • 时间复杂度:平均和最坏情况下为O(n^2),最好情况(已排序)为O(n)
  • 空间复杂度:O(1),原地排序
  • 稳定排序:相等元素的相对顺序不会改变
  • 对于小型数组或基本有序的数组,插入排序可能比其他更复杂的排序算法表现更好
3、希尔排序 O(n log n)
#include <iostream>
#include <vector>// 对子序列进行插入排序
void unguarded_insert_sort(vector<int>&arr, int l, int r, int step) {int ind = l;//确定最小值的边界for (int i = l + step; i < r; i += step) {if (arr[i] < arr[ind]) ind = i;}//挨个移动,确保稳定性while (ind > l) {swap(arr[ind], arr[ind - step]);ind -= step;}for (int i = l + 2 * step; i < r; i += step) {int j = i;while (arr[j] < arr[j - step]) {swap(arr[j], arr[j - step]);j -= step;}}return ;
}// 希尔排序实现
void shell_sort(std::vector<int>& arr, int l, int r) {int k = 2, n = (r - l + 1), step;do {step = n / k == 0 ? 1 : n / k;for (int i = l, I = l + step; i < I; i++) {//大范围内分组进行插入排序unguarded_insert_sort(arr, i, r, step);}k *= 2;} while (step != 1);
}// 打印数组
void printArray(const std::vector<int>& arr) {for (int num : arr) {std::cout << num << " ";}std::cout << std::endl;
}int main() {std::vector<int> arr = {64, 34, 25, 12, 22, 11, 90};std::cout << "原始数组: ";printArray(arr);shell_sort(arr, 0, arr.size() - 1);std::cout << "排序后数组: ";printArray(arr);return 0;
}

希尔排序的优点:

  1. 性能优于简单插入排序,尤其是对于大规模的乱序数组。
  2. 不需要额外的存储空间,是原地排序算法。
  3. 对于部分有序的数组,效率较高。

缺点:

  1. 增量序列的选择对算法的性能影响较大,不同的增量序列可能导致不同的时间复杂度。
  2. 不稳定排序:相同键值的记录可能会在排序后改变其相对位置。

时间复杂度:

  • 最坏情况:O(n^2)
  • 平均情况:依赖于增量序列的选择,通常在O(n1.3)到O(n2)之间
  • 最好情况:O(n log n)

空间复杂度:O(1)

4、冒泡排序 O(n^2)
/* 冒泡排序 */
void bubbleSort(vector<int> &nums) {// 外循环:未排序区间为 [0, i]for (int i = nums.size() - 1; i > 0; i--) {// 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端for (int j = 0; j < i; j++) {if (nums[j] > nums[j + 1]) {// 交换 nums[j] 与 nums[j + 1]// 这里使用了 std::swap() 函数swap(nums[j], nums[j + 1]);}}}
}
  1. 性能特征:

    • 时间复杂度:最坏情况和平均情况都是 O(n^2),最好情况(已经排好序)是 O(n)
    • 空间复杂度:O(1),只需要一个临时变量来交换元素
    • 稳定性:稳定排序算法
  2. 优缺点:

    优点:

    • 实现简单,容易理解
    • 不需要额外的存储空间
    • 对于小规模数据或基本有序的数据效率较高

    缺点:

    • 对于大规模数据效率低下
    • 平均时间复杂度较高,为 O(n^2)
5、快速排序 O(n log n)

选择一个基准元素,将数组分为小于和大于基准的两个部分,递归地对这两个部分进行排序,从而实现整体有序。

void quick_sort(int *arr, int l, int r) {if (r - l <= 2) {if (r - l <= 1) return ;if (arr[l] > arr[l + 1]) swap(arr[l], arr[l + 1]);return ;}// partitionint x = l, y = r - 1, z = arr[l];while (x < y) {while (x < y && z <= arr[y]) --y;if (x < y) arr[x++] = arr[y];while (x < y && arr[x] <= z) ++x;if (x < y) arr[y--] = arr[x];}arr[x] = z;quick_sort(arr, l, x);quick_sort(arr, x + 1, r);return ;
}
  1. 时间复杂度:
    • 最佳情况:O(n log n)
    • 平均情况:O(n log n)
    • 最坏情况:O(n^2)(当数组已经排序或近乎排序时)
  2. 空间复杂度:
    • 平均情况:O(log n)
    • 最坏情况:O(n)(递归调用栈的深度)
  3. 优点:
    • 原地排序,不需要额外的存储空间
    • 平均情况下效率高
    • 缓存友好
  4. 缺点:
    • 不稳定排序(相等元素的相对位置可能改变)
    • 对于小数组,可能不如插入排序等简单算法效率高
    • 在最坏情况下性能较差
6、归并排序 O(n log n)
std::vector<int> buff;void merge_sort(std::vector<int>& arr, int l, int r) {if (r - l <= 1) return;int mid = (l + r) / 2;merge_sort(arr, l, mid);merge_sort(arr, mid, r);// mergeint p1 = l, p2 = mid, k = 0;while (p1 < mid || p2 < r) {if (p2 == r || (p1 < mid && arr[p1] <= arr[p2])) {buff[k++] = arr[p1++];} else {buff[k++] = arr[p2++];}}for (int i = l; i < r; i++) arr[i] = buff[i - l];
}
  1. 基本思想:
    • 将待排序的数列递归地分成两半,分别对两半进行排序。
    • 然后将已排序的两半合并成一个有序序列。
  2. 工作流程: a. 分割:将数组递归地分成两半,直到每个子数组只包含一个元素。 b. 合并:将两个有序子数组合并成一个更大的有序数组。
  3. 时间复杂度:
    • 最佳情况:O(n log n)
    • 最坏情况:O(n log n)
    • 平均情况:O(n log n)
  4. 空间复杂度:
    • O(n),因为合并过程中需要额外的空间
  5. 稳定性:
    • 归并排序是稳定的排序算法,即相等元素的相对顺序在排序后不会改变。
  6. 优点:
    • 效率高且稳定
    • 适用于外部排序(当数据量很大,无法全部装入内存时)
  7. 缺点:
    • 需要额外的存储空间
    • 对于小规模数据,可能不如插入排序等简单算法效率高
7、基数排序 O(d(n+k))

基数排序是一种非比较型整数排序算法,通过从最低有效位到最高有效位的顺序,对整数的每一位进行分配和收集,最终实现对整数序列的排序。

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>void radixSort(std::vector<int>& arr) {// 假定arr[0] 是最大数// 1. 通过遍历arr, 找到数组中真正最大值int max = arr[0];for (size_t i = 1; i < arr.size(); i++) {if (arr[i] > max) {max = arr[i];}}// 计算最大数字是几位数int maxLength = std::to_string(max).length();// 定义一个二维数组, 就是10个桶std::vector<std::vector<int>> bucket(10);// 根据最大长度的数决定比较的次数for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {// 把每一个数字分别计算本轮循环的位数的值std::vector<int> bucketElementCounts(10, 0);for (size_t j = 0; j < arr.size(); j++) {// 计算int digitOfElement = (arr[j] / n) % 10;// 把当前遍历的数据放入指定的数组中bucket[digitOfElement].push_back(arr[j]);// 记录数量bucketElementCounts[digitOfElement]++;}// 记录取的元素需要放的位置size_t index = 0;// 把各个桶中(10个桶)存放的数字取出来, 放入到arr中for (size_t k = 0; k < bucketElementCounts.size(); k++) {// 如果这个桶中,有数据才取,没有数据就不取了if (bucketElementCounts[k] != 0) {// 循环取出元素for (size_t l = 0; l < bucket[k].size(); l++) {// 取出元素arr[index++] = bucket[k][l];}// 清空桶bucket[k].clear();}}}
}int main() {std::vector<int> arr = {170, 45, 75, 90, 802, 24, 2, 66};radixSort(arr);// 输出排序结果for (int num : arr) {std::cout << num << " ";}return 0;
}

基数排序的主要特点:

  1. 时间复杂度:O(d(n+k)),其中d是位数,n是元素个数,k是每位的取值范围。
  2. 空间复杂度:O(n+k),需要额外的计数数组和临时数组。
  3. 稳定性:基数排序是稳定的排序算法。
  4. 适用范围:主要用于整数排序,特别是对于数字范围不是很大的情况。

[外链图片转存中…(img-rRxHo7Jy-1724056762931)]

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 如何使用unittest和pytest进行python脚本的单元测试
  • 计算机储存单位换算:1KB等于多少GB
  • MS8561/8562精密、低噪、CMOS、轨到轨输入输出运算放大器
  • AWS 注册一年后是否需要花钱?
  • 贪心算法之重叠区间问题
  • Linux | 深入探究Linux进程控制:从fork到进程等待再到进程替换
  • 使用WINUI3 编写一个小软件1 C#
  • 如何更改select option边框颜色和选中的颜色
  • 鸿蒙(API 12 Beta3版)【录像流二次处理(C/C++)】媒体相机开发指导
  • 图像处理 -- 仿射变换之Affine Transformation
  • 多个条件同时查询时username和name无法被解析
  • git 配置SSH
  • 计算机网络:DNS、子网掩码、网关
  • 查找技术(4/6 改)
  • 【git】 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
  • 分享一款快速APP功能测试工具
  • C学习-枚举(九)
  • Java小白进阶笔记(3)-初级面向对象
  • JS 面试题总结
  • React-Native - 收藏集 - 掘金
  • vue-router的history模式发布配置
  • win10下安装mysql5.7
  • 汉诺塔算法
  • 类orAPI - 收藏集 - 掘金
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 如何优雅地使用 Sublime Text
  • 温故知新之javascript面向对象
  • 一天一个设计模式之JS实现——适配器模式
  • 用mpvue开发微信小程序
  • 移动端高清、多屏适配方案
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #565. 查找之大编号
  • #stm32驱动外设模块总结w5500模块
  • (arch)linux 转换文件编码格式
  • (ZT)薛涌:谈贫说富
  • (分享)自己整理的一些简单awk实用语句
  • (六)DockerCompose安装与配置
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • . NET自动找可写目录
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .net8.0与halcon编程环境构建
  • .NET应用架构设计:原则、模式与实践 目录预览
  • [2013][note]通过石墨烯调谐用于开关、传感的动态可重构Fano超——
  • [20140403]查询是否产生日志
  • [20171101]rman to destination.txt
  • [2023-年度总结]凡是过往,皆为序章
  • [2024-06]-[大模型]-[Ollama] 0-相关命令
  • [BT]小迪安全2023学习笔记(第15天:PHP开发-登录验证)
  • [CDOJ 838]母仪天下 【线段树手速练习 15分钟内敲完算合格】
  • [CentOs7]搭建ftp服务器(2)——添加用户
  • [CF703D]Mishka and Interesting sum/[BZOJ5476]位运算