实现一个通用的冒泡排序 - C语言

159 阅读3分钟

一、前言

在对一组数组进行排序是,我们经常用到冒泡排序,它是一种较简单的排序算法。
冒泡排序的原理如下:

  1. 比较相邻的两个元素。如果第一个比第二个大,就交换它们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序.png

根据上图可以发现:n个元素的比较,需要n-1趟;每一趟比较的次数n-趟数。
通常我们会针对某一种类型实现一个冒泡排序,示例:

#include <stdio.h>

void Bubble_Sort_Int(int* arr, int len)
{
    for (int i = len - 1; i > 0; i--)
    {
        for (int j = 0; j < i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

int main()
{
    int arr[] = {4,5,2,6,9,2,0};
    int len = sizeof(arr)/sizeof(*arr);
    Bubble_Sort_Int(arr, );
    return 0;
}

但是上面的代码存在一个问题,就是只能排序整形数据。如果我们想把一个浮点型、字符串等其他类型也进行排序,显然这套代码是不适用的。那有没有一种能够排序任何数据的算法呢?

二、设计思路

在此之前,我们先看C语言的一个库函数qsort():

void qsort(void* ptr, size_t count, size_t size, int (*comp)(const void*, const void*)); 

/*
作用:把一组数据以快速排序的方式升序排列。
参数: 
    1. ptr - 指向待排序的数组的指针。 
    2. count - 数组的元素数目。 
    3. size - 数组每个元素的字节大小。 
    4. comp - 比较函数。若首个参数小于第二个,则返回负整数值,若首个参数大于第二个,则返回正整数值,
    若两参数等价,则返回零。该函数需要使用者自己实现。
*/

代码示例:

int cmp_int(const void* p1, const void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
int main()
{
    int arr[] = { 4,5,2,6 };
    int len = sizeof(arr) / sizeof(*arr);
    qsort(arr, len, sizeof(*arr), cmp_int);
    return 0;
}

那么我们也可以模拟实现一个qsort函数,但是采用冒泡排序的方式实现。

三、代码实现

#include <stdio.h>
#include <string.h>

//交换两个元素 - p1和p2是两个元素的起始地址,size是每个元素的大小
void Swap(char* p1, char* p2, size_t size)
{
    //将每一个字节进行交换
    for (int i = 0; i < size; i++)
    {
        char tmp = *(p1 + i);
        *(p1 + i) = *(p2 + i);
        *(p2 + i) = tmp;
    }
}

//通用的冒泡排序算法(升序)
void Bubble_Qsort(void* ptr, size_t count, size_t size, int (*comp)(const void*, const void*))
{
    //交换的趟数
    for (int i = 0; i < count - 1; i++)
    {
        //每一趟每个元素的交换
        for (int j = 0; j < count - 1 - i; j++)
        {
            /*
              为什么要把ptr转换成char*类型?
              因为我们并不知道这是一个什么类型的数据,也就不知道一次能访问多少个字节,
              如果我们把它转换成char*类型,再解引用,就可以操作一个字节。
            */
            if (comp((char*)ptr + j*size, (char*)ptr + (j+1)*size) > 0)
            {
                Swap((char*)ptr + j * size, (char*)ptr + (j + 1) * size, size);
            }
        }
    }
}
int cmp_int(const void* p1, const void* p2)
{
    return *(int*)p1 - *(int*)p2;//如果它们的差大于零,说明p1大于p2
}
int cmp_str(const void* p1, const void* p2)
{
    return strcmp((char*)p1, (char*)p2);
}
int main()
{
    //定义两个需要排序的数组
    int arr[] = { 4,5,2,6,9,2,0 };
    char str[] = "dcbaaaaa";
    //计算arr数组的元素个数
    int len = sizeof(arr) / sizeof(*arr);
    //调用排序函数
    Bubble_Qsort(str, strlen(str), sizeof(*str), cmp_str);
    Bubble_Qsort(arr, len, sizeof * arr, cmp_int);
    //打印排序后的元素
    printf("%s\n", str);
    for (int i = 0; i < len; i++)
    {
        if (arr[i] != 0)
        {
            printf("%d ", arr[i]);
        }
    }
    return 0;
}

输出结果:

image.png

ps:如有问题或想法欢迎在讨论区讨论。