news 2026/2/12 0:11:15

排序(包含插入,交换,快速,基数,桶排序)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
排序(包含插入,交换,快速,基数,桶排序)

排序

确实要总结一下,因为下面写完就忘了上面的一些排序是什么了了,最好就是先写一个简单的描述,用来快速回顾。

考研之前的知识点类似,进过三次变换之后排序变成什么样。请问符合什么排序。所以我们要知道这些排序的特点

默认都是从小到大排序的讲

前置知识点

对于每一个可以排序的值,它不止是一个int,还有其他内容,所以提前定义。

#pragma once typedef int keytype; typedef struct { keytype key; int* data; }element; ​ typedef struct { element* data; int lenght; }sorttable; void insertsort(sorttable* table);

排序的依据就是这些元素之间的关键字,key

插入排序

算法本身和两个优化思路

稳定性: 稳定

原始算法

简单描述一下:

就是按从小到大遍历,

如果这个值小于前一个值的话,就临时存储这个值,然后令i-1的值为j,就让这个i值的数一直和j的值比,如果还是小的话就让对应的j指的值后移并使j++(到下一个数),直到i的值是大的,就让临时值放在j+1的位置。

如果这个值本来就是大于i-1的,那就不用动

代码部分:
void insertsort(sorttable* table) { ​ element temp; for (int i = 1; i < table->lenght; i++) { if (table->data[i].key < table->data[i - 1].key) { temp = table->data[i]; int j = i - 1; for (j = i - 1; j >= 0 && table->data[j].key > temp.key; j--) { table->data[j + 1] = table->data[j]; } table->data[j + 1] = temp; } ​ } for (int i = 0; i < table->lenght; i++) { printf("%d ", table->data[i].key); } } ​

交换排序

和选择排序的思路是一样的,所以就直接省略,直接就用交换排序

冒泡排序

稳定性:稳定

简单描述一下:

冒泡排序之神降下了他的仁慈,让饱受排序之苦之人献上最甜美的佳酿(狗头)。

就是双重遍历,外层负责为内层加上一个范围的限制而已,

内层负责比较和交换,如果发现j的值比j+1的值还要大,就交换他们,j++后这个值指的就是这个较大值了,然后重复比较,使得最大的值放在最后面。下一次的范围就排除最后一个,重复这个操作知道只剩下一个数。

代码部分

(这里简单贴一下只是用int数组的)

int a[] = { 5,9,77,36,8,45 }; int t = 0; int n = sizeof(a) / sizeof(a[0]); for (int i = 0; i < n; i++) { for (int j = 0; j < n - i - 1; j++) { if (a[j + 1] < a[j]) { t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; } } } for (int i = 0; i < n; i++) { printf("%d ", a[i]); } }
优化思路:

如果只有后面的极少部分是乱序的,但是前面都是有序的,那么每次遍历的话就会浪费时间,所以提出以下优化。

如果遍历一遍,发现没有交换,就说明这次遍历的范围已经是有序的,就不用再排了,直接break跳出循环。

⼀次优化是为了避免数组有序

void test02() { int a[] = { 1,2,3,4,5,6 }; int t = 0; int n = sizeof(a) / sizeof(a[0]); for (int i = 0; i < n; i++) { /**/ int issort = 1; for (int j = 0; j < n - i - 1; j++) { if (a[j + 1] < a[j]) { t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; /**/ issort = 0; } } /**/ if (issort) { break; } } for (int i = 0; i < n; i++) { printf("%d ", a[i]); } }

/**/下面的对比有变化的部分

优化二:

这个是解决只是中间或较前面是乱的,但是其他是有序的情况下

我们加入了一个挡板,每当做出一次交换,挡板就会指向i+1的部分,挡板最后就会停留在最后一次交换,即说明挡板的后面就是有序的,减少了遍历的次数。

否能够确定出已经有序部分和⽆序部分的边界

