1、排序算法(js)

164 阅读1分钟
  • 换位采用es6写法 [arr[i], arr[j]] = [arr[j], arr[i]],

一、 O(n^2)排序: 三个时间复杂度非常高的算法,冒泡排序、插入排序、选择排序

1、 冒泡排序

  • 每次排列好最大的一个数字,排到后面
/**
 * @param {number[]nums
 * @return {number[]}
 */
var bubbleSort = function(nums) {
   let len = nums.length
   if (len <= 1) {
       return nums
   }
   for(let i =0; i<len; i++) {
   // 每次排好最大的一个,j < len - 1 - i
       for(let j=0; j < len - 1 - i; j++) {
           // 交换
           [nums[j] , nums[j+1]] = [nums[j+1],nums[j]]
       }
   }
   return nums
};

2、插入排序

  • i是要插入数据的位置,0~j是已排序好的位置。 第一步腾出需要插入的位置,第二步插入
/**
 * @param {number[]nums
 * @return {number[]}
 */
var insertSort= function(nums) {
    const len = nums.length
    if(len <= 1) {
        return nums
    }
    for(let i =1; i< len; i++) {
        let j = i -1
        let value = nums[i]
        // 数据移动
        for (;j >= 0; j--) {
            if(nums[j] > value) {
                nums[j+1] = nums[j ]
            } else{
                break
            }
        
        }
        // 插入数据
        nums[j +1] =value 
       
    }
    return nums


};

3、 希尔排序-插入排序优化

  • 增加外面一层for循环分组gap, 里面插入排序采用间隔gap
var sortArray = function(nums) {
  shellSort(nums)
  return nums
  // 希尔函数
  function shellSort(arr) {
      let gap = Math.floor(arr.length/2)
      // gap间隔
      for(; gap > 0; gap = Math.floor(gap/2)) {
          插入算法//
          for(let i = gap; i< arr.length;i++) {
              let j = i - gap // [0~j]之间排序好的数组
              let value = arr[i] // 插入的值
              // 移位,腾出要插入的值
              for(;j >= 0;j -= gap) {
                  if(arr[j] > value) {
                      arr[j + gap] = arr[j]
                  } else{
                      break
                  }
              }
              // 插入
              arr[j + gap] = value
          }
      }

  }
}

4、选择排序

  • 第一个循环是定义最小值位置,第二个循环找出最小值
var selectionSort = function(nums) {
   let len = nums.length
   if (len <= 1) {
       return nums
   }
    
   for(let i = 0; i< len; i++) {
       let min = i
       // 找出最小值
       for (let j = i; j< len; j++) {
           if(nums[j] < nums[min]) {
               min = j
           }
       }
       // 交换
       [nums[i], nums[min]] = [nums[min], nums[i]]

   }
   return nums

};
  • 这三种排序时间算法最坏时间复杂度是O(n^2),插入排序和冒泡排序时间复杂度最好时时O(n),选择排序时间最优复杂度是O(n^2)。空间复杂度是O(1)都是原地排序,冒泡排序和插入排序都是稳定排序,选择排序是不稳定排序。
  • 这三大排序算法时间复杂度都非常高,有2个for循环,适用小数据量。虽然时间复杂度差不多,但是冒泡排序和选择排序需要 3 个赋值操作,而插入排序只需要 1 个。只有插入排序能通过LeetCode排序题
  • 希尔排序是插入排序的升级版,最好时间复杂度O(n)

二、O (nlog(n)): 分治思想、递归

1、归并排序

  • 归并排序的时间复杂度是nlgn,缺点是空间复杂度是O(n),
  • 网上有很些用到shift方法,这个方法导致数组移位,造成时间复杂度升高
// 注意千万不要用 result.push(leftArr.shift())

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var mergeArray = function(nums) {
   let len = nums.length
   if(len <= 1) {return nums}
   return mergeSort(nums)
   function mergeSort(arr) {
       // 终止条件
       if(arr.length === 1) {
           return arr
       }
       // 取中值
       let mid = Math.floor((arr.length)/2)
      
       // 递推公式
       return merge(mergeSort(arr.slice(0, mid)), mergeSort(arr.slice(mid, arr.length)))
   }
   function merge(leftArr, rightArr) {
       let result = []
       let left = 0
       let right = 0
       while(left < leftArr.length && right <rightArr.length) {
           if(leftArr[left] <= rightArr[right]) {
               result.push(leftArr[left])
               left ++
           } else{
                result.push(rightArr[right])
                right++
           }
       }

       return result.concat(leftArr.slice(left)).concat(rightArr.slice(right))
   }

};

2、快排

image.png

  • 快排的优点就是快。缺点是不稳定排序。巧妙的设计可以实现原地排序
  • 快排的分区点选择不好的情况,时间复杂度上升至O(n^2),这个算法也是通过不了leetcode
  • 下面的代码是实现原地排序,网上很多采用两个数组的算法导致空间复杂度上升。这样的快排优势全无

