排序算法总结(C++)

news2025/6/15 5:08:27

目录

  • 一、稳定性
    • 二、排序算法
      • 选择、冒泡、插入排序
      • 归并排序
      • 随机快速排序
      • 堆排序
      • 基数排序
      • 计数排序
  • 三、总结

一、稳定性

排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。

稳定性对基础类型对象(如平常的数组)来说毫无意义;稳定性对非基础类型对象(如一些自定义类)有意义,可以保留之前的相对次序。

二、排序算法

  • 基于比较的排序:
    只需要定义好两个对象之间怎么比较即可,对象的数据特征并不关心,很通用(除下面两种);

  • 不基于比较的排序:
    和比较无关的排序,对于对象的数据特征有要求,并不通用(如计数排序、基数排序);

选择、冒泡、插入排序

// 交换数组中的两个元素
void swap(int arr[], int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

// 选择排序
void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        swap(arr, i, minIndex);
    }
}

// 冒泡排序
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1);
            }
        }
    }
}

// 插入排序
void insertionSort(int arr[], int n) {
    if (arr == nullptr || n < 2) {
        return;
    }
    for (int i = 1; i < n; i++) {
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
            // printarr(arr, n); // 打印当前数组状态
        }
        
    }
}

归并排序

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring> // 用于 std::memset

const int MAXN = 100001;

int arr[MAXN];
int help[MAXN];
int n;

// 归并排序递归版
void mergeSort1(int l, int r) {
    if (l == r) {
        return;
    }
    int m = (l + r) / 2;
    mergeSort1(l, m);
    mergeSort1(m + 1, r);
    int i = l, a = l, b = m + 1;
    while (a <= m && b <= r) {
        help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];
    }
    while (a <= m) {
        help[i++] = arr[a++];
    }
    while (b <= r) {
        help[i++] = arr[b++];
    }
    for (i = l; i <= r; i++) {
        arr[i] = help[i];
    }
}

// 归并排序非递归版
void mergeSort2() {
    for (int step = 1; step < n; step <<= 1) {
        int l = 0;
        while (l < n) {
            int m = l + step - 1;
            if (m + 1 >= n) {
                break;
            }
            int r = std::min(l + (step << 1) - 1, n - 1);
            int i = l, a = l, b = m + 1;
            while (a <= m && b <= r) {
                help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];
            }
            while (a <= m) {
                help[i++] = arr[a++];
            }
            while (b <= r) {
                help[i++] = arr[b++];
            }
            for (i = l; i <= r; i++) {
                arr[i] = help[i];
            }
            l = r + 1;
        }
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);

    std::cin >> n;
    for (int i = 0; i < n; i++) {
        std::cin >> arr[i];
    }

    // 选择一种归并排序方式
    // mergeSort1(0, n - 1);
    mergeSort2();

    for (int i = 0; i < n - 1; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << arr[n - 1] << std::endl;

    return 0;
}

随机快速排序

#include <iostream>
#include <cstdlib> // 用于 rand() 和 srand()
#include <ctime>   // 用于 time()

const int MAXN = 100001;
int arr[MAXN];
int n;

// 随机快速排序改进版
void quickSort2(int l, int r) {
    if (l >= r) {
        return;
    }
    // 随机选择基准值
    int x = arr[l + rand() % (r - l + 1)];
    // 调用荷兰国旗问题的划分函数
    partition2(l, r, x);
    // 获取等于区域的左右边界
    int left = first;
    int right = last;
    // 递归排序左右两部分
    quickSort2(l, left - 1);
    quickSort2(right + 1, r);
}

// 荷兰国旗问题的划分函数
int first, last;//分别控制左右两侧的边界移动

void partition2(int l, int r, int x) {
    first = l;
    last = r;
    int i = l;
    while (i <= last) {
        if (arr[i] == x) {
            i++;
        } else if (arr[i] < x) {
            std::swap(arr[first++], arr[i++]);
        } else {
            std::swap(arr[i], arr[last--]);
        }
    }
}

