新手小白学C语言——排序算法(冒泡、选择、插入)
目录
一、引言
二、三种基础排序方式
1.冒泡排序
(1)代码复现
(2)代码演算
(3)逻辑概括
(4)应用场景
2.选择排序
(1)代码复现
(2)代码演算
(3)逻辑概括
(4)应用场景
3.插入排序
(1)代码复现
(2)代码演算
(3)逻辑概括
(4)应用场景
三、对三种排序的总结与思考
1、核心算法逻辑差异(本质区别)
2、应用场景差异(选型核心依据)
3、三者的相通之处与内在联系
4、核心总结与思考
四、编者的话
一、引言
排序算法作为一种常用的算法,与我们生活的方方面面息息相关,无论是做编程题还是解决具体生活场景中的问题,排序都发挥着重要的作用。因此,在C语言的学习中,要重视排序算法的学习并将其灵活运用。
二、三种基础排序方式
1.冒泡排序
(1)代码复现
void Sort1(int arr[6]) { for (int i = 0; i < 6; i++) { for (int j = 0; j < 6 - i - 1; j++) { int temp; if (arr[j] > arr[j + 1]) { temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } } for (int i = 0; i < 6; i++) { printf("%4d", arr[i]); } }(2)代码演算
下面我们通过一组无序数列进行对冒泡排序的学习:
3 5 1 4 2 8我们先按照上述代码将这个无序数组进行排列:
i = 0:(j:0-->4) j = 0: arr[j] = 3 arr[j+1] = 5 //故if条件不成立,不进行数字交换 j = 1: arr[j] = 5 arr[j+1] = 1 //if成立,arr[j]和arr[j+1]发生交换 j = 2: arr[j] = 5 arr[j+1] = 4 //if成立,arr[j]和arr[j+1]发生交换 j = 3: arr[j] = 5 arr[j+1] = 2 //if成立,arr[j]和arr[j+1]发生交换 j = 4: arr[j] = 5 arr[j+1] = 8 //故if条件不成立,不进行数字交换因此,经过第一大轮交换,数组变为3 1 4 2 5 8 。
i = 1:(j:0-->3) j = 0: arr[j] = 3 arr[j+1] = 1 //if成立,arr[j]和arr[j+1]发生交换 j = 1: arr[j] = 3 arr[j+1] = 4 //if条件不成立,不进行数字交换 j = 2: arr[j] = 4 arr[j+1] = 2 //if成立,arr[j]和arr[j+1]发生交换 j = 3: arr[j] = 4 arr[j+1] = 5 //if条件不成立,不进行数字交换因此,经过第二大轮交换,数组变为1 3 2 4 5 8 。
i = 2:(j:0-->2) j = 0: arr[j] = 1 arr[j+1] = 3 //if条件不成立,不进行数字交换 j = 1: arr[j] = 3 arr[j+1] = 2 //if成立,arr[j]和arr[j+1]发生交换 j = 2: arr[j] = 3 arr[j+1] = 4 //if条件不成立,不进行数字交换因此,经过第三大轮交换,数组变为1 2 3 4 5 8 。
其实可以发现,到这里数组已经变成了有序数组,那么后面 i = 3 和 i = 4的情况就不再继续阐述了,原理与步骤同上述操作。
(3)逻辑概括
相邻元素两两比较,逆序就交换,每轮把最大未排序数“冒”到末尾,重复至有序。
(4)应用场景
对小规模数据和接近有序的数组进行排序。
2.选择排序
(1)代码复现
void Sort4(int arr[6]) { int Idx,i,j,temp; for(i = 0;i <5;i++) { Idx = i; for(j = i+1;j < 6;j++) { if(arr[j] < arr[Idx]) { Idx = j; } } if(Idx != i) { temp = arr[i]; arr[i] = arr[Idx]; arr[Idx] = temp; } } for(int i = 0;i < 6;i++) { printf("%4d",arr[i]); } }(2)代码演算
我们依然通过这个无序数组按照代码进行演算:
3 5 1 4 2 8i = 0:(Idx = 0 j:1-->5) j = 1: arr[Idx] = 3 arr[j] = 5 //if不成立,Idx不与j交换 j = 2: arr[Idx] = 3 arr[j] = 1 //if成立,Idx与j交换 j = 3: arr[Idx} = 1 arr[j] = 4 //if不成立,Idx不与j交换 j = 4: arr[Idx} = 1 arr[j] = 2 //if不成立,Idx不与j交换 j = 5: arr[Idx} = 1 arr[j] = 8 //if不成立,Idx不与j交换因此,经过第一轮循环,Idx变成了2,故数组变为 1 5 3 4 2 8 。
i = 1:(Idx = 1 j:2-->5) j = 2: arr[Idx] = 5 arr[j] = 3 //if成立,Idx与j交换 j = 3: arr[Idx] = 3 arr[j] = 4 //if不成立,Idx不与j交换 j = 4: arr[Idx] = 3 arr[j] = 2 //if成立,Idx与j交换 j = 5: arr[Idx] = 2 arr[j] = 8 //if不成立,Idx不与j交换因此,经过第二轮循环,Idx变成了4,故数组变为 1 2 3 4 5 8。
i = 2:(Idx = 2 j:3-->5) j = 3: arr[Idx] = 3 arr[j] = 4 //if不成立,Idx不与j交换 j = 4: arr[Idx] = 3 arr[j] = 5 //if不成立,Idx不与j交换 j = 5: arr[Idx] = 3 arr[j] = 8 //if不成立,Idx不与j交换因此,我们发现,第三轮循环已经没有交换出现了,故后续i = 3和i = 4不再赘述。
(3)逻辑概括
每次从未排序区间选出最小值,放到已排序区间末尾。
(4)应用场景
对数据量很小的数据进行排序。
3.插入排序
(1)代码复现
void Sort2(int arr[6]) { for (int i = 1; i < 6; i++) { if (arr[i] < arr[i - 1]) { int temp = arr[i]; int j; for (j = i - 1; j >= 0&&(arr[j] > temp); j--) { arr[j+1] = arr[j]; } arr[j+1] = temp; } } for (int i = 0; i < 6; i++) { printf("%4d", arr[i]); } }(2)代码演算
我们依然通过这个无序数组按照代码进行演算:
3 5 1 4 2 8下面进行推演:
3 5 1 4 2 8 i = 0: 3 5 | 1 4 2 8 temp = 1 □ 3 5 | 4 2 8 temp小于3 5 ,故3 5 都后移一位 1 3 5 4 2 8 i = 1: 1 3 5 | 4 2 8 temp = 4 1 3 □ 5 | 2 8 temp小于5 ,故5后移一位 1 3 4 5 2 8 i = 2: 1 3 4 5 | 2 8 temp = 2 1 □ 3 4 5 | 8 temp小于3 4 5 ,故3 4 5 都后移一位 1 2 3 4 5 8 i = 3 此时,arr[i] > arr[i - 1]故排序结束(3)逻辑概括
把未排序元素依次插入到前面有序序列的正确位置。
(4)应用场景
对数据量小和接近有序的数组进行排序。
三、对三种排序的总结与思考
冒泡、选择、插入是最基础的三种内部排序算法,三者时间复杂度均为O(n²),属于简单暴力的排序算法,核心均通过双层循环完成有序化,但在核心逻辑、操作方式、性能特点和应用场景上存在显著差异,同时底层设计又具备高度相通性,以下从区别、场景、关联三方面整体总结。
1、核心算法逻辑差异(本质区别)
三者最核心的不同,在于每轮排序的目标和操作方式完全不同:
1. 冒泡排序:相邻交换,倒逼极值归位
通过相邻元素两两对比,逆序则交换,每一轮遍历都会将当前未排序区间的最大值“冒泡”到区间末尾,通过多次交换逐步实现整体有序。核心是「边比较、边交换」,交换次数最多。
2. 选择排序:先找极值,定点归位
全程遍历未排序区间,先找出区间内的最小值(最大值),仅用一次交换,将极值放到已排序区间末尾。核心是「多比较、少交换」,比较次数多,交换次数极少。
3. 插入排序:逐个插入,构建有序区间
默认数组前段为有序区间,依次取出未排序区间的元素,向前遍历,插入到有序区间的对应正确位置。核心是「移位插入、逐步构建有序」,无无效比较,贴合有序数组特性。
2、应用场景差异(选型核心依据)
三者虽都不适合大规模乱序数据,但根据自身性能特点,适配场景各有侧重:
1. 冒泡排序:仅适用于数据量极小、数组接近有序的场景,优化版冒泡可提前终止循环,效率最优;主要用于算法入门教学,实际项目极少使用,交换开销过高。
2. 选择排序:适配交换操作成本极高的场景(如复杂对象排序),凭借最少的交换次数降低性能损耗;不依赖数组有序度,稳定性差,适合小规模无序数据。
3. 插入排序:三者中实际可用性最高,数据量小、数组基本有序时效率远超另外两者;同时是唯一适配链表排序的算法,也是高级排序(快排、归并)的底层优化辅助算法。
3、三者的相通之处与内在联系
1.复杂度与层级相通:三者均为简单排序,平均、最坏时间复杂度均为O(n²),空间复杂度均为O(1),属于原地排序,无需额外开辟内存,仅依靠原数组完成排序。
2.排序思想同源:均采用「分区间排序」思路,将数组划分为已排序区间+未排序区间,通过多轮遍历,不断缩小未排序区间、扩大已排序区间,直至整体有序。
3.迭代逻辑一致:底层均依赖双层循环实现,外层循环控制排序轮数(确定已排序区间长度),内层循环负责未排序区间的比较、交换或移位操作。
4、核心总结与思考
从算法优劣来看,常规场景:插入排序 > 选择排序 > 冒泡排序。冒泡排序冗余交换最多,性能最差;选择排序牺牲比较次数换取最少交换,适配特殊场景;插入排序利用数据有序性,容错率最高、实用性最强。
三种基础排序的设计,体现了排序算法的核心优化思路:从“盲目交换”(冒泡),到“减少无效交换”(选择),再到“利用已有有序性、减少无效遍历”(插入),是算法从粗糙到精细的迭代过程。同时,三者的分区排序思想,也是后续快速排序、归并排序等高效分治算法的基础逻辑雏形。
此外,三者的特性也印证了算法选型的核心原则:没有最优算法,只有最适配场景的算法,需根据数据规模、有序程度、操作开销(比较/交换)灵活选择。
四、编者的话
小编作为安工大25级物联网工程学生,学习三种基础排序让我收获很多。
三者虽都是O(n²)算法,但设计思路、适用场景差异明显,也让我明白算法没有绝对好坏,要结合硬件资源取舍,其中插入排序更贴合物联网有序传感器数据处理需求。简单排序是高阶算法的基础,代码细节直接影响嵌入式设备功耗与效率。
希望以后能跟大家一起坚持手写推演吃透基础,结合单片机实验落地代码,同步刷题进阶分治、轻量化算法,依托竞赛与大创项目,把算法知识融入物联网软硬件开发,实现自己的专业梦!
PS:小编对算法类内容接触较少,也是刚开始系统性学习这方面的知识,如有不当,请您批评指正,小编将不尽感激,谢谢您!
