标签:smm fmm vhdx vbs pos 序列 tty 扩展 结束
快不一定就好,比如说。。。咳咳,你们懂得。但是在排序界,排序速度的快慢可以说是衡量一个算法好坏的重要指标。今天AP哥要给大家介绍的这一款排序算法,可以说是出了名的慢,以至于好像只在书上见过它,在实际应用中并没有它的影子,那就是冒泡排序。可是,它就真的一无是处吗?先别着急下结论,且听我慢慢道来。
首先我们来看一下冒泡排序的原理,然后你就知道为什么它这么不受待见了。
以整型数组A = { 19,14,10,4,15,26,20,96 }
为例,冒泡排序步骤如下:
i=A.len
(A.len
表示数组A的长度);j=0
,当 j < i-1 时,重复步骤4、5A[j]
和A[j+1]
的值;j
的值加1
;i
的值减1
;用代码实现如下:
1#include <iostream>
2
3using namespace std;
4
5void swap(int &a, int &b) {
6 a = a ^ b;
7 b = a ^ b;
8 a = a ^ b;
9
10 /*如果是其他类型,应该选择下面这种交换方式*/
11 //int temp = a;
12 //a = b;
13 //b = temp;
14}
15
16/*A:数组首地址
17 low:要排序的序列的第一个元素的位置
18 high:要排序的序列的最后一个元素的位置*/
19void bubbleSort(int *A, int low, int high) {
20 for (int i = high; i > low; i--) {
21 for (int j = low; j < i; j++) {
22 if (A[j] > A[j + 1]) {
23 swap(A[j], A[j + 1]);
24 }
25 }
26 }
27}
28
29int main() {
30 int A[8] = { 19,14,10,4,15,26,20,96 };
31 bubbleSort(A, 0, 7);
32 for(int i = 0; i < 8; i++)
33 cout << A[i] << " ";
34 system("pause");
35}
在冒泡排序中,每经过一趟交换(也就是步骤3到步骤5),就会有一个元素就位,所以如果一个序列有n个元素 的话,那么整个排序过程就要经过n-1趟交换。为什么是n-1趟排序而不是n趟呢?因为当第2个元素到第n个元素就位时,第1个元素自然也就位了,所以就用不着第n趟排序了。
从上述算法描述中我们可以看到:
所以整个排序过程要遍历的元素个数为:
再加上它交换元素的值所花费的时间……所以冒泡排序的时间复杂度是,这样看来,说冒泡排序速度慢好像也没冤枉它。不过,我们刚才看到的,只是它在最坏情况下的时间复杂度,也就是说直到进行了n-1趟排序,整个序列才被排好序。
但是小伙伴们肯定已经发现了,在上面那张图中,第3趟排序结束后,序列就已经整体有序了,但是我们的算法并不在乎,它只是忠实地执行着我们的指令,第1趟排序让最大的元素就位,第2趟排序让第二大的元素就位……说到这儿,AP哥不禁为计算机的忠实所感动……要是人与人之间也能这么单纯就好了。
因此,每经过一趟排序,我们就要判断一下序列是否已经有序,如果已经有序,就退出排序。判断的方法也很简单,当这一趟排序没有进行元素交换时,就说明序列已经有序了,所以我们需要设置一个标记,通过这个标记来记录这一趟排序有没有交换元素。
代码实现如下:
1#include <iostream>
2
3using namespace std;
4
5void swap(int &a, int &b) {
6 a = a ^ b;
7 b = a ^ b;
8 a = a ^ b;
9
10 /*如果是其他类型,应该选择下面这种交换方式*/
11 //int temp = a;
12 //a = b;
13 //b = temp;
14}
15
16/*A:数组首地址
17 low:要排序的序列的第一个元素的位置
18 high:要排序的序列的最后一个元素的位置*/
19void bubbleSort2(int *A, int low, int high) {
20 for (int i = high; i > low; i--) {
21 bool swaped = false; //记录是否发生过元素交换
22 for (int j = low; j < i; j++) {
23 if (A[j] > A[j + 1]) {
24 swap(A[j], A[j + 1]);
25 swaped = true;
26 }
27 }
28 if (swaped == false)
29 return;
30 }
31}
32
33int main() {
34 int A[8] = { 19,14,10,4,15,26,20,96 };
35 bubbleSort2(A, 0, 7);
36 for(int i = 0; i < 8; i++)
37 cout << A[i] << " ";
38 system("pause");
39}
经过改进,减少了冒泡排序的趟数,从而减少了冒泡排序的所花费的时间,那么这样就是最优的冒泡排序了吗?并不是。
再仔细观察一下上图,不难发现,第1趟排序结束后,「15」~「96」之间的这几个数已经是有序的了,它们已经处在自己应该处的位置上了。既然第1趟排序结束后已经有5个元素就位,那么到了第2趟时我们只需要关注剩下的没有就位的元素就可以了。
将这个问题扩展一下,可以用下面这张图表示:
如果序列的状态像上图这样,右侧大半部分就已经有序,那么我们在每一趟的扫描时就不需要扫描到②处,而是仅扫描到①处就够了,因为我们只需要对乱序的那部分进行排序。
要做到这一点,在每趟排序中,就不能只记录是否发生了交换,而是要记录该趟排序中最右侧逆序对的位置,最右侧的逆序对表明了序列的有序程度,也表明该逆序对右侧的元素已经有序。这样,到了下一趟排序时,只需要扫描到这次记录的最右侧逆序对的位置就够了。
下面是代码实现:
1#include <iostream>
2
3using namespace std;
4
5void swap(int &a, int &b) {
6 a = a ^ b;
7 b = a ^ b;
8 a = a ^ b;
9
10 /*如果是其他类型,应该选择下面这种交换方式*/
11 //int temp = a;
12 //a = b;
13 //b = temp;
14}
15
16/*A:数组首地址
17 low:要排序的序列的第一个元素的位置
18 high:要排序的序列的最后一个元素的位置*/
19void bubbleSort3(int *A, int low, int high) {
20 while(low < high){
21 int last = low;//最右侧的逆序对初始化为[low, low+1]
22 for(int j = low; j < high; j++){
23 if (A[j] > A[j+1]){
24 last = j;
25 swap(A[j], A[j + 1]);
26 }
27 }
28 high = last;
29 }
30}
31
32int main() {
33 int A[8] = { 19,14,10,4,15,26,20,96 };
34 bubbleSort3(A, 0, 7);
35 for(int i = 0; i < 8; i++)
36 cout << A[i] << " ";
37 system("pause");
38}
经过两次改进,冒泡排序性能提升了不少,然而这种改进只有遇到我们之前所说的情况时才会生效。换而言之,如果待排序的序列恰好是逆序排列,我们的这种改进对于算法性能的提升依然于事无补。在最坏情况下,冒泡排序的时间复杂度依然是。
但是当序列基本有序时,冒泡排序只做很少趟数的扫描即可完成排序,时间复杂度甚至能达到。所以,一个算法的好坏通常不能一概而论,而是要看具体的情况,根据不同的情况选择不同的、适合的算法,才是聪明的选择。
最后,欢迎关注我的微信公众号:AProgrammer,更多干货等你来发现!
标签:smm fmm vhdx vbs pos 序列 tty 扩展 结束
原文地址:https://www.cnblogs.com/AProgrammer/p/10005941.html