五种排序算法(C语言实现)

五种经典排序算法

文章目录

冒泡排序

  1. 代码实现

总体思路
通过对待排序序列从前向后(从下标较小的元素开始),依次对相邻两个元素的值进行两两比较,若发现前一个数大于后一个数则交换,使值较大的元素逐渐从前移向后部,就如同水底下的气泡一样逐渐向上冒
示意图:

img
#include<stdio.h>voidBubble_Sort(int arr[],int n){for(int i =0; i < n -1; i++)//外层循环控制轮数,即遍历数组的次数,每一轮会确定一个最大值放到末尾{for(int j =0; j < n - i -1; j++)//内层循环控制每一轮比较相邻两元素的次数{if(arr[j]> arr[j +1])//如果前一个比后一个大,交换{int temp = arr[j]; arr[j]= arr[j +1]; arr[j +1]= temp;}}}}intmain(){int arr[5];int n =sizeof(arr)/sizeof(arr[0]);for(int i =0; i < n; i++){scanf("%d",&arr[i]);}Bubble_Sort(arr, n);for(int j =0; j < n; j++){printf("%d ", arr[j]);}return0;}
  1. 关键点解释
  • 先介绍在这个程序中两个重要的概念:轮数和比较次数。轮数是遍历数组的次数,每一次遍历都会确定一个最大数冒泡到最后;次数是每一轮要比较相邻两两元素多少次
  • n代表元素个数
  • 在函数Bubble_Sort的嵌套循环中,外层循环控制轮数i,(i = n-1),内层循环控制比较次数j,(j=n-i-1)

插入排序

  1. 总体思路
    插入排序的核心思想是将未排序的元素逐个插入已排序部分的合适位置,直到整个数组排序完成
    示意图:
在这里插入图片描述
  1. 代码实现
#include<stdio.h>voidInsert_Sort(int arr[],int n){int key,i,j;// 将第一个元素视为已排列部分,从第二个元素开始遍历数组,将其插入到已排序部分的合适位置for(i =1; i < n; i++){ key = arr[i];//key是当前要插入的元素int j = i -1;//循环之前,arr[j]是已排序部分的最后一个元素while(j >=0&& arr[j]> key){ arr[j +1]= arr[j];// 将大于key的元素向后移动 j--;}// 循环j结束之后,arr[j]此时刚好是不大于key的数,将key插入到arr[j]之后一个位置即arr[j+1] arr[j+1]= key;}}// 测试插入排序的例子intmain(){int arr[5];int n =sizeof(arr)/sizeof(arr[0]);for(int i =0; i < n; i++){scanf("%d",&arr[i]);}Insert_Sort(arr, n);for(int i =0; i < n; i++){printf("%d ", arr[i]);}return0;}
  1. 关键点解释
  • 初始已排序部分:将第一个元素视为已排序部分
  • 逐个插入:遍历未排序部分的每个元素,在已排序部分中找到合适的位置并插入
  • 重复直到完成:重复以上步骤,直到所有元素都插入到已排序部分

选择排序

  1. 总体思路
    选择排序是通过遍历数组,选择出数组的最小值,与指定位置交换数据,遍历完整个数组的所有位置就完成排序
    示意图:
在这里插入图片描述
  1. 代码实现
#include<stdio.h>voidSelect_Sort(int arr[],int n){int i, j, min_index;//i是未排序部分的第一个元素的索引,j是i之后一个元素的索引,min_index是未排序部分最小元素的索引for(i =0; i < n -1; i++)//外层循环遍历数组的前n-1个元素{ min_index = i;for(j = i +1; j < n; j++)//内层循环用来遍历未排序部分第一个元素之后的所有元素,找到其中的最小值与未排序部分第一个元素进行交换{if(arr[min_index]> arr[j]){ min_index = j;}}int temp = arr[i];//交换 arr[i]= arr[min_index]; arr[min_index]= temp;}}//主函数测试intmain(){int arr[5];int n =sizeof(arr)/sizeof(arr[0]);for(int i =0; i < n; i++){scanf("%d",&arr[i]);}Select_Sort(arr, n);for(int j =0; j < n; j++){printf("%d ", arr[j]);}return0;}
  1. 关键点解释
  • 本方法的核心是每次将数组未排序部分第一个元素之后的若干个数字中的最小值和未排序部分的第一个元素交换
  • i是未排序部分第一个元素的索引,j是i后面一个元素的索引,min_index是未排序部分最小元素的索引

