排序-1-冒泡排序

393 阅读5分钟

根据时间复杂度的不同,主流的排序算法可以分为3大类:

1. 时间复杂度为O(n2)的排序算法

冒泡排序

选择排序

插入排序

希尔排序(希尔排序比较特殊,它的性能略优于O(n2),但是又比不上 O(nlogn),姑且将它归为本类)

2. 时间复杂度为O(nlogn)的排序算法

快速排序

归并排序

堆排序

3. 时间复杂度为线性的排序算法

计数排序

桶排序

基数排序

此外排序算法还可以根据其稳定性,划分为稳定排序不稳定排序

即如果值相同的元素在排序后仍保持着排序前的顺序,则这样的排序算法是稳定排序,如果值相同的元素在排序后打乱了排序前的顺序,则这样的排序算法是不稳定排序,

栗子:

冒泡排序

如需要从小到大排序一个无序序数列 5,8,6,3,9,2,1,7

按照冒泡排序的思想:**把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或者等于右侧相邻元素时,位置不变。**详细过程如下:

这时,元素 9 作为数列中最大的元素,就来到了最右侧,冒泡排序第一轮结束,数列最右侧元素 9 的位置,可以认为是一个有序区域,有序区域目前只有一个元素

接下来进行第二轮排序:

第二轮结束后,数列右侧有序区域有了两个元素,

第三轮至第七轮的状态如下:

这就是冒泡排序的整体思路,

冒泡排序是一种稳定排序,值相等的元素,并不会打乱原有的顺序,该算法要遍历n -1 轮,每一轮都要遍历 n - 1 - 有序区域长度, 所以时间复杂度为 O(n2)

代码实现

  function bubbleSort(list:Array<number>) {
      // 注意 这里是 <  length - 1 也就是说遍历次数为 n-1 
      for (let i = 0; i < list.length - 1; i++) {
          // 这里 < length -1的原因同上 - i的原因是 有序区
          for (let j = 0; j < list.length - i -1; j++) {
              if( list[j] > list[j+1]) {
                  let temp = list[j];
                  list[j] = list[j+1];
                  list[j+1] = temp
  
  
              }
          }
      }
      return list
  }

优化1-标识是否已经有序

回顾刚才数列 5,8,6,3,9,2,1,7 的排序细节,经过 第 6 轮排序后,执行 第 7 轮的时候,其实整个数列已经是有序的了,但是还是执行了第 7 轮,

在这种情况下,其实可以添加一个标识,如果一轮下来未执行交换,那么可以确定数列已经有序,就可以提前结束遍历了

优化代码如下

  // 是否有序标识优化
  function bubbleSortTwo(list:Array<number>) {
      // 注意 这里是 <  length - 1 也就是说遍历次数为 n-1 
      for (let i = 0; i < list.length - 1; i++) {
          // 这里 < length -1的原因同上 - i的原因是 有序区
  
          let isSorted = true // 有序标识 每一轮都为true
          for (let j = 0; j < list.length - i -1; j++) {
              if( list[j] > list[j+1]) {
                  let temp = list[j];
                  list[j] = list[j+1];
                  list[j+1] = temp
                  // 如果有交换发生,则不是有序的
                  isSorted = false
              }
          }
          if(isSorted) {
              break
          }
      }
      return list
  }

优化2-标记有序区

为了说明问题,我们准备一个新的数列,

这个数列的特点是:前半部分元素 3,4,2,1 是无序的,后半部分元素 5,6,7,8是升序排列的,并且后半部分元素中的最小值也大于前半部分元素的最小值

下边按照冒泡排序的思路来进行排序

第一轮:

元素 4 和元素 5 比较,发现 4 < 5,所以位置不变

元素 5 和元素 6 比较,发现 5 < 6,所以位置不变

元素 6 和元素 7 比较,发现 6 < 7,所以位置不变

元素 7 和元素 8 比较,发现 7 < 8,所以位置不变

第一轮结束,数列中的有序区包含 1 个元素

第二轮:

元素 3 和元素 2 比较,3 > 2,所以交换,元素 3 和元素 1 比较,发现 3 > 1,所以交换

元素 3 和元素 4 比较,发现 3 < 4,所以位置不变

元素 4 和元素 5 比较,发现 4 < 5,所以位置不变

元素 5 和元素 6 比较,发现 5 < 6,所以位置不变

元素 6 和元素 7 比较,发现 6 < 7,所以位置不变

元素 7 和元素 8 比较,发现 7 < 8,所以位置不变

