文章目录
- 0.概述
- 1 起泡排序(基础版)
- 1.1 算法分析
- 1.2 算法实现
- 1.3 重复元素与稳定性
- 1.4 复杂度分析
- 2 起泡排序(改进版)
- 2.1 目标
- 2.2 改进思路
- 2.3 实现
- 2.4 复杂度分析
- 3 起泡排序(改进版2)
- 3.1 目标
- 3.1 改进思路
- 3.2 代码实现
- 4 起泡排序(改进版3)
- 4.1 目标
- 4.2 改进思路
- 4.3 实现
0.概述
介绍起泡排序算法的优化思路与实现。
1 起泡排序(基础版)
1.1 算法分析
1.2 算法实现
算法思想:反复调用单趟扫描交换算法,直至逆序现象完全消除。
template <typename T> //向量的起泡排序
void Vector<T>::bubbleSort ( Rank lo, Rank hi ) //assert: 0 <= lo < hi <= size
{ while ( !bubble( lo, hi-- ) ); } //逐趟做扫描交换,直至全序
算法思想:依次比较各对相邻元素,每当发现逆序即令二者彼此交换;一旦经过某趟扫描之后未发现任何逆序的相邻元素,即意味着排序任务已经完成,则通过返回标志“sorted”,以便主算法及时终止。
template <typename T>
void Vector<T>::bubble ( Rank lo, Rank hi) { //0 <= nbool sorted = true; //整体排序标志while ( ++lo < hi ) { //自左向右,逐一检查各队相邻元素if ( _elem[lo - 1] > _elem[lo] ) { //若逆序,则sorted = false; //因整体排序不能保证,需要清除排序标志swap ( _elem[lo - 1], _elem[lo]); //交换}}return sorted;
} //借助布尔型标志位sorted,可及时提前退出,而不致总是蛮力地做n - 1趟扫描交换
1.3 重复元素与稳定性
稳定算法的特征是,重复元素之间的相对次序在排序前后保持一致。
该起泡排序过程中元素相对位置有所调整的唯一可能是,某元素_elem[i - 1]严格大于其后继_elem[i]。也就是说,在这种亦步亦趋的交换过程中,重复元素虽可能相互靠拢,但绝对不会相互跨越。由此可知,起泡排序属于稳定算法。
1.4 复杂度分析
如图,前r个元素无序,后n-r元素按顺序排列并严格就位。
bubblesort1A()算法由内、外两层循环组成。内循环从前向后,依次比较各对相邻元素,如有必要则将其交换。
扫描交换的趟数不会超过O( r ),算法总体消耗时间不会超过O(n *r)次。
故乱序元素仅限于 A[0, n \sqrt n n)区间,最坏情况下仍需调用 bubblesort ()做 Ω \Omega Ω( n \sqrt n n)次调用,共做 Ω \Omega Ω(n)次交换操作和 Ω \Omega Ω(n 3 2 ^{\frac 32} 23)次比较操作,因此累计运行 Ω \Omega Ω(n 3 2 ^{\frac 32} 23)时间。
2 起泡排序(改进版)
2.1 目标
乱序元素仅限于 A[0, n \sqrt n n)区间,仅需 O(n)时间。
2.2 改进思路
基础版问题:
所多余出来的时间消耗,无非是在后缀中对已就位元素的反复扫描交换,这些元素都是不必扫描交换的,可惜,基础版算法无法将其分解出来。
改进思路:
通过方法记录在上一趟扫描交换过程中所进行的最后一次交换,便可确定在上一趟扫描的区间中有一个多长的后缀实际上没有做过任何交换。如何可以,直接将hi指向新的位置。
2.3 实现
template <typename T> //向量的起泡排序
void Vector<T>::bubbleSort ( Rank lo, Rank hi ) //assert: 0 <= lo < hi <= size
{ while ( lo < ( hi = bubble ( lo, hi ) ) ); } //逐趟做扫描交换,直至全序
template <typename T>
Rank Vector<T>::bubble ( Rank lo, Rank hi ) { //一趟扫描交换Rank last = lo; //最右侧的逆序对初始化为[lo - 1, lo]while ( ++lo < hi ) //自左向右,逐一检查各对相邻元素if ( _elem[lo - 1] > _elem[lo] ) { //若逆序,则last = lo; //更新最右侧逆序对位置记录,并swap ( _elem[lo - 1], _elem[lo] ); //通过交换使局部有序}return last; //返回最右侧的逆序对位置
}
2.4 复杂度分析
这里将逻辑型标志sorted改为秩last,以记录各趟扫描交换所遇到的最后(最右)逆序元素。如此,在乱序元素仅限于A[0, n)区间时,仅需一趟扫描交换,即可将问题范围缩减至这一区间。累计耗时:
O(n + ( n ) 2 ( \sqrt n)^2 (n)2) = O(n)
3 起泡排序(改进版2)
3.1 目标
继续改进,使之在如下情况下仅需 O(n)时间:乱序元素仅限于 A[n - n \sqrt n n, n)区间;
3.1 改进思路
仿照改进版1)的思路与技巧,将扫描交换的方向调换为自后(右)向前(左),记录最前(最左)逆序元素。
3.2 代码实现
template <typename T> //向量的起泡排序
void Vector<T>::bubbleSort ( Rank lo, Rank hi ) //assert: 0 <= lo < hi <= size
{ while ((lo = bubble(lo, hi)) < hi); } //逐趟做扫描交换,直至全序
template <typename T>
bool Vector<T>::bubble ( Rank lo, Rank hi ) { //一趟扫描交换Rank last = hi; //最右侧的逆序对初始化为[hi - 1, hi]while ( lo < --hi ) //自右向左,逐一检查各对相邻元素if ( _elem[hi - 1] > _elem[hi] ) { //若逆序,则last = hi; //更新最右侧逆序对位置记录,并swap ( _elem[hi - 1], _elem[hi] ); //通过交换使局部有序}return last; //返回最右侧的逆序对位置
}
4 起泡排序(改进版3)
4.1 目标
综合以上改进,使之在如下情况下仅需 O(n)时间:乱序元素仅限于任意的 A[m, m+ n \sqrt n n)区间。
4.2 改进思路
综合以上的思路与技巧,方向交替地执行扫描交换,同时动态地记录和更新最左和最右的逆序元素。
4.3 实现
#include <iostream>using namespace std;using Rank = int;// 自左向右,逐一检查各对相邻元素
Rank bubbleHi(Rank* A, Rank lo, Rank hi) { //一趟扫描交换int last = lo; //最右侧的逆序对初始化为[lo - 1, lo]while (++lo < hi) //自左向右,逐一检查各对相邻元素if (A[lo - 1] > A[lo]) { //若逆序,则last = lo; //更新最右侧逆序对位置记录,并swap(A[lo - 1], A[lo]); //通过交换使局部有序}return last; //返回最右侧的逆序对位置
}//自右向左,逐一检查各对相邻元素
Rank bubbleLo(Rank* A, Rank lo, Rank hi) { //一趟扫描交换int last = hi; //最右侧的逆序对初始化为[hi - 1, hi]while (lo < --hi) {//自右向左,逐一检查各对相邻元素if (A[hi - 1] > A[hi]) { //若逆序,则last = hi; //更新最右侧逆序对位置记录,并swap(A[hi - 1], A[hi]); //通过交换使局部有序}}return last; //返回最右侧的逆序对位置
}//向量的起泡排序
int bubbleSort(Rank* A, Rank lo, Rank hi) //assert: 0 <= lo < hi <= size
{int lo2 = bubbleLo(A, lo, hi);while (lo2 < (hi = bubbleHi(A, lo2, hi)));return 0;
} //逐趟做扫描交换,直至全序int main()
{//int a[19] = { 5,10,12,14,26,31,38,39,42,46,49,51,54,59,72,79,82,86,92 };//int a[19] = { 72,59,54,51,49,46,42,39,38,31,26,14,12,10,5,79,82,86,92 };int a[18] = { 5,10,12,14,26,31,38,39,54,51,49,46,42,59,72,82,86,92 };bubbleSort(a, 0, 18);int i;for (i = 0; i < 18; i++) { cout << a[i] << " "; }cout << endl;return 0;
}