上第一门语言课,相信大家接触的第一个排序算法应该就是冒泡排序,相信大学四年过去,许多人对冒泡排序还是停留在最原始的版本,其实,冒泡排序是存在许多优化方法的,下面讲解常见的优化方法。
首先,上菜最原始的冒泡排序方法
看不懂的同学,就先补补相关知识
void bubble1(int *array, int len) {
int counter = 0;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
//升序排序
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
}
counter++;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
一、第一种优化方法,设置交换完成flag
冒泡排序最基本的问题是他是个暴力算法,就算是【1, 2, 3, 4】, 他也给需要你遍历6次;原始的冒泡排序相当于一个开环系统,没有反馈,那我们为了提高排序效率,我们可以判断当前序列是否有序。那什么时候序列已经有序了呢,根据冒泡排序的特性,当没有一个气泡冒泡,那么就是有序了,意思就是在第二个循环时,没有元素进行交换。
void bubble2(int *array, int len) {
int counter = 0;
bool flag = true;
for (int i = 0; i < len - 1; i++) {
//默认已排序完成
flag = true;
for (int j = 0; j < len - i - 1; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//未排序完成
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
二、加入边界判断
我们可以注意到我们每次进行排序都是进行到 len - i - 1, 这也是冒泡排序的一个洉病, 过分自信,认为自己排过的才是有序的,忽略了原序列本来就由局部有序,那么我们可以记录有序的边界,从而提高排序效率。
void bubble3(int *array, int len) {
int counter = 0;
bool flag = true;
int sortBorder = len - 1;
int lastExchangeIndex = 0;
for (int i = 0; i < len - 1; i++) {
flag = true;
for (int j = 0; j < sortBorder; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//记录这次是否已经交换
flag = false;
lastExchangeIndex = j;
}
counter++;
}
if (flag == true) {
break;
}
sortBorder = lastExchangeIndex;
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
三、鸡尾酒排序(双向冒泡)
鸡尾酒排序最要是解决大部分元素已经有序的情况,例如,需要将 [2, 3, 4, 5, 6, 7, 8, 1] 升序排序, 按照上述比较策略
if (array[j + 1] < array[j])
我们需要将 2, 3, 4, 5, 6, 7, 8依次冒泡起来,那我们可以想到,把 1 沉下去不是更加容易吗,但是程序的比较策略是固定的,我们无法用一种比较策略去解决两种情况,这个时候我们就需要引进鸡尾酒排序,在排序过程中,我们不仅需要把大的元素冒泡,同时也需要把数值小的元素下沉,实现双向交换。
void bubble4(int *array, int len) {
int counter = 0;
bool flag = true;
for (int i = 0; i < len / 2; i++) {
//正向
flag = true;
for (int j = i; j < len - i - 1; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//记录这次是否已经交换
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
//反向
flag = true;
for (int j = len - i - 1; j > i; j--) {
if (array[j - 1] > array[j]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
//记录这次是否已经交换
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
tips:交换元素我们也可以使用异或的方式,异或计算是比加法快速的。
四、四种算法比较
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void bubble1(int *array, int len) {
int counter = 0;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
//升序排序
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
}
counter++;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
void bubble2(int *array, int len) {
int counter = 0;
bool flag = true;
for (int i = 0; i < len - 1; i++) {
//默认已排序完成
flag = true;
for (int j = 0; j < len - i - 1; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//未排序完成
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
void bubble3(int *array, int len) {
int counter = 0;
bool flag = true;
int sortBorder = len - 1;
int lastExchangeIndex = 0;
for (int i = 0; i < len - 1; i++) {
flag = true;
for (int j = 0; j < sortBorder; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//记录这次是否已经交换
flag = false;
lastExchangeIndex = j;
}
counter++;
}
if (flag == true) {
break;
}
sortBorder = lastExchangeIndex;
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
void bubble4(int *array, int len) {
int counter = 0;
bool flag = true;
for (int i = 0; i < len / 2; i++) {
//正向
flag = true;
for (int j = i; j < len - i - 1; j++) {
if (array[j + 1] < array[j]) {
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
//记录这次是否已经交换
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
//反向
flag = true;
for (int j = len - i - 1; j > i; j--) {
if (array[j - 1] > array[j]) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
//记录这次是否已经交换
flag = false;
}
counter++;
}
if (flag == true) {
break;
}
}
for (int i = 0; i < len; i++) {
printf("%d\t", array[i]);
}
printf("\ncounter:%d\n", counter);
}
int main() {
//最基本的冒泡排序
int array1[] = { 5, 1, 6, 3, 9, 2, 8, 7 };
int len1 = 8;
bubble1(array1, len1);
//加入flag的改进冒泡排序
int array2[] = { 5, 1, 6, 3, 9, 2, 8, 7 };
int len2 = 8;
bubble2(array2, len2);
//加入边界判断以及flag的冒泡排序
int array3[] = { 5, 1, 6, 3, 9, 2, 8, 7 };
int len3 = 8;
bubble3(array3, len3);
//最基本的冒泡排序
int array1_1[] = { 2, 3, 4, 5, 6, 7, 8, 1 };
int len1_1 = 8;
bubble1(array1_1, len1_1);
//鸡尾酒排序
int array4[] = { 2, 3, 4, 5, 6, 7, 8, 1 };
int len4 = 8;
bubble4(array4, len4);
system("pause");
return 0;
}