// 主函数
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);

    std::cin >> n;
    for (int i = 0; i < n; i++) {
        std::cin >> arr[i];
    }

    // 初始化随机数种子
    std::srand(std::time(nullptr));

    // 调用快速排序
    quickSort2(0, n - 1);

    // 输出排序结果
    for (int i = 0; i < n - 1; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << arr[n - 1] << std::endl;

    return 0;
}

堆排序

#include <iostream>
#include <vector>

// 主函数,对数组进行排序
std::vector<int> sortArray(std::vector<int>& nums) {
    if (nums.size() > 1) {
        // heapSort1为从顶到底建堆然后排序
        // heapSort2为从底到顶建堆然后排序
        // 用哪个都可以
        // heapSort1(nums);
        heapSort2(nums);
    }
    return nums;
}

// i位置的数,向上调整大根堆
// arr[i] = x,x是新来的!往上看,直到不比父亲大,或者来到0位置(顶)
void heapInsert(std::vector<int>& arr, int i) {
    while (arr[i] > arr[(i - 1) / 2]) {
        swap(arr, i, (i - 1) / 2);
        i = (i - 1) / 2;
    }
}

// i位置的数,变小了,又想维持大根堆结构
// 向下调整大根堆
// 当前堆的大小为size
void heapify(std::vector<int>& arr, int i, int size) {
    int l = i * 2 + 1;
    while (l < size) {
        // 有左孩子,l
        // 右孩子,l+1
        // 评选,最强的孩子,是哪个下标的孩子
        int best = (l + 1 < size && arr[l + 1] > arr[l]) ? l + 1 : l;
        // 上面已经评选了最强的孩子,接下来,当前的数和最强的孩子之前,最强下标是谁
        best = (arr[best] > arr[i]) ? best : i;
        if (best == i) {
            break;
        }
        swap(arr, best, i);
        i = best;
        l = i * 2 + 1;
    }
}

void swap(std::vector<int>& arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

// 从顶到底建立大根堆,O(n * logn)
// 依次弹出堆内最大值并排好序,O(n * logn)
// 整体时间复杂度O(n * logn)
void heapSort1(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n; i++) {
        heapInsert(arr, i);
    }
    int size = n;
    while (size > 1) {
        swap(arr, 0, --size);
        heapify(arr, 0, size);
    }
}

// 从底到顶建立大根堆,O(n)
// 依次弹出堆内最大值并排好序,O(n * logn)
// 整体时间复杂度O(n * logn)
void heapSort2(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = n - 1; i >= 0; i--) {
        heapify(arr, i, n);
    }
    int size = n;
    while (size > 1) {
        swap(arr, 0, --size);
        heapify(arr, 0, size);
    }
}

