C语言 数据结构 --排序 (直接插入排序,选择排序,交换排序......)

引言:本章简洁的讲解一下数据结构中的几个常见的排序 ,作复习之用,后面可能会补一些其他的排序 。并给出一些小编学习中遇到的坑,作借鉴。


1.直接插入排序

直接插入排序是一种简单直观的排序算法,其基本思想是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。

易写错的地方: 

1. 循环边界问题

// 错误:可能导致数组越界
for(i = 0; i < n; i++)  // 应改为 i < n-1

原因
当 i 达到 n-1 时,end 为 n-1,此时 a[end+1] 即 a[n],会访问数组越界。

正确写法

for(i = 0; i < n-1; i++)  // 循环到 n-2,确保 end+1 <= n-1

2. 待插入元素保存位置错误

错误写法
在循环内部错误更新 tmp 的值。

// 错误:每次循环都重新赋值 tmp
while(end >= 0) {int tmp = a[end+1];  // 错误:应在循环外保存一次...
}

原因
tmp 应在每次外层循环开始时保存当前待插入的元素,而非在循环内部重复赋值。

正确写法

// 正确:在循环外保存待插入元素
int tmp = a[end+1];
while(end >= 0) {...
}

3. 插入位置错误

错误写法
插入操作位置错误,导致元素覆盖。

// 错误:可能覆盖未处理的元素
a[end] = tmp;  // 应改为 a[end+1] = tmp

原因
当 while 循环结束时,end 指向的是第一个小于等于 tmp 的元素,因此 tmp 应插入到 end+1 位置

a[end+1] = tmp;  // 插入到正确位置

 【示范代码】

//直接插入排序
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++)//循环边界是 n-1,防止越界{//单趟排序int end = i;    //end是数组下标int tmp = a[end+1];    //tmp是数组元素//保存a[i+1]while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];//向后移位--end;}else{break;}}a[end + 1] = tmp;//将tmp放在正确的位置}
}

2.希尔排序

希尔排序(Shell Sort)是插入排序的一种改进版本,也被叫做缩小增量排序。和直接插入排序比起来,它的效率更高,主要是通过将原始数据分成多个子序列来减少元素移动的次数,不过子序列的划分并非简单地逐段分割,而是依据特定的增量来进行。

希尔排序的核心思路是:先将整个待排序的记录序列分割成若干个子序列,分别对这些子序列进行直接插入排序。随着增量逐渐减小,子序列的长度越来越大,整个序列会变得越来越接近有序。当增量减至 1 时,整个序列就会被合并为一个,再进行一次直接插入排序,此时排序完成。

【示范代码】 

插入排序 ---  希尔排序
void ShellSort(int* a, int n)
{int gap = n;//gap = gap/3+1;while (gap > 0){gap /= 2;for (int i = 0; i + gap < n; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end = end - gap;}else{break;}}a[end + gap] = tmp;}}}

3.选择排序

选择排序(Selection Sort)是一种简单直观的排序算法,它的基本思想是每一轮从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完(同时它也是最烂的排序)

【示范代码】 

//选择排序 同时选出大和小  a.
void DSelectSort(int* a, int n)
{int left = 0, right = n - 1;while (left < right){int mini = left, maxi = left;for (int i = left+1; i <= right; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[left], &a[mini]);if (left == maxi){maxi = mini;}Swap(&a[right], &a[maxi]);left++;right--;}
}//选择排序 -- 只选出小的进行交换  b.
void SSelectSort(int* a, int n)
{int left = 0;while(left < n){int min = left;for (int i = left; i < n; i++){if (a[min] > a[i]){min = i;}}Swap(&a[left], &a[min]);left++;}
}

4.堆排序

堆排序(Heap Sort)是一种基于二叉堆数据结构的高效排序算法,它利用堆的特性进行排序,时间复杂度为 O (n log n),且是一种原地排序算法(空间复杂度 O (1))。

易写错的地方:

  1. AdjustDown 函数中的条件判断
    AdjustDown函数里,要判断右孩子是否存在,同时还要比较左右孩子的大小。要是这一步没处理好,可能会造成数组越界。
if (child + 1 < n && a[child] < a[child + 1])
  1. 建堆的起始位置
    建堆得从最后一个非叶子节点开始,也就是(n-1-1)/2。要是起始位置搞错了,堆的性质就无法保证。
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
  1. 交换元素和调整范围
    在排序阶段,每次把堆顶元素和当前未排序部分的末尾元素交换之后,要对剩余元素重新进行调整。此时,调整范围会逐步减小。
Swap(&a[end], &a[0]);
AdjustDown(a, end, 0); // 注意这里是end,而不是n

【示范代码】 

void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//堆排序
void HeapSort(int* a, int n)
{//建大堆for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a,n,i);}//向下调整排序int  end = n - 1;while (end > 0){Swap(&a[end], &a[0]);AdjustDown(a, end, 0);end--;}}

5.冒泡排序

冒泡排序(Bubble Sort)是一种简单直观的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成(具有教学意义)

【示范代码】 

// 交换排序
//冒泡排序
void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){for (int i = 0; i < n - j - 1; i++){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);}}}
}

