基于冒泡排序的算法研究

简介: 基于冒泡排序的算法研究

冒泡排序:将一个无序数组排序成有序数组(升序/降序)

时间复杂度O(n²)

空间复杂度O(1)

int nums[] = { 3,5,7,1,2,8,9,4,6,10 };
首先随机初始化一个无序数组 这里统一将它调整为升序(降序同理)

int main()
{

int nums[] = { 3,5,7,1,2,8,9,4,6,10 };
int numsSize = sizeof(nums) / sizeof(nums[0]);

int i = 0;
for (i = 0; i < numsSize; i++)
{
    printf("%d ", nums[i]);
}

printf("\n");
bubble_sort(nums,numsSize);

for (i = 0; i < numsSize; i++)
{
    printf("%d ", nums[i]);
}

return 0;

}
在冒泡排序前后分别打印一次数组 ,对比冒泡排序前后数组的变化。

接下来定义冒泡排序函数主体

void bubble_sort(int* nums,int numsSize)
{

int i = 0;
for (i = 0; i < numsSize - 1; i++)//确定冒泡排序趟数
{
    //定义每一趟冒泡排序具体内容
    int j = 0;
    int flag = 1;//假设这趟冒泡排序已经有序
    for (j = 0; j < numsSize - 1 - i; j++)//两两比较次数为numsSize-1-i次
    {
        if (nums[j] > nums[j + 1])
        {
            int tmp = nums[j];
            nums[j] = nums[j + 1];
            nums[j + 1] = tmp;
            flag = 0;
        }
    }
    if (flag == 1)//如果flag == 1 说明此时数组已经有序
        break;
}

}
其实冒泡排序的核心内容就是交换两个变量的值

参考基于交换变量的值的研究_lovepotato_的博客-CSDN博客

这里来讲一讲如何减少两两比较的次数

目录

1.首先要确定冒泡排序的趟数

2.定义一趟冒泡排序的具体内容

3.减少比较次数

1.首先要确定冒泡排序的趟数
趟数最多是 (数组元素个数 - 1) 次。为什么是最多呢

这里假设一个数组是完全倒序的 把它排成升序(例如如下这个数组)

int nums[] = { 9,8,7,6,5,4,3,2,1 };
从i = 0 开始 第一趟冒泡排序的实际上就是把 9 一直移动到最后 其余元素相对顺序不变

int nums[] = { 8,7,6,5,4,3,2,1,9 };
同理 第二趟冒泡排序把 8 移动到倒数第二个位置 依次类推

排到最后会发现 最后两个元素(1和2)的顺序是一趟冒泡排序中排好的,所以次数是numsSize - 1次

这就是次数最多的情况。

2.定义一趟冒泡排序的具体内容

//定义每一趟冒泡排序具体内容
    int j = 0;
    int flag = 1;//假设这趟冒泡排序已经有序
    for (j = 0; j < numsSize - 1 - i; j++)//两两比较次数为numsSize-1-i次
    {
        if (nums[j] > nums[j + 1])
        {
            int tmp = nums[j];
            nums[j] = nums[j + 1];
            nums[j + 1] = tmp;
            flag = 0;
        }
    }
    if (flag == 1)//如果flag == 1 说明此时数组已经有序
        break;

这里有个小坑 内存for循环的循环次数是numsSize-1-i次

for (j = 0; j < numsSize - 1 - i; j++)
理由:i=0 当第一趟冒泡排序排好之后 i++变成1 ,此时最后一个元素已经定好了最终位置,只需要剩下的前8个元素进行冒泡排序即可,下标最大为numsSize -1 -i

3.减少比较次数
这里为什么要再定义一个flag变量呢?

为了减少两两比较的次数,优化算法。

在每一趟冒泡排序开始之前 初始化flag = 1 用来假设此趟冒泡排序之前数组已经有序

为什么要这样做呢?随机的数组不一定都是无序的 有可能只是其中某几个元素需要交换

例如下面这个数组

int nums[] = { 1,2,3,4,6,5,7,8,9 };
这个数组其实只需要5和6交换位置就有序了

