【数据结构初阶】--从“最小值筛选”到代码落地,解锁选择排序的核心思想!

【数据结构初阶】--从“最小值筛选”到代码落地,解锁选择排序的核心思想!


在这里插入图片描述

🔥@晨非辰Tong: 个人主页
👀专栏:《C语言》《数据结构与算法入门指南》
💪学习阶段:C语言、数据结构与算法初学者
⏳“人理解迭代,神理解递归。”


文章目录


–引言

在算法修仙界,选择排序一脉两大功法——“直接选择”与“堆排序”,为争夺“话事人”之位已论道多年。一者大道至简,一者内蕴玄机。本期《排序算法卷二》将带您深入这场核心对决,揭晓谁能以绝对实力,执掌选择排序之牛耳。

源码点我


一、排序宗门:选择排序

1.1 流派基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,将其存放到序列的起始(或末尾)位置,直到全部待排序的数据元素排完。

二、 流派1:直接选择排序

1.1 基本思想

每一趟(如第 i 趟)在后面n - i + 1个待排序的元素中选取最小(或最大)的元素,作为有序序列的第i个元素(原地操作数组),直到第n-1趟做完,待排序元素只剩下一个,就不用再选了。

1.1.1 算法思路

首先定义四个变量–> begin 指向待排序元素的首元素、end 指向待排序元素的末元素、min 指向待排序元素中的最小值(初始值想首元素)、max 指向待排序元素的最大值(初始值想首元素)。

开始进行循环遍历数组,变量beginend 充当未排序元素的边界。每次遍历让 min 指向最小的,并且与 begin 指向的元素交换;同样,每次遍历让 max 指向最大的,并且与 end 指向的元素交换。

每交换完一次,未排序元素的边界就缩小一位:begin++end--

在这里插入图片描述

1.1.2 特性总结

  • 直接选择排序的思考很简单,但是整体的效率有所欠缺,实际中很少用到;
  • 时间复杂度:O(N2);
  • 空间复杂度:O(1);

1.2 排序源码呈现

1.2.1 残缺排序功法

//Sort.c文件//1)直接选择排序voidSelectSore(int* arr,int n){//定义边界变量int begin =0;int end = n -1;//总体循环:在未排序界限以内while(begin < end){//定义最大值、最小值变量int min = begin;int max = begin;//循环寻找min、max//因为初始都指向begin,从begin + 1开始for(int i = begin +1; i <= end; i++){if(arr[i]< arr[min]){ min = i;}if(arr[i]> arr[max]){ max = i;}}//对应交换Swap(&arr[begin],&arr[min]);Swap(&arr[end],&arr[max]);//遍历完一次,边界缩小 begin++; end--;}}//————————————////test.c文件test01(){//int arr[] = {5,3,9,6,2,4};int arr[]={5,3,9,6,2,4,7,1,8};int n =sizeof(arr)/sizeof(arr[0]);printf("排序之前:");PrintArr(arr, n);//直接选择排序SelectSore(arr, n);printf("排序之后:");PrintArr(arr, n);}intmain(){test01();return0;}
在这里插入图片描述


经过两个不同的乱序数组的直接选择排序发现,第一组成功完成排序任务,但是第2组在中间却出现了问题,这是为什么呢?

画图看看:

在这里插入图片描述


当我们画完整个的循环遍历过程,就发现了问题所在:maxbegin 的位置,Swap(arr[min], arr[begin])就覆盖了原本 max 指向的最大值。尽管已经调换了arr[min]、arr[begin] ,这个时候排序完成,但是Swap(arr[max], arr[end])又换回去了,导致排序失败。

那么就需要对 maxbegin 进行特殊处理,加上一定的条件:当 max == begin ,提前将 max 移到 min

1.2.2 完成排序功法

//修正voidSelectSore(int* arr,int n){//定义边界变量int begin =0;int end = n -1;//总体循环:在未排序界限以内while(begin < end){//定义最大值、最小值变量int min = begin;int max = begin;//循环寻找min、max//因为初始都指向begin,从begin + 1开始for(int i = begin +1; i <= end; i++){if(arr[i]< arr[min]){ min = i;}if(arr[i]> arr[max]){ max = i;}}//对应交换if(max == begin){ max = min;}Swap(&arr[begin],&arr[min]);Swap(&arr[end],&arr[max]);//遍历完一次,边界缩小 begin++; end--;}}
在这里插入图片描述

1.3注意要点

  • 数据边界的确定:begin、end;
  • max与begin重合,导致调换重复。

三、流派2:堆排序

对于堆排序,想必大家都不陌生,在前面对于堆的专题学习中已经进行了堆排序的实现具体操作。

3.1 基本思想

堆排序是一种高效的比较型排序算法,其核心思想是利用“堆”这种数据结构进行排序。

