冒泡

思路

  1. 比较所有的相邻元素,如果第一个比第二个大,则交换它们
  2. 一轮下来,可以保证最后一个数是最大的
  3. 执行n-1轮,就可以完成排序(例如第二轮,可以保证倒数第二个数是数据中第二大的)

代码实现

数组原型上的方法和this

  <script>
    Array.prototype.bubbleSort = function () {
      console.log(this); //[5, 4, 3, 2, 1];
    }
    const arr = [5, 4, 3, 2, 1];
    arr.bubbleSort()
  </script>

获取数组中的所有相邻元素

  <script>
    Array.prototype.bubbleSort = function () {
      for(let j =0;j<this.length-1;j++){
        console.log(this[j],this[j+1]);
        //5 4
        //4 3
        //3 2
        //2 1
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.bubbleSort()
  </script>

比较前后数大小,交换位置

  1. 如果前一个数比后一个数要大,则交换他们的位置
  2. 第一轮循环下来,数组中的最后一个数就是数组中第一大的数,(后面类推)
  <script>
    Array.prototype.bubbleSort = function () {
      for(let j =0;j<this.length-1;j++){
        if(this[j] > this[j+1]){
          let temp = this[j]
          this[j] = this[j+1]
          this[j+1] = temp
        }
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.bubbleSort()
    console.log('arr',arr); //[4, 3, 2, 1, 5]
  </script>

所有元素排完序

执行n-1轮循环,就可以对数组中所有的元素排完序

  <script>
    Array.prototype.bubbleSort = function () {
      for (let i = 0; i < this.length - 1; i++) {
        for (let j = 0; j < this.length - 1; j++) {
          if (this[j] > this[j + 1]) {
            let temp = this[j]
            this[j] = this[j + 1]
            this[j + 1] = temp
          }
        }
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.bubbleSort()
    console.log('arr', arr); //[1, 2, 3, 4, 5]
  </script>

优化(完整代码)

例如:第二轮循环下来,倒数的第二个数就是第二大的数,所以就不用去循环数组中第二大后面的数

  <script>
    Array.prototype.bubbleSort = function () {
      for (let i = 0; i < this.length - 1; i++) {
        for (let j = 0; j < this.length - 1-i; j++) {
          if (this[j] > this[j + 1]) {
            let temp = this[j]
            this[j] = this[j + 1]
            this[j + 1] = temp
          }
        }
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.bubbleSort()
    console.log('arr', arr);  //[1, 2, 3, 4, 5]
  </script>

时空复杂度

时间复杂度

O(n^2),因为2个嵌套循环

空间复杂度

空间复杂度就是在交换元素时那个临时变量所占的内存空间;
最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n);
平均的空间复杂度为:O(1);

选择

思路

  1. 找到数组中的最小值,选中它并将其放置在第一位
  2. 找到数组中的第二小的值,选中它并将其放置在第二位
  3. 以此类推,执行n-1轮

代码实现

一轮循环下来找到数组中最小值的下标

  <script>
    Array.prototype.selectSort = function(){
      let indexMin = 0; //默认数组最小值的下标为0
      for(let j =0;j<this.length;j++){
        let thisJ = this[j]
        let thisIndexMin = this[indexMin]
        if(thisJ < thisIndexMin){
          indexMin = j
        }
      }
      console.log('indexMin',indexMin); //4
    }
    const arr = [5, 4, 3, 2, 1];
    arr.selectSort()
  </script>

一轮循环下来,把找到的数组中最小值,与数组第一个值进行交换

  <script>
    Array.prototype.selectSort = function(){
      let indexMin = 0; //默认数组最小值的下标为0
      for(let j =0;j<this.length;j++){
        let thisJ = this[j]
        let thisIndexMin = this[indexMin]
        if(thisJ < thisIndexMin){
          indexMin = j
        }
      }
      const temp = this[0]
      this[0] = this[indexMin]
      this[indexMin] = temp;
      // console.log('indexMin',indexMin);
    }
    const arr = [5, 4, 3, 2, 1];
    arr.selectSort()
    console.log('arr', arr) //[1, 4, 3, 2, 5]
  </script>

执行n-1轮循环

把0都换成i,因为i代表当前第几轮,也就是从第几个元素开始循环

 <script>
    Array.prototype.selectSort = function () {
      for (let i = 0; i < this.length - 1; i++) {
        let indexMin = i; //默认数组最小值的下标为0
        for (let j = i; j < this.length; j++) {
          let thisJ = this[j]
          let thisIndexMin = this[indexMin]
          if (thisJ < thisIndexMin) {
            indexMin = j
          }
        }
        const temp = this[i]
        this[i] = this[indexMin]
        this[indexMin] = temp;
        // console.log('indexMin',indexMin);
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.selectSort()
    console.log('arr', arr) //[1, 2, 3, 4, 5]
  </script>

完整代码

一轮循环下来,找到了数组中最小值的下标,如果最小值的下标与当前值不相等,才进行交换,(以此类推)

  <script>
    Array.prototype.selectSort = function () {
      for (let i = 0; i < this.length - 1; i++) {
        let indexMin = i; //默认数组最小值的下标为0
        for (let j = i; j < this.length; j++) {
          let thisJ = this[j]
          let thisIndexMin = this[indexMin]
          if (thisJ < thisIndexMin) {
            indexMin = j
          }
        }
        // 一轮循环下来,找到了数组中最小值的下标,
        // 如果最小值的下标与当前值不相等,才进行交换
        if (indexMin !== i) {
          const temp = this[i]
          this[i] = this[indexMin]
          this[indexMin] = temp;
        }
      }
    }
    const arr = [5, 4, 3, 2, 1];
    arr.selectSort()
    console.log('arr', arr) //[1, 2, 3, 4, 5]
  </script>

时空复杂度

时间复杂度

O(n^2),因为2个嵌套循环

空间复杂度

插入排序

思路

  1. 从第二个数开始往前比
  2. 比它大就往后排

代码实现

   Array.prototype.quickSort = function () {
      const temp = this[1]
      let j =1;
      while(j >0){
        j--
      }
    }
    const arr =  [5,4,3,2,1]
    arr.quickSort()

第一轮循环

    Array.prototype.quickSort = function () {
      const temp = this[1]
      let j =1;
      while(j >0){
        if(this[j-1] > temp){
          this[j] = this[j-1]
        }else{
          break;
        }
        j--
      }
      this[j] = temp;
    }
    const arr = [5,4,3,2,1] //[4,5,3,2,1]
    arr.quickSort()

循环数组的长度n轮

    Array.prototype.quickSort = function () {
      for (let i = 1; i < this.length; i++) {
        const temp = this[1]
        let j = 1;
        while (j > 0) {
          if (this[j - 1] > temp) {
            this[j] = this[j - 1]
          } else {
            break;
          }
          j--
        }
        this[j] = temp;
      }
    }
    const arr = [5, 4, 3, 2, 1]
    arr.quickSort()

时间复杂度

嵌套2层循环 O(n^2)

快速排序

思路

  1. 分区: 从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面
  2. 递归: 递归地对基准前后的子数组进行分区

代码实现

基准

选择数组第一个元素做基准

    Array.prototype.quickSort = function(){
      const rec = (arr) => {
        const left = []
        const right = []
        const mid = arr[0]
      }
      const res = rec(this)
      console.log('res',res);
      res.forEach((n, i) => { this[i] = n });

    }
    const arr = [2,4,5,3,1]
    arr.quickSort()

分区

从第2个元素开始,小于基准的放左边,大于基准放右边

    Array.prototype.quickSort = function(){
      const rec = (arr) => {
        const left = []
        const right = []
        const mid = arr[0]
        for(let i =1;i<arr.length;i++){
          if(arr[i] < mid){
            left.push(arr[i])
          }else{
            right.push(arr[i])
          }
        }
      }
      const res = rec(this)
      console.log('res',res);
      res.forEach((n, i) => { this[i] = n });
    }

递归

例如在左分区中,又定义基准,再分区

    Array.prototype.quickSort = function(){
      const rec = (arr) => {
        const left = []
        const right = []
        const mid = arr[0]
        for(let i =1;i<arr.length;i++){
          if(arr[i] < mid){
            left.push(arr[i])
          }else{
            right.push(arr[i])
          }
        }
        return [...rec(left),mid,...rec(right)]
      }
      const res = rec(this)
      console.log('res',res);
      res.forEach((n, i) => { this[i] = n });
    }

终结条件,边界

数组只有一个时,就不用排序了

    Array.prototype.quickSort = function(){
      const rec = (arr) => {
        if(arr.length <=1){
          return arr;
        }
        const left = []
        const right = []
        const mid = arr[0]
        for(let i =1;i<arr.length;i++){
          if(arr[i] < mid){
            left.push(arr[i])
          }else{
            right.push(arr[i])
          }
        }
        return [...rec(left),mid,...rec(right)]
      }
      const res = rec(this)
      console.log('res',res);
      res.forEach((n, i) => { this[i] = n });
    }

时间复杂度

递归:O(logN)
分区: O(n)
总体: O(n*logN)

另一种写法

const quickSort1 = arr => {
    if (arr.length <= 1) {
        return arr;
    }
    //取基准点
    const midIndex = Math.floor(arr.length / 2);
    //取基准点的值,splice(index,1) 则返回的是含有被删除的元素的数组。
    const valArr = arr.splice(midIndex, 1);
    const midIndexVal = valArr[0];
    const left = []; //存放比基准点小的数组
    const right = []; //存放比基准点大的数组
    //遍历数组,进行判断分配
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < midIndexVal) {
            left.push(arr[i]); //比基准点小的放在左边数组
        } else {
            right.push(arr[i]); //比基准点大的放在右边数组
        }
    }
    //递归执行以上操作,对左右两个数组进行操作,直到数组长度为 <= 1
    return quickSort1(left).concat(midIndexVal, quickSort1(right));
};
const array2 = [5, 4, 3, 2, 1];
console.log('quickSort1 ', quickSort1(array2));
// quickSort1: [1, 2, 3, 4, 5]

归并

思路

  1. 分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数
  2. 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并成为一个完整的数组

合并两个有序数组

  1. 新建一个空数组,用于存放最终排序后的数组
  2. 比较两个有序数组的头部,较小者出队并推入res中
  3. 如果两个数组还有值,就重复第二步

代码实现

数组分成两半

    Array.prototype.mergeSort = function (n) {
      const rec = (arr) => {
        const mid = Math.floor(arr.length /2)
        const left = arr.slice(0,mid)
        const right = arr.slice(mid.arr.length)
      }
      rec(this)
    }
    const arr = [5, 4, 3, 2, 1];
    var result = arr.mergeSort(0)
    console.log('result', result)

递归

    Array.prototype.mergeSort = function (n) {
      const rec = (arr) => {
        if(arr.length === 1){
          return arr
        }
        const mid = Math.floor(arr.length /2)
        const left = arr.slice(0,mid)
        const right = arr.slice(mid.arr.length)
        const orderLeft = rec(left)
        const orderRight = rec(right)
      }
      rec(this)
    }
    const arr = [5, 4, 3, 2, 1];
    arr.mergeSort(0)
    console.log('arr', arr)

合并

    Array.prototype.mergeSort = function (n) {
      const rec = (arr) => {
        if(arr.length === 1){
          return arr
        }
        const mid = Math.floor(arr.length /2)
        const left = arr.slice(0,mid)
        const right = arr.slice(mid,arr.length)
        const orderLeft = rec(left)
        const orderRight = rec(right)
        const res = []
        while(orderLeft.length || orderRight.length){
          if(orderLeft.length && orderRight.length){
            res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
          }else if(orderLeft.length){
            res.push(orderLeft.shift())
          }else if(orderRight.length){
            res.push(orderRight.shift())
          }
        }
        return res;
      }
      const res = rec(this)
    }
    const arr = [5, 4, 3, 2, 1];
    arr.mergeSort(arr)
    console.log('arr', arr)

把rec方法拷贝到数组this上

Array.prototype.mergeSort = function (n) {
      const rec = (arr) => {
        if (arr.length === 1) {
          return arr
        }
        const mid = Math.floor(arr.length / 2)
        const left = arr.slice(0, mid)
        const right = arr.slice(mid, arr.length)
        const orderLeft = rec(left)
        const orderRight = rec(right)
        const res = []
        while (orderLeft.length || orderRight.length) {
          if (orderLeft.length && orderRight.length) {
            res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
          } else if (orderLeft.length) {
            res.push(orderLeft.shift())
          } else if (orderRight.length) {
            res.push(orderRight.shift())
          }
        }
        return res;
      }
      const res = rec(this)
      //只是把一个数组排序好放到rec里面,要把它拷贝到this上
      res.forEach((n, i) => { this[i] = n })
    }
    const arr = [5, 4, 3, 2, 1];
    arr.mergeSort(arr)
    console.log('arr', arr)

时间复杂度

分: O(logN)
合: O(n)
总: O(n*logN)

另一种写法

 // const mergeSort2 = arr => {
    //   //采用自上而下的递归方法
    //   const len = arr.length;
    //   if (len < 2) {
    //     return arr;
    //   }
    //   // length >> 1 和 Math.floor(len / 2) 等价
    //   let middle = Math.floor(len / 2),
    //     left = arr.slice(0, middle),
    //     right = arr.slice(middle); // 拆分为两个子数组
    //   return merge2(mergeSort2(left), mergeSort2(right));
    // }

    function merge2(left, right) {
      const result = []
      // 写法一
      while (left.length || right.length) {
        if (left.length && right.length) {
          if (left[0] <= right[0]) {
            result.push(left.shift())
          } else {
            result.push(right.shift())
          }
        } else if (left.length) {
          result.push(left.shift())
        } else if (right.length) {
          result.push(right.shift())
        }
      }
      // 写法二 while与if相比,if只做一次判断,while是递归循环判断
      // while (left.length && right.length) {
      //   // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
      //   if (left[0] <= right[0]) {
      //     result.push(left.shift());
      //   } else {
      //     result.push(right.shift());
      //   }
      // }
      // while (left.length) result.push(left.shift());

      // while (right.length) result.push(right.shift());

      return result
    }
    const arr2 = [5, 4, 3, 2, 1];
    var result2 = mergeSort2(arr2)
    console.log('result2', result2)

渣渣辉
1.3k 声望147 粉丝

« 上一篇
2020年终总结
下一篇 »
算法-搜索