但如果没有flag变量的话 计算机就需要从头比较到尾 每两个元素都比较一下,看看是否要交换

重复性高。

flag具体实现:

if (nums[j] > nums[j + 1])

        {
            int tmp = nums[j];
            nums[j] = nums[j + 1];
            nums[j + 1] = tmp;
            flag = 0;
        }

在if语句中定义flag的值为0,一趟冒泡排序中,但凡两个元素需要交换,就会进入这个if语句

把flag重新定义为0。

if (flag == 1)//如果flag == 1 说明此时数组已经有序

        break;

如果没有进入if语句,flag仍然为1,说明此时数组已经有序了(没有任何两个元素需要交换),然后立马break跳出 ,后面的循环就不再进行了

这就很好的减少了重复比较的问题。

整体代码实现:
c语言:

void bubble_sort(int* nums,int numsSize)
{

int i = 0;
for (i = 0; i < numsSize - 1; i++)//确定冒泡排序趟数
{
    //定义每一趟冒泡排序具体内容
    int j = 0;
    int flag = 1;//假设这趟冒泡排序已经有序
    for (j = 0; j < numsSize - 1 - i; j++)//两两比较次数为numsSize-1-i次
    {
        if (nums[j] > nums[j + 1])
        {
            int tmp = nums[j];
            nums[j] = nums[j + 1];
            nums[j + 1] = tmp;
            flag = 0;
        }
    }
    if (flag == 1)//如果flag == 1 说明此时数组已经有序
        break;
}

}
int main()
{

int nums[] = { 3,5,7,1,2,8,9,4,6,10 };
int numsSize = sizeof(nums) / sizeof(nums[0]);

int i = 0;
for (i = 0; i < numsSize; i++)
{
    printf("%d ", nums[i]);
}

printf("\n");
bubble_sort(nums,numsSize);

for (i = 0; i < numsSize; i++)
{
    printf("%d ", nums[i]);
}
return 0;

}

Java:

public class Test {

public static void bubbleSort(int[] arr){
    for (int i = 0; i < arr.length-1; i++) {
        boolean flag = true;
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if(arr[j] > arr[j+1]){
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
                flag = false;
            }
        }
        if(flag == true){
            return;
        }
    }
}
public static void main(String[] args) {
    int[] arr = {3,1,2,5,7,9,6,10};
    System.out.println(Arrays.toString(arr));
    bubbleSort(arr);
    System.out.println(Arrays.toString(arr));
}

打印的结果如下:

但是冒泡排序函数有一个局限:只能排序整型类型的数组

但有时候需要比较浮点型数组,结构体数组,冒泡排序函数就无法继续使用

能不能拓展到任意类型呢? 答案肯定是有的。

这里类比qsort函数来实现bubble_sort函数的拓展形式

void bubble_sort(void base,int sz,int width,int (cmp)(void e1,voide2))
1.第一个参数:待排序数组的首元素地址

2.第二个参数:待排序数组的元素个数

3.第三个参数:待排序数组的每个元素的大小,单位是字节

4.第四个参数:函数指针,比较两个元素所用函数的地址 - (这个函数是使用者自己实现的)

这个函数指针的两个参数:待比较的两个元素的地址

解释:

1.第一个参数void base,因为未知待排序数组的类型,使用void类型的指针可以接收任意类型的数组名。

2.第四个参数cmp,是一个函数指针,由于每一种类型的数组元素不同,所使用的比较函数也不同

bubble_sort函数主体定义:

void bubble_sort(void base,int sz,int width,int cmp(const void e1,const void* e2))
{

int i = 0;
for (i = 0; i < sz - 1; i++)
{
    int j = 0;
    int flag = 1;
    for (j = 0; j < sz - 1 - i; j++)
    {
        if (      cmp(    (char*)base+j*width,   (char*)base+(j+1)*width       )  > 0     ) // > 0说明 第一个参数大于第二个  说明需要交换
        {
            //交换
            swap(      (char*)base + j * width, (char*)base + (j + 1) * width      ,    width      );
            flag = 0;
        }
    }
    if (flag == 1)
        break;
}

}