void test03() { int a[] = { 5,9,77,36,8,45 }; int t = 0; int n = sizeof(a) / sizeof(a[0]); int index = 0; do { index = 0; for (int i = 0; i < n - 1; i++) { if (a[i] > a[i + 1]) { t = a[i]; a[i] = a[i + 1]; a[i + 1] = t; index = i + 1; } } n = index; } while (index > 0); for (int i = 0; i < sizeof(a) / sizeof(a[0]) ; i++) { printf("%d ", a[i]); } }

快速排序

稳定性:不稳定

双边快速排序

运用递归的思想,定义三个指针,即left,right,和pivot,每次都将pivot做为分割,pivot前面的是比pivot小的,pivot后面的是比pivot大的,直到分成分成最小的两个一组,就是left大于等于right的时候

更加具体一点的步骤:就是每次都取这个区间的第一个(或者最后,中间,随机)作为pivot,首先先让right指的值与pivot的值作比较,如果大的话,就继续right--,如果发现比他小的话就是已经发现了一个不应该放在右边的东西,然后使得left的和pivot的比,如果是小的话就一直++,如果是大的话就和right的交换,又从新从right比较开始。(如果一直都是left++,直到right==left也没有关系,因为还是 要进行left和pivot的交换,还是会使这个较小值放在pivot的左边)

代码部分:

代码部分分成三个部分:对外接口的部分,递归部分,核心函数的部分:

对外接口的部分:仅仅提供要排序的表的部分作为参数

void quickSortV1(SortTable* table) { quickSort1(table, 0, table->length - 1); }

递归部分,自顶向下的递归思想,不断把一个大人物拆成小任务,先排pivot左边的部分,在排pivot右边的部分,直到这个区间只剩下一个元素就是排好了,用代码表示就是left>=right;

