冒泡排序(BubbleSort)

目标

将一组数,按从小到大进行排序

如:
输入:3,4,1,43,67,65,5;
输出:1,3,4,5,43,65,67;

分析

第一步,找到数组中最小值项,然后将此项放到最左侧;

第二步,找到数组中的次最小值项,然后将些项放在倒数第二侧;

...

由上面的步骤,最大的数会慢慢地移动到最右侧,这个过程就是像是大大小小的泡泡依次排序一样,越大的泡泡,越排在最右侧。这个方法也被称为冒泡排序
并可以得出以下信息
第一步的操作,可以看成这个数组中的最小值一直移到最左侧,第二步的操作,是除了已知的最小值的数组,重新找到最小数移到最左侧。
假如原数组元素个为N,则要执行的N-1步才能完成操作。

第一步的操作如何实现呢?从数组尾部开始两两比较大小,将小的值放到左侧
在此可以判断需要两个操作
比较大小操作

int cmp (int *a, int *b);

数值交换操作

void swap(int *a, int *b);

这样的比较操作,在这一步中需要多少次呢?
剩余未排序元素个数 unsort_meb

cmp_count = unsort_meb - 1;

unsort_meb初始值为一开始的元素个数nmeb

unsort_meb = nmeb;

至此,上面的操作可以表述为

unsort_meb = nmeb;
cmp_count = unsort_meb - 1;
int index = nmeb -1;	//获得数据最右侧的下标,开始从最右侧开始比较
for (int i = 0; i < cmp_count; i ++)
{
	if(cmp(&data[index - 1], &data[index]) > 0)
		swap(&data[index - 1], &data[index]);
	index --;
}

unsort_meb --;

第二步,也是从最右侧开始交换,由于完成了第一步,所以unsort_meb-1, 次数比上一次少了一次。

cmp_count = unsort_meb - 1;
int index = nmeb -1;	//获得数据最右侧的下标,开始从最右侧开始比较
for (int i = 0; i < cmp_count; i ++)
{
	if(cmp(&data[index - 1], &data[index]) > 0)
		swap(&data[index - 1], &data[index]);
	index --;
}
unsort_meb --;

...
到了最后,unsort_meb = 1,就完成了排序

由此可以得到以下代码:

void bubble_sort(int data[], const int nmeb)
{
	for (int unsort_meb = nmeb; unsort_meb > 1; unsort_meb --)
	{
		int index = nmeb -1;
		for (int cmp_count = unsort_meb - 1; cmp_count > 0; cmp_count --)
		{
			if(cmp(&data[index - 1], &data[index]) > 0)
				swap(&data[index - 1], &data[index]);
			index --;
		}
	}
}

本例的整体代码如下:

#include <stdio.h>

int cmp(const int *a, const int *b)
{
    return (*a - *b);
}

void swap(int *a, int *b)
{
    int dummy;

    dummy = *b;
    *b = *a;
    *a = dummy;
}

//从后向前排
void bubble_sort(int data[], const int nmeb)
{
    for (int unsort_count = nmeb; unsort_count > 1; unsort_count--)
    {
        int index = nmeb - 1; //比较时的初始索引
        for (int cmp_count = unsort_count - 1; cmp_count > 0; cmp_count--)
        {
            if (cmp(&data[index - 1], &data[index]) > 0)
            {
                swap(&data[index - 1], &data[index]);
            }
            index--;
        }
    }
}

void show_array(int data[], int nmeb)
{
    for (size_t i = 0; i < nmeb; i++)
    {
        printf("%d ", data[i]);
    }
    printf("\n");
}

int main(void)
{
    int test[] = {3, 4, 1, 43, 67, 65, 5};

    show_array(test, sizeof(test) / sizeof(test[0]));
    bubble_sort(test, sizeof(test) / sizeof(test[0]));
    show_array(test, sizeof(test) / sizeof(test[0]));

    return 0;
}

功能扩展

目标

要实现一个用于任意数据类型的冒泡排序函数并做简单测试,其要求是同一个函数既可以从小到大排列,也可以从大到小排序,且同时支持多种数据类型

分析

为了实现上述功能,要对bubble_sort函数加以改造。
由于bubble_sort是对数组进行的操作,所以需要数组起始地址,以及数组的元素个数
由于要传入数组的数据类型未知,故采用通用指针

bubble_sort(void *base, size_t nmeb);

由于数组的类型是未知的,那个数组中单个元素的长度也是未知的,所以需要知道数组中的单个元素的大小

bubble_sort(void *base, size_t nmeb, size_t meb_size);

其中size_t是C标准库中预定义的类型,专门用于保存变量的大小。

为了实现了多种排序方式,会用到不同的cmp函数,为了通用性,在参数中增加cmp函数指针参数

bubble_sort(void *base, size_t nmeb, size_t meb_size, int (*cmp)(const void *a, const void b));

由于排序是对数据的操作,因此bubble_sort()没有返回值,其类型为void

//交换两块内存空间值
void swap (void *a, void *b, size_t size)
{
	unsigned char temp;
	
	while(size)
	{
		temp = *a;
		*b = *a;
		*a = temp;
		a ++;b++;
		size --;
	}
}

