当前位置: 首页 > news >正文

排序算法大全:从插入到快速排序

排序算法大全:从插入到快速排序

  • 1. 插入排序
  • 2. 希尔排序
  • 3. 冒泡排序
  • 4. 选择排序
  • 5. 堆排序
  • 6. 快速排序
    • 6.1 递归算法
    • 6.2 非递归算法
  • 7. 归并排序
    • 7.1 递归算法
    • 7.2 非递归算法
  • 8. 计数排序
  • 9. 对比

  在我们日常生活中,排序的应用无处不在,几乎涉及到生活的方方面面。从简单的日常事务到复杂的系统管理,排序都发挥着重要作用。

  • 在图书馆里,图书管理员会按照图书编号的字母和数字顺序将书籍排列在书架上。

  • 手机通讯录默认按照联系人姓名的拼音首字母顺序排列,这种排序方式让我们能够快速找到想要联系的人。

  • 学校公布考试成绩时,通常会按照分数从高到低排序,这样能直观地看到学生的成绩分布情况。

  这些例子都说明了排序在提高生活效率、优化用户体验方面的重要作用。合理的排序方式能让信息更易获取,决策更有效率,是现代生活中不可或缺的组织工具。
  那么本章将会介绍排序相关内容。

1. 插入排序

  插入排序是一种简单直观的基于比较的排序算法,适用于小规模数据或部分有序的数据集。它的核心思想是将数组分为“已排序”和“未排序”两部分,逐个将未排序元素插入到已排序部分的正确位置。

如图所示:

插入排序

  

void InsertSort(int* a, int n) {	//a:数组  n:数组长度for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0) {if (tmp < a[end]) {		//<:升序  >:降序a[end + 1] = a[end];end--;}else {break;}}a[end + 1] = tmp;}
}

2. 希尔排序

  希尔排序(Shell Sort)是插入排序的改进版本,由 Donald Shell 于 1959 年提出。它通过分组插入排序的方式,使得数据能够更快地接近有序,从而减少比较和交换的次数,提高排序效率。

