C语言十大排序

C语言十大排序



1、冒泡排序

基本思想:

​ 冒泡排序基本思想是依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

​ 在进行第一轮上面的从左到右的比较时,则会把一个最小或者最大的元素(取决于你想要的排列方式)"冒泡"到最右边的位置,第二轮则是冒泡第二大或第二小的数到最右边,因此我们总共只需要进行n-1轮即可,最后一个数的位置也被固定了(其余n-1个数都比他大且都在其右边)。

​ 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

动图演示:

冒泡排序.gif

函数实现:

/** 
 * [swap 交换两个整数]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:20:44+0800
 * @param    num1      [参数1]
 * @param    num2      [参数2]
 */
void swap(int *num1, int *num2)
{
    int temp = 0;

    // 入参指针判空
    if((NULL == num1) || (NULL == num2))
    {
        perror("swap function is error, Participate is NULL!\n");

        return;
    }

    temp = *num1;
    *num1 = *num2;
    *num2 = temp;

    return;
}

/** 
 * [bubble_sort 冒泡排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:29:41+0800
 * @param    a[]       [数组首地址]
 * @param    n         [数组长度]
 */
void bubble_sort(int a[], int n)
{
    int i, j;    // 定义两个整型变量用于循环处理

    // 入参判断
    if((NULL == a) || (1 > n))
    {
        perror("bubble_sort function is error, in function parameter error!\n");
    }

    for (j = 0; j < n - 1; j++)    //用一个嵌套循环来遍历一遍每一对相邻元素,(时间复杂度高)  
    {                           
        for (i = 0; i < n - 1 - j; i++)
        {
            if(a[i] > a[i + 1])  //从大到小排就把左边的">"改为"<"!!!
            {
                swap(&a[i], &a[i + 1]);    //做交換
            }
        }
    }
}

2、选择排序

基本思想:

​ 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

动图演示:

选择排序.gif

函数实现:

/** 
 * [swap 交换两个整数]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:20:44+0800
 * @param    num1      [参数1]
 * @param    num2      [参数2]
 */
void swap(int *num1, int *num2)
{
    int temp = 0;

    // 入参指针判空
    if((NULL == num1) || (NULL == num2))
    {
        perror("swap function is error, Participate is NULL!\n");

        return;
    }

    temp = *num1;
    *num1 = *num2;
    *num2 = temp;

    return;
}

/** 
 * [selection_sort 选择排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:29:41+0800
 * @param    arr[]       [数组首地址]
 * @param    len         [数组长度]
 */
void selection_sort(int arr[], int len)
{
    int i, j;    // 定义两个整型变量用于循环处理
    int min;    // 用于记录最小值

    // 入参判断
    if((NULL == arr) || (1 > len))
    {
        perror("selection_sort function is error, in function parameter error!\n");
    }

    for (i = 0 ; i < len - 1 ; i++)
    {
        min = i;
        
        for (j = i + 1; j < len; j++)     // 遍历未排序的元素
        {
            if (arr[j] < arr[min])    // 找到目前最小值
            {
                min = j;    // 记录最小值
            }
        }

        swap(&arr[min], &arr[i]);    //做交換
    }
    
    return;
}

3、插入排序

基本思想:

​ 插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而形成一个新的、记录数增1的有序表。

​ 一般将第一个元素看做最小的有序组,然后用第二个元素插入到第一个元素组成的有序表中(其实就是个简单的比较大小),然后将第三个元素插入到前两个元素组成的有序表中,形成一个三个元素的有序表,以此类推,最终获得一个包含全部元素的有序表
动图演示:

插入排序.gif

函数实现:

/** 
 * [selection_sort 插入排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:29:41+0800
 * @param    arr[]       [数组首地址]
 * @param    len         [数组长度]
 */
void insertion_sort(int arr[], int len)
{
    int i, j, key;

    if((NULL == arr) || (1 > len))
    {
        perror("insertion_sort function is error, in function parameter error!\n");
    }

    for (i = 1; i < len; i++) // 从1开始是因为默认第一个元素是最小的有序表
    {
       key = arr[i];
       j = i - 1; // a[j]是有序表最后一个元素

       // 从后往前遍历并且判断arr[j]是否大于key
       // j>=0防止数组越界
       while ((j >= 0) && (arr[j] > key))
       {
          arr[j + 1] = arr[j];// 后移
          j--;// j前移
       }

       arr[j + 1] = key; // arr[j]是第一个比key小的元素,将key置于其后(比key大的有序表元素都已经后移一个位置了)
    }

    return;
}

4、希尔排序

基本思想:

​ 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本

​ 基本思想是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

具体实现方法:

