七大经典排序算法优化:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序、归并排序代码详解

目录

排序算法

1.插入排序

2.希尔排序

3.选择排序

4.冒泡排序

5.堆排序

6.快速排序

7.归并排序

排序算法

排序算法是一类用于将数据按照特定顺序(如升序或降序)排列的算法,常用于优化数据检索和处理。常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序和归并排序等。不同的排序算法在时间复杂度和空间复杂度上各有优劣,适用于不同规模和类型的数据集。选择合适的排序算法需要根据数据特点和实际应用需求进行权衡。

1.插入排序

基本思想:和前面的比,找到对应位置插入

插入排序的基本思想是通过构建一个有序序列,将未排序部分的元素逐个插入到已排序部分的合适位置。

  • 初始状态:第一个元素默认视为已排序。
  • 逐一插入:从第二个元素开始,依次与已排序部分的元素进行比较,将其插入到合适的位置。
  • 元素移动:如果当前元素比已排序部分的某个元素小,则将已排序的元素向右移动,腾出插入位置。
  • 重复过程:重复此过程,直到所有元素都被插入到正确的位置,整个数组变得有序。

// 插入排序函数
void insertionSort(int arr[], int n) {         
    int i, j, key;                              
    for (i = 1; i < n; ++i) {                  
        key = arr[i];  // 记录当前要插入的值      
        j = i - 1;                              
        // 向左移动比 key 大的元素                 
        while (j >= 0 && arr[j] > key) {       
            arr[j + 1] = arr[j];  // 元素右移   
            j--;                                
        }
        arr[j + 1] = key;  // 在找到的位置插入 key 
    }
}

2.希尔排序

基本思想:对每一个子表进行直接插入排序

希尔排序(Shell Sort)的基本思想是通过将整个数组分割成若干个子序列分别进行插入排序,从而加速排序进程。它是对插入排序的优化版本,通过逐步缩小间隔(增量)对元素进行比较和交换,使得数据在整体上趋于有序,最终再进行一次标准的插入排序来完成排序。

  • 分组:希尔排序会根据一个初始增量(通常是数组长度的一半)将数据分组。组内的元素相隔该增量距离。
  • 组内插入排序:对每一组内的元素执行插入排序,利用增量减少的数据交换,使得数据逐步接近有序状态。
  • 缩小增量:每次排序后,将增量逐步缩小,一般缩小为原来的一半,直到增量为1。增量为1时,相当于对整个数组执行了一次标准的插入排序。
  • 最终排序:由于前几轮已经将数据初步排序,最后一轮插入排序的效率会大大提高。

// 希尔排序函数
void shellSort(int arr[], int n) {
    // 初始步长为数组长度的一半,然后逐步减小步长
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // 对每个步长子序列进行插入排序
        for (int i = gap; i < n; ++i) {
            int temp = arr[i];  // 保存当前元素
            int j;

            // 对步长为 gap 的子表进行直接插入排序
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];  // 将较大的元素向后移动
            }

            arr[j] = temp;  // 将当前元素插入到合适位置
        }
    }
}

3.选择排序

基本思想:先扫 再找 放入最后

选择排序(Selection Sort)的基本思想是通过反复选择未排序部分中的最小(或最大)元素,将其放到已排序部分的末尾,直到所有元素都被排序为止。其主要步骤如下:

  • 初始状态:将整个数组视为未排序部分。
  • 选择最小元素:在未排序部分中找到最小的元素(或最大的元素),并记录其索引。
  • 交换位置:将找到的最小元素与未排序部分的第一个元素进行交换,这样该元素就被放到了已排序部分的末尾。
  • 缩小范围:将已排序部分和未排序部分的边界向右移动一位,重复步骤2和3,直到所有元素都被排序。

