04 查找与哈希算法

静态查找(Static Search):在查找过程中,该查找数据不会有添加、删除或更新等操作。
动态查找 (Dynamic Search)

  • 顺序查找法/线性查找法
    将数据一项一项地按顺序逐个查找,不管数据顺序如何,都得从头到尾遍历一次。
    优点:查找前不需要任何处理与排序。
    缺点:慢。适合小数据。

  • 二分法查找
    数据事先 排好序 了。
    将数据分割成两等份,再比较键值与中间值的大小,如果键值小于中间值,可确定要查找的数据在前半段,否则在后半段。

      def bin_search(data, val):
          low = 0
          high = len(data) - 1
          while low <= high:
              mid = (low+high) // 2
              if data[mid] < val:
                  low = mid + 1
              elif data[mid] > val:
                  high = mid - 1
              else:
                  return mid
          return -1
    
  • 插值查找法/插补查找法(Interpolation Search)
    二分法的改进。
    按照数据位置的分布,利用公式预测数据所在位置,在以二分法的方式逐渐逼近。
    使用插值法是假设数据平均分布在数组中,而每一项的数据的差距相当接近或有一定的距离比例。
    插值法的公式为:
    Mid = low + (( key - data[low] ) / ( data[high] - data[low] )) * ( high - low )
    其中,key 是要查找的键,data[high],data[low]是剩余待查数据中的最大、最小值。
    假设数据有 n 项,插值法查找的步骤如下:
    ① 将记录按从小到大的顺序给予1、2、3、...、n的编号
    ② 令 low = 1,high = n
    ③ 当 low < high 时,重复 ④ 和 ⑤
    ④ 令 Mid = low + (( key - data[low] ) / ( data[high] - data[low] )) * ( high - low )
    ⑤ 若 key < keyMid 且 high ≠ Mid - 1,则令 high = Mid - 1;若 key = keyMid 成功查到;若 key > keyMid 且 low ≠ Mid + 1,则令 low = Mid + 1

      def interpolation_search(data, val):
          low = 0
          high = len(data) - 1
          while low < high:
              mid = low + int((val - data[low]) * (high - low) / (data[high] - data[low]))
              if mid > high:
                  return -1
              if data[mid] > val:
                  high = mid - 1
              elif data[mid] < val:
                  low = mid + 1
              else:
                  return mid
          return -1
    
  • 常见哈希法

    • 除留余数法
      将数据除以某一个常数后,取余数来当索引。
      h( key ) = key mod B
      B 最好是质数
    • 平方取中法
      例如 65,求平方得 4225,取百位数和十位数作为键值。f( 22 ) = 65。
      如果有10个空间,而取百位和个位数的有100个(00~99)。需要把第一次求得的键除以 10 取整(压缩),才能将 100 个可能产生的值对应到 10 个空间。f( 2 ) = 65。
    • 折叠法
      将数据转换成一串数字后,先将这串数字拆成几个部分,再把它加起来,就可以计算出这个键值的 Bucket Address(桶地址)。
      例如,转换后的数字位 1238765439873,以 4 个数字为一部分,1238、7654、3987、3,将四个数字相加即为索引值。这种直接相加的做法称为”移动折叠法“。
      为了降低碰撞,可以将每一部分数字中的奇数和偶数反转再相加。这种做法叫”边界折叠法“(folding at the boundaries)。例如将偶数反转,8321、4567、3987、3 相加得出桶地址。
    • 数字分析法
      适用于数据不会更改,且为数字类型的静态表。
      在决定哈希函数时先逐一检查数据的相对位置和分布情况,将重复性高的部分删除。
  • 溢出( Overflow )与碰撞( Collision )

    • 线性探测法
      当发生碰撞时,若该索引对应的存储位置已有数据,则以线性的方式往后往后寻找空的存储位置,一旦找到就把数据放进去。线性探测法通常把哈希的位置视为环形结构,后面的位置被填满前面还有位置时,可以将数据放到前面。
        import random
      
        INDEXBOX = 10  # 哈希表可放元素个数
        MAXNUM = 7  # 数据个数
      
        def print_data(data, max_number):  # 打印数组子程序
            print('\t', end='')
            for i in range(max_number):
                print('[%2d] ' % data[i], end='')
            print()
      
        def create_table(num, index):  # 建立哈希表子程序
            tmp = num % INDEXBOX  # 哈希函数 = 数据 % INDEXBOX
            while True:
                if index[tmp] == -1:  # 如果数据对应的数据是空的
                    index[tmp] = num  # 则直接存入数据
                    break
                else:
                    tmp = (tmp + 1) % INDEXBOX  # 否则往后找位置存放
       
       # 主程序
        index = [None] * INDEXBOX
        data = [None] * MAXNUM
        print('原始数组值:')
        for i in range(MAXNUM):
            data[i] = random.randint(1, 20)
        for i in range(INDEXBOX):
            index[i] = -1
        print_data(data, MAXNUM)
      
        print('哈希表内容:')
        for i in range(MAXNUM):
            create_table(data[i], index)
            print(' %2d =>' %data[i], end='')
            print_data(index, INDEXBOX)
      
        print('完成哈希表:')
        print_data(index, INDEXBOX)
      

      原始数组值:
      [ 7] [13] [13] [12] [14] [15] [12]
      哈希表内容:
      7 => [-1] [-1] [-1] [-1] [-1] [-1] [-1] [ 7] [-1] [-1]
      13 => [-1] [-1] [-1] [13] [-1] [-1] [-1] [ 7] [-1] [-1]
      13 => [-1] [-1] [-1] [13] [13] [-1] [-1] [ 7] [-1] [-1]
      12 => [-1] [-1] [12] [13] [13] [-1] [-1] [ 7] [-1] [-1]
      14 => [-1] [-1] [12] [13] [13] [14] [-1] [ 7] [-1] [-1]
      15 => [-1] [-1] [12] [13] [13] [14] [15] [ 7] [-1] [-1]
      12 => [-1] [-1] [12] [13] [13] [14] [15] [ 7] [12] [-1]
      完成哈希表:
      [-1] [-1] [12] [13] [13] [14] [15] [ 7] [12] [-1]

    • 平方探测法
      当溢出发生时,下一次查找的地址是 ( f(x)+i² ) mod B 与 ( f(x) - i² ) mod B。例如数据值 key,哈希函数 f:
      第一次查找:f ( key )
      第二次查找:( f ( key ) + 1² ) % B
      第三次查找:( f ( key ) - 1² ) % B
      第四次查找:( f ( key ) + 2² ) % B
      第五次查找:( f ( key ) - 2² ) % B
      ... ...
      第 n 次查找:( f ( key ) ± ( ( B-1 ) / 2 )² ) % B,其中,B必须为 4j + 3 型的质数,且 1 ≤ i ≤ ( B-1 ) / 2
    • 再哈希法
      先设置一系列哈希函数,如果使用第一种哈希函数出现溢出时就改用第二种,如果第二种也溢出,就是用第三种,...,一直到没有溢出。

用哈希法将下列 7 个数字存在0、1...6的 7 个位置:101,186,16,315,202,572,463。若要存入 1000 开始的 11 个 位置,如何存放?
data mod 7
( data mod 11 ) + 1000

碰撞:两个不同的数据经过哈希运算对应到相同的地址。

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