int main() {
    // 示例测试
    std::vector<int> nums = {3, 2, 1, 5, 6, 4};
    std::vector<int> sortedNums = sortArray(nums);

    std::cout << "排序后的数组: ";
    for (int num : sortedNums) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

基数排序

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring> // 用于 memset

using namespace std;

const int BASE = 100; // 可以设置进制
const int MAXN = 50001;

int help[MAXN];
int cnts[BASE];

// 返回 number 在 BASE 进制下有几位
int bits(int number) {
    int ans = 0;
    while (number > 0) {
        ans++;
        number /= BASE;
    }
    return ans;
}

// 基数排序核心代码
// arr 内要保证没有负数
// n 是 arr 的长度
// bits 是 arr 中最大值在 BASE 进制下有几位
void radixSort(vector<int>& arr, int n, int bits) {
    for (int offset = 1; bits > 0; offset *= BASE, bits--) {
        memset(cnts, 0, sizeof(cnts)); // 初始化计数数组
        for (int i = 0; i < n; i++) {
            // 数字提取某一位的技巧
            cnts[(arr[i] / offset) % BASE]++;
        }
        // 处理成前缀次数累加的形式
        for (int i = 1; i < BASE; i++) {
            cnts[i] = cnts[i] + cnts[i - 1];
        }
        for (int i = n - 1; i >= 0; i--) {
            // 前缀数量分区的技巧
            // 数字提取某一位的技巧
            help[--cnts[(arr[i] / offset) % BASE]] = arr[i];
        }
        for (int i = 0; i < n; i++) {
            arr[i] = help[i];
        }
    //      for (int num : arr) {
    //     cout << num << " ";
    // }
    // cout << endl;
    }
}

vector<int> sortArray(vector<int>& arr) {
    if (arr.size() > 1) {
        int n = arr.size();
        // 找到数组中的最小值
        int min_val = arr[0];
        for (int i = 1; i < n; i++) {
            min_val = min(min_val, arr[i]);
        }
        int max_val = 0;
        for (int i = 0; i < n; i++) {
            // 数组中的每个数字,减去最小值,转成非负数组
            arr[i] -= min_val;
            // 记录数组中的最大值
            max_val = max(max_val, arr[i]);
        }
        // 根据最大值在 BASE 进制下的位数,决定基数排序做多少轮
        radixSort(arr, n, bits(max_val));
        // 数组中所有数都减去了最小值,最后还原
        for (int i = 0; i < n; i++) {
            arr[i] += min_val;
        }
    }
    return arr;
}

int main() {
    int n;
    cout << "Enter the number of elements: ";
    // cin >> n;
    vector<int> arr({170, 45, 75, 90, 802, 24, 2, 66}); // 示例数组
    cout << "Enter the elements: ";
    // for (int i = 0; i < n; i++) {
    //     cin >> arr[i];
    // }

    vector<int> sorted_arr = sortArray(arr);
    cout << "Sorted array: ";
    for (int num : sorted_arr) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

计数排序

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 计数排序方法,接受一个整数数组作为参数,返回排序后的数组
vector<int> countingSort(const vector<int>& arr) {
    // 如果输入数组为空或者长度小于等于1,直接返回原数组
    // 因为空数组或只有一个元素的数组本身就是有序的
    if (arr.size() <= 1) {
        return arr;
    }

    // 找出数组中的最大值和最小值,用于确定计数数组的范围
    int max_val = arr[0];
    int min_val = arr[0];
    for (int num : arr) {
        // 如果当前数字大于最大值,则更新最大值
        if (num > max_val) {
            max_val = num;
        }
        // 如果当前数字小于最小值,则更新最小值
        else if (num < min_val) {
            min_val = num;
        }
    }

    // 创建计数数组,其长度为最大值与最小值的差加1
    // 这样计数数组的每个索引就可以对应于原数组中的一个值(经过最小值偏移)
    vector<int> count(max_val - min_val + 1, 0);
    // 统计原数组中每个值出现的次数
    // 通过将每个数减去最小值来得到在计数数组中的偏移索引
    for (int num : arr) {
        count[num - min_val]++;
    }

    // 将计数数组转换为每个元素的最终位置索引数组
    // 这里的操作是对计数数组进行累加
    // 例如,如果count[0]=3,count[1]=2,那么经过这个循环后count[1]=5
    // 表示原数组中值为1(假设最小值为0)的最后一个元素应该放在新数组的第5个位置(索引为4)
    for (size_t i = 1; i < count.size(); i++) {
        count[i] += count[i - 1];
    }

    // 创建一个与原数组长度相同的输出数组,用于存储排序后的结果
    vector<int> output(arr.size());
    // 从原数组的末尾开始遍历
    for (int i = arr.size() - 1; i >= 0; i--) {
        // 根据计数数组中的位置信息,将原数组中的元素放入输出数组中正确的位置
        // 注意这里要先减1,因为数组索引从0开始
        output[count[arr[i] - min_val] - 1] = arr[i];
        // 将计数数组中对应位置的值减1,表示已经处理了一个该值的元素
        count[arr[i] - min_val]--;
    }

    // 返回排序后的数组
    return output;
}

// 主函数,用于测试计数排序算法
int main() {
    vector<int> arr = {4, 2, 2, 8, 3, 3, 1};
    // 调用计数排序方法对数组进行排序
    vector<int> sorted = countingSort(arr);
    // 输出排序后的数组
    for (int num : sorted) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

三、总结

在这里插入图片描述

  • 数据量非常小的情况下可以做到非常迅速:插入排序
  • 性能优异、实现简单且利于改进(面对不同业务可以选择不同划分策略)、不在乎稳定性:随机快排
  • 性能优异、不在乎额外空间占用、具有稳定性:归并排序
  • 性能优异、额外空间占用要求O(1)、不在乎稳定性:堆排序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2407267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …