冒泡排序,选择排序,数据类型之间的区别

48 阅读9分钟
冒泡排序
    var arr = [9, 6, 5, 2, 1, 4]

      需求: 将数组 下标0 与 下标1 交换位置
      arr[0] = arr[1]
      arr[1] = arr[0]

      var temp = arr[0]   // 存储 arr[0] 原本的值
      arr[0] = arr[1]     // 将 arr[0] 的值 重新赋值为 arr[1]
      arr[1] = temp       // 将 原本 arr[0] 的值, 重新赋值给 arr[1]

      console.log(arr)    // [6, 9, 5, 2, 1, 4]

    
       冒泡排序:
           属于数组排序的算法之一
           其实就是通过一种算法, 将 一个乱序的数组, 调整为指定顺序的数组(从大到小/从小到大)
      
       什么是算法?
           解决某一个问题最简单的方式 / 最高效的方式
      
       从 1~10万, 这组数字中, 少了一个数字, 要求我们找出来
           
           常规写法: 后一位 - 前一位   如果 差值 === 2     那么就找出来了
      
           将 1~10万 分为两组  1~55万零1~10万     然后去找这两组数字中 那一组的数量不够5万, 找到之后将这组再次一分为二
    

     准备一个乱序数组
     var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
     console.log('原始数组: ', arr)

     冒泡排序的核心: 对比数组前一项和后一项, 如果前一项的值较大, 那么就往后挪 (这个排序之后是按照从小到大的顺序)
     for (var i = 0; i < arr.length; i++) {
         if (arr[i] > arr[i + 1]) {
             var temp = arr[i]
             arr[i] = arr[i + 1]
             arr[i + 1] = temp
         }
     }
     console.log('1 轮冒泡排序后的数组: ', arr)

     基础版
     for (var k = 0; k < arr.length; k++) {  // 决定执行几次 所谓的 冒泡排序
         for (var i = 0; i < arr.length; i++) {  // 拿到数组的前一项与后一项, 做一轮排序
             if (arr[i] > arr[i + 1]) {
                 var temp = arr[i]
                 arr[i] = arr[i + 1]
                 arr[i + 1] = temp
             }
         }
     }

     console.log('冒泡排序后的数组: ', arr)

    
       基础版 有 3 个地方可以优化
      
           自己优化时: 1. 效果不变     2. 有一个合适的理由
    

     var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
                0  1  2  3  4  5  6  7  8
     console.log('原始数组: ', arr)
          优化1
     for (var k = 0; k < arr.length; k++) {
         for (var i = 0; i < arr.length - 1; i++) {
             
               // i 的值为 0~7, 在最后一次循环的时候, i === 7
               
               // 判断在执行的时候, 相当于是对比 arr[7] > arr[8]
               
               // 判断次数少了一次        arr[8] > arr[9]
             
             if (arr[i] > arr[i + 1]) {
                 var temp = arr[i]
                 arr[i] = arr[i + 1]
                 arr[i + 1] = temp
             }
         }
     }

     console.log('冒泡排序后的数组: ', arr)


     var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1]
                0  1  2  3  4  5  6  7  8
     console.log('原始数组: ', arr)
    
          优化2
     
          k === 01 次循环 确定了 [8] 的值
          k === 12 次循环 确定了 [7][8] 的值
          k === 23 次循环 确定了 [6][7][8] 的值
          k === 34 次循环 确定了 [5][6][7][8] 的值
          k === 45 次循环 确定了 [4][5][6][7][8] 的值
          k === 56 次循环 确定了 [3][4][5][6][7][8] 的值
          k === 67 次循环 确定了 [2][3][4][5][6][7][8] 的值
          k === 78 次循环 确定了 [1][2][3][4][5][6][7][8] 的值
          k === 89 次循环 确定了 [0][1][2][3][4][5][6][7][8] 的值
     
      分析后发现, 外层循环的最后一次 是没有必要, 所以 我们可以将外层循环次数 - 1
    
     for (var k = 0; k < arr.length - 1; k++) {
         for (var i = 0; i < arr.length - 1; i++) {
             if (arr[i] > arr[i + 1]) {
                 var temp = arr[i]
                 arr[i] = arr[i + 1]
                 arr[i + 1] = temp
             }
         }
     }

     console.log('冒泡排序后的数组: ', arr)

    var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1]
               0  1  2  3  4  5  6  7  8
    console.log('原始数组: ', arr)

    
           优化3
      
           在打印之后, 我们发现 每一轮 循环都有一些没有必要的判断
      
           规律是: 每一轮判断的执行次数, 减少 kfor (var k = 0; k < arr.length - 1; k++) {
         console.log('这是第', k + 1, '轮循环, 此时 k === ', k)
         for (var i = 0; i < arr.length - 1 - k; i++) {
             console.log(arr[i], arr[i + 1])
             if (arr[i] > arr[i + 1]) {
                 var temp = arr[i]
                 arr[i] = arr[i + 1]
                 arr[i + 1] = temp
             }
         }
         console.log(arr)
     }

     console.log('冒泡排序后的数组: ', arr)



          完整版
    for (var k = 0; k < arr.length - 1; k++) {
        for (var i = 0; i < arr.length - 1 - k; i++) {
            if (arr[i] > arr[i + 1]) {
                var temp = arr[i]
                arr[i] = arr[i + 1]
                arr[i + 1] = temp
            }
        }
    }
    
选择排序
     var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
                0  1  2  3  4  5  6  7  8
     console.log('原始数组: ', arr)

         第一轮选择排序
         
     var minIndex = 0    // 假设当前最小值的下标 为 0
     for (var i = 1; i < arr.length; i++) {
         if (arr[minIndex] > arr[i]) {   // 如果当前分支执行, 说明在数组中找到了一个 比假设的最小值要小的  元素
             minIndex = i
         }
     }
     
        上边的 for 循环执行完毕后, minIndex 记录的就是 真实的最小的值的下标
       
        此时交换 真实最小值 与 我们假设的最小值
     
     var temp = arr[0]       // 存储 数组下标0的值
     arr[0] = arr[minIndex]  // 将 下标0的值重新赋值     当前数组中最小的值
     arr[minIndex] = temp    // 将 下标 minIndex 的值 重新赋值为 arr[0] 原本的值     此时就完成了 真实最小值与假设最小值位置的交换

     console.log('第 1 轮选择排序后的数组: ', arr)

         第二轮选择排序
         
     var minIndex = 1    // 假设当前最小值的下标为 1
     for (var i = 2; i < arr.length; i++) {
         if (arr[minIndex] > arr[i]) {
             minIndex = i
         }
     }
          for 循环执行完毕 minIndex 就是真实的最小值的下标, 此时交换 真实最小值与假设的最小值的位置即可
     var temp = arr[1]
     arr[1] = arr[minIndex]
     arr[minIndex] = temp

     console.log('第 2 轮选择排序后的数组: ', arr)

         第三轮选择排序
         
     var minIndex = 2    // 假设当前最小值的下标为 2
     for (var i = 3; i < arr.length; i++) {
         if (arr[minIndex] > arr[i]) {
             minIndex = i
         }
     }
      for 循环执行完毕 minIndex 就是真实的最小值的下标, 此时交换 真实最小值与假设的最小值的位置即可
     var temp = arr[2]
     arr[2] = arr[minIndex]
     arr[minIndex] = temp

     console.log('第 3 轮选择排序后的数组: ', arr)




     将上述流程 简化为 for 循环


    var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
             0  1  2  3  4  5  6  7  8
    console.log('原始数组: ', arr)

    
                   第几次循环      假设谁是最小值      和谁交换        内层循环从几开始
      
       k === 0         1               0               0                   1
       k === 1         2               1               1                   2
       k === 2         3               2               2                   3
    
    for (var k = 0; k < arr.length; k++) {
        var minIndex = k
        for (var i = k + 1; i < arr.length; i++) {
            if (arr[minIndex] > arr[i]) {
                minIndex = i
            }
        }
        var temp = arr[k]
        arr[k] = arr[minIndex]
        arr[minIndex] = temp
    }

    console.log('选择排序之后的 数组: ', arr)
数据类型之间的区别
           数据类型分为两种
               1. 基本数据类型(简单数据类型)
               2. 引用数据类型(复杂数据类型)
      
           1. 存储
                           变量的数据存储的地方是 内存中, 内存分为两个 栈内存, 堆内存
                   * 基本数据类型存储在 栈内存中, 比如: string number undefined null  boolean
                   * 复杂数据类型, 将数据本体存放在堆内存中, 比如对象或者数组或者函数
                       然后将指向该内存的地址, 存放在数组名或者对象名或者函数名中
                       数组/对象/函数  名  存放在 栈内存中
                   
                   面试官: 数据类型之间有什么区别?
                           基本数据类型有哪些, 然后他们存储的地方是 栈内存中
                           引用数据类型有哪些, 然后他们数据本体存放的地方是 堆内存中, 然后变量名存储的位置是 栈内存中
      
                   基本数据类型内部存储的是值; 引用数据类型内部存储的是地址
      
           2. 赋值
                   基本数据类型: 赋值以后, 两个变量之间没有任何关系, 相当于将我自己的某一个东西, 复制一份给你, 然后你的就是你的, 我的就是我的
                               例子: 我有一张考试卷, 然后我复制一份给你, 然后你在卷子上书写答案, 并不会影响到我自己原本的这张卷子
                   
                   复杂数据类型: 因为变量内部存储的是指向堆内存的地址, 所以在赋值的时候, 其实是将 这个地址给到了另外一个变量
                                   那么相当于这两个变量存储的是  同一个 钥匙, 所以操作其中一个变量的时候, 会影响另外一个变量
                               例子: 我房间有一个开门的钥匙, 我将我的钥匙复制一份, 给到你, 那么此时我们两个共同拥有了一个房间的钥匙
                                       此时如果我对房间的布局做了修改, 那么你进入房间的时候你能看到布局的修改
                                       此时如果你将房间的所有东西全都偷走, 那么我进入房间的时候 能看到房间所有东西都被偷走了
           
           3. 比较
                   基本数据类型: 就是 值 的比较
                   引用数据类型: 比较的时候 比较的是 存储地址
               
           4. 传参
                   基本数据类型: 将值拷贝一份传递给形参, 在函数内修改不会影响外界
                   引用数据类型: 将存储地址赋值给形参, 在函数内修改会影响外界
    

     var num = 100
     var str = 'abc'
     var obj = {
         a: 1,
         b: 2
     }
     var arr = [1, 2, 3, 4]

     2. 赋值
     var num1 = 100
     var num2 = num1     // num2 === 100
     num2 = 666
     console.log(num1)   // 100
     console.log(num2)   // 666

     var obj1 = {
         name: 'QF001',
         age: 18
     }

         这一步相当于将 变量 obj1 内部存储的 "钥匙", 给到了 变量 obj2, 那么此时 obj2 和 obj1 相当于操作的是一个内存空间
     var obj2 = obj1
         console.log(obj2)   // {name: 'QF001', age: 18}

     obj2.name = 'QF666'
     console.log(obj2)   // {name: 'QF666', age: 18}
     console.log(obj1)   // {name: 'QF666', age: 18}

    
       首先创建了一个 对象 obj1    数据本体为: {name: 'QF001', age: 18}        然后变量名存储的是指向堆内存的地址, 我们假设为 XF001
      
       然后创建了一个对象 obj2 内部存储的是 和 obj1一样的地址
       
           此时相当于 obj2 和 obj1 共同保管一个内存空间
      
           换句话说: 操作obj2会影响 obj1       操作obj1也会影响obj2
    


     var obj1 = {
         name: 'QF001',
         age: 18
     }
     var obj2 = {
         name: 'QF001',
         age: 18
     }

     obj2.name = 'QF666'

     console.log(obj1.name)   // QF001

    
       1. 创建一个对象 obj1    内部存储的地址假设为 XF001
       2. 创建一个对象 obj2    内部存储的地址假设为 XF002
      
       注意此时两个对象除了长得一模一样之外  毫无关系
      
       也就是说  操作 其中一个对象, 并不会影响另外一个
    

     3. 比较
     console.log(obj1 === obj2)  // false
    
       引用数据类型在对比的时候, 对比的是 地址     而这两个对象的地址完全不同, 所以返回的结果就是 false
    

     var num1 = 100
     var num2 = '100'
     console.log(num1 === num2)  //  false

     var arr1 = [1, 2, 3]
     var arr2 = arr1

     console.log(arr1 === arr2)  // true
    
       引用数据类型在对比的时候, 对比的是 地址     因为 他们两个的地址完全相同, 所以返回的结果是 true
    


     4. 传参
    function fn(num) {
        num = 'QF001'
    }

    var str = 'abc'
    fn(str)

     console.log(str)    // 'abc'

    function fn1(o) {
        o.name = 'qwer'
    }

    var obj = {
        name: 'ABC'
    }
    fn1(obj)

    console.log(obj.name)   // 'qwer'