查找算法 - python

查找

根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。


线性查找

线性查找是查找中最为暴力的一种方法,它的查找过程为:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,如果某个记录的关键字和给定值相等,则查找成功,找到所查的记录。如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。

def seqSearch(nums, target):
    if nums == []: return -1

    for i in range(len(nums)):
        if nums[i] == target:
            return i

    return -1

时间复杂度: 最优情况为第一个就是target,最坏需查找到最后一个元素,所以平均时间复杂度 O ( N ) O(N)


二分查找

二分查找针对于有序表而言,基本思想:

  • 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
  • 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
  • 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
 def binarySearch(nums, target):
      if nums == []: return -1
    
      left, right = 0, len(nums) - 1

      while left <= right:
          mid = left + (right - left) // 2
          if nums[mid] == target:
              return mid
          elif nums[mid] < target:
              left = mid + 1
          elif self.nums[mid] > target:
              right = mid - 1
      
      return -1

时间复杂度: O ( log N ) O(\log N)


插值查找

当有序表元素分布均匀时,一种更聪明的方法是根据target的大小自适应的调整查找中点的位置,使得中点的变化更加靠近target,从而间接的减少比较的次数。
m i d = l e f t + t a r g e t n u m s [ l e f t ] n u m s [ r i g h t ] n u m s [ l e f t ] ( r i g h t l e f t ) mid = left + \frac{target - nums[left]}{nums[right] - nums[left]}(right - left)

def interpolationSearch(nums, target):
    if nums == []: return -1

    left, right = 0, len(nums) - 1

    while left <= right:
        mid = left + (right - left) * (target - nums[left]) // (nums[right] - nums[left])
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    
    return -1

时间复杂度: O ( log N ) O(\log N)


斐波那契数列查找

