03 排序算法

直接移动 ( 交换数据位置 )
逻辑移动 ( 改变指针,更快 )

  • 冒泡排序法 / 交换排序法
    从第一个元素开始,比较两个相邻元素的大小,若大小顺序有误,则对调后再进行下一次元素比较,确定最后一个元素位于正确的顺序。接着再进行第二次扫描,直到完成所有元素的排序。

      for i in range(len(data)-1, 0, -1):
          for j in range(i):
              if data[j] > data[j+1]:
                  data[j], data[j+1] = data[j+1], data[j]
    
  • 选择排序法( Selection Sort )
    算是枚举法的应用
    反复从未排序的数列中取出最小的元素,加入到另一个数列中。

      for i in range(len(data)-1):
          for j in range(i+1, len(data)):
              if data[i] > data[j]:
                  data[i], data[j] = data[j], data[i]
    
  • 插入排序法( Insert Sort )
    将数组中的元素逐一与 已排好序 的数据进行比较,前两个元素先排好,再将第三个元素插入合适的位置,重复直到完成。

      for i in range(1, len(data)):
          cur = data[i]
          pre = i - 1
          while pre >= 0 and cur < data[pre]:
              data[pre + 1] = data[pre]  # 把数后置一位,给小数腾地儿
              pre -= 1
          # pre = -1,说明该值是目前最小的,把他放在第一个,即 pre = 0
          data[pre + 1] = cur
    
  • 希尔排序法( Shell Sort )
    可以减少插入排序搬移数据的次数。
    将数据区分成 特定间隔 的几个小区快,以插入排序法排完区块内的数据后再渐渐减少隔离的距离。

    • 划分数,不一定是 2,质数最好。间隔为 8/2
      8,7,6,5,4,3,2,1
      (8, 4),(7, 3),(6, 2),(5, 1) # 并不是真的把两个数搬到一起,只是在形式上
      插入排序后
      (4, 8),(3, 7),(2, 6),(1, 5)
      再缩小间隔 (8/2)/2
      (4, 3, 2, 1),(8, 7, 6, 5)
      插入排序后
      (1, 2, 3, 4),(5, 6, 7, 8)
      jmp = len(data) // 2
      while jmp != 0:
          # 插入排序
          for i in range(jmp, len(data)):
              tmp = data[i]
              j = i - jmp
              while j >= 0 and tmp < data[j]:
                  data[j + jmp] = data[j]
                  j -= jmp
              data[j + jmp] = tmp
          jmp = jmp // 2
    
  • 合并排序法( Merge Sort )
    针对以排好序的两个或两个以上的数列(或数据文件),通过合并的方式,将其组合成一个大的且已排好序的数列(或数据文件)。
    2 路( 2-way )合并排序:
    ① 将 N 个长度为 1 的键值,成对的合并成 N/2 个长度为 2 的有序键值组
    ② 将 N/2 个长度为 2 的键值组,成对的合并成 N/4 个长度为 4 的有序键值组
    ③ 不断合并,直到键值组长度为 N,且已排好序

      l3 = []
      idx1 = idx2 = 0
      for i in range(len(l1) + len(l2)-2):
          if l1[idx1] < l2[idx2]:
              l3.append(l1[idx1])
              idx1 += 1
              if idx1 == len(l1):
                  l3.extend(l2[idx2:])
                  break
          else:
              l3.append(l2[idx2])
              idx2 += 1
              if idx2 == len(l2):
                  l3.extend(l1[idx1:])
                  break
    
  • 快速排序法( Quick Sort ) / 分割交换排序法
    目前公认最佳。使用了分而治之( Divide and Conquer )。
    先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分为两部分。小于中间值的再左边,大于的再右边。再以同样的方式分别处理左、右两边数据,直到排完序为止。
    假设有 n 项记录 R1,R2,R3,...,Rn,其键值为 k1, k2,k3,...,kn:
    ① 先假设 K 的值为第一个键值
    ② 从左向右找出键值 Ki,使得 Ki > K
    ③ 从右向左找出键值 Kj,是的 Kj < K
    ④ 如果 i < j,那么 Ki 与 Kj 互换,并回到步骤 ②
    ⑤ 若 i ≥ j,则将 K 与 Kj 互换,并以 j 为基准点分割成左右两部分。然后针对左右两部分进行步骤 ① 至 ⑤,直到左半边键值等于右半边键值

      def quick(d, size, lf, rg):
          # 第一项键值为 d[lf]
          # size = len(d)
          if lf < rg:  # 左右两边索引值
              # ②
              lf_idx = lf + 1
              while d[lf_idx] < d[lf]:
                  if lf_idx + 1 == size:
                      break
                  lf_idx += 1
              # ③
              rg_idx = rg
              while d[rg_idx] > d[lf]:
                  rg_idx -= 1
              # ④
              while lf_idx < rg_idx:
                  d[lf_idx], d[rg_idx] = d[rg_idx], d[lf_idx]
                  lf_idx += 1
                  while d[lf_idx] < d[lf]:
                      lf_idx += 1
                  rg_idx -= 1
                  while d[rg_idx] > d[lf]:
                      rg_idx -= 1
              # ⑤
              d[lf], d[rg_idx] = d[rg_idx], d[lf]
              quick(d, size, lf, rg_idx-1)  # 以rg_idx为基准点分成左右两半,以递归方式分别为左右两边进行排序直至完成排序
              quick(d, size, rg_idx+1, rg)
    
  • 基数排序法
    并不需要元素间进行比较,属于一种分配模式排序方式。
    按比较的方向可分为最高位优先 ( Most Significant Digit First , MSD ) 和最低位优先 ( Least Significant Digit First , LSD ) 两种。
    MSD 是从最左边的位数开始比较,LSD 是从最右边的位数开始比较。

      def radix(data):
          """0~999 整数比较"""
          n = 1  # n 为基数,从个位开始排序
          while n <= 100:
              tmp = [[None] * 100 for row in range(10)]  # 设置暂存数组 [[n位为0的所有元素], [?1], [?2], ... , [?9]]。
              for i in range(len(data)):
                  m = (data[i] // n) % 10  # n 位的数值,n=1为个位,n=10为10位
                  tmp[m][i] = data[i]  # 放入棋盘对应位置
              k = 0
              for i in range(10):
                  for j in range(len(data)):
                      if tmp[i][j] is not None:
                          data[k] = tmp[i][j]  # 读出棋盘里的值,重构数组
                          k += 1
              n *= 10
    

    原数组 [3, 5, 32, 74, 169, 2, 111, 0]
    过程:
    比较个位 [0, 111, 32, 2, 3, 74, 5, 169]
    比较十位 [0, 2, 3, 5, 111, 32, 169, 74]
    比较百位 [0, 2, 3, 5, 32, 74, 111, 169]
    结果:
    [0, 2, 3, 5, 32, 74, 111, 169]


冒泡、选择、插入排序三种数据搬移量最大的是 插入排序。
合并排序法是稳定的。不会改变两个相同数字的原有顺序。

posted @ 2019-09-25 10:25  catyuang  阅读(243)  评论(0编辑  收藏  举报