var sortArray = function(nums) {
   let len = nums.length
   if(len <= 1) {return nums}
    quickSort(nums)
    return nums
    
   function quickSort(arr, start = 0, last = nums.length-1) {
       // 终止条件
       if(last - start < 1) {
           return 
       }
       
       let pivot = arr[last] // 分区点
        let i= start  // i 代表分区的位置,i之前的是已经处理好的
        let j = start //代表处理的数据
        // 遍历 数据,换位分区
        for(; j< last; j++) {
            if(arr[j] <= pivot) {
                [arr[i],arr[j]] = [arr[j], arr[i]]
                i++   
            }
        }
        // 分区点的位置换位
        [arr[i], arr[j]] = [arr[j], arr[i]]
        
         // 分区后 递归 
         quickSort(arr,start, i -1)
         quickSort(arr, i+1, last)
   }
};
  • 代码实现原理如图

image.png


三、 线性排序

1、桶排序

var sortArray = function(nums) {
  const size = 3
  let buckets = []
  let max= min = nums[0]
  for(let i =0; i < nums.length; i++) {
      if(nums[i]< min) {
          min = nums[i]
      } else if(nums[i] > max) {
          max = nums[i]
      }
  }   
  // 生成桶
  for(let i = 0; i < nums.length; i++) {
      let j =~~((nums[i] - min) / size)
      if(buckets[j]) {
          buckets[j].push(nums[i])
      }else{
          buckets[j] = [nums[i]]
      }
  }
  let result = []
  for(let i = 0; i< buckets.length; i++) {
      let sortArr = shellSort(buckets[i])
      result = result.concat(sortArr)  
  }
  return result

  function shellSort(arr = []) {
      let gap = Math.floor(arr.length/2)
      for(;gap > 0;gap = Math.floor(gap/2)){
          for(let i = gap; i<arr.length; i++) {
              let insertValue = arr[i]
              let j = i - gap
              for(; j >=0; j-=gap) {
                  if(insertValue < arr[j]) {
                      arr[j + gap] = arr[j]
                  }else{
                      break
                  }
              }
              arr[j + gap] = insertValue
          }
      }
      return arr
  }
  
}

2、计数排序

var sortArray = function(nums) {
   if(nums.length <=1) {return nums}
   return countSort(nums)
   function countSort(arr) {
       let min = max = arr[0]
        // 查找最小值
       for(let i =0; i < arr.length;i++) {
           if(arr[i] < min) {
               min = arr[i]
           } else if(max < arr[i]) {
               max = arr[i]
           }
       }
       let arrLen = max + 1 - min
       let countArr = new Array(arrLen)
       for (let i = 0; i < arr.length; i++) {
           let index = arr[i] -min // 相对位移
           if(countArr[index]) {
               countArr[index]++
           } else {
               countArr[index] = 1
           }
       }
       let result = []
       
       for(let j = 0; j< countArr.length; j++) {
           if(countArr[j]) {
              while(countArr[j] > 0) {
                   result.push(j+min)
                   countArr[j] --
              }
           }
       }
       return result
   }
  
}

3、基数排序

Radix Sort,⾮⽐较型整数排序,将所有待⽐较数值(正整数)统⼀为同样的数位⻓

度,数位较短的数前⾯补零。然后,从最低位开始,依次进⾏⼀次排序。这样从最低位

排序⼀直到最⾼位排序完成以后,数列就变成⼀个有序序列。

var sortArray = function(nums) {
    if(nums.length <= 1) {return nums}
    let max = Math.max(...nums)
    let maxIndend = 1
    while(max = Math.floor(max/10) ) {
        maxIndend ++
    }
    let indent = 1
    let count = []
    let dev = 1
    let mod = 10
    for(let i =0; i< maxIndend; i++) {
        count = []
        // 按位数排序
        for(let j =0;j<nums.length; j++) {
            let bucket = Math.floor(nums[j] % mod / dev)
            if(count[bucket]){
                count[bucket].push(nums[j])
            }else{
                count[bucket] = [nums[j]]
            }
        }
        let index = 0
        // 按位数重新放进数组
        for(let j = 0; j < count.length; j++) {
            if(count[j]){
                while(count[j].length) {
                    nums[index++] = count[j].shift()
                }
            }
        }
        dev *= 10
        mod *= 10
    }
    return nums
}

四、堆排序

Heap Sort,是指利⽤堆这种数据结构所设计的⼀种排序算法。

var sortArray = function (nums) {
     function max_heapify(arr, start, end) {
         let parentIndex = start,sonIndex = parentIndex * 2 + 1
         if(sonIndex >= end) return 
         // right > left
         if(sonIndex + 1 < end && arr[sonIndex + 1] > arr[sonIndex]) {
             sonIndex ++
         }
         // 父子节点交换
         if(arr[sonIndex] > arr[parentIndex]) {
             [arr[sonIndex], arr[parentIndex]] = [arr[parentIndex], arr[sonIndex]]
         }
         max_heapify(arr, sonIndex, end)
     }
     let len = nums.length 
     if(len<2) { return nums }
     // 有子节点的构建大堆顶
     let i = (len >> 1) - 1
     for(; i >= 0; i--) {
         max_heapify(nums, i ,len)
     }
     // 最大值移到最后面
     for(let j = len - 1; j >= 0; j--){
         [nums[0],nums[j]] = [nums[j], nums[0]]
         max_heapify(nums, 0 , j)
     }
     return nums
    
   
}

总结

截屏2023-05-06 11.04.43.png