f ( n ) = { 1 , n = 1 , 2 f ( n 1 ) + f ( n 2 ) , n > 2 f(n)=\left\{\begin{array}{l} 1, n=1,2 \\ f(n-1)+f(n-2), n>2 \end{array}\right.

随着数列的递增,前后两个数的比值会越来越接近0.618。它本身也是二分查找的一种提升方法,不同之处在于

隔设置的不同,它是通过斐波那契数列来设置的。斐波那契查找的一个限制就是,nums的数据个数需要是斐波那契数列中的元素之一。如果不满足,需要将数组按最后元素镜像扩容。

def fibSearch(nums, target):
    if nums == []: return -1

    F = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 
            610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368]

    left, right = 0, len(nums) - 1

    # 为了使有序表满足斐波那契数列特性,使用nums[right]进行扩容,
    # 个数由F[k] - 1 - right 确定
    k = 0
    while right > F[k] - 1:
        k += 1
    i = right
    while F[k] - 1 > i:
        nums.append(nums[right])

    while left <= right:
        if k < 2: 
            mid = left
        else: 
            mid = left + F[k - 1] -1

        if target < nums[mid]:
            right = mid - 1
            k -= 1
        elif target > nums[mid]:
            left = mid + 1
            k -= 2
        else:
            if mid <= right:
                return mid
            else:
                return right
    return -1	

分块查找

分块有序需要满足两个条件:块内无序(有序更好,就是代价比较大)、块间有序

对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。索引项结构分为三个数据项:最大关键码、块长和块首指针

查找流程

  • 在分块索引表中查找关键字所在的块,由于索引表是有序的,因此可使用二分查找等算法
  • 根据块首指针找到相应的块,并在块中顺序查找target
def blockSearch(nums, count, target):
    def binarySearch(nums, target):
        left, right = 0, len(nums) - 1

        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
        
        return False

    if nums == []: return -1

    # 有序表元素个数
    l = len(nums)
    # 块长
    blockLen = l // count
    if count * blockLen != l:
        blockLen += 1
    
    for b in range(blockLen):
        block = []
        for i in range(count):
            if b * count + i >= l:
                break
            block.append(nums[b * count + i])
        res = binarySearch(block, target)
        if res != False:
            return b * count + res

    return -1

时间复杂度: O ( log ( m ) + N m ) O(\log(m) + \frac{N}{m})


哈希查找

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f f ,使得每个关键字 k e y key 对应一个存储位置 f ( k e y ) f(key)

常见的散列函数的构造方法:

  • 直接定址法: k ( k e y ) = a × k e y + b k(key) = a \times key + b
  • 数字分析法
  • 平方取中法
  • 除留余数法: f ( k e y ) = k e y  mod  p f(key) = key \ \text{mod} \ p
  • 随机数法

解决散列冲突的方法

  • 开放地址法: f i ( k e y ) = ( f ( k e y ) + d i )  MOD  m f_i(key) = (f(key) + d_i) \ \text{MOD} \ m ,其中常用线性探测法和随机探测法
  • 再散列函数法:发生冲突时换散列函数
  • 链地址法:将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表前面的指针
  • 公共溢出区法
class HashTable:
    def __init__(self, size):
        self.elem = [None for i in range(size)]  # 使用list数据结构作为哈希表元素保存方法
        self.count = size  # 最大表长
 
    def hash(self, key):
        return key % self.count  # 散列函数采用除留余数法
 
    def insert_hash(self, key):
        """插入关键字到哈希表内"""
        address = self.hash(key)  # 求散列地址
        while self.elem[address]:  # 当前位置已经有数据了,发生冲突。
            address = (address+1) % self.count  # 线性探测下一地址是否可用
        self.elem[address] = key  # 没有冲突则直接保存。
 
    def search_hash(self, key):
        """查找关键字,返回布尔值"""
        star = address = self.hash(key)
        while self.elem[address] != key:
            address = (address + 1) % self.count
            if not self.elem[address] or address == star:  # 说明没找到或者循环到了开始的位置
                return False
        
        return True

时间复杂度: O ( 1 ) O(1)


二叉树搜索

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:

  • 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 任意节点的左、右子树也分别为二叉查找树

基本思想: 先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。

时间复杂度: 插入和查找均为 O ( log N ) O(\log N) ;当插入和删除元素时,如果没有保证树的平衡,最坏情况下为 O ( N ) O(N)

class TreeNode(object):
    def __init__(self, val, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right

class Tree(object):
    def __init__(self, root = None):
        self.list = []
        self.root = root
        self.preorder_find = False
        self.inorder_find = False
        self.postorder_find = False
    
    # 构建树
    def add(self, item):
        self.list.append(item)

        node = TreeNode(item)
        if not self.root:
            self.root = node
            return 
        else:
            queue = []
            queue.append(self.root)
            while queue:
                cur_node = queue.pop()
                if not cur_node.left:
                    cur_node.left = node
                    return
                elif not cur_node.right:
                    cur_node.right = node
                    return
                else:
                    queue.append(cur_node.left)
                    queue.append(cur_node.right)

    def bfsSearch(self, target):
        if not self.root: return ''

        queue = [self.root]
        while queue:
            nextLayers = []
            for node in queue:
                if node.val == target:
                    return True
                if node.left:
                    nextLayers.append(node.left)
                if node.right:
                    nextLayers.append(node.right)

            del queue[0]
            queue = nextLayers

        return False

    def preOrderSearch(self, root, target):
        if not root:
            return 

        if root.val == target:
            self.preorder_find = True
        self.preOrderSearch(root.left, target)
        self.preOrderSearch(root.right, target)

    def inOrderSearch(self, root, target):
        if not root:
            return 

        self.inOrderSearch(root.left, target)
        if root.val == target:
            self.inorder_find = True
        self.inOrderSearch(root.right, target)
    
    def postOrderSearch(self, root, target):
        if not root:
            return 

        self.postOrderSearch(root.left, target)
        self.postOrderSearch(root.right, target)
        if root.val == target:
            self.postorder_find = True
        

时间复杂度: O ( log N ) O(\log N)


完整测试代码:

def seqSearch(nums, target):
    if nums == []: return -1

    for i in range(len(nums)):
        if nums[i] == target:
            return i

    return -1

def binarySearch(nums, target):
    if nums == []: return -1

    left, right = 0, len(nums) - 1

    while left <= right:
        mid = left + (right - left) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    
    return -1


def interpolationSearch(nums, target):
    if nums == []: return -1

    left, right = 0, len(nums) - 1

    while left <= right:
        mid = left + (right - left) * (target - nums[left]) // (nums[right] - nums[left])
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    
    return -1

def fibSearch(nums, target):
    if nums == []: return -1

    F = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 
            610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368]

    left, right = 0, len(nums) - 1

    # 为了使有序表满足斐波那契数列特性,使用nums[right]进行扩容,
    # 个数由F[k] - 1 - right 确定
    k = 0
    while right > F[k] - 1:
        k += 1
    i = right
    while F[k] - 1 > i:
        nums.append(nums[right])

    while left <= right:
        if k < 2: 
            mid = left
        else: 
            mid = left + F[k - 1] -1

        if target < nums[mid]:
            right = mid - 1
            k -= 1
        elif target > nums[mid]:
            left = mid + 1
            k -= 2
        else:
            if mid <= right:
                return mid
            else:
                return right
    return -1


def blockSearch(nums, count, target):
    def binarySearch(nums, target):
        left, right = 0, len(nums) - 1

        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
        
        return False

    if nums == []: return -1

    # 有序表元素个数
    l = len(nums)
    # 块长
    blockLen = l // count
    if count * blockLen != l:
        blockLen += 1
    
    for b in range(blockLen):
        block = []
        for i in range(count):
            if b * count + i >= l:
                break
            block.append(nums[b * count + i])
        res = binarySearch(block, target)
        if res != False:
            return b * count + res

    return -1


class HashTable:
    def __init__(self, size):
        self.elem = [None for i in range(size)]  # 使用list数据结构作为哈希表元素保存方法
        self.count = size  # 最大表长
 
    def hash(self, key):
        return key % self.count  # 散列函数采用除留余数法
 
    def insert_hash(self, key):
        """插入关键字到哈希表内"""
        address = self.hash(key)  # 求散列地址
        while self.elem[address]:  # 当前位置已经有数据了,发生冲突。
            address = (address+1) % self.count  # 线性探测下一地址是否可用
        self.elem[address] = key  # 没有冲突则直接保存。
 
    def search_hash(self, key):
        """查找关键字,返回布尔值"""
        star = address = self.hash(key)
        while self.elem[address] != key:
            address = (address + 1) % self.count
            if not self.elem[address] or address == star:  # 说明没找到或者循环到了开始的位置
                return False
        
        return True

class TreeNode(object):
    def __init__(self, val, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right

class Tree(object):
    def __init__(self, root = None):
        self.list = []
        self.root = root
        self.preorder_find = False
        self.inorder_find = False
        self.postorder_find = False
    
    # 构建树
    def add(self, item):
        self.list.append(item)

        node = TreeNode(item)
        if not self.root:
            self.root = node
            return 
        else:
            queue = []
            queue.append(self.root)
            while queue:
                cur_node = queue.pop()
                if not cur_node.left:
                    cur_node.left = node
                    return
                elif not cur_node.right:
                    cur_node.right = node
                    return
                else:
                    queue.append(cur_node.left)
                    queue.append(cur_node.right)

    def bfsSearch(self, target):
        if not self.root: return ''

        queue = [self.root]
        while queue:
            nextLayers = []
            for node in queue:
                if node.val == target:
                    return True
                if node.left:
                    nextLayers.append(node.left)
                if node.right:
                    nextLayers.append(node.right)

            del queue[0]
            queue = nextLayers

        return False

    def preOrderSearch(self, root, target):
        if not root:
            return 

        if root.val == target:
            self.preorder_find = True
        self.preOrderSearch(root.left, target)
        self.preOrderSearch(root.right, target)

    def inOrderSearch(self, root, target):
        if not root:
            return 

        self.inOrderSearch(root.left, target)
        if root.val == target:
            self.inorder_find = True
        self.inOrderSearch(root.right, target)
    
    def postOrderSearch(self, root, target):
        if not root:
            return 

        self.postOrderSearch(root.left, target)
        self.postOrderSearch(root.right, target)
        if root.val == target:
            self.postorder_find = True

if __name__ == "__main__":
    # 5
    nums = [1,2,4,6,7,9,12,34]
    target = 9
    # 线性查找
    print (seqSearch(nums, target))
    # 二分查找
    print (binarySearch(nums, target))
    # 插值查找
    print (interpolationSearch(nums, target))
    # 斐波那契数列查找
    print (fibSearch(nums, target))
    # 块查找
    # 第二个参数是块的长度,最后一个参数是要查找的元素
    print (blockSearch(nums, 3, target))
   
    # 哈希查找
    hash_table = HashTable(12)
    for i in nums:
        hash_table.insert_hash(i)
 
    print(hash_table.search_hash(target))

    # 二叉树查找
    t = Tree()
    for i in nums:
        t.add(i)

    print(t.bfsSearch(target))

    t.preOrderSearch(t.root, target)
    print (t.preorder_find)

    t.inOrderSearch(t.root, target)
    print (t.inorder_find)

    t.postOrderSearch(t.root, target)
    print (t.postorder_find)

八个常用查找算法——python3实现

[Data Structure & Algorithm] 七大查找算法

发布了449 篇原创文章 · 获赞 122 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/Forlogen/article/details/105215577