// 选择排序函数
void selectionSort(int arr[], int n) {
    int i, j, minIndex, temp;

    // 外层循环控制已排序部分的范围
    for (i = 0; i < n - 1; i++) {
        minIndex = i;  // 假设当前元素为最小值的索引

        // 内层循环找到未排序部分的最小值
        for (j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;  // 更新最小值索引
            }
        }

        // 交换当前元素和找到的最小值
        if (minIndex != i) {
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

4.冒泡排序

基本思想:从后两两对比,更小的往前放

冒泡排序(Bubble Sort)的基本思想是通过重复地比较相邻的元素并交换它们的顺序,使得较大的元素逐渐“冒泡”到数组的顶端(末尾)。这个过程不断重复,直到没有需要交换的元素为止,整个数组就变得有序。

  1. 相邻比较:从数组的第一个元素开始,依次比较相邻的两个元素。
  2. 元素交换:如果前一个元素大于后一个元素,就交换它们的位置。这样,每一轮后,最大的元素会“冒泡”到数组的末尾。
  3. 重复过程:对整个数组进行多轮比较和交换,每一轮都将未排序部分中的最大元素移动到正确位置。
  4. 优化:如果在某一轮中没有进行任何交换,说明数组已经有序,可以提前结束排序。

// 冒泡排序函数
void bubbleSort(int arr[], int n) {
    int i, j, temp;                             
    // 外层循环控制排序轮次                       
    for (i = 0; i < n - 1; ++i) {               
        // 内层循环从后往前两两比较,将较小的数往前放
        for (j = n - 1; j > i; --j) {
            if (arr[j] < arr[j - 1]) {
                // 交换两个元素
                temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
            }
        }
    }
}

5.堆排序

基本思想:先建堆 再找数 找一次 建一次 找到数 就输出 输出完 排序完

堆排序(Heap Sort)的基本思想是利用堆这种数据结构的特性来实现排序。堆是一种完全二叉树,具有堆性质,即每个节点的值都大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。堆排序的核心步骤如下:

  • 构建最大堆:将待排序的数组构建成一个最大堆。最大堆的根节点是数组中最大的元素,确保父节点的值总是大于其子节点的值。

  • 交换元素:将堆的根节点(即最大元素)与堆的最后一个元素交换。此时,最大元素已经放到了正确的位置。

  • 调整堆:减少堆的大小,并对根节点进行调整,以恢复最大堆的性质。通过调整,新的根节点将成为新的最大元素。

  • 重复操作:重复步骤2和3,直到所有元素都被排序。每次提取最大元素并调整堆时,堆的有效大小都会减小,直到堆为空。

// 交换两个元素的函数
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 调整堆,使其满足最大堆的性质
void heapify(int arr[], int n, int i) {
    int largest = i; // 初始化最大值为根节点
    int left = 2 * i + 1; // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    // 如果左子节点比根节点大
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }

    // 如果右子节点比当前最大值大
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }

    // 如果最大值不是根节点,交换并递归调整
    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        heapify(arr, n, largest); // 递归调整
    }
}

// 堆排序函数
void heapSort(int arr[], int n) {
    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }

    // 一个个提取元素
    for (int i = n - 1; i >= 0; i--) {
        swap(&arr[0], &arr[i]); // 将当前最大元素移到数组末尾
        heapify(arr, i, 0); // 调整堆
    }
}

6.快速排序

基本思想:小放枢轴左 大放枢轴右高低所指换 换针向枢轴高低所遇处 枢轴所落入递归再排至 左右仅一头

快速排序(Quick Sort)的基本思想是通过分治法(Divide and Conquer)来对数组进行排序。它的核心步骤如下:

  • 选择枢轴:从数组中选择一个元素作为枢轴(pivot)。常见的选择方式包括选择第一个元素、最后一个元素或中间元素。

  • 分区:重新排列数组,使得所有小于枢轴的元素都放在枢轴的左侧,所有大于枢轴的元素都放在右侧。经过这一步骤,枢轴的最终位置就是它在排序后数组中的正确位置。

  • 递归排序:

    • 对于枢轴左侧的子数组,重复步骤1和步骤2。
    • 对于枢轴右侧的子数组,同样重复步骤1和步骤2。
  • 结束条件:当子数组的大小为1或0时,递归结束,整个数组就变得有序。

