冒泡排序


本文将介绍冒泡排序

冒泡排序基本思想

基本思想:对元素个数为N的的待排序序列进行排序时,共进行N-1次循环。在第K次循环中,对于从第1到k-1个元素从前往后进行比较,每次比较相邻的两个元素,若前一个元素大于后一个元素(相等的时候不交换,这也是为什么冒泡是稳定的),则两者交换位置,否则保持位置不变。这样一次循环下来,就把第k大的元素移动到第N-k个位置上,称为第k趟的冒泡。整个过程一共进行N-1趟冒泡,直到第1个和第2个元素比较完成,最终剩余最小的元素,留在第1个位置上,排序结束

动画演示:

原理

冒泡排序代码

SortHelper.h存放相关工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#ifndef ALGORITHM_SORTHELPER_H
#define ALGORITHM_SORTHELPER_H
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
int *generateRandomArray(int n, int rangeL, int rangeR)
{
int * arr = NULL;
int i;
if (n > 0 && rangeL <= rangeR)
{
arr = (int *) malloc(sizeof(int) * n); // 用malloc 分配数组 是因为不用malloc的话 随着此函数的结束 数组空间会被释放掉
srand(time(NULL));
for (i = 0; i < n; ++i)
{
arr[i] = rand() % (rangeR - rangeL + 1) + rangeL; // 注意区间是[rangeL, rangeR]
}
}
return arr;
}

// 交换两个数位置
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}

// 生成近乎有序的数组 n数据个数 swapTimes:交换次数 最多只有2*交换次数个元素无序
int * generateNearlyOrderArray(int n, int swapTimes)
{
int i;
int *arr = NULL;
if (n > 0)
{
arr = (int *) malloc(sizeof(int) * n);
for (i = 0; i < n; ++i)
{
arr[i] = i;
}

srand(time(NULL));
for (i = 0; i < swapTimes; ++i)
{
int x = rand() % n;
int y = rand() % n;
swap(&arr[x], &arr[y]);
}
}
return arr;
}


// 生成近乎倒序的数组 n数据个数 swapTimes:交换次数 最多只有2*交换次数个元素无序 左闭右开
int * generateNearlyReverseOrderArray(int n, int swapTimes)
{
int i;
int *arr = NULL;
if (n > 0)
{
arr = (int *) malloc(sizeof(int) * n);
for (i = 0; i < n; ++i)
{
arr[i] = n-1-i;
}

srand(time(NULL));
for (i = 0; i < swapTimes; ++i)
{
int x = rand() % n;
int y = rand() % n;
swap(&arr[x], &arr[y]);
}
}
return arr;
}

