十大排序之 冒泡排序
- 一、算法原理
- 二、算法的时空分析
- 三、拓展:双向冒泡排序
- 四、冒泡排序改进版
一、排序原理
排序在计算机科学中是非常重要的一环,对数据的查找和分析都有至关重要的作用。通常,排序是指按照某种约定的次序(如非降),将给定的一组元素进行顺序的排列。在日常生活中,我们经常在做排序。比如在学校早操时,老师总是会让我们按身高从矮到高排序,然而聪明的我们往往不会去跟最高或最矮的那个人比较,而是去排在与身边身高差不多的同学前后。这是为什么呢? 因为我们懂得局部有序整体就会有序,这也是所有排序算法的一个整体思想。善于用减而知治的思想解决问题往往会事半功倍!
-
局部有序
经过上述的分析,我们知道了要从局部去窥探排序的潜在问题,那就是相邻元素的顺序性。 在一个长度为1到n的序列A[0,n-1]中,若满足A[i]≤A[i+1]的相邻元素则称作是顺序的,否则是逆序的。我们通常的做法是将每一对逆序的元素调整为顺序的,简单举个例子,下面的动画描述了一次交换排序的过程,做法是每趟排序都简单判断相邻元素是否顺序,如果是逆序则进行交换:
2.冒泡排序(bubble sort)
我们可以很清晰的看到这种交换排序的每一趟都可以将一个待定的元素变为最终状态,以元素非降排序为例,即为每一趟排序都会将最大的元素放在最终的位置。但是这样的一趟排序却是未必会导致整体有序的。我们很容易想到,因为每一趟排序都会导致一个元素到达最终位置,因此我们只需每一趟做n-1,n-2,…,1次比较即可达到最终的序列。
3.代码实现
/*java*/
public static void bubbleSort1(int[] a) {
boolean unorder= true;
for (int i = 0; unorder&& i < a.length - 1; i++) {
unorder= false;/*整体有序*/
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {/*逆序交换*/
int temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
unorder= true;
}
}
}
}
/*C++*/
void bubbleSort1(int A[],int n)
{
bool sorted=false; //排序状态为无序
while(!sorted)/*逐趟扫描交换,直到sorted=true,即为有序状态*/
{
sorted=true; //如果接下来if判断一次也不执行,则序列整体有序
for(int i=1;i<n;i++)//A[0,n)
{
if(A[i-1]>A[i]) //逆序
{
swap(A[i-1],A[i]);//交换
sorted=false; //序列状态未定
}
}
--n; //进行下一趟比较,序列向前缩进
}
}
二、时空分析与稳定性
- 时空:如前所述,在最坏情况下,每趟排序均需要比较n-1,n-2,…1次才能确定最终位置。因此在渐进意义下T(n)=n*(n-1)/2,即时间复杂度为O(n2)。从代码和演示可以看出不需要额外的辅助空间(O(1)),即为就地算法。
- 稳定性:因为冒泡排序中,要求A[i-1]是严格大于A[i]的,在交换的过程中。重复元素虽然有所聚集,但不会相互跨越。因此冒泡排序是稳定的。
三、双向冒泡排序
传统冒泡排序均为自左向右或自由想坐逐趟单向扫描交换,然而在抵达最终位置时,我们可以逆向继续进行扫描交换。这样冒泡就可以从两端进行。
public static void bubbleSortBothWay(int[] a) {
int i, lo = 0, hi = a.length - 1;
boolean flag = true;
while (lo < hi && flag) {
flag = false;
for (i = lo; i < hi; ++i) {
if (a[i] > a[i + 1]) {
flag = true;
swap(a[i], a[i + 1]);
}
}
--hi;
for (i = hi; i > lo; --i) {
if (a[i] < a[i - 1]) {
flag = true;
swap(a[i], a[i - 1]);
}
}
++lo;
}//while
}
/*c++*/
template<typename T>
void bubbleSort2(T& a)
{
int len=sizeof(a)/sizeof(a[0]);
int k=1,i,j;
bool sorted;
do
{
for(i=k,sorted=false;i<=len-k;++i)
{
if(a[i+1]<a[i]) swap(a[i+1],a[i]);
sorted=true;
}
for (j=i-1,sorted=false; j>=k+1;--j)
{
/* code */
if(a[j]<a[j-1])swap(a[j],a[j-1]);
sorted=true;
}
++k;/*向前或向后缩进*/
}while(sorted);
}
四、冒泡排序改进版
通过观察发现,冒泡排序的记忆性很差。它从来不关注局部的顺序性,只是一味的冲锋陷阵,细心观察就会发现,在一趟排序后某一元素必定会到达最终位置,然后此前必定有某一序列已经局部有序,此时只需要定位到局部有序的末尾重新开始扫描即可,而不必重头开始。
/*c++*/
void bubbleSort3(int A[],int n)/*冒泡排序改进版*/
{
for (int m ; 1 < n ; n = m) /*逐趟扫描交换,并且记录下次扫描位置*/
{
/* code */
for (int i = m = 1; i < n ; ++i) //自左向右逐趟检查A[0,m)中相邻元素
{
if(a[i-1] > a[i]) swap(a[i-1],a[i]);//发现逆序对交换
m = i; //更新长度
}
}
}
public static void bubbleSort2(int[] A, int n) {
int m = 0;
for (; 1 < n; n = m) {
for (int i = m = 1; i < n; i++) {
if (A[i - 1] > A[i]) {
swap(A, i-1, i);
m = i;
}
}
}
}