基本排序算法总结与对比 之一 ——冒泡排序
1、未改进的冒泡排序 —— 标准冒泡排序
template<typename T>
void bubbleSort(T arr[], int len)
{
for(int i = 0; i < len - 1; i++)
{
for(int j = 0; j < len - 1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
T temp = arr[j +1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
未改进的冒泡排序,最好,最坏以及平均情况下的时间复杂度均为O(n^2)。
排序过程可能在k(k < n - 1)次外循环后已经达到有序状态,但该算法仍然会继续比较相邻元素,直到n - 1次外循环结束。基于以上考虑,提出改进的冒泡排序算法A。
2、改进的冒泡排序 A —— 记录数列逆序状态
如果在k(k < n - 1)次内循环中没有数据交换,即可认为该数列已经达到有序状态。因此排序已经完成,可结束循环。
设置一个标志位,用来记录数列是否达到有序状态,若达到有序则结束循环。
template<typename T>
void bubbleSort(T arr[], int len)
{
bool sorted = false; //先假设整体无序状态
for(int i = 0; i < len - 1 && !sorted; i++) //无序状态才会进行循环
{
for(int j = 0; j < len - 1 -i; j++)
{
sorted = true; //若以下一次交换都没有,则数列为有序的,有一次交换就会改为false
if(arr[j] > arr[j + 1])
{
T temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
sorted = false; //存在一次交换,肯定是无序的;存在一次不交换,不一定是有序的
}
}
}
}
改进的冒泡排序A,最好的情况下时间复杂度均为O(n),最坏的情况下时间复杂度为O(n^2),平均时间复杂度为O(n^2)。
但是,排序过程可能在k(k < n - 1)次外循环后,数列的乱序元素仅限于(0,l)区间(l< n ),但是数列整体仍为无序的,每轮循环仍然进行(l,n)区间元素的比较操作。基于以上考虑,提出改进的冒泡排序算法B。
3、改进的冒泡排序 B —— 记录最右(或最左)端逆序对位置
每次循环都保持更新最右端逆序对位置为最近一次元素交换的位置,若在k(k < n - 1)次外循环后,数列的乱序元素仅限于(0,l)区间(l< n ),则在内循环中,从元素l开始,不会再进行交换操作,即最右端逆序对位置在 l 后不会再改变,内循环结束后记录下位置 l,后续排列中将不再考虑 l 右边的元素,即待排序数列缩短为范围(0,l)。
template<typename T>
void bubbleSort(T arr[], int len)
{
int high, last = len - 1; //假设最右端逆序对为数列末尾
while (last > 0)
{
high = 0; //最右端逆序对初始化为0
for (int j = 0; j < last; j++)
{
if (arr[j] > arr[j + 1])
{
T temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
high = j; //更新最右端逆序对位置
}
}
last = high; //记录最右端逆序对位置
}
}
改进的冒泡排序B,最好的情况下时间复杂度均为O(n),最坏的情况下时间复杂度为O(n^2),平均时间复杂度为O(n^2)。
在乱序元素仅限于A[0, l)区间时,仅需一趟扫描交换,即可将问题范围缩减至这一区间。累计耗时:O(n + l^2) = O(l^2)。
同理,排序过程可能在k(k < n - 1)次外循环后,数列的乱序元素仅限于[l,n)区间(l< n ),但是数列整体仍为无序的,每轮循环仍然进行[0,l)区间元素的比较操作。基于以上考虑,可以基于算法B修改,使扫描过程从尾部开始,向左边记录最左端逆序对位置即可。
基于以上总结,算法B可进一步拓展为排序过程可能在k(k < n - 1)次外循环后,乱序元素仅限于(l,m)区间(0 ≤ l < m ≤ n)的情形,结合算法B及其修改,提出改进的冒泡排序算法C。
4、改进的冒泡排序 C —— 双向扫描 记录两端逆序对位置
每次循环双向扫描,并保持更新最右端逆序对和最左端逆序对位置为最近一次元素交换的位置,若在k(k < n - 1)次外循环后,数列的乱序元素仅限于(l,m)区间(0 ≤ l < m ≤ n),则在内循环中,向左从l开始,向右从m开始,不会再进行元素交换。即最右端逆序对位置记录为m,最左端逆序对位置记录为l,后续排列将仅仅考虑(l,m)区间元素。
template<typename T>
void bubbleSort(T arr[], int len)
{
int first = 0, last = len - 1;
int low, high;
while(first < last)
{
high = first, low = last;
//向右扫描
for(int i = first; i < last; i++)
{
if(arr[i] > arr[i + 1])
{
T temp = arr[i + 1];
arr[i + 1] = arr[i];
arr[i] = temp;
high = i;
}
}
last = high; //记录last 放在左向扫描前面,右端有序的子数列不再进行左向扫描!
//向左扫描
for(int j = last; j > first; j--)
{
if(arr[j] < arr[j - 1])
{
T temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
low = j;
}
}
first = low;
}
}
改进的冒泡排序C,最好的情况下时间复杂度均为O(n),最坏的情况下时间复杂度为O(n^2),平均时间复杂度为O(n^2)。
在乱序元素仅限于A(l, m)区间时,仅需两趟扫描交换,即可将问题范围缩减至这一区间。累计耗时:O(n + m + (m-l)^2) = O((m-l)^2)。
冒泡排序法的改进仅仅是该算法的内部改进,无论怎么改进,平均情况下的时间复杂度均为O(n^2),效率并没有从根本上提升。但是改进使得数列排序在某些情形下减少多余操作,从而减少耗时。