1. 把待排序列,分成多个间隔为gap的子序列,
2. 然后对每个子序列进行插入排序
3. 重复上述,每次间隔gap不同(并且越来越小),直到最后一次选取间隔gap=1,完成排序。
4. 我们一般是取数组长度的一半为第一个间隔,即第一次将整个数组以len/2为间隔分为2个子序列,再进行插入排序,然后以len/2/2为间隔一直到gap=1重复上述动作。

动图演示:

希尔排序1.png

希尔排序2.gif

函数实现:

/** 
 * [selection_sort 希尔排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:29:41+0800
 * @param    arr[]       [数组首地址]
 * @param    len         [数组长度]
 */
void shell_sort(int arr[], int len)
{
    int gap, i, j;
    int temp;

    if((NULL == arr) || (1 > len))
    {
        perror("shell_sort function is error, in function parameter error!\n");
    }

    for (gap = len / 2; gap >= 1; gap /= 2) //第一个间隔为len/2,然后不断除2缩小
    {
       for (i = gap; i < len; i++) //对每一个下标大于gap的元素进行遍历
                                 //arr[gap]是第一组最后一个元素
       {
          temp = arr[i]; //将要插入的值赋值给temp,因为它所处的位置可能被覆盖
          for (j = i - gap; arr[j] > temp && j >= 0; j -= gap)
          {                         //i所处的子序列:i  i-gap  i-2gap i-n*gap( i-n*gap  >= 0)
             arr[j + gap] = arr[j]; //arr[j]若大于要插入的值则将位置后移
          }
          arr[j + gap] = temp; //无论是arr[j]<temp还是j<0了,都将temp插入到arr[j]这一个子序列的后一个位置(j+gap)
       }
    }

    return;
}

5、归并排序

基本思想:

​ 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

动图演示:

归并排序.gif

函数实现:

/** 
 * [min 比较两个数]
 * @Author   HulinHuang
 * @DateTime 2021年8月28日T10:55:10+0800
 * @param    x         [参数1]
 * @param    y         [参数2]
 * @return             [返回最大的数]
 */
int min(int x, int y)
{
    return x < y ? x : y;
}

/** 
 * [selection_sort 希尔排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:29:41+0800
 * @param    arr[]       [数组首地址]
 * @param    len         [数组长度]
 */
void merge_sort(int arr[], int len)
{
    if((NULL == arr) || (1 > len))
    {
        perror("merge_sort function is error, in function parameter error!\n");
    }

    int *a = arr;                              //左指针->首元素
    int *b = (int *)malloc(len * sizeof(int)); //右指针->尾元素
    int seg, start;

    for(seg = 1; seg < len; seg += seg)
    {
        for(start = 0; start < len; start += seg * 2)
        {
            int low = start;
            int mid = min(start + seg, len);
            int high = min(start + seg * 2, len);

            int k = low;
            int start1 = low, end1 = mid;
            int start2 = mid, end2 = high;

            while(start1 < end1 && start2 < end2)
            {
                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            }

            while(start1 < end1)//将左边剩下的元素放置到数组b中
            {
                b[k++] = a[start1++];
            }

            while(start2 < end2)//将左边剩下的元素放置到数组b中
            {
                b[k++] = a[start2++];
            }
        }

        int *temp = a;
        a = b;
        b = temp;
    }

    if (a != arr)
    {
        int i;
        for (i = 0; i < len; i++)
        {
            b[i] = a[i];
        }

        b = a;
    }

    free(b);

    return;
}

6、快速排序

基本思想:

​ 从数列中挑出一个元素,称为 “基准”(pivot);

​ 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

​ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

动图演示:

快速排序.gif

函数实现:

/** 
 * [partition 依照基准值分区]
 * @Author   HulinHuang
 * @DateTime 2021年8月31日T21:18:38+0800
 * @param    arr[]     [数组]
 * @param    start     [基准左值]
 * @param    end       [基准右值]
 * @return             [分区基准值]
 */
int partition(int arr[], int start, int end)
{
    int temp = arr[start];//以arr[start]作为基准,将被放置在数组中间 
    					//同时将start位置作为交换元素的缓冲点-类比交换两个数的第三个数
    int li = start, ri = end;//li->left index 左索引 li==start 所以初始li是第一个缓冲点
    while(li < ri)
    {
        while(li < ri && arr[ri] > temp)//找到我们右起第一个小于基准值的元素索引ri
        {
            ri--;
        }

        if(li < ri)
        {
            arr[li] = arr[ri];//将右起第一个小于基准值的元素索引放置在缓冲点(li)
          				//同时此时的ri成为新的缓冲点
            li++;
        }

        while (li < ri && arr[li] < temp)//找到我们右起第一个小于基准值的元素索引li
        {
            li++;
        }
            
        if (li < ri)
        {
            arr[ri] = arr[li];//将左起第一个大于基准值的元素索引放置在缓冲点 (ri)
          				//同时此时的li成为新的缓冲点
            ri--;
        }
        
        //结束上述操作后li和ri分别是左右已排序部分(置于两端)的后面一个和前面一个元素(不包含在其中)
        //明显若li==ri则只剩下最后一个位置
    }
    
    arr[li] = temp;
    return li;//返回的是基准值最终的索引
}

