C语言实现的常见排序算法

排序是计算机科学中非常重要的基础算法之一。无论是在数据分析、数据库查询还是图形界面中,我们都可能会遇到排序问题。本文将介绍几种常见的排序算法,并提供其C语言实现代码。排序算法的效率和应用场景有很大关系,不同的算法有不同的时间复杂度和空间复杂度。

1. 冒泡排序(Bubble Sort)

冒泡排序是最简单的排序算法之一。它通过重复交换相邻的元素,将最大或最小的元素逐渐“冒泡”到序列的一端。虽然实现简单,但其时间复杂度较高,通常不适用于大规模数据的排序。

实现逻辑

冒泡排序的核心思想是通过不断交换相邻的元素,将较大的元素逐步“冒泡”到数组的一端。每一轮排序,都会将一个最大(或最小)元素放到正确的位置。具体步骤如下:

  1. 从数组的第一个元素开始,依次比较相邻的元素。如果前一个元素比后一个元素大,就交换它们的位置。
  2. 这样,经过第一轮比较后,最大的元素被移动到数组的最后一个位置。
  3. 在下一轮比较时,忽略已经排序好的部分(即已排好序的元素),只比较剩余部分,依此类推。
  4. 当某一轮排序中没有发生任何交换时,说明数组已经有序,可以提前结束排序。
1.1 冒泡排序的C语言实现
#include <stdio.h>// 冒泡排序函数
void bubbleSort(int arr[], int n) {int i, j, temp;// 外层循环控制比较的轮次for (i = 0; i < n-1; i++) {// 内层循环进行相邻元素的比较与交换for (j = 0; j < n-i-1; j++) {// 如果当前元素大于下一个元素,则交换它们if (arr[j] > arr[j+1]) {temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}
}// 打印数组的函数
void printArray(int arr[], int size) {int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[] = {64, 34, 25, 12, 22, 11, 90};int n = sizeof(arr)/sizeof(arr[0]); // 获取数组长度printf("原始数组: \n");printArray(arr, n);bubbleSort(arr, n); // 调用冒泡排序printf("排序后的数组: \n");printArray(arr, n); // 打印排序后的数组return 0;
}
1.2 时间复杂度:
  • 最坏时间复杂度:O(n^2)
  • 最好时间复杂度:O(n)(当数组已经有序时)

2. 选择排序(Selection Sort)

实现逻辑

选择排序的核心思想是每一次从未排序部分中选择一个最小(或最大)元素,并将其放到已排序部分的末尾。具体步骤如下:

  1. 从数组的第一个元素开始,假设它是最小的元素,遍历剩余部分,找到真正的最小元素。
  2. 将最小元素与当前元素交换位置。
  3. 继续从下一个元素开始,重复上述操作,直到整个数组有序。

与冒泡排序不同,选择排序每次只进行一次交换,即使找到最小值,也不会像冒泡排序那样进行多次交换。

2.1 选择排序的C语言实现
#include <stdio.h>// 选择排序函数
void selectionSort(int arr[], int n) {int i, j, minIndex, temp;// 外层循环每次选择一个最小值for (i = 0; i < n-1; i++) {minIndex = i; // 假设当前元素是最小的// 内层循环找出未排序部分中的最小元素for (j = i+1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j; // 更新最小值的索引}}// 交换当前元素和最小元素temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}
}// 打印数组的函数
void printArray(int arr[], int size) {int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[] = {64, 25, 12, 22, 11};int n = sizeof(arr)/sizeof(arr[0]); // 获取数组长度printf("原始数组: \n");printArray(arr, n);selectionSort(arr, n); // 调用选择排序printf("排序后的数组: \n");printArray(arr, n); // 打印排序后的数组return 0;
}
2.2 时间复杂度:
  • 最坏时间复杂度:O(n^2)
  • 最好时间复杂度:O(n^2)

3. 插入排序(Insertion Sort)

插入排序将数组分为已排序部分和未排序部分,每次取未排序部分的第一个元素,将其插入到已排序部分的合适位置,直到整个数组有序。

实现逻辑

插入排序的核心思想是将数组分为已排序和未排序两部分,依次将未排序部分的元素插入到已排序部分的合适位置。具体步骤如下:

  1. 从第二个元素开始,将其与前面的元素进行比较,找到它应该插入的位置。
  2. 将插入位置之后的元素依次右移,直到找到合适的位置。
  3. 将当前元素插入到合适位置。
  4. 重复上述操作,直到数组全部有序。
