算法-冒泡排序
前置知识
C++入门(啊这)- 算法的概念
思路
我们现在有一个序列,怎么对它排序?
这是一个非常经典的问题,这里我们使用一个经典的基础算法——冒泡排序解决。
这里有一个序列,要对它升序排序
5
1
3
4
2
\begin{array}{cc} 5&1&3&4&2 \end{array}
51342
我们把序列分为无序序列和有序序列。
这里,我们将无序序列用红色表示,有序序列用蓝色表示
很显然,这里的所以元素默认都是无序的,换句话说,所有元素都属于无序序列
5
1
3
4
2
\begin{array}{cc} \color{red}5&\color{red}1&\color{red}3&\color{red}4&\color{red}2 \end{array}
51342
此时我们开始冒泡排序。
首先选取前两个数(下划线表示)
5
‾
1
‾
3
4
2
∵
5
>
1
∴
交换
1
‾
5
‾
3
4
2
\begin{array}{cc} \color{red}\underline5&\color{red}\underline1&\color{red}3&\color{red}4&\color{red}2 \end{array}\\ \because5>1\therefore\text{交换}\\ \begin{array}{cc} \color{red}\underline1&\color{red}\underline5&\color{red}3&\color{red}4&\color{red}2 \end{array}
51342∵5>1∴交换15342
然后依次向后
1
5
‾
3
‾
4
2
∵
5
>
3
∴
交换
1
3
‾
5
‾
4
2
1
3
5
‾
4
‾
2
∵
5
>
4
∴
交换
1
3
4
‾
5
‾
2
1
3
4
5
‾
2
‾
∵
5
>
2
∴
交换
1
3
4
2
‾
5
‾
\begin{array}{cc} \color{red}1&\color{red}\underline5&\color{red}\underline3&\color{red}4&\color{red}2 \end{array}\\ \because5>3\therefore\text{交换}\\ \begin{array}{cc} \color{red}1&\color{red}\underline3&\color{red}\underline5&\color{red}4&\color{red}2 \end{array}\\\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}\underline5&\color{red}\underline4&\color{red}2 \end{array}\\ \because5>4\therefore\text{交换}\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}\underline4&\color{red}\underline5&\color{red}2 \end{array}\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}4&\color{red}\underline5&\color{red}\underline2 \end{array}\\ \because5>2\therefore\text{交换}\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}4&\color{red}\underline2&\color{red}\underline5 \end{array}
15342∵5>3∴交换1354213542∵5>4∴交换1345213452∵5>2∴交换13425
(所以呢)
我们惊喜地发现,最大的元素
5
5
5 经过第一轮操作被换到了后面。
所以,将元素
5
5
5 加入有序序列
1
3
4
2
5
\begin{array}{cc} \color{red}1&\color{red}3&\color{red}4&\color{red}2&\color{blue}5 \end{array}
13425
继续上述操作,但是因为
5
5
5 已经固定,所以不参与操作
1
‾
3
‾
4
2
5
∵
1
<
3
∴
不交换
1
3
‾
4
‾
2
5
∵
3
<
4
∴
不交换
1
3
‾
4
‾
2
5
∵
3
<
4
∴
不交换
1
3
4
‾
2
‾
5
∵
4
>
2
∴
交换
1
3
2
‾
4
‾
5
将 4 加入有序序列,继续操作
1
‾
3
‾
2
4
5
∵
1
<
3
∴
不交换
1
3
‾
2
‾
4
5
∵
3
>
2
∴
交换
1
2
‾
3
‾
4
5
将 3 加入有序序列,继续操作
1
‾
2
‾
3
4
5
∵
1
<
2
∴
不交换
将 2 加入有序序列,继续操作
1
2
3
4
5
将 1 加入有序序列,继续操作
1
2
3
4
5
排序结束
\begin{array}{cc} \color{red}\underline1&\color{red}\underline3&\color{red}4&\color{red}2&\color{blue}5 \end{array}\\ \because1<3\therefore\text{不交换}\\ \begin{array}{cc} \color{red}1&\color{red}\underline3&\color{red}\underline4&\color{red}2&\color{blue}5 \end{array}\\ \because3<4\therefore\text{不交换}\\ \begin{array}{cc} \color{red}1&\color{red}\underline3&\color{red}\underline4&\color{red}2&\color{blue}5 \end{array}\\ \because3<4\therefore\text{不交换}\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}\underline4&\color{red}\underline2&\color{blue}5 \end{array}\\ \because4>2\therefore\text{交换}\\ \begin{array}{cc} \color{red}1&\color{red}3&\color{red}\underline2&\color{red}\underline4&\color{blue}5 \end{array}\\ \text{将 4 加入有序序列,继续操作}\\ \begin{array}{cc} \color{red}\underline1&\color{red}\underline3&\color{red}2&\color{blue}4&\color{blue}5 \end{array}\\ \because1<3\therefore\text{不交换}\\ \begin{array}{cc} \color{red}1&\color{red}\underline3&\color{red}\underline2&\color{blue}4&\color{blue}5 \end{array}\\ \because3>2\therefore\text{交换}\\ \begin{array}{cc} \color{red}1&\color{red}\underline2&\color{red}\underline3&\color{blue}4&\color{blue}5 \end{array}\\ \text{将 3 加入有序序列,继续操作}\\ \begin{array}{cc} \color{red}\underline1&\color{red}\underline2&\color{blue}3&\color{blue}4&\color{blue}5 \end{array}\\ \because1<2\therefore\text{不交换}\\ \text{将 2 加入有序序列,继续操作}\\ \begin{array}{cc} \color{red}1&\color{blue}2&\color{blue}3&\color{blue}4&\color{blue}5 \end{array}\\ \text{将 1 加入有序序列,继续操作}\\ \begin{array}{cc} \color{blue}1&\color{blue}2&\color{blue}3&\color{blue}4&\color{blue}5 \end{array}\\ \text{排序结束}
13425∵1<3∴不交换13425∵3<4∴不交换13425∵3<4∴不交换13425∵4>2∴交换13245将 4 加入有序序列,继续操作13245∵1<3∴不交换13245∵3>2∴交换12345将 3 加入有序序列,继续操作12345∵1<2∴不交换将 2 加入有序序列,继续操作12345将 1 加入有序序列,继续操作12345排序结束
这就是基本的冒泡排序了。
算法参数
- 平均时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 最好时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 最坏时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
- 稳定性:稳定
(极其均匀)
极端
基本的冒泡排序是有很大改进空间的。
比如说,这个序列就能使得冒泡排序造成极大的浪费
1
2
3
4
5
⋯
n
\begin{array}{cc} 1&2&3&4&5&\cdots&n \end{array}
12345⋯n
你会发现冒泡排序在什么也没干的情况下进行了
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1) 次比较,造成极大的浪费。
优化一
手动模拟以上极端情况,我们发现:
\>\>\>\>
若一轮循环中没有干任何事情,可以直接结束排序。
这样,我们就得到了一个优化。
优化二
继续研究,我们不难发现,优化一仍然存在继续优化的空间。
\>\>\>\>
从无序序列末尾至上一轮循环中最后交换的后一个元素都是有序的,可以并入有序序列。
这就是终极版的冒泡排序。
虽然时间复杂度还是
O
(
n
2
)
O(n^2)
O(n2)。
实现代码
- 基础版本
void BubbleSort(int a[],int n){//对长度为n的数组a[]升序排序
for (int i=n;i>=2;i--)//目前的无序序列长度
for (int j=1;j<i;j++)//枚举下标,进行冒泡
if (a[j]>a[j+1])
swap(a[j],a[j+1]);
}
- 优化一版本
void BubbleSort(int a[],int n){//对长度为n的数组a[]升序排序
bool flag;
for (int i=n;i>=2;i--){//目前的无序序列长度
flag=0;//标记
for (int j=1;j<i;j++)//枚举下标,进行冒泡
if (a[j]>a[j+1]){
swap(a[j],a[j+1]);
flag=1;
}
if (flag==false) break;//没有交换过,直接退出
}
}
- 优化二版本
void BubbleSort(auto a[],int n){//对长度为n的数组a[]升序排序
int flag;
for (int i=n;i>=2;i=flag){//目前的无序序列长度
flag=0;//标记
for (int j=1;j<i;j++)//枚举下标,进行冒泡
if (a[j]>a[j+1]){
swap(a[j],a[j+1]);
flag=j;
}
i=flag;//有序序列扩张至j处
}
}
练习
此算法过于基础,我只找到了含有冒泡排序作为部分分的例题
- 洛谷P1177 【模板】排序的 20 p t s 20pts 20pts
- 洛谷P1908 逆序对的 25 p t s 25pts 25pts(本题的全分将在归并排序中讲到)