C语言实现归并排序、计数排序、桶排序、基数排序

排序与查找算法目录

【C语言实现交换排序】交换排序(冒泡排序、快速排序)

【C语言实现插入排序】插入排序(直接、折半、二路、希尔)

【三大经典查找】三大经典查找(跳跃、插值、斐波那契)

【三大基础查找】三大基础查找(顺序、二分、分块)


目录

排序与查找算法目录

一、归并排序

核心思想

核心思想

执行步骤

优化 

三、桶排序

核心思想 

四、基数排序


前言

查找和排序是数据结构与算法中不可或缺的一环,是前辈们在算法道路上留下的重要且方便的一些技巧,学习这些经典的查找和排序也能让我们更好和更快的解决问题。在这个专栏中我们会学习六大查找和十大排序的算法与思想,而本篇将详细讲解其中的归并排序、计数排序、桶排序、基数排序;

注意:本文中所有排序按照升序排序,降序只需要把逻辑反过来即可!


一、归并排序

核心思想

归并排序在各种考试中考查形式多种多样,但是万变不离其宗!归并排序的核心思想就是分治,并且说道分治我们就应该想起递归,”归并“中的“归”指的就是递归中的归过程;归并排序在递归中的递过程中将每个数据分别处理,而在归过程中把数据合“并”处理,这就是所谓归并;

有一个数组 { 5, 1, 4, 2, 8, 4 }

 第一步:设置两个指针 left 和 right 分别指向数组的头和尾,计算中间指针 mid = (left + right)/2,将数组分为两个区间【left, mid】和【mid + 1,right】;

 第二步:针对于分出来的子数组,按照第一步的步骤继续划分;

继续拆分如下所示: 

第三步:直到不能拆了(子数组长度为1),就两两合并:比如对arr[0] 和 arr[1] 进行和并,在合并的时候比较大小并交换;

从逻辑的角度来看归并排序其实并不难,而通过这个过程我们可以得到它的时间复杂度应该是:

  • 子数组排序的时间 + 子数组合并的时间;

 在合并的时候我们需要不断开辟数组去存储元素,所以空间复杂度是O(n),是一个典型的以空间换时间的例子;

并且我们在合并的时候是两两合并并且合并后的子数组已经有序,那么其实可以得到归并排序是一个稳定的排序算法

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

//归并排序
void merge(int a[], int temp[], int low, int mid, int high)
{
    //定义指针
    int left_low = low;
    int left_high = mid;
    int right_low = mid + 1;
    int right_high = high;
    int Tpos = low;
    //比较两个指针所指向的元素
    while (left_low <= left_high && right_low <= right_high)
    {
        if (a[left_low] <= a[right_low])
        {
            temp[Tpos++] = a[left_low++];
        }
        else
        {
            temp[Tpos++] = a[right_low++];
        }
    }
    //要么全部跑完了 要么左边有剩余 要么右边有剩余 把剩下的一个放进temp中
    //第一个序列还有剩余
    while (left_low <= left_high)
    {
        temp[Tpos++] = a[left_low++];
    }
    //后一个序列还有剩余
    while (right_low <= right_high)
    {
        temp[Tpos++] = a[right_low++];
    }
    //把新数组 归 还到原来的数组中
    for (int i = low; i <= high; i++)
    {
        a[i] = temp[i];
    }
    return;
}

void Msort(int a[], int temp[], int first, int last)
{
    int mid = 0;
    if (first < last)
    {
        mid = (first + last) / 2;
        //分下标去拆分
        Msort(a, temp, first, mid);
        Msort(a, temp, mid + 1, last);
        //对分开之后的元素归并排序
        merge(a, temp, first, mid, last);
    }
}

void mergeSort(int a[], int n)
{
    int* temp = (int*)malloc(sizeof(int) * n);//申请空间
    if (temp != NULL)
    {
        Msort(a, temp, 0, n - 1);
        free(temp);
    }
    else
    {
        printf("ERROR!MALLOC FAIL\n");
    }
}

int main()
{
    int arr[] = { 5, 1, 4, 2, 8, 4 };
    int length = sizeof(arr) / sizeof(arr[0]);
    mergeSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    system("pause");
    return 0;
}

运行结果

 


 二、计数排序  

核心思想

计数排序是针对于特定范围之间的整数排序,它的思路是:统计数组中不同元素的数量;计数排序在某些特定情况下甚至比快排还要快;