函数的整体框架与原来的冒泡排序函数一样,唯二变的是内层for循环中的 if判断条件和交换的内容

比较函数cmp的定义是:

cmp(char e1,char e2)

若 e1 > e2 则返回 > 0的数字

 e1 = e2 则返回0

 e1 < e2 则返回 < 0的数字

这里固定将待排序数组调整为升序,所以当比较函数cmp的返回值 > 0才进行交换

注意点:

为什么这里要把base(待交换的元素地址)强制类型转化为char*呢

解决这个问题首先要了解一个概念:

指针的类型决定了+/-整数向后/向前访问的字节数

char*类型每次访问1个字节

int*类型每次访问4个字节

size*类型每次访问size大小的字节

由于未知待排序数组的类型,不同类型的地址每次访问的时候对应不同的字节数,这里将base强制类型转化为char,每次固定只能访问一个字节,charbase+j*width等价于向后访问了width个字节,也就解决了指针访问步长的问题,排序的时候只需要将width赋值为相同类型的字节数即可。

例如:排序整型数组,就把width赋值为sizeof(int),也就是4

接下来就定义swap函数:

void swap(char buf1,char buf2,int width)
{

int i = 0;
for (i = 0; i < width; i++)
{
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
}

}
buf1和buf2为待交换的两个元素的首字节地址,由于未知类型,这里需要在传一个width,以便直到具体需要交换几个字节。

这样函数的主体部分就实现完了。

接下来依次排序一下整型,浮点型,结构体类型的数组

目录

1.整型:

2.浮点型:

3.结构体类型

1.整型:
定义数组:

int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
定义比较函数:

int int_cmp(const void e1,const void e2)
{

return *(int*)e1 - *(int*)e2;//整数 可以直接 > < = 比较

}
由于比较类型是整型,整型是可以直接 > < = 比较的,

void类型的指针是无法+/-访问字节的,先把e1,e2强制类型转化为int,然后直接return相减的结果就行了。

函数传参:

bubble_sort(arr, sz, sizeof(int), int_cmp);
调试看效果:

2.浮点型:
同整型数组一样

定义数组:

float arr2[] = { 9.0,8.0,7.0,6.0,5.0 };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
比较函数:

int float_cmp(const void e1,const voide2)
{

if (*(float*)e1 - *(float*)e2 > 0)
{
    return 1;
}
else
    return 0;

}
比较函数这里有个小注意点:

比较函数cmp的参数固定为int,(float)e1 - (float)e2 为两个浮点数相减,结果还是浮点型,

直接return (float)e1 - (float)e2 会报警告,float 到 int的不兼容 可能会丢失数据,这里为了避免

警告使用if语句判断,如果差值大于0就返回1,否则返回0,这样不仅避免了警告,同时也满足差

值>0就返回>0的数字的设定。

函数传参:

bubble_sort(arr2,sz2,sizeof(float),float_cmp);
调试看结果:

3.结构体类型
同上面一样

定义结构体数组:

struct Stu
{

char name[20];
int age;

};
struct Stu s[3] = { {"zhangsan",20}, {"lisi",30}, {"wangwu",10}};
int sz3 = sizeof(s) / sizeof(s[0]);
这里分别将数组中的name和age排序:

(1)age:

比较函数:

int Stu_by_age(const void e1, const void e2)
{

return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;

}
由于是结构体指针,需要使用 -> ——结构体成员访问操作符

age也是整型,所以这里同样是return它们的差值即可

调试看效果:

最后成功按照年龄升序排序

(2)name:

比较函数:

int Stu_by_name(const void e1, const void e2)
{

return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);

}
由于name是字符串,这里需要用到字符串比较函数 strcmp

strcmp函数的返回类型与cmp刚好一致:

所以可以直接return strcmp函数的返回值

调试看效果:

整体代码实现:

