排序算法-冒泡排序

294 阅读4分钟

冒泡排序

冒泡排序是一种交换排序。核心思想是把相邻的元素两两比较,根据大小来交换元素的位置。冒泡排序是稳定排序(如果有相同元素排序后元素的位置不变),也是原地排序(不需要额外的空间),平均时间复杂度是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);

参考

冒泡排序

鸡尾酒排序