冒泡排序
冒泡排序是一种交换排序。核心思想是把相邻的元素两两比较,根据大小来交换元素的位置。冒泡排序是稳定排序(如果有相同元素排序后元素的位置不变),也是原地排序(不需要额外的空间),平均时间复杂度是O(n2),空间复杂度是O(1)。
代码实现
使用双循环来进行排序。外部循环控制所有的回合,内部循环代表每一轮的冒泡处理,先进行元素比较,再进行元素交换。
基础实现
function bubbleSort(array) {
// 外循环控制总的循环次数
for (let i = 0; i < array.length; i++) {
// 内循环进行比较交换,最右侧的元素已经有序不需要比较
for (let j = 0; j < array.length - i - 1; j++) {
// 相邻元素比较,交换
if (array[j] > array[j + 1]) {
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
const array = [5, 8, 6, 3, 9, 2, 1, 7];
bubbleSort(array);
console.log(array);
优化第一版
考虑到交换到后面如果数组已经有序了(数组的前面部分),就不用再对该区域进行比较了。所以需要用一个标识来表示数组是否已经有序,如果有序就直接跳出循环。
function bubbleSort(array) {
// 外循环控制总的循环次数
for (let i = 0; i < array.length; i++) {
// 默认就是有序的
let isSorted = true;
// 内循环进行比较交换,最右侧的元素已经有序不需要比较
for (let j = 0; j < array.length - i - 1; j++) {
// 相邻元素比较,交换
if (array[j] > array[j + 1]) {
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
// 有交换则证明无序
isSorted = false;
}
}
// 如果有序退出循环
if (isSorted) {
break;
}
}
}
const array = [5, 8, 6, 3, 9, 2, 1, 7];
bubbleSort(array);
console.log(array);
优化第二版
考虑到如果数组后半部分已经有序了,就不用再对该区域进行比较了。所以遍历时直接遍历到无序的部分就行。
function bubbleSort(array) {
let count = 0;
// 记录最后一次交换的索引
let lastExchangeIndex = 0;
// 无序数列的边界,每次比较只需要比到这里为止
let sortBorder = array.length - 1;
// 外循环控制总的循环次数
for (let i = 0; i < array.length; i++) {
count++;
// 默认就是有序的
let isSorted = true;
// 内循环进行比较交换,最右侧的元素已经有序不需要比较
for (let j = 0; j < sortBorder; j++) {
// 相邻元素比较,交换
if (array[j] > array[j + 1]) {
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
// 有交换则证明无序
isSorted = false;
// 把无序数列的边界更新为最后一次交换元素的位置
lastExchangeIndex = j;
}
}
// 跟新无序数据的边界位置
sortBorder = lastExchangeIndex;
// 如果有序退出循环
if (isSorted) {
break;
}
}
console.log(count);
}
const array = [3, 4, 2, 1, 5, 6, 7, 8];
bubbleSort(array);
console.log(array);
优化第三版
冒泡排序是从一个方向进行排序的,但是考虑到数组本来前半部分已经有序了,只有后面几个元素无序,如果用一般冒泡排序,会进行很多无用的比较和交换。所以优化的方案就是从两个方向进行比较,奇数轮从左往右比较交换,偶数轮从右往左进行比较交换。此排序方式就像是钟摆一样左右摆,也叫鸡尾酒排序。
function bubbleSort(array) {
let count = 0;
// 记录最后一次交换的索引
let lastLeftExchangeIndex = 0;
// 无序数列的边界,每次比较只需要比到这里为止
let leftSortBorder = 0;
let lastRightExchangeIndex = 0;
let rightSortBorder = array.length - 1;
// 外循环控制总的循环次数
for (let i = 0; i < array.length / 2; i++) {
count++;
// 默认就是有序的
let isSorted = true;
// 奇数轮 从左向向右遍历
for (let j = leftSortBorder; j < rightSortBorder; j++) {
// 相邻元素比较,交换
if (array[j] > array[j + 1]) {
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
// 有交换则证明无序
isSorted = false;
// 把无序数列的边界更新为最后一次交换元素的位置
lastRightExchangeIndex = j;
}
}
// 跟新无序数据的边界位置
rightSortBorder = lastRightExchangeIndex;
// 如果有序退出循环
if (isSorted) {
break;
}
// 从右往左遍历时先把isSorted置为true
isSorted = true;
for (let j = rightSortBorder; j > leftSortBorder; j--) {
// 相邻元素比较,交换
if (array[j] < array[j - 1]) {
const temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
// 有交换则证明无序
isSorted = false;
// 把无序数列的边界更新为最后一次交换元素的位置
lastLeftExchangeIndex = j;
}
}
// 跟新无序数据的边界位置
leftSortBorder = lastLeftExchangeIndex;
// 如果有序退出循环
if (isSorted) {
break;
}
}
console.log(count);
}
const array = [2, 3, 4, 5, 6, 7, 8, 1];
bubbleSort(array);
console.log(array);