背景
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”(来自百度百科doge)。
算法思想
按照惯例,我们首先从一个生活中的实例开始。假设你现在正在上体育课,体育老师让大家按照身高从高到矮排成一排准备做拉伸运动,大部分同学都找到了自己的位置,但是有少部分和旁边的个子差不多高,从他们自身的角度很难看出谁高谁低,这个时候他们就请求了老师的帮助。老师看了看之后,让其中一位同学往右边(个子更高的一边)挪了一位,对比了一下发现还是比较高,于是就又移了一位,最终帮助这位同学找到了合适的位置。老师在帮助这位同学找位置的过程中其实就使用了冒泡排序的思想。
冒泡排序的思想很简单,用伪代码的形式描述如下:
for i = 1..A.length
for j = 1..A.length
tmp = A[j]
if arr[j] > arr[j+1]
exchange arr[j] with arr[j+1]
下面用一副图向大家展示一下算法思想
我们选取的例子元素很少,但是事实上还是经历了数组的size
趟冒泡,即4次,每次会让最大的元素冒到相应的位置,下面展示了源代码,不过这里的版本是经过改进的冒泡排序:如果数组一开始就有序,直接退出不用继续排序。
#include <stdio.h>
/** void swap(int *a, int *b) -> none
* 冒泡排序辅助交换函数,用于交换数组中的两个元素
* @params
* @param a: 需要交换的第一个元素的地址
* @param b: 需要交换的第二个元素的地址
* @return: none
*/
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
/** void BubbleSort(int *arr, int size) -> none
* 冒泡排序
* @params
* @param arr: 需要排序的数组
* @param size: 数组的大小
* @return: none
*/
void BubbleSort(int *arr, int size) {
for (int i = 0; i < size - 1; i++) {
int flag = 0;
for (int j = 0; j < size - 1; j++) {
if (arr[j] > arr[j+1]) {
swap(&arr[j], &arr[j+1]);
flag = 1;
}
}
if(!flag) {
return ;
}
}
}
int main() {
int arr[] = {6, 10, 13, 5, 8, 3, 2, 11};
int len = sizeof(arr) / sizeof(arr[0]);
BubbleSort(arr, len);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
算法分析
时间复杂度
最坏的情况是数组是刚好逆序的,那么冒泡排序一共要进行
n
−
1
n-1
n−1次循环,而每次循环又要进行
n
−
1
n-1
n−1次交换,因此这个时候一共的比较次数是:
∑
i
=
1
n
−
1
(
n
−
i
)
=
n
(
n
−
1
)
2
\sum_{i=1}^{n-1}(n-i) = \frac{n(n-1)}{2}
∑i=1n−1(n−i)=2n(n−1),时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
最好的情况是数组刚好是有序的,那么就只用进行一次冒泡,时间复杂度为
O
(
n
)
O(n)
O(n)。
空间复杂度
显然我们仅仅使用了一个
f
l
a
g
flag
flag标志变量,并未额外开辟空间用来存储,因此冒泡排序的空间复杂度为
O
(
1
)
O(1)
O(1)。
算法稳定性
可以看到在判断的时候我们并未考虑元素相等的情况,因此也就不会交换数值相同元素的位置,所以冒泡排序是稳定的算法