void bubble_sort(void *base, size_t nmeb, size_t meb_size, int (*cmp)(cosnt void *a, const void *b))
{
	for (size_t unsort_count = nmeb; unsort_count > 1; unsort_count --)
	{
		size_t index = nmeb - 1;
		for(int cmp_count = unsort_meb -1; cmp_count > 0; cmp_count --)
		{
			void *p_right = data + index * meb_size;
			void *p_left = data + (index - 1) * meb_size;
			if(cmp(p_left, p_right) > 0)
				swap(p_left, p_right, meb_size);
			index --;
		}
	}
}

具体代码实现

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

void swap(void *a, void *b, size_t size)
{
    unsigned char temp;
    unsigned char *p1 = a;
    unsigned char *p2 = b;

    while (size)
    {
        temp = *p1;
        *p1 = *p2;
        *p2 = temp;
        p1++;
        p2++;
        size--;
    }
}

void bubble_sort(void *base, size_t nmeb, size_t meb_size, int (*cmp)(const void *, const void *))
{
    for (size_t unsort_meb = nmeb; unsort_meb > 1; unsort_meb--)
    {
        int index = nmeb - 1;
        for (size_t cmp_count = unsort_meb - 1; cmp_count > 0; cmp_count--)
        {
            unsigned char *p_left = (unsigned char *)base + (index - 1) * meb_size;
            unsigned char *p_right = (unsigned char *)base + (index)*meb_size;
            if (cmp(p_left, p_right) > 0)
            {
                swap(p_left, p_right, meb_size);
            }
            index--;
        }
    }
}

//降序比较
int cmp_int(const void *a, const void *b)
{
    return (*(int *)a - *(int *)b);
}
//升序比较
int cmp_int_r(const void *a, const void *b)
{
    return (*(int *)b - *(int *)a);
}
//字符串比较
int cmp_str(const void *a, const void *b)
{
    return strcmp(*(char **)a, *(char **)b);
}

int main(void)
{
    int array_int[] = {39, 33, 18, 64, 73, 30, 49, 81, 51};
    int num_array = sizeof(array_int) / sizeof(array_int[0]);
    for (size_t i = 0; i < num_array; i++)
    {
        printf("%d ", array_int[i]);
    }
    printf("\n");
    bubble_sort(array_int, num_array, sizeof(array_int[0]), cmp_int);
    for (size_t i = 0; i < num_array; i++)
    {
        printf("%d ", array_int[i]);
    }
    printf("\n");

    bubble_sort(array_int, num_array, sizeof(array_int[0]), cmp_int_r);
    for (size_t i = 0; i < num_array; i++)
    {
        printf("%d ", array_int[i]);
    }
    printf("\n");

    char *array_str[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Tursday", "Friday", "Saturdy"};
    num_array = sizeof(array_str) / sizeof(array_str[0]);
    for (size_t i = 0; i < num_array; i++)
    {
        printf("%s ", array_str[i]);
    }
    printf("\n");

    bubble_sort(array_str, num_array, sizeof(array_str[0]), cmp_str);
    for (size_t i = 0; i < num_array; i++)
    {
        printf("%s ", array_str[i]);
    }
    printf("\n");

    return 0;
}

对上面算法的进一步优化

上面的排序方法,对固定数组长度N,会必然比较(N-1)*(N-1)次,但是实际情况,有的数组只需要调整一次数据,就完成了比较,根本不需要比较那多次。也就是说
如果某一次比较遍历时,没有发现有进行交换的数据,就说明这个数据已经排序完成了,不需要下面的步骤了。
故改进算法如下

void bubble_sort(void *base, size_t nmeb, size_t meb_size, int (*cmp)(const void *, const void *))
{
	for (size_t unsort_count = nmeb; unsort_cmount > 1; unsort_count --)
	{
		int swap_flag = 0;
		int index = nmeb - 1;
		for (size_t cmp_count = unsort_count - 1; cmp_count > 0; cmp_count --)
		{
			void *left = base + (index - 1)*meb_size;
			void *right = base + index * meb_size;
			if(cmp(left,right) > 0)
			{
				swap(left, right);
				swap_flag = 1;
			}
		}
		
		if(0 == swap_flag)
		{
			break;
		}
	}
}

对冒泡排序的评价

在数据不大时,所有排序算性能差别不大。因为高级算法只有在元素个数多于1000时,性能才出现显著提长。
其实90%以上的情况下,我们存储的元素个数只有几十到几百个,冒泡排序可能是更好的选择。

更好的选择--使用C库

NAME
       qsort, qsort_r - sort an array

SYNOPSIS
       #include <stdlib.h>

       void qsort(void *base, size_t nmemb, size_t size,
                  int (*compar)(const void *, const void *));

       void qsort_r(void *base, size_t nmemb, size_t size,
                  int (*compar)(const void *, const void *, void *),
                  void *arg);
posted @ 2021-06-15 19:58  海林的菜园子  阅读(469)  评论(0)    收藏  举报