冒泡排序
算法介绍
一种最简单的交换排序方法, 它通过两两比较相邻记录的关键字,如果发生逆序,则进行交换,从而使关键字小的记录如气泡一般逐渐往上“漂浮”(左移),或者使关键字大的记录如石块一样逐渐向下“坠落”(右移)。
算法例题
用随机函数生成16个2位正整数(10~99),利用 冒泡排序法 将其排序。
算法思路
书面概括
- 设待排序的元素存放在数组 r[1...m] 中。首先将第一个元素的关键字和第二个元素的关键字进行比较,若为逆序(即 L.r[1].key > L.r[2].key ),则交换两个记录。然后比较第二个元素和第三个元素的关键字。以此类推,直至第 n-1 个元素和第n个元素的关键字进行过比较为止。上述过程称作第一趟冒泡排序,其结果使得关键字最大的元素被安置到最后一个元素的位置;
- 然后进行第二趟冒泡排序,对前 n-1 个元素进行同样的操作,其结果是使关键字次大的元素被安置到第 n-1 个元素的位置上;
- 重复上述比较和交换过程,第 i 趟是从 L.r[1] 到 L.r[n-i+1] 依次比较相邻两个元素的关键字,并在“逆序”时交换相邻元素,其结果是这 n-i+1 个元素中关键字最大的元素被交换到第 n-i+1 的位置上。直到在某一趟排序过程中没有进行过交换元素的操作,说明该序列已全部达到排序要求,则完成排序。
个人概括
每趟排序通过待排序序列中元素的比较和交换位置,将待排序序列中最大的元素移到最后,将其更新为有序序列中的最小元素。
考虑情况
- 当 某趟排序(不只是第一趟) 没有移动任何一个元素,则说明该大排序序列实际上是有序序列,比较过程中设置监视哨进行监视。
- 每趟排序都会确定待排序序列中一个最大元素为有序序列中的最小元素,即每趟排序后都会少一次比较次数。
- 两个结束条件
- 某趟排序中没有移动元素;
- 比较了 n-1 趟,所有待排序元素确定为有序序列。(最后剩下一个待排序元素时直接将其更新为有序序列)
图解过程
( ) 为监视哨,[ ] 为待排序序列,{ } 为有序序列,< > 为比较中的元素," " 为交换位置元素
1.第一趟排序
引入待排序序列,监视哨置0
( 0 ) [ 49 38 65 97 76 13 27 49 ]
比较,前者元素大于后者元素
( 0 ) [<49><38> 65 97 76 13 27 49 ]
交换,监视哨置1
( 1 ) ["49""38" 65 97 76 13 27 49 ]
( 1 ) ["38""49" 65 97 76 13 27 49 ]
比较,前者元素小于等于后者元素,依次后移
( 1 ) [ 38 <49><65> 97 76 13 27 49 ]
( 1 ) [ 38 49 <65><97> 76 13 27 49 ]
( 1 ) [ 38 49 65 <97><76> 13 27 49 ]
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 <97><76> 13 27 49 ]
交换
( 1 ) [ 38 49 65 "97""76" 13 27 49 ]
( 1 ) [ 38 49 65 "76""97" 13 27 49 ]
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 76 <97><13> 27 49 ]
交换
( 1 ) [ 38 49 65 76 "97""13" 27 49 ]
( 1 ) [ 38 49 65 76 "13""97" 27 49 ]
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 76 13 <97><27> 49 ]
交换
( 1 ) [ 38 49 65 76 13 "97""27" 49 ]
( 1 ) [ 38 49 65 76 13 "27""97" 49 ]
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 76 13 27 <97><49>]
交换
( 1 ) [ 38 49 65 76 13 27 "97""49"]
( 1 ) [ 38 49 65 76 13 27 "49""97"]
待排序序列中最大元素已冒泡出来,更新为有序序列最小元素
( 1 ) [ 38 49 65 76 13 27 49 ]{ 97 }
2.第二趟排序
监视哨置0
( 0 ) [ 38 49 65 76 13 27 49 ]{ 97 }
比较,前者元素小于等于后者元素,依次后移
( 0 ) [<38><49> 65 76 13 27 49 ]{ 97 }
( 0 ) [ 38 <49><65> 76 13 27 49 ]{ 97 }
( 0 ) [ 38 49 <65><76> 13 27 49 ]{ 97 }
( 0 ) [ 38 49 65 <76><13> 27 49 ]{ 97 }
比较,前者元素大于后者元素
( 0 ) [ 38 49 65 <76><13> 27 49 ]{ 97 }
交换,监视哨置1
( 1 ) [ 38 49 65 "76""13" 27 49 ]{ 97 }
( 1 ) [ 38 49 65 "13""76" 27 49 ]{ 97 }
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 13 <76><27> 49 ]{ 97 }
交换
( 1 ) [ 38 49 65 13 "76""27" 49 ]{ 97 }
( 1 ) [ 38 49 65 13 "27""76" 49 ]{ 97 }
比较,前者元素大于后者元素
( 1 ) [ 38 49 65 13 27 <76><49>]{ 97 }
交换
( 1 ) [ 38 49 65 13 27 "76""49"]{ 97 }
( 1 ) [ 38 49 65 13 27 "49""76"]{ 97 }
待排序序列中最大元素已冒泡出来,更新为有序序列最小元素
( 1 ) [ 38 49 65 13 27 49 ]{ 76 97 }
3.第三趟排序
监视哨置0
( 0 ) [ 38 49 65 13 27 49 ]{ 76 97 }
比较,前者元素小于等于后者元素,依次后移
( 0 ) [<38><49> 65 13 27 49 ]{ 76 97 }
( 0 ) [ 38 <49><65> 13 27 49 ]{ 76 97 }
( 0 ) [ 38 49 <65><13> 27 49 ]{ 76 97 }
比较,前者元素大于后者元素
( 0 ) [ 38 49 <65><13> 27 49 ]{ 76 97 }
交换,监视哨置1
( 1 ) [ 38 49 "65""13" 27 49 ]{ 76 97 }
( 1 ) [ 38 49 "13""65" 27 49 ]{ 76 97 }
比较,前者元素大于后者元素
( 1 ) [ 38 49 13 <65><27> 49 ]{ 76 97 }
交换
( 1 ) [ 38 49 13 "65""27" 49 ]{ 76 97 }
( 1 ) [ 38 49 13 "27""65" 49 ]{ 76 97 }
比较,前者元素大于后者元素
( 1 ) [ 38 49 13 27 <65><49>]{ 76 97 }
交换
( 1 ) [ 38 49 13 27 "65""49"]{ 76 97 }
( 1 ) [ 38 49 13 27 "49""65"]{ 76 97 }
待排序序列中最大元素已冒泡出来,更新为有序序列最小元素
( 1 ) [ 38 49 13 27 49 ]{ 65 76 97 }
4.第四趟排序
监视哨置0
( ) [ 38 49 13 27 49 ]{ 65 76 97 }
比较,前者元素小于等于后者元素,依次后移
( 0 ) [<38><49> 13 27 49 ]{ 65 76 97 }
( 0 ) [ 38 <49><13> 27 49 ]{ 65 76 97 }
比较,前者元素大于后者元素
( 0 ) [ 38 <49><13> 27 49 ]{ 65 76 97 }
交换,监视哨置1
( 1 ) [ 38 "49""13" 27 49 ]{ 65 76 97 }
( 1 ) [ 38 "13""49" 27 49 ]{ 65 76 97 }
比较,前者元素大于后者元素
( 1 ) [ 38 13 <49><27> 49 ]{ 65 76 97 }
交换
( 1 ) [ 38 13 "49""27" 49 ]{ 65 76 97 }
( 1 ) [ 38 13 "27""49" 49 ]{ 65 76 97 }
到达待排序最后一个元素,且前者元素小于等于后者元素,无需交换
( 1 ) [ 38 13 27 <49><49>]{ 65 76 97 }
待排序序列中最大元素已冒泡出来,更新为有序序列最小元素
( 1 ) [ 38 13 27 49 ]{ 49 65 76 97 }
5.第五趟排序
监视哨置0
( 0 ) [ 38 13 27 49 ]{ 49 65 76 97 }
比较,前者元素大于后者元素
( 0 ) [<38><13> 27 49 ]{ 49 65 76 97 }
交换,监视哨置1
( 1 ) ["38""13" 27 49 ]{ 49 65 76 97 }
( 1 ) ["13""38" 27 49 ]{ 49 65 76 97 }
比较,前者元素大于后者元素
( 1 ) [ 13 <38><27> 49 ]{ 49 65 76 97 }
交换
( 1 ) [ 13 "38""27" 49 ]{ 49 65 76 97 }
( 1 ) [ 13 "27""38" 49 ]{ 49 65 76 97 }
到达待排序最后一个元素,且前者元素小于等于后者元素,无需交换
( 1 ) [ 13 27 <38><49>]{ 49 65 76 97 }
待排序序列中最大元素已冒泡出来,更新为有序序列最小元素
( 1 ) [ 13 27 38 ]{ 49 49 65 76 97 }
6.第六趟排序
监视哨置0
( 0 ) [ 13 27 38 ]{ 49 49 65 76 97 }
比较,前者元素小于等于后者元素,依次后移
( 0 ) [<13><27> 38 ]{ 49 49 65 76 97 }
( 0 ) [ 13 <27><38>]{ 49 49 65 76 97 }
到达待排序最后一个元素,且前者元素小于等于后者元素,无需交换,监视哨为0,则待排序序列已为有序序列
( 0 ) { 13 27 38 49 49 65 76 97 }
7.结果
{ 13 27 38 49 49 65 76 97 }
算法效率
- 最好情况:待排序序列为顺序序列
- 比较次数KCN:n-1;
- 移动次数RMN:0;
- 最坏情况:待排序序列为逆序序列
- 比较次数KCN:(n - 1) + (n - 2) + ... + 1 ≈ n²/2;
- 移动次数RMN:3((n - 1) + (n - 2) + ... + 1) ≈ 3n²/2;
所以时间复杂度为O(n²);
下面的代码是我按照自己的思路进行编写的,算法效率能达到应有的程度。
空间复杂度为O(1),移动时需要辅助空间。
算法特点
- 稳定排序;
- 链式存储结构也适合;
- 移动次数较多;
- 更适合于初始记录基本有序(正序)的情况,当序列完全无序,尤其是逆序,且元素过多,时间复杂度会大大提高。
算法代码
#include<iostream>
#include<ctime>
using namespace std;
void BubbleSort(int* array, int n)
{
//记录比较次数和移动次数
int recordMove = 0;
int recordCompare = 0;
//监视哨
//标记排序是否发生交换
//若不发生交换(为0)
//则该序列已为有序序列
int flag = 1;
while (flag == 1 && n - 1 > 0)
{
//若没有发生交换
//flag为0,则不会发生下一趟排序
flag = 0;
for (int i = 1; i < n; i++)
{
recordCompare++;
//如果前者大于后者
//进行交换
//监视哨置1
if (array[i] > array[i + 1])
{
//发生交换
//flag为1
flag = 1;
//array[0]为辅助空间
array[0] = array[i];
recordMove++;
array[i] = array[i + 1];
recordMove++;
array[i + 1] = array[0];
recordMove++;
}
}
//每趟排序后
//下一趟排序的比较次数都-1
n--;
cout << "第" << 16 - n << "趟排序:" << endl;
for (int j = 1; j <= 16; j++)
{
if (j == n + 1) cout << "[ ";
cout << array[j] << " ";
if (j == 16) cout << "]";
}
cout << endl << endl;
}
cout << "比较次数:" << recordCompare << endl;
cout << "移动次数:" << recordMove << endl << endl;
}
int main()
{
//生成随机16个正整数
int positiveInteger[17];
time_t t;
srand((unsigned)time(&t));
cout << "生成16个2位正整数:" << endl;
for (int i = 1; i <= 16; i++)
{
positiveInteger[i] = (rand() % (100 - 10)) + 10;
cout << positiveInteger[i] << " ";
}
cout << endl << endl;
//冒泡排序
BubbleSort(positiveInteger, 16);
cout << "排序后数组:" << endl;
for (int i = 1; i <= 16; i++)
{
cout << positiveInteger[i] << " ";
}
cout << endl << endl;
system("pause");
return 0;
}