6.快速排序

快速排序(QuickSort)是一种基于分治(Divide and Conquer)策略的排序算法,由托尼・霍尔(Tony Hoare)于 1959 年提出。它通过选择一个 “基准值”(Pivot),将数组分成两部分,一部分的元素都比基准值小,另一部分都比基准值大,然后递归地对这两部分进行排序,最终实现整个数组有序。

6.1 快速排序的简单写法
//快速排序 -- 递归 最简单的一种写法
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int end = right, begin = left;//单趟排序int keyi = left;while (left < right){//右边先走 找小while(left < right && a[right] >= a[keyi]){right--;}//找大while(left < right  &&  a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;//递归循环QuickSort(a, begin,keyi-1);QuickSort(a, keyi+1,end);}
6.2 优化取值
 6.21优化方式 1 -- 三数取中

1.什么是三数取中? 2.为什么要三数取中?

1.1 三数取中(Median of Three)是快速排序的一种优化策略,核心思想是在当前排序区间中选取左端、右端、中间位置的三个元素,比较它们的值后选择中间值作为基准值(Pivot)。
例如,对于数组区间arr[low...high],计算中间位置mid = low + (high - low) / 2,比较arr[low]、arr[mid]、arr[high],将中间值与区间左端(或右端)元素交换,使基准值位于区间边界,再进行常规的快速排序分区操作。

2.1避免最坏情况
传统快速排序若直接选择固定端点(如左端)作为基准值,在数组已排序或接近排序时会退化为最坏时间复杂度O(n²)(每次分区极度不平衡)。
三数取中能显著降低基准值为极值的概率,使分区更平衡,平均时间复杂度趋近于理想的O(n log n)。
2.2. 减少极端数据影响
对于存在大量重复元素或有序性较强的数组,三数取中可有效避免基准值偏向某一端,提升算法稳定性。
2.3. 实现简单高效
仅需额外 3 次元素比较和 1 次交换操作,代价极低,却能大幅优化基准值的选择

6.22 优化方式2 -- 随机取值

随机取值(Random Pivot)是另一种基准值优化策略,具体做法是在当前排序区间内随机选择一个元素作为基准值,通常通过生成随机索引(如randomIndex = low + rand() % (high - low + 1))实现。

6.3 三种快速排序的三种优化的方法(三数取中)
a.hoare 霍尔