void swap(char buf1,char buf2,int width)
{

int i = 0;
for (i = 0; i < width; i++)
{
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
}

}
void bubble_sort(void base,int sz,int width,int cmp(const void e1,const void* e2))
{

int i = 0;
for (i = 0; i < sz - 1; i++)
{
    int j = 0;
    int flag = 1;
    for (j = 0; j < sz - 1 - i; j++)
    {
        if (      cmp(    (char*)base+j*width,   (char*)base+(j+1)*width       )  > 0     ) // > 0说明 第一个参数大于第二个  说明需要交换
        {
            //交换
            swap(      (char*)base + j * width, (char*)base + (j + 1) * width    ,    width      );
            flag = 0;
        }
    }
    if (flag == 1)
        break;
}

}
int int_cmp(const void e1,const void e2)
{

return *(int*)e1 - *(int*)e2;//整数 可以直接 > < = 比较

}
int float_cmp(const void e1,const voide2)
{

if (*(float*)e1 - *(float*)e2 > 0)
{
    return 1;
}
else
    return 0;

}
struct Stu
{

char name[20];
int age;

};
int Stu_by_age(const void e1, const void e2)
{

return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;

}
int Stu_by_name(const void e1, const void e2)
{

return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);

}
int main()
{

int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);

float arr2[] = { 9.0,8.0,7.0,6.0,5.0 };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);

struct Stu s[3] = { {"zhangsan",20}, {"lisi",30}, {"wangwu",10}};
int sz3 = sizeof(s) / sizeof(s[0]);

bubble_sort(arr, sz, sizeof(int), int_cmp);

bubble_sort(s, sz3, sizeof(s[0]), Stu_by_age);
bubble_sort(s, sz3, sizeof(s[0]), Stu_by_name);

bubble_sort(arr2,sz2,sizeof(float),float_cmp);
return 0;

}

相关文章
|
2月前
|
搜索推荐 算法 Python
python实现冒泡排序算法
python实现冒泡排序算法
27 0
|
3月前
|
机器学习/深度学习 数据采集 搜索推荐
Paper Digest | 突破个性化推荐数据稀疏性:长尾增强的图对比学习算法研究
本文提出了一种新的长尾增强的图对比学习方法(LAGCL),该方法促使模型同时兼顾头部节点与尾部节点之间的知识,并通过长尾增强技术来使模型产出更均匀更准确的节点表征,从而改进基于 GNN 的推荐任务。
|
15天前
|
编解码 监控 算法
图像和视频处理中DSP算法的研究与发展
图像和视频处理中DSP算法的研究与发展
23 2
|
2天前
|
算法 调度
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
|
2天前
|
算法 Serverless 调度
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)
基于分布式ADMM算法的考虑碳排放交易的电力系统优化调度研究(matlab代码)
|
4天前
|
算法 搜索推荐
R语言混合SVD模型IBCF协同过滤推荐算法研究——以母婴购物平台为例
R语言混合SVD模型IBCF协同过滤推荐算法研究——以母婴购物平台为例
|
4天前
|
机器学习/深度学习 自然语言处理 算法
【大模型】关于减轻 LLM 训练数据和算法中偏差的研究
【5月更文挑战第6天】【大模型】关于减轻 LLM 训练数据和算法中偏差的研究
|
11天前
|
机器学习/深度学习 数据采集 SQL
R语言K-Means(K均值聚类)和层次聚类算法对微博用户特征数据研究
R语言K-Means(K均值聚类)和层次聚类算法对微博用户特征数据研究
|
11天前
|
搜索推荐 算法 大数据
C排序算法研究
C排序算法研究
7 2
|
13天前
|
搜索推荐 算法 Java
sort-01-bubble sort 冒泡排序算法详解
这是一个关于排序算法的系列文章摘要。作者整理了10种不同的排序算法,包括冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、计数排序、桶排序和大文件外部排序。文章详细介绍了冒泡排序的工作原理、流程,并提供了代码实现,强调了在实现中考虑的改进点,如统一接口、实用性增强和日志输出。此外,还提供了一个排序接口和工具类以方便使用,并通过测试代码和日志展示了排序过程。整个系列旨在帮助读者理解和掌握排序算法。相关代码已开源在GitHub。