C语言实现八种排序算法

article/2025/9/30 15:07:57

文章目录

  • 排序算法
    • 选择排序
    • 冒泡排序
    • 插入排序
    • 归并排序
    • 希尔排序
    • 快速排序
    • 堆排序
    • 计数排序

排序算法

平时用惯了高级语言高级工具高级算法,难免对一些基础算法感到生疏。但最基础的排序算法中实则蕴含着相当丰富的优化思维,熟练运用可起到举一反三之功效。

选择排序

选择排序几乎是最无脑的一种排序算法,通过遍历一次数组,选出其中最大(小)的值放在新数组的第一位;再从数组中剩下的数里选出最大(小)的,放到第二位,依次类推。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 从数组第 i i i位开始便利,找到最大值,将之与数组第 i i i位交换位置。
  2. i i i从0循环到 n n n

由于每次遍历需要计算 O ( n ) O(n) O(n)次,且需要便利 n n n次,故时间复杂度为 O ( n 2 ) O(n^2) O(n2);由于只在交换的过程中需要额外的数据,所以空间复杂度为 O ( n ) O(n) O(n)

C语言实现

//sort.c
void SelectionSort(double *p, int n);
int main(){double test[5] = {3,2,5,7,9};SelectionSort(test,5);for (int i = 0; i < 5; i++)printf("%f\n", test[i]);return 0;
}//交换数组中i,j处的值
void mySwap(double *arr, int i, int j){double temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}//选择排序
void SelectionSort(double *arr, int n){int pMax;double temp;for (int i = 0; i < n-1; i++){pMax = i;for (int j = i+1; j < n; j++)if (arr[j]>arr[pMax])pMax = j;mySwap(arr,pMax,i);}
}

验证

>gcc sort.c
>a
9.000000
7.000000
5.000000
3.000000
2.000000

冒泡排序

冒泡排序也是一种无脑的排序方法,通过重复走访要排序的数组,比较相邻的两个元素,如果顺序不符合要求则交换两个数的位置,直到不需要交换为止。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 比较相邻的元素 a i , a i + 1 a_i,a_{i+1} ai,ai+1,如果 a i > a i + 1 a_i>a_{i+1} ai>ai+1,则交换二者。
  2. 由于每遍历一次都可以将最大的元素排到最后面,所以下一次可以少便利一个元素。
  3. 重复遍历数组 n n n

由于每次遍历需要计算 O ( n ) O(n) O(n)次,且需要遍历 n n n次,故时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( n ) O(n) O(n)

C语言实现

//冒泡排序
void BubbleSort(double *arr, int n)
{n = n-1;double temp;for (int i = 0; i < n; i++)for (int j = 0; j < n-i; j++)if (arr[j]>arr[j+1])mySwap(arr,i,j);        /*前文定义的函数*/
}

插入排序

插入排序是算法导论中的第一个算法,说明已经不那么无脑了。其基本思路是将数组划分位前后两个部分,前面是有序数组,后面是无序数组。逐个扫描无序数组,将每次扫描的数插入到有序数组中。这样有序数组会越来越长,无序数组越来越短,直到整个数组都是有序的。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 假设数组中的第0个数已经有序
  2. 取出无序数组的第0个元素,将其与有序数组比较,插入到有序数组中。

可见,插入排序的每次插入都有 O ( n ) O(n) O(n)的复杂度,而需要遍历 n n n次,所以时间复杂度仍为 O ( n 2 ) O(n^2) O(n2)

C语言实现