执行步骤

第一步:遍历数组并记录相对应的数字出现的次数;

第二步:按照记录的元素的次数输出即可;

优化 

很明显计数排序是一个不稳定的排序算法, 优化的方法也很简单,还是以空间换时间的思想并且加上数学方法,用一个变量Location计算元素应该出现的正确位置(遇事不决加变量( ̄▽ ̄)~*);

第一步:令count[i] = count[i] + count[i - 1],得到新的count数组;

第二步:从后往前遍历计算location[i]= count[arr[i]] - 1],并在count中将访问的元素减一;

得到的Location数组就是元素应该出现的正确位置; 

计数排序的思想和代码可以说是最简单最简单的排序了,如果输入的数据不大于待排序数组的长度,那么效率会很高甚至快于快排,但是不能用于小数,却适用于负数


三、桶排序

核心思想 

桶排序也是一个十分经典的排序算法,现在的应用不太广泛,但重要的是我们需要学习的是它的思想

  • 有一组数据分布均匀,跨度不大;
  • 还有若干个“桶”;
  • 每个桶有容量,可以存储有多个元素;
  • 从整体看,我们希望每一个桶都非常均匀;

而我们要做的:就是要遍历数组,将数据放到桶中,然后桶内元素进行排序;

有一个数组{ 49, 26, 53, 47, 89, 31, 72, 11, 33 }

第一步:我们可以根据数据的特点分出8个桶,每一个桶都能存储10个元素; 

第二步:遍历数组,把每一个元素放到符合特点的桶中去;

第三步:每一个桶内排序,遍历桶得到有序数列;

代码实现也很很很简单,只需要把这三步转化成计算机语言就行了;

源码,这里我选择用链表的方式实现(方法不唯一,能实现就行

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

typedef struct node
{
    int key;//用来记录桶中元素的个数
    struct node* pnext;
}Bnode;

void bucketSort(int a[], int n, int bucketSize)
{
    //初始化桶
    Bnode** bucket = (Bnode**)malloc(sizeof(Bnode*) * bucketSize);
    for (int i = 0; i < bucketSize; i++)
    {
        bucket[i] = (Bnode*)malloc(sizeof(Bnode));
        bucket[i]->key = 0;
        bucket[i]->pnext = NULL;
    }

    for (int j = 0; j < n; j++)
    {
        //把数组中的每一个元素存入节点中 在满足条件时链入桶中
        Bnode* node = (Bnode*)malloc(sizeof(Bnode));
        node->key = a[j];
        node->pnext = NULL;
        //计算该节点应该被放入的桶的下标
        int idx = a[j] / 10;
        //初始化p为桶中数据的头指针
        Bnode* p = bucket[idx];
        //如果桶是空的
        if (p->key == 0)
        {
            bucket[idx]->pnext = node;
            bucket[idx]->key++;
        }
        //当桶不为空时
        else
        {
            while (p->pnext != NULL && p->pnext->key <= node->key)
            {
                p = p->pnext;
            }
            node->pnext = p->pnext;
            p->pnext = node;
            bucket[idx]->key++;
        }
    }
    //打印结果
    for (int i = 0; i < bucketSize; i++)
    {
        for (Bnode* k = bucket[i]->pnext; k != NULL; k = k->pnext)
        {
            printf("%d ", k->key);
        }
    }
}

int main()
{
    int arr[] = { 49, 26, 53, 47, 89, 31, 72, 11, 33 };
    int length = sizeof(arr) / sizeof(arr[0]);
    bucketSort(arr, length, 10);
    system("pause");
    return 0;
}

 运行结果


四、基数排序

基数排序是一个特殊的排序算法,它能排序一些特定的场景;

 比方说如下一个数组:

这个数组数据之间的跨度很大,如果用计数排序那么是不是要开辟一个【802 - 2 + 1】的空间?所以肯定不能用计数排序了,而基数排序就是为了解决这类问题诞生的;

基数就是指位数——个、十、百、千,基数排序就从元素的最低有效位到最高有效位逐渐比较的过程

 由此我们可以得到:从低位到高位的数据都可以使用基数排序;

它的时间复杂度计算是一个数学问题: O((b+n) * d)

其中n用来表示数组长度,b表示数的进制,k表示计算机可以表示的最大整数,d = \log_b k

猜你喜欢

转载自blog.csdn.net/ZER00000001/article/details/125645083