// 插入排序
void InsertionSort(int * arr, int left, int right)
{
int i, j;
for (i = left + 1; i <= right; ++i)
{
int temp = arr[i];
for (j = i; j > left && arr[j-1] > temp; j--)
{
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}

// 打印数组
void printArray(int *arr, int n)
{
int i;
for (i = 0; i < n; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}


void testSort(char * sortName, void(*sort)(int* , int ), int * arr, int n)
{
clock_t startTime = clock();
sort(arr, n);
clock_t endTime = clock();
printf("%s cost: %lfs\n",sortName, (double)(endTime - startTime) / CLOCKS_PER_SEC);
}

// 拷贝arr数组
int * copyArray(int *arr, int n)
{
int i;
int *arr2 = (int *) malloc(sizeof(int) * n);
for (i = 0; i < n; ++i)
{
arr2[i] = arr[i];
}
return arr2;
}
#endif //ALGORITHM_SORTHELPER_H

BubbleSort.c冒泡排序源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdlib.h>
#include "SortHelper.h"


void bubbleSort1(int *arr, int n)
{
int p, i;
for (p = n - 1; p > 0; --p) // 趟数 n-1趟
{
for (i = 0; i < p; ++i)
{
// 每趟找出一个最大元素 被交换到最右端
if (arr[i] > arr[i + 1]) // 若前一个元素大于后一个元素 交换
{
swap(&arr[i], &arr[i + 1]);
}
}
}
}

// 优化
void bubbleSort2(int *arr, int n)
{
int p,i,flag;
for (p = n - 1; p > 0; --p) // 趟数 n-1趟
{
flag = 1; // 1:代表此时整个序列已经有序
for (i = 0; i < p; ++i)
{
// 每趟找出一个最大元素 被交换到最右端
if (arr[i] > arr[i + 1]) // 若前一个元素大于后一个元素 交换
{
swap(&arr[i], &arr[i + 1]);
flag = 0; // 说明无序
}
}
if (flag) // 如果flag还保持为1 说明已经有序
{
break;
}
}
}

int main(void)
{
int n = 100000;
// 随机数组 区间[0,n]
printf("test RandomArray size = %d [0,%d]\n", n, n);
int *arr = generateRandomArray(n, 0, n);
int *arr1 = copyArray(arr, n);
testSort("bubbleSort1 ", bubbleSort1, arr, n);
testSort("bubbleSort2 ", bubbleSort2, arr1, n);

// 近乎有序的数组 swapTimes:交换次数 区间[0,n)
int swapTimes = 100;
printf("\ntest NearlyOrderArray size = %d [0,%d) swapTimes: %d\n", n, n, swapTimes);
int *arr2 = generateNearlyOrderArray(n, swapTimes);
int *arr3 = copyArray(arr, n);
testSort("bubbleSort1 ", bubbleSort1, arr2, n);
testSort("bubbleSort2 ", bubbleSort2, arr3, n);

// 近乎倒序的数组 区间[0,n)
printf("\ntest NearlyReverseArray size = %d [0,%d) swapTimes: %d\n", n, n, swapTimes);
int *arr4 = generateNearlyReverseOrderArray(n, swapTimes);
int *arr5 = copyArray(arr4, n);
testSort("bubbleSort1 ", bubbleSort1, arr4, n);
testSort("bubbleSort2 ", bubbleSort2, arr5, n);

// 含大量重复元素的数组 [0,10]
printf("\ntest RandomArray size = %d [0,10]\n", n);
int *arr6 = generateRandomArray(n, 0, 10);
int *arr7 = copyArray(arr6, n);
testSort("bubbleSort1 ", bubbleSort1, arr6, n);
testSort("bubbleSort2 ", bubbleSort2, arr7, n);

free(arr);free(arr1);free(arr2);free(arr3);free(arr4);free(arr5);free(arr6);free(arr7);
return 0;
}

Clion运行结果如下:

原理

双向冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 双向冒泡:奇数趟,从前往后比较相邻元素,关键字最大的放在序列尾
// 偶数趟,从后往前比较相邻元素,关键字最小的放在序列头
void BubbleSort(int * nums, int n){
int left = 0, right = n - 1; // [left,right]排序
while (left < right) {
bool flag = true; // 默认为true表示有序,如果flag在冒泡过程中没有改变,说明已经有序
// 奇数趟:从左到右冒泡 将关键字大的放到序列尾
for (int i = left; i < right; ++i) {
if (nums[i] > nums[i + 1]) {
swap(nums[i], nums[i + 1]);
flag = false;
}
}
right--; // 更新

// 偶数趟:从右到左冒泡 将关键字小的放到序列头
for (int i = right; i > left; --i) {
if (nums[i] < nums[i - 1]) {
swap(nums[i], nums[i - 1]);
flag = false;
}
}
left++; // 更新
if (flag == true) { // 说明已经有序
return;
}
}
}

总结

时间复杂度O(n^2)    指的是平均时间复杂度
空间复杂度O(1)       拿bubbleSort2来说 只开辟了p,i,flag常数个空间
稳定性 :    稳定的     元素关键字相等的两个元素进行比较时并不会发生交换,即相对位置不会发生变化
冒泡排序最坏的情况下(序列时逆序排序的),每次比较都需要进行交换,时间复杂度O(N^2)
冒泡排序最好的情况下(序列已经有序),这时由于用了flag标志只要进行O(N)次比较就可以从循环跳出来.看上面运行结果图在几乎有序的情况下,未经flag优化的运行时间。

pS: 源代码链接

文章目录
  1. 1. 冒泡排序基本思想
  2. 2. 冒泡排序代码
  3. 3. 双向冒泡排序
  4. 4. 总结
|