等等,已经发现问题了,后边许多元素已经是有序的了,但是还是进行了多余的比较

这个问题的关键在于,对于有序区的界定,按照现有的逻辑,有序区的的长度和轮数是相等的,例如:第一轮结束后,有序区的长度为 1,第二轮结束后,有序区的长度为 2

实际上,数列真正的有序区可能会大于这个长度,如上述例子,在第一轮结束后,数列的后五个元素其实是已经有序的,因此后边的多次元素比较是多余的,

 那么我们可以在每一轮排序后,记录下最后一次元素交互的位置,设该位置为有序区的边界,代码如下:

  function bubbleSortThree(list:Array<number>) {
      // 记录边界位置
      let sortBorder = list.length - 1;
      //记录最后一次交换的位置
      let lastExcChangeIndex = 0;
  
  
      // 注意 这里是 <  length - 1 也就是说遍历次数为 n-1 
      for (let i = 0; i < list.length - 1; i++) {
          let isSorted = true; // 有序标识 每一轮都为true
          for (let j = 0; j < sortBorder; j++) {
              if( list[j] > list[j+1]) {
                  let temp = list[j];
                  list[j] = list[j+1];
                  list[j+1] = temp;
                  // 如果有交换发生,则不是有序的
                  isSorted = false;
                  // 把边界更新为最后一次交换元素的位置
                  lastExcChangeIndex = j
              }
          }
          sortBorder = lastExcChangeIndex
          if(isSorted) {
              break
          }
      }
      return list
  }

优化3-鸡尾酒排序

首先还是一个例子说明问题,比如有一个数列 2,3,4,5,6,7,8,1,如果按照冒泡排序的思想,排序过程如下:

元素 2,3,4,5,6,7,8已经有顺序了,却还要进行7轮排序,

冒泡排序的每一轮都是从左到右来比较元素,进行单向比较,而鸡尾酒排序的元素比较和交换是双向的

对于这个数列,我们来看一下鸡尾酒排序

第一轮(和冒泡排序一样):

第二轮开始不一样,我们反过来,从右往左比较进行交换

第三轮,1和2比较,位置不变,2和3比较,位置不变,3和4比较,位置不变,4和5比较,位置不变,5和6比较,位置不变,6和7比较,位置不变,7和8比较,位置不变,没有元素进行交换,证明已经有序,排序结束

代码如下:

    function bubbleSortFour(list:Array<number>) {
        // 外层总轮数 这里取一半 floor
        let outLength = Math.floor(list.length/2)
        for (let i = 0; i < outLength; i++) {
            let sorted = true; // 有序标识
            for (let j = i; j < list.length - 1 - i; j++) {
                if(list[j] > list[j + 1]){
                    let temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp
                    sorted = false
                }
            }
            if(sorted){
                break
            }
    
    
            sorted = true;
            // 从右往左 比较 
            for (let j = list.length - 1 - i; j > i ; j--) {
                if(list[j] < list[j - 1]){
                    let temp = list[j];
                    list[j] = list[j - 1];
                    list[j - 1] = temp;
                    sorted = false;
                }
            }
            if(sorted) {
                break
            }
        }
        return list
    }

增加有序边界代码如下

    function bubbleSortFive(list:Array<number>) {
        let leftSortBorder = 0;
        let leftLastSortIndex= 0;
        let rightSortBorder = list.length - 1;
        let rightLastSortIndex = 0;
        // 外层总轮数 这里取一半 floor
        let outLength = Math.floor(list.length/2)
        for (let i = 0; i < outLength; i++) {
            let sorted = true; // 有序标识
            for (let j = leftSortBorder; j < rightSortBorder; j++) {
                if(list[j] > list[j + 1]){
                    let temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp
                    sorted = false;
                    // 记录右边界
                    rightLastSortIndex = j;
                }
            }
            // 赋值右边界
            rightSortBorder = rightLastSortIndex
            if(sorted){
                break
            }
    
    
            sorted = true;
            // 从右往左 比较 
            for (let j = rightSortBorder; j > leftSortBorder ; j--) {
                if(list[j] < list[j - 1]){
                    let temp = list[j];
                    list[j] = list[j - 1];
                    list[j - 1] = temp;
                    sorted = false;
                    // 记录左边界
                    leftLastSortIndex = j - 1;
                }
            }
            // 赋值左边界
            leftSortBorder = leftLastSortIndex
            if(sorted) {
                break
            }
        }
        return list
    }

摘要总结自: 漫画算法 小灰的算法之旅