//插入排序
void InsertionSort(double *arr, int n){double temp;int j;for (int i = 1; i < n; i++){j = i-1;temp  = arr[i];while (temp<arr[j] && j>=0){arr[j+1] = arr[j];      //第j个元素后移j--;}arr[j+1] = temp;}
}

归并排序

归并排序是算法导论中介绍分治概念时提到的一种排序算法,其基本思路为将数组拆分成子数组,然后令子数组有序,再令数组之间有序,则可以使整个数组有序。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 如果数组元素大于2,则将数组分成左数组和右数组,如果数组等于2,则将数组转成有序数组
  2. 对左数组和右数组执行1操作。
  3. 合并左数组和右数组。

可以发现,对于长度为 n n n的数组,需要 log ⁡ 2 n \log_2n log2n次的拆分,每个拆分层级都有 O ( n ) O(n) O(n)的时间复杂度和 O ( n ) O(n) O(n)的空间复杂度,所以其时间复杂度和空间复杂度分别为 O ( n log ⁡ 2 n ) 和 O ( n ) O(n\log_2n)和O(n) O(nlog2n)O(n)

C语言实现

首先考虑两个有序数组的合并问题

//sort.cvoid myMerge(double *arr, int nL, int nR);
int main(){int n = 6;double arr[6] = {2,4,5,1,3,7};Merge(arr,3,3);for (int i = 0; i < n; i++)printf("%f\n", arr[i]);return 0;
}//两个有序数组的混合,arr为输入数组
//nL、nR分别为左数组和右数组的长度
void Merge(double *arr, int nL, int nR){nL = nL-1;                    //左侧最后一个元素的索引int sInsert = 0;               //左侧待插入起始值double temp;for (int i = 1; i <= nR; i++){while (arr[nL+i]>arr[sInsert])sInsert++;if (sInsert<nL+i){temp = arr[nL+i];for (int j = nL+i; j > sInsert; j--)arr[j]=arr[j-1];arr[sInsert] = temp; }elsebreak;    //如果sInsert==nL+i,说明右侧序列无需插入}
}

验证

> gcc sort.c
> a
1.000000
2.000000
3.000000
4.000000
5.000000
7.000000

然后考虑归并排序的递归过程

void MergeSort(double *arr, int n);
void myMerge(double *arr, int nL, int nR);int main(){int n = 6;double arr[6] = {5,2,4,7,1,3};MergeSort(arr,n);for (int i = 0; i < n; i++)printf("%f\n", arr[i]);return 0;
}void MergeSort(double *arr, int n){if (n>2){int nL = n/2;int nR = n-nL;MergeSort(arr,nL);MergeSort(arr+nL,nR);Merge(arr,nL,nR);}else if(n==2)Merge(arr,1,1);//当n==1时说明数组中只剩下一个元素,所以什么也不用做
}

验证

> gcc sort.c
> a
1.000000
2.000000
3.000000
4.000000
5.000000
7.000000

至此,排序算法终于有一点算法的味道了。

希尔排序

据说是第一个突破 O ( n 2 ) O(n^2) O(n2)的排序算法,又称为缩小增量排序,本质上也是一种分治方案。

在归并排序中,先将长度为n的数组划分为长度为nL和nR的两个数组,然后继续划分,直到每个数组的长度不大于2,再对每个不大于2的数组进行排序。这样,每个子数组内部有序而整体无序,然后将有序的数组进行回溯重组,直到重新变成长度为n的数组为止。

希尔排序的划分策略则不然,这里在将数组划分为nL和nR之后,对nR和nR进行按位排序,使得nL和nR内部无序,但整体有序。然后再将数组进行细分,当数组长度变成1的时候,内部也就谈不上无序了,而所有长度为1的数组整体有序,也就是说有这些子数组所组成的数组是有序的。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 如果数组元素大于2,则将数组分成左数组和右数组,并对左数组和右数组的元素进行一对一地排序。
  2. 对每一个数组进行细分,然后将每个子数组进行一对一地排序。

C语言实现

//希尔排序
void ShellSort(double *arr, int n){double temp;int j;for (int nSub = n/2; nSub >0; nSub/=2)      //nSub为子数组长度for (int i = nSub; i < n; i++){temp = arr[i];for (j = i-nSub; j >= 0&& temp<arr[j]; j -= nSub)arr[j+nSub] = arr[j];arr[j+nSub] = temp;}
}

快速排序

快速排序的分治策略与希尔排序类似,其核心思想都是从组内无序而组间有序出发,当子数组长度缩减至1的时候,则整个数组便是有序的。

算法步骤 \textbf{算法步骤} 算法步骤

设数组有 n n n个元素, { a 0 , a 1 , … , a n } \{a_0,a_1,\ldots,a_n\} {a0,a1,,an}

  1. 在数组中随机选出一个基准 a m i d a_{mid} amid
  2. 通过 a m i d a_{mid} amid将数组分成两部分,其中左边小于 a m i d a_{mid} amid,右边大于 a m i d a_{mid} amid
  3. 对左右子数组重复1、2操作,直到子数组长度为1。

由于快速排序的过程中有一个随机选择,所以其时间复杂度可能会受到这个随机选择的影响,如果运气不好的话,快速排序可能会变成冒泡排序。当然,一般来说运气不会那么差,快速排序的平均时间复杂度为 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)

C语言实现

//快速排序
void QuickSort(double *arr, int n){double pivot = arr[0];      //选取第0个点为基准int i = 1;int j = n-1;while (i<j){if (arr[i]<pivot)i++;else{mySwap(arr,i,j);j--;}}if (arr[i]>pivot)i--;mySwap(arr,i,0);if (i>1)QuickSort(arr,i);    //对i前的数组进行快排i++;if (i<n-1)QuickSort(arr+i,n-i);//对i+1后的数组进行快排
}

堆排序

堆是算法导论中介绍的第一种数据结构,本质是一种二叉树,最大堆要求子节点的值不大于父节点,最小堆反之。由于堆是一种带有节点的数据结构,所以结构表示更加直观。好在二叉树父子节点的序号之间存在简单的递推关系,所以在C语言中可以用数组表示堆,

在这里插入图片描述
根据上图可知,若父节点的序号为 n n n,则左子节点序号为 2 n + 1 2n+1 2n+1,右子节点序号为 2 n + 2 2n+2 2n+2

可以将上图理解为一个排好序的最小堆,如果1位变成15,那么此时这个节点将违反最小堆的原则,经过比对,应该调换15和3的位置。调换之后,15仍然比7和8大,则再调换15和7的位置,则这个最小堆变为

在这里插入图片描述

从而继续满足最小堆的性质,最大堆亦然,其C语言实现为

//如果堆中节点号为m的点不满足要求,则调整这个点
//此为最大堆
void adjustHeap(double *arr, int m, int n){int left = m*2+1;       //左节点while (left<n){if (left+1<n)       //判断右节点if (arr[left]<arr[left+1])left++;     //当右节点大于左节点时,left表示右节点if (arr[m]>arr[left])break;          //如果父节点大于子节点,则说明复合最大堆else{mySwap(arr,m,left);m = left;left = m*2+1;}}
}

堆的调整过程就是父节点与其左右两个子节点比较的过程,也就是说通过这种方式得到的堆能够满足父子节点的大小关系,但两个孙节点之间并不会被排序。但是,如果一个数组已经满足最大堆要求,那么只需让所有的节点都在根节点处重新参与排序,那么最终得到的最大堆一定满足任意节点间的有序关系。

//堆排序
void HeapSort(double *arr, int n){for (int i = n/2; i >= 0; i--)adjustHeap(arr,i,n);        //初始化堆for (int i = n-1; i > 0 ; i--){mySwap(arr,0,i);            //将待排序元素放到最顶端adjustHeap(arr,0,i);        //调整最顶端的值 }   
}

计数排序

此前所有的排序算法均没有考虑到数组的内在分布,如果我们输入的数据为某个区间内的整数,那么我们只需建立这个区间内的整数索引,然后将每个数归类到这个索引之中即可。

这便是桶排序的思路,所谓桶排序即通过将已知数据划分为有序的几个部分,放入不同的桶中,这个分部过程即桶排序。除了计数排序,基数排序是一种更广泛的桶排序形式,其划分方式可以为数据的位数,把这个桶定义为数据最高位的值。

例如,我们有一组均匀分布在 [ 0 , 100 ] [0,100] [0,100]之内的数据,所谓基数排序,即先划分出十个不同的桶 [ 0 , 10 ) , [ 10 , 20 ) , … , [ 90 , 100 ) [0,10),[10,20),\ldots,[90,100) [0,10),[10,20),,[90,100),然后再对每个桶进行单独的排序。这样划分下去,那么基数排序的复杂度则为 O ( 10 ∗ n ) O(10*n) O(10n)

词典中的排序方式也可以理解为一种基数排序,即首先看第一个字母的顺序,然后第二个,依次类推。由于桶排序对数据做了假设,所以其最优时间复杂度可以达到 O ( n + k ) , k O(n+k),k O(n+k),k为桶的数目。

例如,我们有一个由一百个由1和2组成的数组,那么我们只需建立一个索引 1 : n 1 , 2 : n 2 {1:n_1,2:n_2} 1:n1,2:n2,然后统计1和2分别出现的个数即可,其时间复杂度也将变成 O ( n ) O(n) O(n)

在这里只做出最简单的计数排序。

/计数排序,输入数组为整数
void CountingSort(int *arr,int n){int aMax = arr[0];int aMin = arr[0];for (int i = 0; i < n; i++) //查找最大值和最小值if (arr[i]>aMax)aMax = arr[i];else if (arr[i]<aMin)aMin = arr[i];int m = aMax-aMin+1;         //索引长度int *arrSort = (int*)malloc(sizeof(int)*m);for (int i = 0; i < m; i++)arrSort[i]=0;           //初始化索引for (int i = 0; i < n; i++) //排序arrSort[arr[i]-aMin] += 1;n = 0;for (int i = 0; i < m; i++){aMax = i+aMin;          //此值为真实数据for (int j = n; j < n+arrSort[i]; j++)arrSort[j] = i+aMin;n += arrSort[i];}
}

http://chatgpt.dhexx.cn/article/MNvTApjH.shtml

相关文章

c语言八大排序算法详细版

概述排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时…

C语言各大排序算法整理及动画演示

&#xff08;一&#xff09;插入排序 插入排序基本思想&#xff1a;从初始的子集合开始&#xff0c;不断地将新的元素插入到已排序好的子集合当中的合适位置。&#xff08;未排序的插入到已排序当中&#xff09;具体分为直接插入排序和希尔排序两种。 ①直接插入排序 void Ins…

浅谈排序算法:冒泡排序法和选择排序法的区别

之前学习了冒泡排序法和选择排序法&#xff0c;最近被老师问某个道题用的是什么排序法。自己居然答不出来&#xff0c;才发现自己没有真正弄懂&#xff0c;这两个算法的原理和区别&#xff0c;所以 1冒泡排序法 1.1什么是冒泡排序法&#xff1f; 顾名思义&#xff0c;冒泡排…

Java语言实现常用的十种排序算法

排序问题一直都是程序工作或面试中的重点&#xff0c;特别是对于排序算法而言&#xff0c;在一些公司的笔试中&#xff0c;手写个冒泡啥的也不足为奇&#xff0c;因此今天抽空整理一下Java语言实现的各类排序算法&#xff0c;互勉学习一下。根据排序的不同方式大概可归为一下五…

排序算法(二)—— 选择法排序算法

1、选择法排序简介 选择法排序算法是一种常用的排序算法&#xff0c;他的实现方法是遍历数组所有元素&#xff0c;找出最小的元素&#xff0c;将它与第一个元素交换&#xff1b;然后遍历剩下的元素&#xff0c;找出最小的元素并与第二个元素交换&#xff1b;接下来再遍历剩下的…

Java编程:排序算法——选择排序

基本介绍 选择式排序也属于内部排序法&#xff0c;是从欲排序的数据中&#xff0c;按指定的规则选出某一元素&#xff0c;再依规定交换位置后达到排序的目的。 选择排序思想: 选择排序&#xff08;select sorting&#xff09;也是一种简单的排序方法。它的基本思想是&#x…

Java常用排序算法/程序员必须掌握的8大排序算法

本文由网络资料整理而来&#xff0c;如有问题&#xff0c;欢迎指正&#xff01; 参考链接&#xff1a;维基百科-排序算法 // 排序原始数据 private static final int[] NUMBERS {49, 38, 65, 97, 76, 13, 27, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15,…

排序算法:选择排序

1. 什么是选择排序&#xff1f;&#xff08;摘抄自百度百科&#xff09; 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。 它的工作原理是&#xff1a; 第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c…

排序算法——选择排序

目录 &#x1f43e;基本介绍 &#x1f31e;算法思想&#xff1a; &#x1f330;实例&#xff1a; ⛅思路分析&#xff1a; &#x1f308;总结&#xff1a; &#x1f6f4;代码实现: &#x1f6f9;算法性能分析 &#x1f355;时间复杂度 &#x1f367;空间复杂度 &…

基本算法-选择排序

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 本文介绍一种经典排序算法——选择排序&#xff0c;是入门级的排序算法之一。以下是本篇文章正文内容&#xff0c;包括算法简…

程序员八大排序算法之直接选择排序算法(java版)

一&#xff0c;选择排序算法思路&#xff1a;每趟选择序列的最小值/最大值&#xff0c;采取贪心选择策略。 二&#xff0c;选择排序算法有两种&#xff1a;1.直接选择排序 2.堆排序&#xff08;基于二叉树&#xff09;。 &#xff08;这里讲解直接选择排序&#xff09; 三&a…

排序算法--选择排序(Java实现)

选择排序概念 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。它的工作原理是&#xff1a;第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;然后再从剩余的未排序元素中寻…

java之选择排序

基本介绍 选择排序同样属于内部排序法&#xff0c;是从欲排序的数据中&#xff0c;按指定的规则选出某一元素&#xff0c;再按规定交换位置达到排序的目的。 排序思想 选择排序是一种简单的排序方法。它的基本思想是&#xff1a;第一次从arr[0]~arr[n-1]中选取最小值&#xf…

java选择排序(含选择排序代码)

目录 一&#xff1a;选择排序的思想 ​二&#xff1a;选择排序的代码 三&#xff1a;结果 一&#xff1a;选择排序的思想 选择排序是一种简单直观的排序算法。它的工作原理是&#xff1a;第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&…

选择排序算法

选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的工作原理是&#xff1a;第一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;然后再从剩余的未排序元素中寻找到最小&…

Java——常见的几种排序算法

一、冒泡排序 每次冒泡过程都是从数列的第一个元素开始&#xff0c;然后依次和剩余的元素进行比较, 跟列队一样, 从左到右两两相邻的元素比大小, 高的就和低的换一下位置. 最后最高(值最大)的肯定就排到后面了. 但是这只是把最高的排到后面了, 还得找出第二高的, 于是又从第一…

Java实现选择排序

Java实现选择排序 选择排序原理为&#xff1a;随机确定一个标志位&#xff08;一般为第一个数字&#xff09;作为最小数&#xff0c;然后向后遍历&#xff0c;找到比标志位更小的数便与标志位互换位置并更新最小数&#xff0c;实现步骤为&#xff1a; 将数组的第一个数字设置…

【算法】选择排序法

一、介绍 1.选择排序法是将序列分为两段&#xff0c;有序前列和无序后列&#xff0c;每次查找无序后列中最大元素&#xff0c;将其插入到有序前列的最末尾处&#xff0c;直至无序后列最后一个元素&#xff0c;最终排序后的序列为降序序列 2.适用于包括数组和向量在内的序列 …

选择排序的两种算法(Java代码实现)

目录 选择排序&#xff1a; 基本思想&#xff1a; 1&#xff1a;简单选择排序&#xff1a; 基本思想&#xff1a; 过程&#xff1a; 2&#xff1a;堆排序&#xff1a; 基本思想&#xff1a; 过程&#xff1a; 选择排序&#xff1a; 基本思想&#xff1a; 每一趟从待排序…

Java选择排序

1. 选择排序 选择排序是一种简单直观的排序算法&#xff0c;其基本原理是每一次从待排序的数组里找到最小值&#xff08;最大值&#xff09;的下标&#xff0c;然后将最小值&#xff08;最大值&#xff09;跟待排序数组的第一个进行交换&#xff0c;然后再从剩余的未排序元素中…