堆排序分为两个主要阶段:首先将待排序的序列建成大堆(升序),此时堆顶元素为最大值;然后反复的将堆顶元素与堆末尾元素交换,并将堆的大小减一,再对新的堆顶元素进行调整维持大堆的性质,直到堆中只剩一个元素。这样每次都将当前最大值放到正确位置,最终完成排序。

3.1.1 特性总结

  • 时间复杂度:O(n logn);
  • 空间复杂度:O(1);

3.2 排序源码呈现

//Sort.c文件//向下调整算法voidAdjustDown(HPDataType* arr,int parent,int n)//传父节点下标、有效数据个数{int child = parent *2+1;while(child < n)//child超过n,代表没有子节点了{//大堆结构:<,小堆结构: >if(child +1< n && arr[child]> arr[child +1]){ child++;}//孩子和父亲比较//大堆结构:>,小堆结构:<if(arr[child]< arr[parent]){//父子调换Swap(&arr[child],&arr[parent]); parent = child; child = parent *2+1;}else{break;}}}//堆排序voidHeapSort(int* arr,int n){//向下调整算法——建小堆for(int i =(n -1-1)/2; i >=0; i--){AdjustDown(arr, i, n);}//实现排序--升序数组int end = n -1;while(end >0){Swap(&arr[0],&arr[end]);AdjustDown(arr,0, end);//logn end--;}}//——————————————////test.c文件test01(){//int arr[] = {5,3,9,6,2,4};int arr[]={5,3,9,6,2,4,7,1,8};int n =sizeof(arr)/sizeof(arr[0]);printf("排序之前:");PrintArr(arr, n);//堆排序HeapSort(arr, n);printf("排序之后:");PrintArr(arr, n);}intmain(){test01();return0;}


四、流派的决斗:直接选择 VS 堆排序

综合评判:谁才是选择排序的「话事人」?

特性维度直接选择排序堆排序胜出方
时间复杂度O(n²)O(n log n)堆排序
空间复杂度O(1)O(1)势均力敌
实现复杂度简单直观相对复杂直接选择排序
实际应用应用极少,主教学应用广泛堆排序

综合对比后:堆排序胜出!
在算法修仙界中,堆排序凭借其稳定的高性能,当之无愧成为选择排序家族的「话事人」!


五、两种排序的全部源码

1.直接选择排序

Sort.h文件

//打印voidPrintArr(int* arr,int n);//调换Swap(int* x,int* y);//1)直接选择排序voidSelectSore(int* arr,int n);

Sort.c文件

//1)直接选择排序//修正voidSelectSore(int* arr,int n){//定义边界变量int begin =0;int end = n -1;//总体循环:在未排序界限以内while(begin < end){//定义最大值、最小值变量int min = begin;int max = begin;//循环寻找min、max//因为初始都指向begin,从begin + 1开始for(int i = begin +1; i <= end; i++){if(arr[i]< arr[min]){ min = i;}if(arr[i]> arr[max]){ max = i;}}//对应交换if(max == begin){ max = min;}Swap(&arr[begin],&arr[min]);Swap(&arr[end],&arr[max]);//遍历完一次,边界缩小 begin++; end--;}}

2. 堆排序

Sort.h文件

//打印voidPrintArr(int* arr,int n);//向下调整算法voidAdjustDown(int* arr,int parent,int n);//传父节点下标、有效数据个数//堆排序voidHeapSort(int* arr,int n);

Sort.c文件

//向下调整算法voidAdjustDown(int* arr,int parent,int n)//传父节点下标、有效数据个数{int child = parent *2+1;while(child < n)//child超过n,代表没有子节点了{//大堆结构:<,小堆结构: >if(child +1< n && arr[child]> arr[child +1]){ child++;}//孩子和父亲比较//大堆结构:>,小堆结构:<if(arr[child]< arr[parent]){//父子调换Swap(&arr[child],&arr[parent]); parent = child; child = parent *2+1;}else{break;}}}//堆排序voidHeapSort(int* arr,int n){//向下调整算法——建小堆for(int i =(n -1-1)/2; i >=0; i--){AdjustDown(arr, i, n);}//实现排序--升序数组int end = n -1;while(end >0){Swap(&arr[0],&arr[end]);AdjustDown(arr,0, end);//logn end--;}}

–总结

🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:

选择排序一脉的"直接选择"与"堆排序"之争,揭示了算法世界的核心真理:真正的强者不仅在于思想纯粹,更在于效率卓越。

这印证了我们的编程哲学:理解每个算法的本质,才能在万千场景中做出最精准的选择。选择排序家族的这场内部较量,让我们看到了从"简单粗暴"到"精妙高效"的进化之路。

