排序学习:冒泡排序

440 阅读3分钟

排序算法

初识排序

排序算法算是程序员在学习过程中必不可少的基本功了,但是还是有很多人对于基础排序的思想不是很了解。正好卡在重学排序的时间,干脆就写一篇博客来记录排序算法。

推荐一个可以看算法动画的网站 ==> visualgo.net/zh

冒泡排序

冒泡排序(Bubble Sort)是一种典型的交换排序算法,通过交换数据元素的位置进行排序。

基本思想: 从无序序列头部开始,进行两两比较,根据大小交换位置,直到最后将最大(小)的数据元素交换到了无序队列的队尾,从而成为有序序列的一部分;下一次继续这个过程,直到所有数据元素都排好序。

核心在于每次通过两两比较交换位置,选出剩余无序序列里最大(小)的数据元素放到队尾。

思想==> 实现:

  1. 比较相邻的元素。如果第一个比第二个大(小),就交换他们两个。
  2. 对每一对相邻元素做同样的处理,从开始第一对到结尾的最后一对。每轮处理后,最后的元素会是最大(小)的数。
  3. 针对所有的元素重复以上的步骤,除了最后已经选出的元素(有序)。
  4. 持续每次对越来越少的元素(无序元素)重复上面的步骤,直到没有任何一对数字需要比较,则序列最终有序。

实现==> 代码: [1,2,3]=>[2,1,3]==>[2,3,1]
[2,3,1]=>[3,2,1]

  1. 假设存在n个元素,则需要冒泡n-1轮,因为每轮只能冒泡一个元素到正确位置。
  2. 假设存在n个元素无序状态,每次排序都是最坏情况,一共要排序n-1次才能将最小元素冒泡到队尾。
  3. 由此分析得到,代码结构应该选择双循环,外轮控制冒泡的轮数,内部控制冒泡的次数。
function bubSort(numbers) {
  let temp;
  for (let j = 0; j < numbers.length - 1; j++) {
    for (let i = 0; i < numbers.length - j; i++) {
      if (numbers[i] > numbers[i + 1]) {
        temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
      }
    }
    console.log(`这是第${j + 1}轮排序,结果为` + numbers);
  }
  return numbers;
}

增加Flag改进

改进之前,先考虑一个问题,对于[1,4,3,2,5]。冒泡排序要排几次呢?每次的结果是什么? 结果: 这是第1轮排序,结果为1,3,2,4,5 这是第2轮排序,结果为1,2,3,4,5 这是第3轮排序,结果为1,2,3,4,5 这是第4轮排序,结果为1,2,3,4,5

思考: 很容易发现,在已经是有序的情况下,冒泡并不会结束运行,而是继续排序,这样的浪费是可耻的。考虑通过检测已经排序完成后去打断循环,以减少浪费。那什么时候排序已经完成呢?就是当一轮循环中不存在交换位置的时候就说明已经完成了排序

代码:

function bubSort(numbers) {
  let temp;
  let check = false;
  for (let j = 0; j < numbers.length - 1; j++) {
    for (let i = 0; i < numbers.length - j; i++) {
      if (numbers[i] > numbers[i + 1]) {
        temp = numbers[i];
        numbers[i] = numbers[i + 1];
        numbers[i + 1] = temp;
        check = true;
      }
    }
    console.log(`这是第${j + 1}轮排序,结果为` + numbers);
    if (!check) {
      break;
    }
    check = !check;
  }
  return numbers;
}

其实还可以更精细化的处理,如果在排序中,有部分元素已经排好序,怎么去忽略这些元素呢?

鸡尾酒冒泡

鸡尾酒冒泡的思路是,外轮循环次数减半,正反轮换,正向冒泡大数,反向冒泡小数。

function cocktailSort(array) {
  let count = 0;
  let temp;
  for (let i = 0; i < array.length / 2; i++) {
    //外层循环次数折半
    for (let j = count; j < array.length - 1 - count; j++) {
      //一次正向循环,从头开始循环
      if (array[j] > array[j + 1]) {
        temp = array[j];
        array[j] = array[j + 1];
        array[j + 1] = temp;
      }
    }
    count++; //一次循环,冒泡出一个大数,记录已找出"较大"数个数+1
    for (let k = array.length - 1 - count; k > count - 1; k--) {
      //一次反向循环,从尾开始循环 k为除去正向冒泡数的未排序元素
      if (array[k] < array[k - 1]) {
        temp = array[k];
        array[k] = array[k - 1];
        array[k - 1] = temp;
      }
    }
  }
}