void ShellSort(int* a, int n) {int gap = n;while (gap > 1) {gap = gap / 3 + 1;		//也可改成gap = gap / 2;(保证gap最后为 1 即可)////一组	:将当前数据与当前数据间隔为gap的所有数据进行排序//for (int j = 0; j < gap; j++) {//	for (int i = j; i < n - gap; i += gap) {//		int end = i;//		int tmp = a[end + gap];//		while (end >= 0) {//			if (tmp > a[end]) {//				a[end + gap] = a[end];//				end - gap;//			}//			else {//				break;//			}//		}//		a[end + gap] = tmp;//	}//}//多组 :将当前数据与当前数据间隔为gap的下一个数据进行排序for (int i = 0; i < n - gap; i++) {int end = i;int tmp = a[end + gap];while (end >= 0) {if (tmp < a[end]) {a[end + gap] = a[end];end -= gap;}else {break;}}a[end + gap] = tmp;}}
}

如图所示(一组):
希尔排序(一组)
如图所示(多组):
希尔排序(多组)


3. 冒泡排序

  冒泡排序是一种简单的基于比较的排序算法,它通过反复交换相邻的逆序元素,使得较大的元素逐渐“浮”到数组的末端。尽管效率不高,但由于其实现简单,常用于教学和小规模数据排序。

如图所示:

冒泡排序

  

void BubbleSort(int* a, int n) {for (int i = 0; i < n; i++) {bool flag = true;for (int j = 0; j < n - i - 1; j++) {if (a[j] > a[j + 1]) {int tmp = a[j];a[j] = a[j + 1];a[j + 1] = tmp;flag = false;}}if (flag)	//若没发生交换,则表明有序,直接跳出break;}
}

4. 选择排序

  选择排序是一种简单直观的基于比较的排序算法,其核心思想是每次从未排序部分选出最小(或最大)元素,放到已排序部分的末尾(也可放在开头)。虽然效率不高,但由于实现简单,适用于小规模数据或辅助教学。

如图所示:

选择排序

  

void Swap(int* x, int* y) {	//交换int tmp = *x;*x = *y;*y = tmp;
}//普通选择排序
void Normal_SelectSort(int* a, int n) {for (int i = 0; i < n - 1; i++) {int m = i;for (int j = i + 1; j < n; j++) {if (a[j] < a[m]) {	//<:升序  >:降序m = j;}}Swap(&a[i], &a[m]);	//交换}
}// 选择排序(两端同时进行)
void SelectSort(int* a, int n) {int begin = 0;int end = n - 1;while (begin < end) {int max = begin;int min = begin;for (int i = begin + 1; i <= end; i++) {if (a[i] > a[max]) {max = i;}else if (a[i] < a[min]) {min = i;}}Swap(&a[min], &a[begin]);if (max == begin)max = min;Swap(&a[max], &a[end]);begin++;end--;}
}

5. 堆排序

  堆排序是一种基于二叉堆数据结构的比较排序算法。它是一种选择排序的改进版本,具有O(n log n)的时间复杂度。
可参考:堆排序与Top K问题精解


6. 快速排序

  快速排序是一种高效的、基于分治思想的排序算法,由Tony Hoare于1959年提出。它是实际应用中最快的已知排序算法之一,多使用递归实现,平均时间复杂度为O(n log n)。
  

6.1 递归算法

// hoare版本
int PartSort1(int* a, int begin, int end) {int mid = GetMid(a, begin, end);Swap(&a[begin], &a[mid]);int left = begin;int right = end;int key = begin;while (left < right) {//右边找小while (left < right && a[right] >= a[key]) {right--;}//左边找大while (left < right && a[left] <= a[key]) {left++;}Swap(&a[left], &a[right]);		//交换}Swap(&a[key], &a[left]);		//交换return left;
}
// 挖坑法
int PartSort2(int* a, int begin, int end) {int mid = GetMid(a, begin, end);Swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end) {//右边找小while (begin < end && a[end] >= key) {end--;}a[hole] = a[end];hole = end;//左边找大while (begin < end && a[begin] <= key) {begin++;}a[hole] = a[begin];hole = begin;}a[hole] = key;	//最后要把“坑”填上return hole;
}
// 前后指针法
int PartSort3(int* a, int begin, int end) {int mid = GetMid(a, begin, end);Swap(&a[begin], &a[mid]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end) {if (a[cur] < a[key] && ++prev != cur) {	//这一步是关键Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[key]);	//交换key = prev;return prev;
}
//时间复杂度O(N*logN)
void QuickSort(int* a, int begin, int end) {if (begin >= end) {return;}//数据长度小于10 直接使用插入排序(用于处理大量数据)if (end - begin + 1 <= 10) {InsertSort(a + begin, end - begin + 1);	//小区间的优化}else {//int key = PartSort1(a, begin, end);//QuickSort(a, begin, key - 1);//QuickSort(a, key + 1, end);//int key = PartSort2(a, begin, end);//QuickSort(a, begin, key - 1);//QuickSort(a, key + 1, end);int key = PartSort3(a, begin, end);QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);}
}

hoare版:
快排(hoare1)
快排(hoare2)

挖坑法:
挖坑法1
挖坑法2

前后指针法:
前后指针法1
前后指针法2

6.2 非递归算法

  非递归算法通常借助栈结构实现,利用其先进后出的特性来模拟递归过程,从而完成快速排序的非递归实现。

typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;		// 栈顶int capacity;  // 容量 
}Stack;//栈的初始化
void StackInit(Stack* ps) {assert(ps);ps->a = NULL;ps->top = -1;ps->capacity = 0;
}// 入栈 
void StackPush(Stack* ps, STDataType data) {assert(ps);if (ps->top + 1 == ps->capacity) {int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);if (tmp == NULL) {perror("realloc fail!");return;}ps->a = tmp;ps->capacity = newcapacity;}++ps->top;ps->a[ps->top] = data;
}// 出栈 
void StackPop(Stack* ps) {assert(ps);assert(ps->top >= 0);ps->top--;
}// 获取栈顶元素 
STDataType StackTop(Stack* ps) {assert(ps);assert(ps->top >= 0);return ps->a[ps->top];
}// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps) {assert(ps);return ps->top == -1;
}
// 销毁栈 
void StackDestroy(Stack* ps) {assert(ps);free(ps->a);ps->a = NULL;ps->top = -1;ps->capacity = 0;
}// 快速排序 非递归实现
void QuickSortNonR(int* a, int begin, int end) {Stack s;StackInit(&s);			//初始化栈StackPush(&s, end);		//end入栈StackPush(&s, begin);	//begin入栈while (!StackEmpty(&s)) {int left = StackTop(&s);	//取栈顶元素StackPop(&s);				//出栈int right = StackTop(&s);	//取栈顶元素StackPop(&s);				//出栈int key = PartSort3(a, left, right);	//通过前后指针法找到keyif (left < key - 1) {StackPush(&s, key - 1);	//入栈顺序与上面保持一致StackPush(&s, left);}if (key + 1 < right) {StackPush(&s, right);StackPush(&s, key + 1);}}StackDestroy(&s);
}

注意:栈存储的是数组下标而非直接操作数据,通过循环方式实现递归逻辑。


7. 归并排序

  归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法,其核心思想是:

  • 分解(Divide):将数组递归地分成两半,直到每个子数组只含一个元素(自然有序)。

  • 合并(Merge):将两个有序子数组合并成一个更大的有序数组。

如图所示:

归并排序

7.1 递归算法

void _MergeSort(int* a, int begin, int end, int* tmp) {		//tmp是临时数组if (begin >= end) {		//递归终止条件return;}int mid = (begin + end) / 2;	//找中//分割成两组//[begin,mid][mid+1,end]_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid + 1, end, tmp);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++];		//右区间剩余的数据}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));//复制将tmp中的元素复制到原数组中(begin不要掉了)
}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);
}

7.2 非递归算法

  归并排序的非递归实现采用自底向上的策略。算法首先将数组划分为最小单元(两个元素)进行排序,然后逐步扩大区间范围,通过不断合并相邻有序子数组来完成整体排序。具体步骤为:初始阶段将数组划分为两两一组进行排序;随后将相邻有序子数组合并为更大的有序区间(四个元素一组);重复这一过程直至整个数组合并完成,最终得到完全有序的数组。

void MergeSortNonR(int* a, int n) {int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL) {perror("malloc fail!");return;}int gap = 1;	//当前归并的子数组的长度while (gap < n) {printf("gap->%2d:", gap);for (int i = 0; i < n; i += 2 * gap) {int begin1 = i, end1 = i + gap - 1;				//左子区间int begin2 = i + gap, end2 = i + 2 * gap - 1;	//右子区间if (end1 >= n || begin2 >= n) {break;}if (end2 >= n) {end2 = n - 1;	//数组个数为奇数的情况}printf("[%2d,%2d][%2d,%2d]", begin1, end1, begin2, end2);int j = begin1;while (begin1 <= end1 && begin2 <= end2) {if (a[begin1] < a[begin2]) {tmp[j++] = a[begin1++];}else {tmp[j++] = a[begin2++];}}while (begin1 <= end1) {tmp[j++] = a[begin1++];}while (begin2 <= end2) {tmp[j++] = a[begin2++];}memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));//将tmp中处理的数据复制到原数组中}printf("\n");gap *= 2;}free(tmp);
}

8. 计数排序

  计数排序是一种非比较型的线性时间排序算法,特别适用于 整数数据数据范围较小 的情况。

如图(静态):
计数排序(静态)

动态:

计数排序(动态)

注意:数组count计算的是对应的元素个数。

void CountSort(int* a, int n) {int min = a[0], max = a[0];for (int i = 1; i < n; i++) {if (a[i] < min) {min = a[i];}if (a[i] > max) {max = a[i];}}int range = max - min + 1;		//实际需要的元素个数int* count = (int*)calloc(range, sizeof(int));	//开辟空间并初始化为 0 if (count == NULL) {perror("calloc fail!");return;}//统计次数for (int i = 0; i < n; i++) {count[a[i] - min]++;	//计算数据个数并放在count数组中,a[i] - min 节省了数组空间 }//排序int i = 0;for (int j = 0; j < range; j++) {while (count[j]--) {a[i++] = j + min;	//前面统计时减掉了min ,这里要记得加上}}
}

9. 对比

  不同的排序方法各有利弊,需结合实际场景进行选择。

排序算法最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度稳定性
冒泡排序O(n)(优化后)O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
插入排序O(n)(基本有序)O(n²)O(n²)O(1)稳定
希尔排序O(n log n)O(n²)(最坏步长)O(n^1.3)O(1)不稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
快速排序O(n log n)O(n²)O(n log n)O(log n)(递归)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定
计数排序O(n + k)O(n + k)O(n + k)O(n + k)稳定
  • 时间复杂度:

    最好:数据已经接近有序时的表现。

    最坏:数据完全逆序或极端情况下的表现。

    平均:随机数据下的期望表现。

  • 空间复杂度:

    O(1):原地排序,仅用常数额外空间(如冒泡、选择、插入、希尔、堆排序)。

    O(log n):快速排序的递归栈空间。

    O(n):归并排序和计数排序需要额外存储空间。

  • 稳定性:

    稳定:相等元素的相对顺序不变(冒泡、插入、归并、计数排序)。

    不稳定:相等元素的顺序可能改变(选择、希尔、堆、快速排序)。

http://www.dtcms.com/a/310991.html

相关文章:

  • 通过解决docker network connect实现同一个宿主机不同网络的容器间通信
  • 深入理解 Docker 容器网络:为什么用 host 网络模式能解决连通性问题?
  • DockerFile文件执行docker bulid自动构建镜像
  • 前端手撕题总结篇(算法篇——来自Leetcode牛客)
  • mac 安装pytho3 和pipx
  • docker desktop入门(docker桌面版)(提示wsl版本太低解决办法)
  • uboot armv8 启动流程之 linker script
  • 电脑手机热点方式通信(下)
  • QT中使用OpenCV保姆级教程
  • Vue项目根据OpenAPI自动生成请求后端接口ts文件
  • 嵌入式 - 数据结构:数据结构基础与链表
  • opencv自定义滤波
  • 计算机网络:任播和负载均衡的区别
  • 机动车超时停车识别准确率↑32%:陌讯动态时序建模算法实战解析
  • c++显示优化
  • 原生JS使用svg-pan-zoom库平移和缩放svg
  • 【网络与爬虫 37】ScrapeFly深度解析:云端爬虫革命,告别复杂部署拥抱一键API
  • ICCV2025 | 对抗样本智能安全方向论文汇总 | 持续更新中~
  • 数字人开发01--后端服务配置
  • ABP VNext + Redis Bloom Filter:大规模缓存穿透防护与请求去重
  • 嵌入式第十八课!!数据结构篇入门及单向链表
  • Python 类三大方法体系深度解析:静态方法、类方法与实例方法
  • LeetCode 每日一题 2025/7/28-2025/8/3
  • js的BOM
  • Redis核心机制与实践深度解析:从持久化到分布式锁
  • 中科院开源HYPIR图像复原大模型:1.7秒,老照片变8K画质
  • 北斗变形监测在地质灾害预警中的应用
  • 机器学习消融实验:方法论演进、跨领域应用与前沿趋势
  • sqli-labs:Less-19关卡详细解析
  • 基于单片机智能雨刷器/汽车刮水器设计