3.1 插入排序的C语言实现
#include <stdio.h>// 插入排序函数
void insertionSort(int arr[], int n) {int i, key, j;// 从第二个元素开始,每次将一个元素插入到已排序部分for (i = 1; i < n; i++) {key = arr[i]; // 保存当前要插入的元素j = i - 1;// 将已排序部分大于 key 的元素向右移动while (j >= 0 && arr[j] > key) {arr[j+1] = arr[j];j = j - 1;}// 将 key 插入到正确的位置arr[j+1] = key;}
}// 打印数组的函数
void printArray(int arr[], int size) {int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[] = {12, 11, 13, 5, 6};int n = sizeof(arr)/sizeof(arr[0]); // 获取数组长度printf("原始数组: \n");printArray(arr, n);insertionSort(arr, n); // 调用插入排序printf("排序后的数组: \n");printArray(arr, n); // 打印排序后的数组return 0;
}
3.2 时间复杂度:
  • 最坏时间复杂度:O(n^2)
  • 最好时间复杂度:O(n)(当数组已经有序时)

4. 快速排序(Quick Sort)

快速排序是一种分治法排序算法,它通过一个基准元素将数组分为两个子数组,然后递归地排序这两个子数组。由于其平均时间复杂度较低,快速排序在实际应用中非常高效。

实现逻辑

快速排序是一种分治算法,其核心思想是通过一个基准元素将数组分为两部分:一部分比基准元素小,另一部分比基准元素大。然后递归地对这两部分进行排序,直到整个数组有序。具体步骤如下:

  1. 选择一个基准元素(通常选择数组的第一个元素、最后一个元素或随机选择一个元素)。
  2. 将数组重新排列,使得所有比基准元素小的元素都在基准元素的左边,所有比基准元素大的元素都在右边。
  3. 基准元素就位后,将左子数组和右子数组递归地进行快速排序。
  4. 递归的终止条件是子数组的大小为1或0。

快速排序通过分治策略来将大问题分解为小问题,因此具有较高的效率。