static void quickSort2(SortTable *table, int startIndex, int endIndex) { if (startIndex >= endIndex) { return; } // 找到犄点 int pivot = partitionSingle(table, startIndex, endIndex); quickSort2(table, startIndex, pivot - 1); quickSort2(table, pivot + 1, endIndex); }

核心函数:思路就是上面描述的部分

static int partitionSingle(SortTable *table, int startIndex, int endIndex) { keyType tmpValue = table->data[startIndex].key; int mark = startIndex; ​ for (int i = startIndex + 1; i <= endIndex; i++) { if (table->data[i].key < tmpValue) { mark++; swapElement(&table->data[i], &table->data[mark]); } } swapElement(&table->data[startIndex], &table->data[mark]); return mark; }
单边快速排序

算法思路:就是分格点边成了mark指针,保证mark 的左边比他小,他的右边比他大,然后对mark和pivot的进行交换。

具体步骤:

令i不断往前++,如果是大于mark的就继续走,如果发现比他小的,就先让mark++ 这时mark指的值已经是确定比pivot大的,而i当前指的值一定是比pivot小的然后交换mark和i 的值,那么mark一直指的就是小于等于pivot的,i最后遍历完之后,在再交换mark和pivot(指向的是第一个,(最后一个,随机))就可以保证前面的是小的,后面的是大的。后面重复这个操作。

对外接口

void quickSortV2(SortTable* table) { quickSort2(table, 0, table->length - 1); }

递归的部分:

static void quickSort2(SortTable *table, int startIndex, int endIndex) { if (startIndex >= endIndex) { return; } // 找到犄点 int pivot = partitionSingle(table, startIndex, endIndex); quickSort2(table, startIndex, pivot - 1); quickSort2(table, pivot + 1, endIndex); }

核心部分;

static int partitionSingle(SortTable *table, int startIndex, int endIndex) { keyType tmpValue = table->data[startIndex].key; int mark = startIndex; ​ for (int i = startIndex + 1; i <= endIndex; i++) { if (table->data[i].key < tmpValue) { mark++; swapElement(&table->data[i], &table->data[mark]); } } swapElement(&table->data[startIndex], &table->data[mark]); return mark; }

堆排序

从1开始

稳定性:不稳定

简单描述一下

堆排序就是依赖类似完全二叉树的逻辑结构,分成小顶堆(根小于左右孩子)和大顶堆(根大于左右孩子)。这里默认写一个小顶堆。主要逻辑,就是分为建堆和排序。

堆的操作可以分为创造 释放 插入 提取

代码部分

创造:在内存中开辟一个空间并初始化一个完全二叉树

miniheap* creatheap(int n) { miniheap* heap= malloc(sizeof(miniheap)); if (heap == NULL) { return NULL; } heap->data = malloc(sizeof(keyType) * (n + 1)); // 完全二叉树从1号下标开始存储 if (heap->data == NULL) { free(heap); return NULL; } heap->capcity = n; heap->len = 0; return heap; ​ }

释放:释放空间

void releasemini(miniheap* heap) { if (heap) { if (heap->data) { free(heap->data); heap->data = NULL; } free(heap); } }

插入:逻辑上插入元素的时候都是插在了对应完全二叉树的最后一个节点,这就导致了插入这个元素可能会导致这个不符合完全二叉树的性质。

这时候我们就要内置一个上浮函数,让这个值放到他正确位置。逻辑:如果他比他的爸还要小,那么久交换他和他的爸的位置。

void insert(miniheap* heap, keyType e) { /*少考虑了边界的情况,就是如果已经满了就不能插入了*/ if (heap->len + 1 > heap->capcity) { return; } heap->data[++heap->len] = e; up(heap, heap->len); }
static void up(miniheap* heap,int k) { /*既然是上浮那么就是找爸,这个k就是这个根节点的下标*/ keyType t; while (k > 1&&heap->data[k] < heap->data[k / 2]) { t = heap->data[k]; heap->data[k] = heap->data[k / 2]; heap->data[k / 2] = t; k /= 2; } } ​

提取:这里我们都是从小到大的排序,所以我们要提取根元素,但是直接取走,逻辑上根的位置就位空了。所以我们将最后的元素放在第一个保证他们是完整的,然后用下沉函数调整位置。

下沉函数:选择这两个孩子中较小的那一个,然后交换。

keyType extra(miniheap* heap) { if (heap->len <= 0) { return 0; } keyType ret = heap->data[1]; heap->data[1] = heap->data[heap->len--]; shiftDown(heap, 1); return ret; } ​
/*上浮*/ static void up(miniheap* heap,int k) { /*既然是上浮那么就是找爸,这个k就是这个根节点的下标*/ keyType t; while (k > 1&&heap->data[k] < heap->data[k / 2]) { t = heap->data[k]; heap->data[k] = heap->data[k / 2]; heap->data[k / 2] = t; k /= 2; } } ​

归并排序

稳定性:不稳定

简单描述一下:

利用递归的思想,不断分成最小的部分(仅有一个元素),然后合并左右集合。集合合并的方法,分别产生左右两个集合的临时数组,和分别的指针指向他们的第一个元素,比较大小,然后不小的那个放在原来的数组空间上,小的那个数组向前移动。直到其中一个数组遍历完了,另一个数组是已经排好序了,那么直接将这个数组放在原来的数组后面就行了。

代码部分:

#include"mergesort.h" #include<stdlib.h> ​ /*递归*/ static void merge(SortTable* table, int left,int mid,int right) { /* *1.脑测步骤:先开两个数组 别用i j 表示是两个数组当前指的下标 * * */ int n1 = mid - left + 1; int n2 = right - mid; Element* leftauto = malloc(sizeof(Element) * n1); Element* rightauto = malloc(sizeof(Element) * n2); if (leftauto == NULL) { return ; } if (rightauto == NULL) { free(leftauto); return ; } for (int i = 0; i < n1; i++) { //因为这个不只是前面的那一个 leftauto[i] = table->data[left+i]; } for (int i = 0; i < n2; ++i) { rightauto[i] = table->data[mid + 1 + i]; } int i = 0; int j = 0; int k = left; while (i < n1 && j< n2) { if (leftauto[i].key <= rightauto[j].key) { table->data[k] = leftauto[i++]; } else { table->data[k] = rightauto[j++]; } k++; } while (i < n1) { table->data[k++] = leftauto[i++]; } while (j < n2) { table->data[k++] = rightauto[j++]; } free(leftauto); free(rightauto); } /*这里拆分已经做好,然后就是合并的环节了*/ static void loop(SortTable* table, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; loop(table, left, mid); loop(table, mid + 1, right); merge(table, left, mid, right); ​ } /*归并排序,递归和并*/ void mergeSort(SortTable* table) { loop(table, 0, table->length - 1); } ​

统计(基数)排序

上面的排序都是基于比较

稳定性:不稳定

这个只能处理整数

简单描述一下:

就是对于一组要排序的数,遍历一次,建立一个数组,每遍历到一个数,就让这个数对应下标的数组++,最后遍历这个数组,输出结果即可。

桶排序

和统计排序差不多,就是把数组改成了一个个区间(桶)

下期更新二分查找和哈希表,喜欢的话记得点点关注,你的一个关注就是我更新的动力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/11 6:06:56

黑客大神都会玩这 10 个 Linux 命令,我不允许你还不知道!

Linux当中有很多比较有趣的命令&#xff0c;可以动手看看&#xff0c;很简单的。 1.rev命令 一行接一行地颠倒所输入的字符串。 运行&#xff1a; $rev 如输入&#xff1a;shiyanlou shiyanlou 2.asciiview命令 1.先安装aview $sudo apt-get install aview 2.再安装im…

作者头像 李华
网站建设 2026/2/4 20:59:15

Wi-Fi CERTIFIED Data Elements™ 技术概述

引言 在住宅网络中,Wi-Fi 是占据主导地位的技术 。由于对互联设备的日益依赖,所以服务提供商确信有必要按照需求,在确保网络高效率运行的同时,提升 Wi-Fi 的服务质量。Wi-Fi CERTIFIED Data Elements™是 Wi-Fi Alliance 的一项认证计划,为 Wi-Fi 网络提供了一套标准化的…

作者头像 李华
网站建设 2026/2/9 6:14:04

基于YOLO的小目标检测增强:一种提升精度与效率的新框架

摘要 本文研究并开发了在大规模航拍图像中检测小目标的方法。当前航拍图像中的小目标检测方法通常涉及图像裁剪和检测器网络架构的修改。滑动窗口裁剪以及包括更高分辨率特征图和注意力机制在内的架构增强技术是常用的方法。鉴于航拍图像在各种关键和工业应用中的重要性日益增长…

作者头像 李华
网站建设 2026/2/11 8:12:45

stm32编码总结

总结&#xff1a; 一个工程里&#xff0c;同时有GBK、UTF-8两种编码的文件 &#xff1b; Keil编码使用GB2312 , 令编译时把汉字解释为两字节&#xff0c;以兼容目前的开发周边; 不要在生成的文件如main.c里头写中文&#xff0c;在用户文件实现中文。 1、用户文件&#xff1a;使…

作者头像 李华
网站建设 2026/2/12 7:42:24

轻量级AI模型高并发应用实战:5大核心技巧深度解析

轻量级AI模型高并发应用实战&#xff1a;5大核心技巧深度解析 【免费下载链接】Qwen3-0.6B Qwen3 是 Qwen 系列中最新一代大型语言模型&#xff0c;提供全面的密集模型和混合专家 (MoE) 模型。Qwen3 基于丰富的训练经验&#xff0c;在推理、指令遵循、代理能力和多语言支持方面…

作者头像 李华