引言:
创造排序算法的先驱们真的是让人佩服,设计的太巧妙了。今天来讲一下冒泡排序,既是加深理解,也是一种分享。
思想:
冒泡排序是这样的,假如我当前有一个数组[x1,x2,x3,...,xn],这n个数是无序的,那么我要对该数组进行排序的话,假如要将该数组进行升序,我每一次都能从前往后(或从后往前)确定一个元素xi的具体位置,排在元素xi之前的元素都要比它小,它之后的元素都比它大,具体是先确定正数第一个位置还是倒数第一个位置,改变循环条件即可。这样讲可能有点抽象,那我们举个例子,逐步讲解冒泡排序的思想:
声明一个包含有8个元素的数组a[7],数组下标从0开始,所以这里为7时代表的第八个元素:
2 | 9 | 3 | 4 | 6 | 8 | 7 | 5 |
目标:
对该数组a[8]进行升序排序,即小的排在前面,大的排在后面。
解决思路:
先假设a[i]为我当前这一轮得出来的最小元素值的存储位置,a[j]和a[j-1]为当前两个元素作比较。
比如我当前开始遍历第一轮,确定一个最小值,并把它存在数组的第一个位置,i=0,即a[0]这个位置。
如何确定最小值?我从该数组的最后一个位置开始往前遍历作两两比较,遍历到当前存储最小值位置的下一个位置,第一轮比较具体到我的数组则是,a[7]与a[6]比较一次,a[6]与a[5]比较一次,a[5]与a[4]比较一次,...,a[1]与a[0]比较一次,在进行a[1]与a[0]比较的时候,如果a[1]小于a[0],则交换他们两个位置,否则不交换,此时a[0]存储的就是这一轮两两比较中的最小值;即我把j=7遍历到j=1;
因为a[0]已经存储了当前的最小值,所以下一轮的最小值应该存储在a[1]中,即i=1;下一轮开始:我依然从该数组的最后一个位置开始往前遍历作两两比较,只不过这一轮我只从后往前遍历到a[2],也就是a[7]与a[6]比较一次,a[6]与a[5]比较一次,a[5]与a[4]比较一次,...,a[2]与a[1]比较一次,不需要再进行a[1]与a[0]比较,因为a[0]已经存储了上一轮整个数组的最小值,现在的a[1]存储第二小值。
下一轮。。。,对于我声明的数组,循环遍历直到i=6即可,也是就数组长度减1的地方为最后一轮,因为当i=[6]的时候,我后面只剩一个元素i=7,这两个元素比较一次就可以确定这两个元素的最终位置了。
把思路演示出来看一下,记着目标是升序数组:
元素组a[7]:
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 6 | 8 | 7 | 5 |
(1)、当i=0,j=7时,a[7]=5,a[6]=7,a[7]<a[6],交换位置,a[6]=5,a[7]=7。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 6 | 8 | 5 | 7 |
(2)、当i=0,j=6时,a[6]=5,a[5]=8,a[6]<a[5],交换位置,a[6]=8,a[5]=5。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 6 | 5 | 8 | 7 |
(3)、当i=0,j=5时,a[5]=5,a[4]=6,a[5]<a[4],交换位置,a[5]=6,a[4]=5。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 5 | 6 | 8 | 7 |
(4)、当i=0,j=4时,a[4]=5,a[3]=4,a[4]>a[3],不交换位置。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 5 | 6 | 8 | 7 |
(5)、当i=0,j=3时,a[3]=4,a[2]=3,a[3]>a[2],不交换位置。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 9 | 3 | 4 | 5 | 6 | 8 | 7 |
(6)、当i=0,j=2时,a[2]=3,a[1]=9,a[2]<a[1],交换位置,a[2]=9,a[1]=3。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 3 | 9 | 4 | 5 | 6 | 8 | 7 |
(7)、当i=0,j=1时,a[1]=3,a[0]=2,a[2]>a[1],不交换位置。
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] |
2 | 3 | 9 | 4 | 5 | 6 | 8 | 7 |
以上就是一轮完整的遍历。接下来就是(i=1,j=7,6,5,4,3,2),(i=2,j=7,6,5,4,3),(i=3,j=7,6,5,4),(i=4,j=7,6,5),(i=5,j=7,6),(i=6,j=7),大家可以手推导加深理解。
第二部分:用数据结构来实现冒泡排序:
声明一个结构体,结构体中声明一个数组,再声明一个当前数数组长度,其中数组最大存储空间可以用关键字define来定义一个常量。
#include <iostream>
#define MaxSize 10
using namespace std;
typedef struct
{
int data[MaxSize];
int length;
}Sqlist;
由于在遍历数组的过程中需要进行两个数组的交换,所以可以定义一个函数,用于实现数组中的两个数的交换。
void swap(Sqlist *L, int j)
{
int temp = L->data[j];
L->data[j] = L->data[j-1];
L->data[j-1] = temp;
}
将排序过程单独定义成一个函数,这个函数是实现排序的核心。
void maopao(Sqlist *L)
{
// i是从前往后遍历,遍历到倒数第二个位置
for(int i=0;i<L->length-1;i++)
{
// flag用于标志,当前一轮如果没有进行位置交换,说明已经排好序,不需要继续遍历比较,结束函数
bool flag = false;
// j是从后往前遍历,从最后一个元素开始,比较j和j-1元素的大小,每一轮都遍历到i+1这个位置,因为i之前说的元素都已经有序
for(int j=L->length-1;j>i;j--)
{
if(L->data[j] < L->data[j-1])
{
// 交换两个元素的位置,调用上面声明的swap函数
swap(L, j);
flag=true;
}
}
if(flag==false)
{
return;
}
}
return;
}
主函数用于定义结构体数组中的元素以及长度,然后调用排序函数。
int main()
{
Sqlist *L;
L->length=8;
L->data[0] = 2;
L->data[1] = 9;
L->data[2] = 3;
L->data[3] = 4;
L->data[4] = 6;
L->data[5] = 8;
L->data[6] = 7;
L->data[7] = 5;
maopao(L);
for(int i = 0; i<L->length; i++)
{
cout<<"L->data["<<i<<"]="<<L->data[i]<<endl;
}
return 0;
}
运行结果如图所示:
完整代码如下:
#include <iostream>
#define MaxSize 10
using namespace std;
typedef struct
{
int data[MaxSize];
int length;
}Sqlist;
void swap(Sqlist *L, int j)
{
int temp = L->data[j];
L->data[j] = L->data[j-1];
L->data[j-1] = temp;
}
void swap1(Sqlist *L, int i, int j)
{
int temp;
temp = L->data[i];
L->data[i] = L->data[j];
L->data[j] = temp;
}
// 冒泡排序
void maopao(Sqlist *L)
{
for(int i=0;i<L->length-1;i++)
{
bool flag = false;
for(int j=L->length-1;j>i;j--)
{
if(L->data[j] < L->data[j-1])
{
swap(L, j);
flag=true;
}
}
if(flag==false)
{
return;
}
}
return;
}
int main()
{
Sqlist *L;
L->length=8;
L->data[0] = 2;
L->data[1] = 9;
L->data[2] = 3;
L->data[3] = 4;
L->data[4] = 6;
L->data[5] = 8;
L->data[6] = 7;
L->data[7] = 5;
maopao(L);
for(int i = 0; i<L->length; i++)
{
cout<<"L->data["<<i<<"]="<<L->data[i]<<endl;
}
return 0;
}
总结:冒泡排序是一个比较基础的排序,其运行时间复杂度最好为数组有序的情况,比较n-1次即可,最坏为数组反序的情况,比较n(n-1)/2,即时间复杂度为n的平方。
书写不易,谢谢点赞加收藏。