// 交换两个元素的函数
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 分区函数,返回枢轴的最终位置
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 选择最后一个元素作为枢轴
    int i = low - 1; // 小于枢轴的元素的索引

    // 遍历数组,将小于枢轴的元素放到左边
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++; // 增加小于枢轴的元素索引
            swap(&arr[i], &arr[j]); // 交换元素
        }
    }
    // 将枢轴放到正确的位置
    swap(&arr[i + 1], &arr[high]);
    return i + 1; // 返回枢轴的索引
}

// 快速排序函数
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pivotIndex = partition(arr, low, high); // 获取分区后枢轴的位置
        quickSort(arr, low, pivotIndex - 1); // 对左半部分递归排序
        quickSort(arr, pivotIndex + 1, high); // 对右半部分递归排序
    }
}

7.归并排序

基本思想:两有序并为一有序 另建表 分别从两表头依次对比

归并排序(Merge Sort)的基本思想是利用分治法将一个待排序的数组分成多个子数组,然后通过合并操作将这些子数组合并为一个有序的数组。其主要步骤如下:

  • 分割:将待排序的数组递归地分割成两个子数组,直到每个子数组的长度为1。此时每个子数组都是有序的。
  • 合并:将两个已排序的子数组合并为一个有序的数组。合并时,从每个子数组的开头依次比较元素,将较小的元素放入新的数组中,直到所有元素都被合并。
  • 重复操作:不断重复以上两个步骤,直到所有子数组都被合并成一个完整的有序数组。

// 合并两个有序数组
void merge(int arr[], int left, int mid, int right) {
    int i, j, k;
    int n1 = mid - left + 1; // 左子数组的大小
    int n2 = right - mid;    // 右子数组的大小

    // 创建临时数组
    int L[n1], R[n2];

    // 将数据复制到临时数组 L[] 和 R[]
    for (i = 0; i < n1; i++)
        L[i] = arr[left + i];
    for (j = 0; j < n2; j++)
        R[j] = arr[mid + 1 + j];

    // 合并临时数组
    i = 0; // 初始索引 L[]
    j = 0; // 初始索引 R[]
    k = left; // 初始索引 arr[]

    // 逐个比较并合并
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }

    // 复制剩余元素
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }

    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

// 归并排序函数
void mergeSort(int arr[], int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2; // 计算中间索引

        // 递归排序左半部分
        mergeSort(arr, left, mid);
        // 递归排序右半部分
        mergeSort(arr, mid + 1, right);

        // 合并已排序的部分
        merge(arr, left, mid, right);
    }
}

最后,在选择合适的排序算法时,应该考虑以下几个因素:

  1. 数据规模:不同算法在处理不同规模的数据时表现不同。例如,插入排序和冒泡排序在小规模数据上表现良好,而快速排序和归并排序则更适合处理大规模数据。

  2. 时间复杂度

    • 插入排序:O(n²)(适用于小规模数据)
    • 希尔排序:O(n log n)(依赖于增量序列)
    • 选择排序:O(n²)(简单,但效率低下)
    • 冒泡排序:O(n²)(效率较低)
    • 堆排序:O(n log n)(适合处理大规模数据)
    • 快速排序:O(n log n)(平均情况好,但最坏情况为 O(n²))
    • 归并排序:O(n log n)(稳定,适合大规模数据)
  3. 稳定性:如果需要保持相同元素的相对顺序,应该选择稳定排序算法。稳定的排序算法包括插入排序、归并排序和冒泡排序,而快速排序和堆排序则不稳定。

  4. 空间复杂度:考虑内存使用情况。归并排序和插入排序的空间复杂度较低,而归并排序需要 O(n) 的额外空间。

选择建议:

  • 小规模数据:可以选择插入排序或冒泡排序,简单易实现。
  • 大规模数据:推荐使用快速排序或归并排序,它们在平均情况下性能优越。
  • 需要稳定性:选择归并排序或插入排序。
  • 内存受限:选择原地排序的堆排序或快速排序。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Deepin】钉钉下载文件图片闪退问题解决(临时方式)

