C语言的十种排序算法

以下介绍默认是升序排序,降序方法自行修改。

为了更好的理解排序的过程,你可以在这个网站里发现一些好玩的东西:https://visualgo.net/zh/sorting

一、冒泡排序

1、简介:

冒泡排序算法是把较小的元素往前调或者把较大的元素往后调。这种方法主要是通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。

这个时候发现最后一个数是最大的,那么
第二步就是将剩下的 n-1 个数据按照上述方法排序
第三步就是将剩下的 n-2 个数据按照上述方法排序
···
最后只剩 1 个数据时即完成。

2、程序实现

C语言程序实现:

#include <stdio.h>

int main()
{
    int temp;                               //定义一个中介容器
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    int i,j;                                //定义循环指数
    for(i = n-1 ; i > 0; i--)               //操作大步骤
    {
        for(j = 0; j < n-1 ; j++)           //操作小步骤,每一步完成后都确定一个最大数
        {
            if(array[j] > array[j+1])       //当左边数字大于右边数字时,两数交换
            {
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    for(i = 0 ; i < n ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出结果:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

由上述可知,我们一共需要进行 i*j 次判断,也就是 n^2 级数,但是每次比较后转换位置时,我们只需要一个额外的数字空间即可。所以,

时间复杂度:O(n2)
空间复杂度:T(1)

二、选择排序

1、简介

首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可;以此类推,重复进行“最小元素”的选择,直至完成第(n-1)个位置的元素选择,则第n个位置就只剩唯一的最大元素,此时不需再进行选择。

以此类推
第二步从第二个数6开始,重复以上操作
第三步从第三个数开始
···
最后一步只剩一个数字时即已完成。

2、程序实现

#include <stdio.h>

int main()
{
    int temp;                               //定义一个中介容器
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    int i,j;                                //定义循环指数
    for(i = 0 ; i < n-1 ; i++)              //比较的第几个数,第几大步
    {
        for(j = i+1 ; j < n ; j++)          //小步骤,将最小元素移至当前最左端
        {
            if(array[i] > array[j])         //找到较小值,交换位置
            {
                temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    for(i = 0 ; i < n ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

选择排序思路和冒泡排序思路大同小异,所以

时间复杂度:O(n2)
空间复杂度:T(1)

三、插入排序

1、简介

插入排序算法是基于某序列已经有序排列的情况下,通过一次插入一个元素的方式按照原有排序方式增加元素。这种比较是从该有序序列的最末端开始执行,即要插入序列中的元素最先和有序序列中最大的元素比较,若其大于该最大元素,则可直接插入最大元素的后面即可,否则再向前一位比较查找直至找到应该插入的位置为止。

2、程序实现

#include <stdio.h>

int main()
{
    int temp;                               //定义一个中介容器
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    int i,j;                                //定义循环指数
    for(i = 1 ; i < n ; i++)                //第i步
    {
        temp = array[i];                    //将待插入数字保存到temp中
        for(j = i-1 ; array[j] > temp ; j--)//j从i-1开始,找到刚好小于temp的那个位置
            array[j+1] = array[j];          //寻找过程中将数据右挪一格,给插入数字腾空
        array[j+1] = temp;                  //找到位置j符合条件,插入到j+1位置
    }
    for(i = 0 ; i < n ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

插入排序是寻找可插入位置后再直接插入。那么如果原数组是有序的,每一步第一次运行时就符合判断条件,就会跳至下一大步。所以此时时间为最小值(n)。
如果恰巧是倒序的,那么每一次都要运算到最前面,所以此时时间最大值为(n^2)。
空间我们只用了temp。所以

时间复杂度:O(n)~O(n2)
空间复杂度:T(1)

四、快速排序

1、简介

快速排序的基本思想是:通过一趟排序算法把所需要排序的序列的元素分割成两大块,其中,一部分的元素都要小于或等于另外一部分的序列元素,然后仍根据该种方法对划分后的这两块序列的元素分别再次实行快速排序算法,排序实现的整个过程可以是递归的来进行调用,最终能够实现将所需排序的无序序列元素变为一个有序的序列。

2、程序实现

#include <stdio.h>

void quick(int * a,int l,int r)             //快排算法
{
    if(l >= r)                              //左边下标不小于右边下标时,说明只有一个数字,无需排序
        return;
    int key = a[l];                         //保存最左边的比较值
    int temp;
    int i = l, j = r;                       //i从左开始, j从右开始
    while (1)                               //无限循环,有退出条件
    {
        while(key <= a[j] && i < j) j--;    //从右边寻找比key小的数,时刻保证i < j
        while(key >= a[i] && i < j) i++;    //从左边寻找比key大的数,时刻保证i < j
        
        if(i < j)                           //当i < j时,
        {                                   //a[i]和a[j]交换数字
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        else                                //当i !< j时,退出循环,此时一般情况下 i == j
            break;
    }
    a[l] = a[i];                            //将最左边的数和 i 所指的数交换位置,i 就是分界线
    a[i] = key;
    
    quick(a,l, i-1);                        //i 左边的再次使用递归
    quick(a,i+1, r);                        //i 右边的再次使用递归
}

int main()
{
    const int n = 8;
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无序数组
    quick(array,0, n-1);                    //调用快排函数,传入array地址
    for(int i = 0 ; i < n ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

其性质是从一个整数组中间不停划分,但是中间并不恰巧是最中间,所以也分好坏情况。最好的情况就是每次都从最中间划分,这样只需要 log2n 次就可以完成,所以最好的时间复杂度为 O(nlog2n) ;最坏的情况就是每次划分都是顶着最边上划分,那么需要n次划分,所以最坏的情况是 O(n^2)。
并且,空间上也只是多用了两个整形空间。所以,

时间复杂度:O(nlog2n)~O(n2)
空间复杂度:T(1)

五、归并排序

1、简介

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

2、程序实现

#include <stdio.h>

void sort(int *a,int *b,const int begin,const int middle,const int end)     //针对从begin开始,end结束的排序算法
{
    int i = begin;                                      //i 定义为左侧开始
    int j = middle+1;                                   //j 定义为右侧开始
    int b_flag = 0;                                     //b数组的下标,从0开始
    while(1)
    {
        if(i > middle)                                  //当左边的数列全部转移后
        {
            while(j <= end)                             //右边数列还没有结束转移
                b[b_flag++] = a[j++];                   //全部挪至数组 b 中
            break;                                      //工作完成,退出循环
        }
        if(j > end)                                     //和上层同理,右边数列转移完成,而左边的还没有
        {
            while(i <= middle)
                b[b_flag++] = a[i++];
            break;
        }
        
        if(a[i] < a[j])                                 //两边数列都没有转移完成,判断哪个小,只转移小的
            b[b_flag++] = a[i++];
        else
            b[b_flag++] = a[j++];
    }
    b_flag = 0;                                         //重新定义 b 的下标
    for(i = begin ; i <= end ; i++)                     //循环 end-begin+1 次,将所得的b数组按照对应位置返还给数组 a
        a[i] = b[b_flag++];
}

void merge(int *a,const int begin,const int end)
{
    if(begin >= end)                                    //左侧和右侧重叠的时候,说明已经是最小单位了,直接返回
        return;
    int b[end-begin+1];                                 //新建一个预备顺序数组
    int mid = (begin+end)/2;                            //网上 m=start+(end-begin)/2 是为了避免int型溢出。根据需要自定
    merge(a, begin, mid);                               //采用递归将划分的数组再次划分。
    merge(a, mid+1, end);
    sort(a, b, begin, mid, end);                        //从begin开始到mid结束以及从mid+1开始到end结束的两个队列进行合并运算
}


int main()
{
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    merge(array,0,n-1);                     //归并排序
    int i;                                  //定义循环指数
    for(i = 0 ; i < n ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

采用二分法运算,时间复杂度和快排相似,空间上我们新建了一个用来保存正确顺序的数组,所以

时间复杂度:O(nlog2n)
空间复杂度:T(n)

六、希尔排序

1、简介

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

具体介绍参考:希尔排序_百度百科

2、程序实现

#include <stdio.h>

void insertsort(int *b, int len)            //直接插入排序
{
    int i,j;
    for(i = 1 ; i < len ; i++)
    {
        int temp = b[i];
        for(j = i-1 ; b[j] > temp ; j--)
                b[j+1] = b[j];
        b[j+1] = temp;
    }
}

void shell(int *a,int n)                    //希尔排序
{
    int as = n/2;                           //定义增量,最好是长度的一半
    do {
        int i,j;
        for(i = 0; i < as ; i++)            //大步内循环次数,比如说增量为4时,那么就循环四次,增量为2就循环2次
        {
            int b[n/as];                    //定义一个可以容纳小数列的数组
            for(j = 0; j < n/as ; j++)      //将增量下标对应的数组保存到 b 数组中
                b[j] = a[as*j+i];
            insertsort(b, n/as);            //使用插入排序将 b 数组排序
            for(j = 0; j < n/as ; j++)      //将排序后的 b 数组按照对应下标返还给原数组
                a[as*j+i] = b[j];
        }
        as /= 2;                            //增量缩小
    }while(as > 1);                        //当增量为1时,推出循环,也可不退,最后一步原数组调用插排,省空间
    insertsort(a, n);
}

int main()
{
    const int n = 8;                         //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};        //定义一个无顺序数组
    shell(array,n);                          //希尔排序
    int i;                                   //定义循环指数
    for(i = 0 ; i < n ; i++)                 //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

希尔算法是基于插入排序算法的,从前文可知,插排的时间复杂度是不稳定的,如果是逆序的情况,那么程序运行会很繁琐。希尔是将一个复杂的数组排序操作划分为多个小数组进行插排;每一步的操作都是为了优化插排的时间效率。
当最后增量为 1 时,调用的插排就和直接调用插排一个概念,但是,希尔将数组操作优化过后,就不会那么繁琐了。所以,

时间复杂度:O(n1.3)~O(n2)
空间复杂度:T(n)

七、基数排序

1、简介

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
主要思路是:将椅子数组的个位数看作标志,在遍历数组时,将对应的标志放入到0~9编号的桶中,这个桶我们用二维数组来表示。
比如35,它的个位数是5,那么就放入bucket[5][]的桶中,以此类推;结束后,将桶中数字按序赋给原数组,然后从十位数继续遍历··· ···直到最大数的最大位算法结束。

详细参考:基数排序_百度百科

2、程序实现

#include <stdio.h>
#include <math.h>

int findTimes(int *a,int n)                                 //返回最大位的位数
{
    int i,max = a[0];
    for(i = 1 ; i < n ; i++)                                //寻找最大值max
        if(max < a[i])
            max = a[i];
    int times = 0;
    while(max)                                              //查看最大值位数
    {
        max /=10;
        times++;
    }
    return times;                                           //返回位数,作为编号管理次数
}

void baseSort(int *a, const int n)                          //基数排序
{
    int times = findTimes(a,n);                             //计算最大位数
    int i,j;
    for(i = 0 ; i < times ; i++)                            //循环最大位数次
    {
        int bucket[10][n];                                  //定义9个数组桶,每一大步都要重新定义
        int flag[10] = {0};                                 //记录下标,每一大步都要重新定义
        for(j = 0 ; j < n ; j++)
        {
            int tens = pow(10, i);                          //10^i
            int value = (a[j] / tens ) % 10;                //当前位置的数字
            int k;
            if(flag[value])                                 //如果下标不为0,也就是有数据了,调用插排算法插入数据
            {
                for(k = flag[value]-1; bucket[value][k] > a[j] ; k--)
                    bucket[value][k+1] = bucket[value][k];
                bucket[value][k+1] = a[j];
            }
            else                                            //否则,下标为0,无插入数据,直接在0下标处赋值
                bucket[value][0] = a[j];
            
            flag[value]++;                                  //插入数据,个数+1
        }
        int m,n,k = 0;
        for(m = 0 ; m < 10 ; m++)                           //将bucket桶内数据依次给原数组
            for(n = 0 ; n < flag[m] ; n++)
                a[k++] = bucket[m][n];
    }                                                       //再次循环,分别使用个位数、十位数、百位数···进行排序
}

int main()
{
    const int n = 8;                                        //数组大小
    int array[n] = {36,4,745,12,7345,34,3675,101};          //定义一个无顺序数组
    baseSort(array,n);                                      //基数排序
    int i;                                                  //定义循环指数
    for(i = 0 ; i < n ; i++)                                //输出排序后的数组
        printf("%6d",array[i]);
    printf("\n");
    return 0;
}

输出:

     4    12    34    36   101   745  3675  7345
Program ended with exit code: 0

3、时间、空间复杂度

时间效率较高,,但是效率的提高降低了空间效率。而且在桶中也是用的插排算法,所以如果标志数字都一样的话,效率和插排差不多,这也是最差的情形。所以,

时间复杂度:O(n)~O(blog2n)
空间复杂度:T(n),其实是T(10*n)

八、堆排序

1、简介

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

详细查看:堆排序_百度百科

2、程序实现

#include <stdio.h>

void swap(int *a, int *b)                   //交换函数,a 和 b 的值互换
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

void heapSort(int *a,const int len)
{
    if(len <= 1)                            //数组长度不大于1时,结束递归
        return;
    int node = len/2 - 1;                   //最后一个非终结点下标
    int i;
    for(i = node ; i >= 0 ; i--)            //从最后一个非终结点开始,依次运算
    {
        if(a[i] < a[i*2+1])                 //该非终结点和其左右节点比较大小,并把最大的节点移至非终结点
            swap(&a[i], &a[i*2+1]);
        if(i*2+2 < len && a[i] < a[i*2+2])  //判断是否存在右节点
            swap(&a[i], &a[i*2+2]);
    }
    swap(&a[0], &a[len-1]);                 //将头节点和末尾节点互换
    heapSort(a,len-1);                      //舍去末尾节点,调用递归,舍弃的节点按顺序降序
}

int main()
{
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    heapSort(array,n);                      //堆排序
    int i;
    for(i = 0 ; i < 8 ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

堆排序看似在使用二叉树的数据结构,但是程序的操作都是在数组内部完成的,所以空间方面无多余使用空间。在运算时,运算完成一次,都会“舍弃”一个单位数组,并且交换的数据最多只在三个数里交换,且运算都是根据终结点位置来算。所以,

时间复杂度:O(nlog2n)
空间复杂度:T(1)

九、计数排序

1、简介

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog2n)的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog2n), 如归并排序,堆排序)
思路就相当于将数组最大值和最小值作差,将这个差值大小+1作为一个新数组的大小,比如说一个序列7,5,7,8,4。最大和最小差为4,那么创建一个大小为5的数组b,那么b[0]对应的就是序列中最小值的个数,b[1]对应的就是序列中最小值+1的个数,b[2]对应的就是序列中最小值+2的个数··· ··· 最后根据b数组重新对原序列赋值。由此可知,在对于相对集中的数组来说,这样的效率明显比快排、堆排序好多了,但是,也仅限于集中数组,只要有一个数组距离过小或过大,那么这个算法极耗空间。(统计学生成绩排序时,就可以使用这种算法,几百甚至几千个数据,保存在100大小以内的数组中,既方便又快捷)

详细查看:计数排序_百度百科

2、程序实现

#include <stdio.h>
#include <stdlib.h>

void findmost(int *a,const int len, int *max, int *min)         //找出最大最小值,不做解释
{
    *max = a[0];
    *min = a[0];
    int i;
    for(i = 1 ; i < len ; i++)
    {
        if(a[i] < *min)
            *min = a[i];
        if(a[i] > *max)
            *max = a[i];
    }
}

void countsort(int *a,int len)                                  //计数排序
{
    int max,min,*b;                                             //定义数组最大最小值,和一个空的数组指针
    findmost(a, len, &max, &min);                               //调用函数,找到最大最小值
    b = (int *)malloc(sizeof(int) * (max-min+1));               //根据最大最小值,申请 最大-最小+1 容量的数组
    int i,j,k=0;
    for(i = 0 ; i < max-min ; i++)                              //初始化b数组
        b[i] = 0;
    for(i = 0 ; i < len ; i++)                                  //遍历原数组,在对应b下标的数组内+1
        b[a[i]-min]++;
    for(i = 0 ; i < len ; i++)                                  //根据b数组内的值和最小值,将原数组按顺序还原
        for(j = 0 ; j < b[i] ; j++)
            a[k++] = min+i;
}

int main()
{
    const int n = 8;                        //数组大小
    int array[n] = {6,4,7,2,5,4,3,1};       //定义一个无顺序数组
    countsort(array, n);                    //计数排序
    int i;
    for(i = 0 ; i < 8 ; i++)                //输出排序后的数组
        printf("%2d",array[i]);
    printf("\n");
    return 0;
}

输出:

 1 2 3 4 4 5 6 7
Program ended with exit code: 0

3、时间、空间复杂度

算法中主要依赖的是空间,空间大小随数据大小变化,时间效率上相当于遍历两次数组。所以,

时间复杂度:O(n)
空间复杂度:T(1)~T(无穷)

十、桶排序

1、简介

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

详细查看:桶排序_百度百科

桶排序和计数排序大致相同。写的太多了,不想写了,想了解的请百度搜索:

基数排序、桶排序、计数排序

~~~
以上资料部分来源于:排序算法_百度百科

  • 20
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值