C语言实现各种排序算法实例代码(选择,冒泡,插入,归并,希尔,快排,堆排序,计数)

目录
  • 前言
  • 选择排序
  • 冒泡排序
  • 插入排序
  • 归并排序
  • 希尔排序
  • 快速排序
  • 堆排序
  • 计数排序
  • 总结

前言

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

选择排序

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

算法步骤

设数组有n个元素,{ a 0 , a 1 , … , a n }

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

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

冒泡排序

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

算法步骤

设数组有n个元素,{ a 0 , a 1 , … , a n }

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

由于每次遍历需要计算O ( n ) 次,且需要遍历n次,故时间复杂度为O ( n 2 ) ,空间复杂度为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);        /*前文定义的函数*/
}

插入排序

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

算法步骤

设数组有n个元素,{ a 0 , a 1 , … , a n }

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

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

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;
    }
}

归并排序

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

算法步骤

设数组有n个元素,{ a 0 , a 1 , … , a n }

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

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

C语言实现

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

//sort.c

void 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;
        }
        else
            break;    //如果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 ) 的排序算法,又称为缩小增量排序,本质上也是一种分治方案。

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

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

算法步骤

设数组有n nn个元素,{ a 0 , a 1 , … , a n }

  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的时候,则整个数组便是有序的。

算法步骤

设数组有n nn个元素,{ a 0 , a 1 , … , a n }

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

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

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 nn,则左子节点序号为2 n + 1 ,右子节点序号为2 n + 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 , 10 ) , [ 10 , 20 ) , … , [ 90 , 100 ) ,然后再对每个桶进行单独的排序。这样划分下去,那么基数排序的复杂度则为O ( 10 ∗ n ) 。

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

例如,我们有一个由一百个由1和2组成的数组,那么我们只需建立一个索引1 : n 1 , 2 : n 2 ,然后统计1和2分别出现的个数即可,其时间复杂度也将变成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];
    }
}

总结

到此这篇关于C语言实现各种排序算法(选择,冒泡,插入,归并,希尔,快排,堆排序,计数)的文章就介绍到这了,更多相关C语言实现各种排序算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言实现快速排序算法

    一.快速排序算法(Quicksort) 1. 定义 快速排序由C. A. R. Hoare在1962年提出.快速排序是对冒泡排序的一种改进,采用了一种分治的策略. 2. 基本思想 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列. 3. 步骤 a. 先从数列中取出一个数作为基准数. b. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全

  • C语言实现排序算法之归并排序详解

    排序算法中的归并排序(Merge Sort)是利用"归并"技术来进行排序.归并是指将若干个已排序的子文件合并成一个有序的文件. 一.实现原理: 1.算法基本思路 设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中. (1)合并过程 合并过程中,设置i,j和p三个指针,其初值分别指向这三个记录区的起始位置.合并时依次比较R[i

  • 必须知道的C语言八大排序算法(收藏)

    概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序-直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

  • c语言实现冒泡排序、希尔排序等多种算法示例

    实现以下排序 插入排序O(n^2) 冒泡排序 O(n^2) 选择排序 O(n^2) 快速排序 O(n log n) 堆排序 O(n log n) 归并排序 O(n log n) 希尔排序 O(n^1.25) 1.插入排序 O(n^2) 一般来说,插入排序都采用in-place在数组上实现.具体算法描述如下:⒈ 从第一个元素开始,该元素可以认为已经被排序⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置⒋ 重复步骤3,直到找到已排序的元素

  • C语言排序算法之插入排序

    算法实现: 使用插入排序将下面的数字按照从小到大的顺序排列 步骤1:数组中已经排好的是{1},将9插入数组中 步骤2:数组中已经排好的是{2,9},将5插入数组中 步骤3:数组中已经排好的是{2,5,9},将4插入数组中 步骤4:数组中已经排好的是{2,4,,5,9},将8插入数组中 步骤5:数组中已经排好的是{2,4,,5,8,9},将1插入数组中 步骤6:数组中已经排好的是{1,2,4,,5,8,9},将6插入数组中 步骤7:排序完成 程序代码: #include <stdio.h> #i

  • c语言实现奇偶排序算法

    =====第2题:奇偶排序(一)===== 总时间限制:1000ms内存限制:65536kB描述输入十个整数,将十个整数按升序排列输出,并且奇数在前,偶数在后.输入输入十个整数输出按照奇偶排序好的十个整数 复制代码 代码如下: #include<stdio.h> #define  COUNT 10#define bool int#define true 1#define false 0 /*****负责冒泡排序***/int* sortFunction(int data[]){ int i,j

  • c语言快速排序算法示例代码分享

    步骤为:1.从数列中挑出一个元素,称为 "基准"(pivot);2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边).在这个分区退出之后,该基准就处于数列的中间位置.这个称为分区(partition)操作.3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序.递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了.虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iterat

  • C语言对堆排序一个算法思路和实现代码

    算法思想简单描述: 堆排序是一种树形选择排序,是对直接选择排序的有效改进. 堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)时称之为堆.在这里只讨论满足前者条件的堆. 由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项.完全二叉树可以很直观地表示堆的结构.堆顶为根,其它为左子树.右子树. 初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的

  • 希尔排序算法的C语言实现示例

    希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本.希尔排序是非稳定排序算法. 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位 希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能.这样可以让一个元素可以一次性地朝最终位置前进一大步.然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几

  • C语言实现基于最大堆和最小堆的堆排序算法示例

    堆定义 堆实际上是一棵完全二叉树,其任何一非叶节点满足性质: Key[i]<=key[2i+1]&&Key[i]<=key[2i+2](小顶堆)或者:Key[i]>=Key[2i+1]&&key>=key[2i+2](大顶堆) 即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字. 堆排序的思想 利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单. 最大堆:所有节点的子节点比其

随机推荐