冒泡排序

冒泡排序是一种很基本的排序算法,步骤如下。
(1) 指向数组中两个相邻的元素(最开始是数组的头两个元素),比较它们的大小。
(2) 如果它们的顺序错了(即左边的值大于右边),就互换位置。如果顺序已经是正确的,那这一步就什么都不用做。
(3) 将两个指针右移一格。重复第(1)步和第(2)步,直至指针到达数组末尾。
(4)重复第(1)至(3)步,直至从头到尾都无须再做交换,这时数组就排好序了。
 
    ArrayList.prototype.swap = function(i, j){
        let temp = this.array[i]
        this.array[i] = this.array[j]
        this.array[j] = temp
    }
 1      ArrayList.prototype.bubble = function () {
 2         let length = this.array.length
 3         //  外层循环可以看做控制每轮比较的次数:从length-1次到0次
 4         for(let j = length-1; j >=0 ; j--){
 5           //  内层循环可以看做一个从头开始不断右移的指针 每移动一位进行一次比较  每轮只需要比较前j个元素
 6           for (let i = 0;i < j ; i++) {
 7             if (this.array[i] > this.array[i + 1]) {
 8               this.swap(i, i+1)
 9             }
10           }
11         }  
12       }
  此种算法名为冒泡排序的原因:每一次轮回过后,未排序的值中最大的那个都会“冒”到正确的位置上。
  对于N个元素,使用冒泡排序需要 (N - 1) + (N - 2) + (N - 3) + … + 1  =  N(N - 1)/ 2次比较。如果数组不只是随机打乱,而是完全反序,在这种最坏的情况下,每次比较过后都得进行一次交换。此时需要的总步数为N(N - 1),因此描述冒泡排序效率的大 O 记法,是 O(N^2)。(最好情况是数组本来就有序,需要0次交换;平均情况需要N(N - 1)/ 4次交换。)
 

选择排序

 

     ArrayList.prototype.selectionSort = function(){
        let length = this.array.length
        //  依次获取第1个到倒数第二个元素
        for(let j = 0; j < length - 1; j++){
          //  从被比较对象开始
          let min = j
          //  让获取到的元素和后面元素进行比较
          for(let i = j + 1; i < length; i++){
            if(this.array[i] < this.array[min]){
              min = i
            }
          }
      // 将参与排序的元素中的最小值放在本轮排序的起点
this.swap(j, min) } }

和冒泡排序相反,选择排序每轮让最小的元素落位。第一轮,获取到所有元素中的最小值,将其放在最前面;第二轮,获取剩余元素中的最小值……依次进行。

对于N个元素,使用选择排序需要 (N - 1) + (N - 2) + (N - 3) + … + 1  =  N(N - 1)/ 2次比较。但每轮的交换最多只有 1 次。如果该轮的最小值已在正确位置,就无须交换,否则要做 1 次交换。相比之下,冒泡排序在最坏情况(完全逆序)时,每次比较过后都要进行 1 次交换。所以,选择排序的步数大概只有冒泡排序的一半,即选择排序比冒泡排序快一倍。但是它们的大 O 记法都为 O(N^2)。

 

插入排序

      ArrayList.prototype.insertionSort = function(){
        let length = this.array.length
        //  依次获取第2个到最后一个元素
        for(let i = 1; i < length; i++){
          //  外循环获取的元素,和它之前的元素依次比较
          let temp = this.array[i]
          let j = i
          while(this.array[j-1]>temp && j > 0){
            this.array[j] = this.array[j - 1]
            j--
          }
          this.array[j] = temp
        }
      }    

  插入排序是完全不同的思路,它每轮过后可以保证前若干个元素是局部有序的。具体来说,外循环依次选中第2个到最后一个元素,保存在temp变量中,然后被选中的位置可以看做一个空位this.array[ j ]

。接着比较temp和其前面的元素。如果temp大,则终止比较,将temp插入空位;如果temp小,则交换空位和前面元素。

  插入排序包含 4 种步骤:移除、比较、平移和插入。比较的总次数为:1 + 2 + 3 + … + (N - 1)次;当数组完全逆序时,有多少次比较就要多少次平移,因为每次比较的结果都会使你将值右移;temp 的移除跟插入在每一轮里都会各发生一次。因为总是有 N - 1 轮,所以可以得出结论:有 N - 1 次移除和 N - 1 次插入。所以将四种操作的步数相加,一共需要N^2 + N - 2 步。所以在最坏的情况里,插入排序的时间复杂度跟冒泡排序、选择排序一样,都是 O(N^2)。
  但插入排序的最大特点就是最好情况、最坏情况、平均情况的复杂的相差很大——最坏情况是所有数据都要比较和平移;最好情况是每轮一次比较、零次平移;对于平均情况,总的来看,是比较和平移一半的数据。(由于插入排序的提前终止机制)事实上,对于插入排序的最坏、平均、最好情况,分别需要 N2、N2 / 2、N 步。
  跟选择排序对比一下。选择排序是无论何种情况,最坏、平均、最好,都要 N2 / 2 步。因为这个算法没有提早结束某一轮的机制,不管遇到什么,每一轮都得比较所选索引右边的所有值。
  那么哪种算法更好?选择排序还是插入排序?答案是:看情况。对于平均情况(数组里的值随机分布),它们性能相近。如果你确信数组是大致有序的,那么插入排序比较好。如果是大致逆序,则选择排序更快。如果你无法确定数据是什么样,那就算是平均情况了,两种都可以。
posted on 2021-06-21 15:34  springxxxx  阅读(247)  评论(0)    收藏  举报