/** 
 * [quick_sort 快速排序]
 * @Author   HulinHuang
 * @DateTime 2021年8月31日T21:16:29+0800
 * @param    arr[]     [数组]
 * @param    start     [基准左值]
 * @param    end       [基准右值]
 */
void quick_sort(int arr[], int start, int end)
{
    if (start < end)
    {
        int index = partition(arr, start, end);//依照基准值分区
        quick_sort(arr, start, index - 1);//基准值之左再排序
        quick_sort(arr, index + 1, end);//基准值之右再排序
    }
}

7、堆排序

基本思想:

  1. 创建一个大顶堆( 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆 );

  2. 把堆首(最大值)和堆尾互换;

  3. 把堆的尺寸缩小 1,利用剩下的元素再次建立一个大顶堆;

  4. 重复步骤 2 3,直到堆的尺寸为 1。

    动图演示:

堆排序.gif

函数实现:

/** 
 * [swap 交换两个整数]
 * @Author   HulinHuang
 * @DateTime 2021年8月25日T21:20:44+0800
 * @param    num1      [参数1]
 * @param    num2      [参数2]
 */
void swap(int *num1, int *num2)
{
    int temp = 0;

    // 入参指针判空
    if((NULL == num1) || (NULL == num2))
    {
        perror("swap function is error, Participate is NULL!\n");

        return;
    }

    temp = *num1;
    *num1 = *num2;
    *num2 = temp;

    return;
}

/** 
 * [heap_Sort 堆排序]
 * @Author   HulinHuang
 * @DateTime 2021年9月1日T21:28:05+0800
 * @param    arr[]     [参数1]
 * @param    len       [参数2]
 */
void heap_sort(int arr[], int len) //构造一个大顶堆并将最大值换至最后一位
{
   int dad = len / 2 - 1; //最后一个父节点
   int son = 2 * dad + 1; //该父节点下的首个子节点
   while (dad >= 0)
   {
      //判断是否有两个子节点若有则在其中寻找最大子节点
      if (son + 1 <= len - 1 && arr[son] < arr[son + 1])
         son++;
      if (arr[dad] < arr[son]) //若父节点小于子节点则交换位置
         swap(&arr[dad], &arr[son]);
      dad--;             //回退到上一个父节点
      son = 2 * dad + 1; //上一个父节点的首个子节点
   }
   swap(&arr[0], &arr[len - 1]);
}

8、计数排序

基本思想:

  1. 找出待排序的数组中最大和最小的元素

  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项

  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)

  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

    动图演示:

计数排序.gif

函数实现:

/** 
 * [counting_Sort 计数排序]
 * @Author   HulinHuang
 * @DateTime 2021年9月1日T21:41:23+0800
 * @param    arr[]     [参数1]
 * @param    LEN       [参数2]
 */
void counting_sort(int arr[], int LEN)
{
   //寻找最大最小值
   int max = arr[0], min = arr[0], i, j = 0;
   for (i = 0; i < LEN; i++)
   {
      if (arr[i] < min)
         min = arr[i];
      if (arr[i] > max)
         max = arr[i];
   }

   //建立计数数组
   int new_len = max - min + 1;

   int conunting_arr[new_len];
   
   for (i = 0; i < new_len; i++) //初始化
      conunting_arr[i] = 0;
   
   for (i = 0; i < LEN; i++)	//计数
      conunting_arr[arr[i] - min]++;

   //根据计数结果进行排序
   for (i = 0; i < new_len; i++)
   {
      int index = conunting_arr[i];
      while (index != 0)
      {
         arr[j] = i + min;
         index--;
         j++;
      }
   }
}

9、桶排序

最简单的桶排序:

​ 观察数组元素范围,看出来是从0到9(可以去遍历取得最大最小值),所以我们建立10个有序桶,将数字一个个塞入对应的桶中,然后根据桶的情况进行输出(桶中有几个元素就输出几个,没有就跳过)-实际上就是最简单的计数排序,但网上有人把这个也算作桶排序了,不要搞混,下面来看真正的桶排序吧。

基本思想:

​ 桶排序(Bucket sort)或所谓的箱排序,计数排序的升级版,工作的原理是将数组分到有限数量的桶子里。 每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