快速排序

  1. 总体思路
  • 概念
    快速排序是一种高效的排序算法,采用分治策略。它的基本思想是:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序
  • 总体步骤
    • 选择基准值:从数列中挑出一个元素作为"基准"(一般挑选数组第一个元素或最后一个元素)
    • 分区操作:将数列重新排列,所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准后面

递归排序:递归地对基准值左右两个子序列进行快速排序
示意图:

img
  1. 代码实现
#include<stdio.h>// 交换两个元素的值voidSwap(int* a,int* b){int temp =*a;*a =*b;*b = temp;}// Hoare法分区函数实现单趟排序intPartSort(int* arr,int left,int right){int key = left;// 使用key保存基准值下标while(left < right){while(left < right && arr[key]<= arr[right]){// 只找小的,等于要过滤,找前判断right有没有走过 right--;}while(left < right && arr[key]>= arr[left]){ left++;}if(left < right){// 没有相遇时左右交换Swap(&arr[left],&arr[right]);}}// left和right相遇后交换基准值和相遇位置的值,并返回基准值的下标Swap(&arr[key],&arr[left]);return left;}// 快速排序递归实现voidQuickSort(int* arr,int begin,int end){// 当区间不存在或只有一个元素时,不需要排序,直接返回if(begin >= end){return;}// 单趟排序,获取基准值位置int keyIndex =PartSort(arr, begin, end);// 递归排序左半部分 [begin, keyIndex-1]QuickSort(arr, begin, keyIndex -1);// 递归排序右半部分 [keyIndex+1, end]QuickSort(arr, keyIndex +1, end);}// 测试代码intmain(){int arr[]={6,1,2,7,9,3,4,5,10,8};int n =sizeof(arr)/sizeof(arr[0]);printf("排序前: ");for(int i =0; i < n; i++){printf("%d ", arr[i]);}printf("\n");QuickSort(arr,0, n -1);printf("排序后: ");for(int i =0; i < n; i++){printf("%d ", arr[i]);}printf("\n");return0;}
  1. 关键点解释
  • PartSort函数思路:
    • 初始化:选择arr[left]作为基准值,key记录基准值位置
    • 右指针移动:从右向左扫描,找到第一个小于基准值的元素
    • 左指针移动:从左向右扫描,找到第一个大于基准值的元素
    • 元素交换:交换左右指针所指的元素
    • 重复过程:继续移动指针并交换,直到左右指针相遇
    • 基准归位:将基准值交换到相遇位置
  • QuickSort函数思路分析:
    • 分解:通过PartSort将数组分成两个子数组。左子数组:所有元素 ≤ 基准值;右子数组:所有元素 ≥ 基准值
    • 解决:递归地排序两个子数组
      QuickSort(arr, begin, keyIndex - 1):排序左半部分
      QuickSort(arr, keyIndex + 1, end):排序右半部分
    • 合并:由于是原地排序,不需要显式合并步骤

归并排序

  1. 总体思路
    归并排序采用分治策略:
    :将数组递归地分成两半,直到每个子数组只有一个元素(这时候认为每一个单元素数组为有序数组)
    :将两个已排序的子数组合并成一个有序数组,直到所有元素排序完成

一张示意图帮助理解这个过程

在这里插入图片描述
  1. 代码实现