//a.霍尔//hoare
int Part1(int* a, int left, int right)
{int midi = GetMidNumi( a, left, right);if (midi != left){Swap(&a[midi], &a[left]);}int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while(left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;return keyi;}
   b.挖坑法 

//b.挖坑法
int Part2(int* a, int left, int right)
{	int midi = GetMidNumi( a, left, right);if (midi != left){Swap(&a[midi], &a[left]);}int key = a[left];int hole = left;while (left < right){while (left < right && a[right] >= key)right--;a[hole] = a[right];hole = right;while(left < right && a[left] <= key)left++;a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

c. 前后指针 

//c.前后指针法
int Part3(int* a, int left, int right) {int midi = GetMidNumi(a, left, right);if (midi != left){Swap(&a[midi], &a[left]);}int keyi = left;int slow = left;int fast = left + 1;while (fast <= right){if(a[fast] < a[keyi] && ++slow!= fast){Swap(&a[fast], &a[slow]);}fast++;}Swap(&a[slow], &a[keyi]);keyi = slow;return keyi;}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;//int keyi = Part1(a, left, right);//int keyi = Part2(a, left, right);int keyi = Part3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

7.归并排序

归并排序(Merge Sort)是一种基于分治思想的高效排序算法,由约翰・冯・诺伊曼(John von Neumann)于 1945 年提出。它将数组分成两个子数组,分别对这两个子数组进行排序,然后将排好序的子数组合并成一个最终的有序数组。

 【示范代码】 

//归并排序
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}void _MergeSort(int* a, int begin, int end, int* tmp)
{// 1. 递归终止条件if (begin >= end)return;// 2. 分割数组int mid = (begin + end) / 2;// [begin, mid] [mid+1,end],子区间递归排序_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid + 1, end, tmp);// 3. 合并两个已排序子数组// [begin, mid] [mid+1,end]归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}// 处理左子数组剩余元素while (begin1 <= end1){tmp[i++] = a[begin1++];}// 处理右子数组剩余元素while (begin2 <= end2){tmp[i++] = a[begin2++];}// 4. 将临时数组结果复制回原数组memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

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

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

相关文章

华为云发布盘古大模型 5.5 新一代昇腾 AI 云服务上线

2025 年 6 月 20 日&#xff0c;华为开发者大会 2025&#xff08;HDC 2025&#xff09;在东莞召开。华为常务董事、云计算 CEO 张平安宣布基于 CloudMatrix 384 超节点的新一代昇腾 AI 云服务全面上线&#xff0c;并发布盘古大模型 5.5&#xff0c;五大基础模型实现技术突破&am…

Reactor Handle

handle 是 Reactor 中一个非常灵活的操作符&#xff0c;它允许你对每个源元素进行处理&#xff0c;并可以选择性地发出零个或多个元素。它既可以用于映射&#xff08;map&#xff09;也可以用于过滤&#xff08;filter&#xff09;&#xff0c;因此可以看作是 map 和 filter 的…

C#哈希加密:原理、实现与应用

C#哈希加密&#xff1a;原理、实现与应用 在当今数字化时代&#xff0c;数据安全是每个应用程序都必须重视的问题。哈希加密作为一种重要的加密技术&#xff0c;在密码存储、数据完整性验证、数字签名等领域发挥着关键作用。本文将深入探讨C#中哈希加密的原理、常用算法以及实…

httpbin.org是什么,有什么作用

httpbin.org 是一个开源的 HTTP 请求与响应测试服务&#xff0c;基于 Python 的 Flask 框架开发 它允许开发者发送各种 HTTP 请求&#xff0c;并返回请求的详细信息&#xff0c;便于调试和验证 HTTP 客户端的行为。以下是其核心功能和作用详解&#xff1a; 一、核心功能与作用…

mongodb生产备份工具PBM

如果你的 MongoDB 数据量特别大&#xff08;例如几十 GB、TB 级别&#xff09;&#xff0c;普通的 mongodump/mongorestore 会显得缓慢且资源消耗大&#xff0c;不适合生产级别大数据集。下面是当前 MongoDB 社区和企业广泛使用的几种备份方案对比和推荐&#xff1a; 工具是否…

【LeetCode#第167题】两数之和Ⅱ

给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 < index2 < numbers…

Python(一)实现一个爬取微信小程序数据的爬虫+工程化初步实践

文章目录 前言用Charles 抓包 iOS 微信小程序在Mac端和iOS端安装Charles 自签名证书Mac端iOS端 能抓到Safari浏览器的包但是抓不到微信小程序的包直接在iOS 上抓包的App如何抓取Android 7.0 以上/Harmony OS微信小程序包 Python 项目工程化pip 切换为国内镜像源工程化参考脚手架…

uview ui request get / post 传参含params和json数据的分析和使用

背景。单独写了controller方法去配合移动端的接口调用。但有的接口与pc端类似。于是进行了复用。但接口得复制不是。 uview js request 文档 注意迪三个参数是header 后端接口GET方法 调用代码截图 浏览器调试 总结。 复制之前的api接口。为了方便复用底层实现。接口类型…

用 pnpm + TurboRepo,构建多项目高效开发体系

在现代前端项目日益复杂的今天&#xff0c;我们越来越多地面对一个场景&#xff1a;多个项目共享逻辑、组件和依赖&#xff0c;而维护和构建效率却在不断拉垮。这种情况下&#xff0c;传统项目结构的痛点就显现无遗。 从我亲身实践来看&#xff0c;选择 pnpm TurboRepo 构建 …

Pytest 使用命令行参数执行指定环境的脚本—— Python 实践

&#x1f9fe; 一、项目背景 在自动化测试中&#xff0c;我们经常需要根据不同的运行环境&#xff08;如测试环境和生产环境&#xff09;来执行测试脚本。本文将详细介绍如何通过命令行参数来指定运行环境&#xff0c;并使用 Python 和 pytest 框架实现这一功能。 &#x1f6e…

利用可控验证码位数实现拒绝服务攻击(DoS)风险与线程模型分析

一、背景介绍&#xff1a;验证码接口中的潜在 DoS 漏洞 在渗透测试过程中&#xff0c;常见验证码接口支持传入“验证码位数”参数&#xff0c;表面看是业务可配置&#xff0c;实则若未做上限控制&#xff0c;极易成为资源消耗型 DoS 攻击入口。 &#x1f9ea; 测试场景&#…

Spring Cloud Feign 整合 Sentinel 实现服务降级与熔断保护

Spring Cloud Feign 整合 Sentinel 实现服务降级与熔断保护 在微服务架构中&#xff0c;服务之间的调用往往依赖 Feign&#xff0c;而服务调用的稳定性又至关重要。本文将介绍如何将 Feign 与 Sentinel 结合使用&#xff0c;实现服务的容错保护&#xff08;如降级与熔断&#…

宠物医院系统的设计与实现(springBoot版)

一、开题报告 一、本选题研究的意义和背景&#xff08;理论与现实意义&#xff09;&#xff1a; 背景&#xff1a;随着人们生活水平的提高&#xff0c;宠物饲养愈发普遍&#xff0c;宠物医院的需求也日益增长。挂号方式主要依赖现场挂号&#xff0c;导致宠物主人需要长时间排队…

SOCKSv5 协议通信的完整阶段与报文格式详解

SOCKSv5 协议的通信通常分为以下几个主要阶段&#xff1a; 方法协商阶段 (Method Negotiation)方法依赖的子协商阶段 (Method-Dependent Sub-negotiation) - 本例为用户名/密码认证请求发送阶段 (Request Sending)请求回复阶段 (Request Reply)数据传输阶段 (Data Transfer) …

​​Git提交代码Commit消息企业级规范

​​Git Commit 类型完整指南​​ 类型用途示例​​feat​​新增功能&#xff08;面向用户的功能性变更&#xff09;git commit -m "feat: 添加用户登录功能"​​fix​​修复 Bug&#xff08;解决代码中的问题&#xff09;git commit -m "fix: 修复首页加载崩溃…

TiDB AUTO_RANDOM 超大主键前端精度丢失排查:JavaScript Number 限制与解决方案

前端长整型主键“失踪”记 ——一次 ArrayIndexOutOfBoundsException 的排查全过程 一、事故现场 最近在维护 SMS-OFFICE 后台系统时&#xff0c;运维同事反馈&#xff1a; 点击「短信详情」或「邮箱账号详情」时&#xff0c;偶尔弹窗空白、日志报错&#xff1a; java.lang.A…

在postgresql使用mybatis动态创建数据库分区表

在postgresql使用mybatis动态创建数据库分区表 1. 整体描述2. 前期准备2.1 创建主表语句2.2 创建分表语句2.3 xxl-job 3. 代码实现3.1 mapper.xml层3.2 mapper.java层3.3 service接口层3.4 service实现层3.5 controller层 4. 总结 1. 整体描述 在java下实现&#xff1a;创建分…

Python网安-zip文件暴力破解

目录 源码在这里 需要的模块 准备一个密码本和需要破解的ZIP文件 一行一行地从密码文件中读取每个密码。 核心部分 注意&#xff0c;需要修改上段代码注释里的这段具有编码问题的代码&#xff1a; 源码在这里 https://github.com/Wist-fully/Attack/tree/cracker 需要的…

聊聊Golang开发工程师

诞生背景 Go由Google三位顶尖工程师&#xff08;Ken Thompson、Rob Pike、Robert Griesemer&#xff09;设计&#xff0c;目标是解决两大行业痛点&#xff1a; 硬件利用率不足&#xff1a;多核CPU普及&#xff0c;但C/C等语言难以高效利用并发能力&#xff1b; 开发效率低下&a…

机器学习6——线性分类函数

线性分类函数 分类问题的两种决策方法&#xff1a; 概率方法&#xff1a;通过计算后验概率进行分类。优点是在概率分布已知的情况下可以得到最优解&#xff0c;缺点是实际中概率密度通常未知&#xff0c;需要通过大量数据估计。判别方法&#xff1a;假设判别函数的形式已知&…