4.1 快速排序的C语言实现
#include <stdio.h>// 分区操作:将数组分为两个子数组
int partition(int arr[], int low, int high) {int pivot = arr[high]; // 选择最右侧元素作为基准int i = low - 1; // i 是已排序部分的边界// 遍历数组,将小于基准的元素放到左边,大于基准的放到右边for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++; // 更新已排序部分的边界// 交换 arr[i] 和 arr[j]int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}// 将基准元素放到正确位置int temp = arr[i+1];arr[i+1] = arr[high];arr[high] = temp;return i + 1; // 返回基准元素的索引
}// 快速排序函数
void quickSort(int arr[], int low, int high) {if (low < high) {// 获取分区后的基准元素索引int pi = partition(arr, low, high);// 分别对左子数组和右子数组进行快速排序quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}
}// 打印数组的函数
void printArray(int arr[], int size) {int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[] = {10, 7, 8, 9, 1, 5};int n = sizeof(arr)/sizeof(arr[0]); // 获取数组长度printf("原始数组: \n");printArray(arr, n);quickSort(arr, 0, n-1); // 调用快速排序printf("排序后的数组: \n");printArray(arr, n); // 打印排序后的数组return 0;
}
4.2 时间复杂度:
  • 最坏时间复杂度:O(n^2)
  • 最好时间复杂度:O(n log n)
  • 平均时间复杂度:O(n log n)

5. 归并排序(Merge Sort)

归并排序也是一种分治法排序算法,它将数组分为两个子数组,分别对它们排序后再合并。归并排序是一个稳定的排序算法,适用于大规模数据。

实现逻辑

归并排序也是一种分治算法,核心思想是将数组分成两个子数组,分别对其进行排序,然后将两个已排序的子数组合并成一个大的有序数组。具体步骤如下:

  1. 将数组递归地分割成两半,直到每个子数组只包含一个元素。
  2. 然后将这些小的有序子数组逐步合并成更大的有序子数组。
  3. 在合并时,始终保持两个子数组之间的有序性。

归并排序的关键操作是合并两个已排序的子数组,合并过程是线性时间复杂度。

5.1 归并排序的C语言实现
#include <stdio.h>// 合并两个已排序的子数组
void merge(int arr[], int l, int m, int r) {int n1 = m - l + 1; // 左子数组的大小int n2 = r - m;     // 右子数组的大小// 创建临时数组int L[n1], R[n2];// 将数据拷贝到临时数组for (int i = 0; i < n1; i++)L[i] = arr[l + i];for (int j = 0; j < n2; j++)R[j] = arr[m + 1 + j];int i = 0, j = 0, k = l;// 合并两个临时数组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++;}
}// 归并排序函数
void mergeSort(int arr[], int l, int r) {if (l < r) {int m = l + (r - l) / 2; // 计算中间位置mergeSort(arr, l, m); // 对左半部分进行归并排序mergeSort(arr, m + 1, r); // 对右半部分进行归并排序merge(arr, l, m, r); // 合并两个已排序的子数组}
}// 打印数组的函数
void printArray(int arr[], int size) {int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[] = {12, 11, 13, 5, 6, 7};int n = sizeof(arr)/sizeof(arr[0]); // 获取数组长度printf("原始数组: \n");printArray(arr, n);mergeSort(arr, 0, n-1); // 调用归并排序printf("排序后的数组: \n");printArray(arr, n); // 打印排序后的数组return 0;
}
5.2 时间复杂度:
  • 最坏时间复杂度:O(n log n)
  • 最好时间复杂度:O(n log n)
  • 平均时间复杂度:O(n log n)

总结

本文介绍了几种经典的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序。每种排序算法都有其特定的优缺点,适用于不同的应用场景。在选择排序算法时,需要根据数据的规模、要求的时间复杂度以及是否需要稳定排序来做出决策。通过理解这些排序算法的实现和性能特点,您可以在实际开发中更加高效地选择合适的排序算法。

版权声明:本文为原创文章,转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/70187.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对于简单的HTML、CSS、JavaScript前端,我们可以通过几种方式连接后端

1. 使用Fetch API发送HTTP请求&#xff08;最简单的方式&#xff09;&#xff1a; //home.html // 示例&#xff1a;提交表单数据到后端 const submitForm async (formData) > {try {const response await fetch(http://your-backend-url/api/submit, {method: POST,head…

[论文阅读] SeeSR: Towards Semantics-Aware Real-World Image Super-Resolution

文章目录 一、前言二、主要贡献三、Introduction四、Methodology4.1 Motivation &#xff1a;4.2Framework Overview.** 一、前言 通信作者是香港理工大学 & OPPO研究所的张磊教授&#xff0c;也是图像超分ISR的一个大牛了。 论文如下 SeeSR: Towards Semantics-Aware Rea…

案例-04.部门管理-删除

一.功能演示 二.需求说明 三.接口文档 四.思路 既然是通过id删除对应的部门&#xff0c;那么必然要获取到前端请求的要删除部门的id。id作为请求路径传递过来&#xff0c;那么要从请求路径中获取&#xff0c;id是一个路径参数。因此使用注解PathVariable获取路径参数。 请求方…

Blazor-父子组件传递任意参数

在我们从父组件传参数给子组件时&#xff0c;可以通过子组件定义的[Parameter]特性的公开属性进行传值&#xff0c;但是当我们需要传递多个值的时候&#xff0c;就需要通过[Parameter]特性定义多个属性&#xff0c;有没有更简便的方式&#xff1f; 我们可以使用定义 IDictionar…

DeepSeek 的创新融合:多行业应用实践探索

引言 在数字化转型的浪潮中&#xff0c;技术的融合与创新成为推动各行业发展的关键力量。蓝耘平台作为行业内备受瞩目的创新平台&#xff0c;以其强大的资源整合能力和灵活的架构&#xff0c;为企业提供了高效的服务支持。而 DeepSeek 凭借先进的人工智能技术&#xff0c;在自然…

STM32创建静态库lib

创建静态库lib 1. 新建工程1.1 创建工程文件夹1.2 编写用户相关代码1.2.1 stm32f4xx_it.h1.2.2 stm32f4xx_it.c1.2.3 标准库配置&#xff1a;stm32f4xx_conf.h1.2.4 HAL库的配置&#xff1a;stm32f4xx_hal_conf.h1.2.5 LL库配置&#xff1a;stm32f4xx_ll_conf.h 1.3 移植通用文…

elabradio入门第二讲——BPSK数字调制与解调(插值、升余弦滤波、速率匹配、符号同步)

数字信号可以通过数字基带传输系统进行传输&#xff0c;而基带传输系统仅仅适用于低频信道下的数字信号传输。然而&#xff0c;在实际的通信系统中信道通常具有带通特性&#xff0c;因而需要将基带信号搬移到适合信道传输的高频载波上&#xff0c;使得信号与信道相匹配&#xf…

汽车 OTA 升级:提升下载与升级速度,优化用户体验

摘要&#xff1a; 随着汽车智能化的飞速发展&#xff0c;OTA&#xff08;Over - the - Air&#xff09;升级已成为汽车行业的重要技术&#xff0c;它能为车辆持续带来功能更新与性能优化。然而&#xff0c;下载及升级速度较慢的问题常常影响用户体验。本文深入探讨在汽车 OTA …

【Spring+MyBatis】留言墙的实现

目录 1. 添加依赖 2. 配置数据库 2.1 创建数据库与数据表 2.2 创建与数据库对应的实体类 3. 后端代码 3.1 目录结构 3.2 MessageController类 3.3 MessageService类 3.4 MessageMapper接口 4. 前端代码 5. 单元测试 5.1 后端接口测试 5.2 使用前端页面测试 在Spri…

SQLite Select 语句详解

SQLite Select 语句详解 SQLite 是一个轻量级的数据库管理系统&#xff0c;以其简洁的设计和高效的性能被广泛应用于各种场景。在 SQLite 中&#xff0c;SELECT 语句是用于查询数据库中的数据的命令。本文将详细介绍 SQLite 的 SELECT 语句&#xff0c;包括其基本语法、常用功…

深度学习05 ResNet残差网络

目录 传统卷积神经网络存在的问题 如何解决 批量归一化BatchNormalization, BN 残差连接方式 ​残差结构 ResNet网络 ResNet 网络是在 2015年 由微软实验室中的何凯明等几位大神提出&#xff0c;斩获当年ImageNet竞赛中分类任务第一名&#xff0c;目标检测第一名。获得CO…

组件库地址

react&#xff1a; https://react-vant.3lang.dev/components/dialoghttps://react-vant.3lang.dev/components/dialog vue用v2的 Vant 2 - Mobile UI Components built on Vue

docker 进阶命令(基于Ubuntu)

数据卷 Volume: 目录映射, 目录挂载 匿名绑定: 匿名绑定的 volume 在容器删除的时候, 数据卷也会被删除, 匿名绑定是不能做到持久化的, 地址一般是 /var/lib/docker/volumes/xxxxx/_data 绑定卷时修改宿主机的目录或文件, 容器内的数据也会同步修改, 反之亦然 # 查看所有 vo…

从入门到精通:Postman 实用指南

Postman 是一款超棒的 API 开发工具&#xff0c;能用来测试、调试和管理 API&#xff0c;大大提升开发效率。下面就给大家详细讲讲它的安装、使用方法&#xff0c;再分享些实用技巧。 一、安装 Postman 你能在 Postman 官网&#xff08;https://www.postman.com &#xff09;下…

将图片base64编码后,数据转成图片

将图片数据进行base64编码后&#xff0c;可以在浏览器上查看图片&#xff0c;只需在前端加上data:image/png;base64,即可 在线工具&#xff1a; Base64转图片 - 加菲工具

【动态规划】详解 0-1背包问题

文章目录 1. 问题引入2. 从 dfs 到动态规划3. 动态规划过程分析4. 二维 dp 的遍历顺序5. 从二维数组到一维数组6. 一维数组的遍历次序7. 背包的遍历顺序8. 代码总结9. 总结 1. 问题引入 0-1 背包是比较经典的动态规划问题&#xff0c;这里以代码随想录里面的例子来介绍下。总的…

LeetCode每日精进:20.有效的括号

题目链接&#xff1a;20.有效的括号 题目描述&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以…

llama.cpp部署 DeepSeek-R1 模型

一、llama.cpp 介绍 使用纯 C/C推理 Meta 的LLaMA模型&#xff08;及其他模型&#xff09;。主要目标llama.cpp是在各种硬件&#xff08;本地和云端&#xff09;上以最少的设置和最先进的性能实现 LLM 推理。纯 C/C 实现&#xff0c;无任何依赖项Apple 芯片是一流的——通过 A…

Web后端 - Maven管理工具

一 Maven简单介绍 Maven是apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 Maven的作用 二 Maven 安装配置 依赖配置 依赖传递 依赖范围 生命周期 注意事项&#xff1a;在同一套生命周期中&#xff0c;当运行后面的阶段时&#xff0c;前面的阶段都…

[LeetCode力扣hot100]-C++常用数据结构

0.Vector 1.Set-常用滑动窗口 set<char> ans;//根据类型定义&#xff0c;像vector ans.count()//检查某个元素是否在set里&#xff0c;1在0不在 ans.insert();//插入元素 ans.erase()//删除某个指定元素 2.栈 3.树 树是一种特殊的数据结构&#xff0c;力扣二叉树相…