#include<stdio.h>#include<stdlib.h>//merge函数:合并两个有序数组voidmerge(int arr[],int left,int mid,int right){// left: 左子数组起始索引, mid: 中间点, right: 右子数组结束索引int n1 = mid - left +1;// 计算左半部分元素个数int n2 = right - mid;// 计算右半部分元素个数//边界检查if(left > right || mid < left || mid >= right){return;}// 检查数组大小是否有效if(n1 <=0|| n2 <=0){return;}// 创建临时数组存放左右两半的数据int* L =(int*)malloc(n1 *sizeof(int));int* R =(int*)malloc(n2 *sizeof(int));//内存分配检查if(L ==NULL|| R ==NULL){if(L)free(L);if(R)free(R);printf("内存分配失败\n");return;}// 拷贝数据:把原数组分成左右两个临时数组for(int i =0; i < n1; i++){ L[i]= arr[left + i];// 拷贝左半部分}for(int j =0; j < n2; j++){ R[j]= arr[mid +1+ j];// 拷贝右半部分}// 关键步骤:合并两个有序数组int i =0, j =0, k = left;// i指向L, j指向R, k指向原数组// 比较两个数组的元素,取较小的放入原数组while(i < n1 && j < n2){if(L[i]<= R[j]){// 稳定排序的关键:相等时取左边的 arr[k]= L[i];// 左数组元素较小,放入原数组 i++;// 左数组指针后移}else{ arr[k]= R[j];// 右数组元素较小,放入原数组 j++;// 右数组指针后移} k++;// 原数组指针后移}// 处理剩余元素:如果左数组还有剩余,全部拷贝过去while(i < n1){ arr[k]= L[i]; i++; k++;}// 处理剩余元素:如果右数组还有剩余,全部拷贝过去while(j < n2){ arr[k]= R[j]; j++; k++;}free(L);// 释放临时数组内存free(R);}//mergeSort函数:归并排序主函数voidmergeSort(int arr[],int left,int right){if(left < right){// 递归终止条件:子数组至少要有2个元素int mid = left +(right - left)/2;// 防止整数溢出的取中方法if(mid >= left && mid < right){mergeSort(arr, left, mid);// 递归排序左半部分mergeSort(arr, mid +1, right);// 递归排序右半部分merge(arr, left, mid, right);// 合并两个已排序的部分}}}//main函数测试intmain(){int arr[]={12,11,13,5,6,7};int n =sizeof(arr)/sizeof(arr[0]);printf("原始数组: ");for(int i =0; i < n; i++){printf("%d ", arr[i]);}printf("\n");mergeSort(arr,0, n -1);printf("归并排序后: ");for(int i =0; i < n; i++){printf("%d ", arr[i]);}printf("\n");return0;}
  1. 关键点解释
  • 关键思想
    • 分治策略:将大问题分解为小问题解决,再合并结果
    • 递归实现:不断将数组二分,直到单个元素自然有序
  • 关键步骤
    分解阶段
    • 计算中间点:mid = left + (right - left) / 2(防溢出)
    • 递归分解左右子数组,直到单个元素
      合并阶段(核心)
    • 创建两个临时数组存放左右子数组
    • 双指针比较:比较两个子数组当前元素,取较小者放入原数组
    • 稳定性保证:L[i] <= R[j]确保相等时左数组元素优先
    • 剩余元素处理
    • 某个子数组有剩余时,直接全部拷贝(因为已有序)
      形象比喻
      就像把一副乱牌分成两半,分别整理好后再像发牌一样合并

总结

  1. 冒泡排序: 重复遍历,两两比较,大的下沉。像气泡一样,每一轮将最大的元素“浮”到最终位置
  2. 选择排序: 打擂台,选最小,放前面。每一轮从未排序部分中选出最小(或最大)的元素,将其与未排序部分的第一个元素交换
  3. 插入排序: 摸牌理牌,逐个插入。像打扑克摸牌一样,将每个新元素插入到前面已经排好序的序列中的正确位置
  4. 快速排序: 选定基准,小数左大数右,递归处理。选择一个“基准”元素,将数组分成“小于基准”和“大于基准”的两部分,然后对这两部分递归地进行相同操作
  5. 归并排序: 先分后合,有序合并。采用分治法,先将数组递归地分成两半,直到不可再分,然后再将两个有序的子数组合并成一个更大的有序数组

Read more

极致高效的数据处理:位图、布隆过滤器与哈希切分的奇妙之旅