环境 故障 下载文件、图片等闪退 解决 cd /opt/apps/com.alibabainc.dingtalk/files/7.6.0-Release.4091801/sudo rm -rf ./libstdc.so.6*注&#xff1a; 7.6.0-Release.4091801可能会略有不同&#xff0c;根据实际情况调整保险起见&#xff0c;操作第二行删除命令时&#…

第二十七篇:传输层讲解,TCP系列一

一、传输层的功能 ① 分割与重组数据 传输层也要做数据分割&#xff0c;所以必然也需要做数据重组。 ② 按端口号寻址 IP只能定位数据哪台主机&#xff0c;无法判断数据报文应该交给哪个应用&#xff0c;传输层给每个应用都设置了一个编号&#xff0c;这个编号就是端口&…

Wails 学习笔记:Wails核心思想理解

文章目录 1. Wails 的核心思想2. 工作流程2.1 前端渲染2.2 后端逻辑2.3 前后端通信2.4 应用打包与分发 3. Wails 主要组件3.1 WebView3.2 事件与数据绑定3.3 窗口管理 4. Wails 的优点5. Wails 的使用场景6. 启动函数Runwails.Run() 的主要功能wails.Run() 的参数&#xff1a;w…

【C++】STL篇 string类(使用)

string的学习会分为两个大步骤&#xff0c;第一步就是会使用string&#xff0c;第二部是模拟实现string。这篇文章我们介绍一下string类以及它的使用。string大概有一百多个接口&#xff0c;我们需要重点掌握的就十几二十个。string其实就是字符串&#xff0c;严格来说string类…

STM32传感器模块编程实践(八) HX711压力传感器称重模块简介及驱动源码

文章目录 一.概要二.HX711主要技术指标三.HX711模块参考原理图四.模块接线说明五.模块工作原理介绍六.模块通讯协议介绍七.STM32单片机与HX711模块实现重量测量实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 八.小结 一.概要 电子秤是将检测与转换技术、计算机技术、信息…

一文通透OpenAI o1:从CoT、Quiet-STaR、Self-Correct、Self-play RL、MCST等技术细节到工程复现

前言 注意&#xff0c;本文自10.12日起&#xff0c;正在每天更新的过程中&#xff0c;包括已写的部分也在不断修改(以增加更多技术细节、更加通俗易懂) 预计10.20完成第一版&#xff0c;10月底修订到第二版——具体修订记录详见本文文末.. 可能是去年写或讲的关于ChatGPT原理的…

植物大战僵尸杂交版之后要出联机版植物大战僵尸?(内测中,可在安卓手机上玩,文末附下载链接)

继植物大战僵尸杂交版之后给大家介绍一个杂交版作者正在酝酿的“植物大战僵尸射击版” 植物大战僵尸射击版介绍 《植物大战僵尸杂交版》的创作者“潜艇伟伟迷”即将推出PVZ改版新作——《植物大战僵尸射击版》。游戏将支持PC、手游和web端&#xff0c;提供单人、双人、三人、…

【java Web如何开发?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

华为eNSP实验:交换机流量控制之风暴控制

一、交换机流量控制之风暴控制 风暴控制是交换机流量控制中的一种重要机制&#xff0c;用于防止网络中的广播、多播或单播风暴对网络性能造成破坏。具体如下&#xff1a; 基本原理&#xff1a;风暴控制通过监控端口的入站流量&#xff0c;并与预设的风暴抑制级别进行对比来管…

java数组讲解

前言&#xff1a; 由上两章&#xff0c;我们已经了解关于java的基础语法&#xff0c;这章我们将讲解数组的相关语法&#xff0c;坐好了没&#xff0c;我们准备要发车啦&#xff01;&#xff01;&#xff01; 我们将从五部分给大家讲解&#xff1a; 1数组的基本概念 2.数组是…

使用Windows创建一个MFC应用【带界面】

