首先需要你对排序算法的评价维度和一个理想排序算法应该是什么样的有一个基本的认知:
《Hello算法之排序算法》
主要内容来自:Hello算法11.3 冒泡排序
我觉得冒泡排序非常有意思,也非常简单,就是不停地交换相邻的元素即可,最大的元素一直横冲直撞往右边过去。
然后我们再经过 n-1 次迭代即可找出顺序规律。
单次循环的流程可以看原文的图片:冒泡排序
对于冒泡排序,需要重点掌握的还是效率优化章节。
算法流程
重点就是抓住到底要冒泡几轮,然后就是我们需要把最大的元素冒到最后面去,也就是说我们的未匹配元素是从0开始,到n、n-1、n-2…以此类推,应该如何处理?
对于数组长度为 n ,冒泡排序流程如下:
1.首先,对 n 个元素执行“冒泡” ,将数组的最大元素交换到正确位置
2. 接下来,对剩余 n - 1个元素执行“冒泡”,将第二大元素交换到正确位置
3. 以此类推,一共经过 n - 1轮“冒泡”,前 n - 1 大的元素都被交换到正确位置
4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。
//版本一:
void bubbleSort(vector<int> &nums) {int n = nums.size();while (--n) {int k = 0; //记录最大元素下标,这样我觉得效率更高,不用频繁swapfor (int i = 1; i <= n; i++) {if (nums[i] > nums[k])k = i;}swap(nums[k], nums[n]);}
}//版本二:
void bubbleSort(vector<int> &nums) {// 外循环:未排序区间为 [0, i]for (int i = nums.size() - 1; i > 0; i--) {// 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端for (int j = 0; j < i; j++) {if (nums[j] > nums[j + 1]) {// 交换 nums[j] 与 nums[j + 1]// 这里使用了 std::swap() 函数swap(nums[j], nums[j + 1]);}}}
}
⭐️效率优化
在冒泡排序中,完全有可能某轮的冒泡没有执行任何交换操作,说明数组已经完成排序,可以直接返回结果。
所以我们可以增加一个标志位flag
来监控该情况,一旦出现立即返回。
经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 O ( n 2 ) O(n^2) O(n2)但当输入数组完全有序时,可达到最佳时间复杂度 O ( n ) O(n) O(n)。
//版本一:
void bubbleSort(vector<int> &nums) {int n = nums.size();while (--n) {int k = 0; //记录最大元素下标,这样我觉得效率更高,不用频繁swapint flag = 0;for (int i = 1; i <= n; i++) {if (nums[i] > nums[k]) {k = i;flag++;}}if (!flag)break;swap(nums[k], nums[n]);}
}
//版本二:
void bubbleSortWithFlag(vector<int> &nums) {// 外循环:未排序区间为 [0, i]for (int i = nums.size() - 1; i > 0; i--) {bool flag = false; // 初始化标志位// 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端for (int j = 0; j < i; j++) {if (nums[j] > nums[j + 1]) {// 交换 nums[j] 与 nums[j + 1]// 这里使用了 std::swap() 函数swap(nums[j], nums[j + 1]);flag = true; // 记录交换元素}}if (!flag)break; // 此轮“冒泡”未交换任何元素,直接跳出}
}
算法特性
- 时间复杂度为 O ( n 2 ) O(n^2) O(n2)、自适应排序:各轮“冒泡”遍历的数组长度依次为 n − 1 、 n − 2 、 . . . 、 2 、 1 n-1 、n-2、...、2、1 n−1、n−2、...、2、1,总和为 ( n − 1 ) n 2 \frac{(n-1)n}{2} 2(n−1)n。在引入优化后,最佳时间复杂度为 O ( n ) O(n) O(n)
- 空间复杂度为 O ( 1 ) O(1) O(1)、原地排序
- 稳定排序:由于在“冒泡”中,遇到相等元素不会进行交换。