极致高效的数据处理:位图、布隆过滤器与哈希切分的奇妙之旅

文章目录 * 前言 * 📮一、位图 * 📧1.1 面试题 * 📧1.2 位图的概念 * 📧1.3 位图的解决方案 * 📩1.3.1 原理 * 📩1.3.2 实现步骤 * 📩1.3.3 实现过程 * 📩1.3.4 优点 * 📧1.4 位图应用 * 📮二、布隆过滤器 * 📧2.1 布隆过滤器的开发历史 * 📧2.2 什么是布隆过滤器 * 📧2.3 布隆过滤器的实现原理 * 📩2.3.1 布隆过滤器的初步认识 * 📩2.3.2

By Ne0inhk
Flutter 组件 humanize 的适配 鸿蒙Harmony 深度进阶 - 驾驭多语言复数逻辑算法、实现鸿蒙端中式大额单位感知与极致人性化文本渲染方案

Flutter 组件 humanize 的适配 鸿蒙Harmony 深度进阶 - 驾驭多语言复数逻辑算法、实现鸿蒙端中式大额单位感知与极致人性化文本渲染方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 humanize 的适配 鸿蒙Harmony 深度进阶 - 驾驭多语言复数逻辑算法、实现鸿蒙端中式大额单位感知与极致人性化文本渲染方案 前言 在前文我们掌握了 humanize 进行基础数据转换的方法。但在鸿蒙(OpenHarmony)面向全球市场的布局中,真正的技术挑战往往隐藏在极其琐碎的“语言表达”中。例如:在英文中我们说 1 items 是错误的,必须是 1 item 与 2 items;而在中文环境下,我们虽然没有复数形变,但却有“万、亿”这类独特的四位一级计数逻辑。 一个真正具备“高级感”的鸿蒙应用,不应在数据展示上显得僵硬且带有明显的机器翻译痕迹。 本文将作为 humanize 适配的进阶篇,带你攻克多语言复数(Pluralization)

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 directed_graph 在鸿蒙应用中优雅处理复杂的拓扑排序与依赖关系(算法级工具)

Flutter for OpenHarmony: Flutter 三方库 directed_graph 在鸿蒙应用中优雅处理复杂的拓扑排序与依赖关系(算法级工具)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 的复杂业务逻辑设计时,我们经常会遇到“依赖关联”问题。例如: 1. 任务调度:任务 A 依赖于任务 B 和 C,任务 B 依赖于 D。你应该按什么顺序运行它们? 2. 数据流建模:在鸿蒙分布式节点中,数据是如何从一个端点流向另一个端点的?是否存在循环引用(Cycle)? 3. 资源加载器:一个大型鸿蒙 HAP 包内的资源加载优先级排序。 directed_graph 是一款纯粹的、算法级别的 Dart 库。它提供了标准的数据结构模型,能帮你极其高效地处理这些复杂的拓扑(Topology)关系。 一、有向图逻辑模型 该库支持对图节点进行深度遍历、

By Ne0inhk

Docker一站式部署:RustFS、GoFastDFS、Gitea与PostgreSQL实战指南

1. 前言 在现代软件开发和部署中,Docker已成为不可或缺的工具。它提供了轻量级、可移植的容器化解决方案,使应用部署变得简单高效。本文将详细介绍如何使用Docker一键部署四个常用服务:RustFS(高性能文件存储)、GoFastDFS(分布式文件系统)、Gitea(自托管Git服务)和PostgreSQL(关系型数据库)。无论你是个人开发者还是团队负责人,这些服务都能为你的项目提供强大支持。 2. 前提条件 * 已安装Docker(版本20.10+) * 已安装Docker Compose(版本1.29+) * 64位操作系统(Windows/Linux/Mac) * 至少4GB可用内存 3. RustFS部署 RustFS是一款使用Rust语言编写的高性能文件存储系统,支持S3协议,适用于私有云存储场景。 3.1 Docker命令部署 Linux: docker run -d \ --name rustfs_container \ --user root \ -p

By Ne0inhk