​ 每个桶对应一个数据或者一个数据段(实际上当每个桶对应一个数据的时候其实就是前面的计数排序)

​ 这里我们使每个桶对应一个数据段并且对桶中元素使用冒泡排序进行排序操作
动图演示:

桶排序.gif

函数实现:

/** 
 * [get_max_val 获得未排序数组中最大的一个元素值]
 * @Author   HulinHuang
 * @DateTime 2021年9月2日T20:49:41+0800
 * @param    arr       [参数1]
 * @param    len       [参数2]
 * @return             [数组中最大的一个元素值]
 */
int get_max_val(int* arr, int len)
{
    
    int maxVal = arr[0]; //假设最大为arr[0]
    
    for(int i = 1; i < len; i++)  //遍历比较,找到大的就赋值给maxVal
    {
        if(arr[i] > maxVal)
            maxVal = arr[i];
    }
    
    return maxVal;  //返回最大值
}

/** 
 * [bucket_sort 桶排序]
 * @Author   HulinHuang
 * @DateTime 2021年9月1日T21:46:40+0800
 * @param    arr[]     [参数1]
 * @param    LEN       [参数2]
 */
void bucket_sort(int arr[], int len)
{
    int tmpArrLen = get_max_val(arr , len) + 1;
    int tmpArr[tmpArrLen];  //获得空桶大小
    int i, j;
    
    for( i = 0; i < tmpArrLen; i++)  //空桶初始化
        tmpArr[i] = 0;
    
    for(i = 0; i < len; i++)   //寻访序列,并且把项目一个一个放到对应的桶子去。
        tmpArr[ arr[i] ]++;
    
    for(i = 0, j = 0; i < tmpArrLen; i ++)
    {
        while( tmpArr[ i ] != 0) //对每个不是空的桶子进行排序。
        {
            arr[j ] = i;  //从不是空的桶子里把项目再放回原来的序列中。
            j++;
            tmpArr[i]--;
        }
    }

}

10、基数排序

基本思想:

​ 基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

动图演示:

基数排序.gif

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>

#define NUM 10

int get_digit(int M, int i);
void radix_sort(int num[], int len);

int main(int argc, char **argv)
{
    int loop = 0;
    int number[NUM] = {0};

    srand((unsigned int)time(NULL));

    // 给数组number赋值,随机数
    for(loop = 0; loop < NUM; loop++)
    {
        // 赋值1 - 99范围的数
        number[loop] = rand() % 98 + 1;
    }

    // 排序前数据的打印
    printf("排序前:\n");
    for(loop = 0; loop < NUM; loop++)
    {
        printf("number[%d]:%d\n", loop, number[loop]);
    }

    printf("\n");

    radix_sort(number, NUM);

    // 排序后数据的打印
    printf("排序后:\n");
    for(loop = 0; loop < NUM; loop++)
    {
        printf("number[%d]:%d\n", loop, number[loop]);
    }

    printf("\n");

    return 0;
}

/** 
 * [get_digit 取整数M的第i位数]
 * @Author   HulinHuang
 * @DateTime 2021年9月2日T20:54:49+0800
 * @param    M         [参数1]
 * @param    i         [参数2]
 * @return             [整数M的第i位数]
 */
int get_digit(int M, int i)
{
	while(i > 1)
	{
		M /= 10;
		i--;
	}
	return M % 10;
}

/** 
 * [radix_sort 基数排序]
 * @Author   HulinHuang
 * @DateTime 2021年9月2日T20:54:16+0800
 * @param    num[]     [参数1]
 * @param    len       [参数2]
 */
void radix_sort(int num[], int len)
{
	int i, j, k, l, digit;
	int allot[10][NUM];	//《分配数组》

	memset(allot, 0, sizeof(allot));//初始化《分配数组》

	for(i = 1; i <= NUM; i++)
	{
		int flag = 0;
		//分配相应位数的数据,并存入《分配数组》
		for(j = 0; j < len; j++)	
		{			
			digit = get_digit(num[j], i);
			k = 0;
			while(allot[digit][k])
				k++;
			allot[digit][k] = num[j];
			if(digit) //判断是否达到了最高位数
				flag = 1;
		}
		if(!flag)	//如果数组每个数的第i位都为零
			break;	//即可直接退出循环

		//将《分配数组》的数据依次收集到原数组中
		l = 0; 
		for(j = 0; j < 10; j++)	
		{	
			k = 0;
			while(allot[j][k] > 0)
			{
				num[l++] = allot[j][k];
				k++;
			}
		}
		//每次分配,收集后初始化《分配数组》,用于下一位数的分配和收集
		memset(allot, 0, sizeof(allot));
	}
}

posted @ 2022-12-16 23:11  H黑先生  阅读(271)  评论(0编辑  收藏  举报