MFC使用教程【对初学者保姆型友好&#xff01;】 目录 前提条件 1&#xff1a;创建MFC应用程序 2. 项目结构解读 引用 外部依赖项 头文件 源文件 资源文件 文件功能详解 项目的主要流程 步骤2&#xff1a;配置OpenCV 安装OpenCV 包含目录与库文件 步骤3&#xff1…

Milvus×Dify半小时轻松构建RAG系统

最近&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术在AI界引起了广泛关注。作为一种将知识库与生成模型结合的新型架构&#xff0c;RAG大大提升了AI应用的实际表现。而在构建RAG系统时&#xff0c;Milvus作为业界领先的开源向量数据库&#xff0c;扮演着关键角色。本…

视频格式在线转换,五种超实用的视频格式转换工具!

视频内容无处不在&#xff0c;从教育课程到娱乐电影&#xff0c;从社交媒体分享到在线会议&#xff0c;视频已成为我们日常生活中不可或缺的一部分。然而&#xff0c;不同的设备和平台支持的视频格式各异&#xff0c;会导致视频文件在某些设备上无法播放。因此&#xff0c;掌握…

计算机毕业设计python+spark知识图谱课程推荐系统 课程预测系统 课程大数据 课程数据分析 课程大屏 mooc慕课推荐系统 大数据毕业设计

指导教师意见&#xff1a; 1&#xff0e;对“文献综述”的评语&#xff1a; 对教育领域数据可视化的相关背景和现状做了综述&#xff0c;明确了课题的研究目标和研究重点&#xff0c;并对研究手段进行了概述。为后面的毕业设计做好了准备。 对本课题的深度、广度及工作量的…

【开源】第三期:数字货币程序化交易终端开源

关于初衷&#xff1a; 这篇文章&#xff0c;其实应该在六年前发出来&#xff0c;但是受制于各种杂事和生活琐事&#xff0c;一直拖到现在&#xff0c;想必有朋友看到在"终端"那期里&#xff0c;聊到的数字货币交易的实践&#xff0c;那个时候遍地都是数字货币交易所&…

git gui基本使用

一、图形化界面 二、创建新项目 创建文件&#xff0c;加入暂存区&#xff0c;提交到版本库 三、创建分支 四、合并分支 1.切换至master 五、更新分支 六、解决冲突 修改冲突&#xff0c;加入暂存区&#xff0c;提交到版本库 七、远程创建库 Gitee - 基于 Git 的代码托管和研…

储能硬件实物图

B 薄膜电容 薄膜电容 D 杜邦线 杜邦线 G 固态电容 固态电容 I IGBT iGBT S 散热片 散热片 Y 压敏电阻 压敏电阻 液冷板 液冷板

瑞萨IDE:CS+ for CC编译过程中执行脚本文件

最近发现使用CS for CC IDE发现一个很有意思的功能。编译工程过程中&#xff0c;IDE自动执行Python脚本和批处理脚本&#xff0c;极大地提高开发效率。 编写好脚本文件后&#xff0c;在IDE中选择CC-RH&#xff08;Build Tool&#xff09;->Common Options->Others。 Co…

SQL进阶技巧:如何找出开会时间有重叠的会议室?| 时间区间重叠问题

目录 0 场景描述 1 数据准备 2 问题分析 方法1&#xff1a;利用 lateral view posexplode()函数将表展开成时间明细表 方法2&#xff1a;利用数学区间讨论思想求解 3 小结 如果觉得本文对你有帮助&#xff0c;想进一步学习SQL语言这门艺术的&#xff0c;那么不妨也可以选…

arm架构ceph pacific部署

背景 合作伙伴实验室的华为私有云原来使用单点的nfs做为存储设备&#xff0c;现有两方面考量&#xff0c;业务需要使用oss了&#xff0c;k8s集群及其他机器也需要一套可扩展的分布式文件系统 部署ceph 初始机器配置规划 IP配置主机名Role10.17.3.144c8g1T数据盘ceph-node01…