希望这篇分析能让你对选择排序流派有了更立体的认识。别忘了在评论区留下你的看法:
➤ 实战中,你会更倾向于使用哪种算法?为什么?
➤ 你还想了解哪些排序算法的对比分析?

【往期回顾】【数据结构】我在修仙界学排序算法之“直接宗与“希尔宗”,谁才是效率王者?

【下期预告】:交换排序家族风云再起——"冒泡排序"能否抵挡"快速排序"的强势挑战?敬请期待《卷三:交换排序论剑》!

Read more

LFU缓存算法全解:从双哈希+双向链表到O(1)艺术,解锁长期热点守护神

LFU缓存算法全解:从双哈希+双向链表到O(1)艺术,解锁长期热点守护神

文章目录 * 本篇摘要 * 一、核心原理 * 二、关键特性与实现机制 * 1. **数据结构设计(高效实现的核心)** * 2. **频率动态更新** * 3.实现思想及代码测试 * 4.为什么LFU用 双哈希表 + 双向链表? * 三、典型优势与劣势 * **优势场景** * **劣势与挑战** * 四、典型问题与优化策略 * 1. **新数据冷启动优化** * 2. **频率衰减(避免历史权重过高)** * 五、适用场景与典型用例 * 六、LFU vs LRU 对比 * 八、一句话总结 * 九、模版源码 * 本篇小结 本篇摘要 一、核心原理 基础规则: 优先淘汰历史访问频率最低的数据(长期统计维度)。 * 每个缓存条目维护两个核心属性:键值对数据 + 访问频率计数器。当缓存容量达到上限时,

By Ne0inhk
【优选算法 | 位运算】位运算基础:深入理解二进制操作

【优选算法 | 位运算】位运算基础:深入理解二进制操作

算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和 在本篇文章中,我们将全面解析位运算的基本原理与实际应用。位运算通过直接操作数字的二进制表示,能够在许多计算中提供极大的效率提升。无论是用于加速数学运算、优化算法,还是解决特定的技术问题,位运算都扮演着至关重要的角色。 🌈个人主页:是店小二呀 🌈C/C++专栏:C语言\ C++ 🌈初/高阶数据结构专栏: 初阶数据结构\ 高阶数据结构 🌈Linux专栏: Linux 🌈算法专栏:算法 🌈Mysql专栏:Mysql 🌈你可知:无人扶我青云志 我自踏雪至山巅 文章目录 * 一、常见位运算总结 * 二、入门题目 * 面试题 01.01. 判定字符是否唯一 * 268.丢失的数字 * 371.两整数之和 * 137.只出现一次的数字 II * 面试题 17.19. 消失的两个数字

By Ne0inhk

链表基础知识回顾+算法题打卡

链表的概念以及结构 概念:链表是一种物理存储结构上不连续的,非顺序的存储结构,数据元素的顺序是通过节点中的指针来实现的。 结构:(此处指单向非循环链表)链表中每个节点的存储元素一般包含两部分,数据和下一个节点的地址。 struct ListNode {    int val;    ListNode* next;    ListNode(int x) : val(x), next(nullptr) {} }; 遍历打印链表 在链表中访问元素需要通过每个节点中存放的下一个元素地址,才能找到下一个节点的位置,具体代码实现如下: //遍历链表 void SListPrint(SListNode* phead) { SList* cur = phead; //这里我们创建一个指针来指向头结点的位置 while(cur) { print("%d->", cur->date); //打印当前节点的数据 cur = cur->next;

By Ne0inhk
程序员怎样才能学好算法?这本书送几本给大家!

程序员怎样才能学好算法?这本书送几本给大家!

文章目录 * 前言 * 一、笔者对算法的理解 * 二、写书的初衷及过程 * 三、主要内容 * 四、本书的内容 * 五、联合推荐 * 六、购买方式 * 七、《算法秘籍》 * 中奖者名单 前言 提示:这里可以添加本文要记录的大概内容: 数据结构和算法是计算机科学的基石,是计算机的灵魂,要想成为计算机专业人员,学习和掌握算法是十分必要的。不懂数据结构和算法的人不可能写出效率更高的代码。计算机科学的很多新行业都离不开数据结构和算法作为基石,比如大数据、人工智能等。底层开发中也需要使用非常多的数据结构和算法知识,以保证底层系统的稳定性和高效性。 提示:以下是本篇文章正文内容,下面案例可供参考 一、笔者对算法的理解 计算机科学家尼古拉斯·沃斯在计算机领域有一句人尽皆知的名言: “算法+数据结构=程序”(Algorithms+Data Structures=Programs) 所以数据结构和算法是程序员必须掌握的技能。尤其是到一些大公司面试的时候,算法更是一个少不了的环节,熟练掌握数据结构和算法,可以开拓我们的视野,提高我们的逻辑思维能力,

By Ne0inhk