python之查找(顺序查找、折半查找、索引查找、二叉排序树查找、哈希表查找)

查找

查找可以分为静态查找和动态查找。

静态查找

定义:
在查找过程中,只是对数据元素执行查找操作,而不对其执行其他操作。

顺序查找
折半查找
索引查找

通常用线性表来表示静态查找表,线性表有顺序存储结构和链式存储结构,此处只考虑顺序结构。

顺序查找

顺序查找思路:
将给定值与静态查找表中数据元素的关键字逐个比较,若表中某个元素的关键字与给定值相等,则查找成功,否则查找失败。
python实现:

class StaticTable:
    def __init__(self):
        self.SequenceList = []
    def CreateStaticTable(self,elements):
        self.SequenceList = elements
    '''顺序查找'''
    def SequenceSearch(self,key):
        iPos = -1
        for i in range(len(elements)):
            if self.SequenceList[i] == key:
                iPos = i
                break
        return iPos
if __name__ == '__main__':
    elements=[3,5,8,123,22,54,7,99,300,222]
    table1 = StaticTable()
    table1.CreateStaticTable(elements)
    iPos = table1.SequenceSearch(99)
    print("位置为:%d"  %iPos)

特点:
简单,但是当n很大时,查找效率低。

折半查找

折半查找思路:
折半查找又被称为二分查找,要求静态查找表必须是有序表(其关键字是非递减顺序序列)。确定查找区间后,从区间的中间位置开始,如果与给定值相等,则查找成功,如果大于给定值,在左半部分继续寻找,如果小于给定值,在右半部分继续寻找。
python实现:

class StaticTable:
    def __init__(self):
        self.BinaryList = []        
    def CreateStaticTable(self,elements):
        self.BinaryList = elements 
    '''折半查找'''
    def BinarySearch(self,key):
        low = 0
        high = len(elements)-1
        while low <= high:
            mid = (high+low)//2
            if key > self.BinaryList[mid]:
                low = mid + 1
            elif key < self.BinaryList[mid]:
                high = mid - 1
            else:
                return mid
        return -1
if __name__ == '__main__':
    elements = [3,5,8,22,54,67,99,107,222,300]
    table1 = StaticTable()
    table1.CreateStaticTable(elements)
    iPos = table1.BinarySearch(99)
    print("位置为:%d"  %iPos)    

特点:
需要比较的次数少,查找效率高,但是要求静态查找表必须是有序的。

索引查找

思路:
索引查找又被称为分块查找,其查找效率介于顺序查找和折半查找之间。在使用索引查找算法时,除了需要长度为n的静态查找表以外,还需要与静态查找表相对应的索引表。通常将静态查找表均分为s块,前s-1块中的每一块内的数据元素个数为 ⌊ n / s ⌋ \left \lfloor n/s \right \rfloor n/s(即向下取整),因为n通常不是s的整数倍,所以最后一块的数据元素的个数小于等于t。尽管每一块内元素不一定有序,但分块有序,即前一块中所有元素关键字小于后一块中所有元素的关键字。对静态查找表的每个子表建立索引像,每一项包括两部分:子表最大关键字和子表中第一个元素在静态查找表中的位置。
python实现:

class IndexTable:
    def __init__(self):
        self.data = []
        self.key = []
    def CreatIndexTable(self,elements,n,s):
        t = n // s;
        for i in range (0,len(elements),t):
            self.data.append(i)
            self.key.append(max(elements[i:i+t]))
        return self.data,self.key
class StaticTable:
    def __init__(self):
        self.SequenceList = []        
    def CreateStaticTable(self,elements):
        self.SequenceList= elements 
    def IndexSearch(self,key,FirstPosList,MaxValueList):
        iPos = -1
        low = 0
        high = 0
        num = FirstPosList[1] - FirstPosList[0]
        for i  in range(len(FirstPosList)):
            if key <= MaxValueList[i]:
                low = 0
                high = low+num
                break
            elif key > MaxValueList[i] and key <= MaxValueList[i+1]:
                low = FirstPosList[i+1]
                high = low+num
                break
        '''此处也可以用折半查找,这里用的是顺序查找'''
        if high is not 0:
            for i  in range(low,high):
                if key == self.SequenceList[i]:
                    return i
        else:
            return iPos
            
if __name__ == '__main__':
    test = IndexTable()
    elements = [3,5,22,8,54,36,29,24,63,67,80,102]
    FirstPosList,MaxValueList = test.CreatIndexTable(elements,len(elements),3)
    table1 = StaticTable()
    table1.CreateStaticTable(elements)
    iPos = table1.IndexSearch(80,FirstPosList,MaxValueList)
    print("位置为:%d"  %iPos)

特点:
其平均查找速度比顺序表的平均查找长度小,但比折半查找的平均查找长度大,和顺序查找相比,索引查找增加了存储空间并且要求查找表分块有序;和折半查找相比,索引查找仅需要索引表分块有序,不需要整个静态索引表有序。

动态查找

在查找过程中,不仅对数据元素执行查找操作,同时还执行其他操作,如插入和删除等)。
二叉树排序树(二叉排序树查找):
需要满足以下性质:
(1)根结点的左子树非空,则左子树中所以结点的值都小于根结点的值;
(2)根结点的右子树非空,则右子树中所以结点的值都大于根结点的值;
(3)根结点的左、右子树均是一棵二叉排序树。
中序遍历一棵二叉排序树可得到一个递增序列。
python实现:

'''队列'''
class CircularSequenceQueue():
    def __init__(self):
        self.MaxQueueSize = 10
        self.s = [None for x in range(0,self.MaxQueueSize)]
        self.front = 0
        self.rear = 0
    '''当前队列是否为空'''
    def IsEmptyQueue(self):
        if self.front == self.rear:
            iQueue = True
        else:
            iQueue = False  
        return iQueue
    '''元素进队'''
    def EnQueue(self,x):
        if (self.rear+1)% self.MaxQueueSize != self.front:
            self.rear = (self.rear+1)% self.MaxQueueSize
            self.s[self.rear] = x
        else:
            print("队列已满")
            return
    '''元素出队'''   
    def DeQueue(self):
        if self.IsEmptyQueue():
            print("队列为空")
        else:
            self.front = (self.front+1)% self.MaxQueueSize
            return self.s[self.front]
'''建立树'''        
class Node():      
    def __init__(self,data="#"):        
        self.data = data
        self.LeftChild = None
        self.RightChild = None
class Tree():     
    def __init__(self):      
        self.root = Node()    
    def add(self,data):            
        node  = Node(data)   
        if self.root.data == '#':           
            self.root = node       
        else:          
            myQueue = []      
            treeNode = self.root       
            myQueue.append(treeNode)    
            while myQueue:                  
                treeNode = myQueue.pop(0)     
                if treeNode.LeftChild is None:          
                    treeNode.LeftChild = node        
                    return              
                elif treeNode.RightChild is None:     
                    treeNode.RightChild = node        
                    return              
                else:                 
                    myQueue.append(treeNode.LeftChild)            
                    myQueue.append(treeNode.RightChild)  
    '''中序遍历'''
    def InOrderStack(self,root):
        stack = []
        node = root
        while len(stack)> 0 or node is not None:
            while node is not None:
                stack.append(node)
                node = node.LeftChild
            if len(stack)> 0:
                node = stack.pop()
                print(node.data,end = ' ')
                node = node.RightChild
    '''查找'''
    def SearchBST(self,root,key):
        if not root:
            return
        elif root.data == key:
            return 1
        elif root.data < key:
            return self.SearchBST(root.RightChild,key)
        else:
            return self.SearchBST(root.LeftChild,key)
    '''插入结点'''
    '''若插入值比根结点小,则插入到根结点的左子树中;若相等则不插入直接返回;若插入值比根结点大,则插入到根结点的右子树中'''
    def InsertNode(self,key):
        bt = self.root
        if bt is None:
            self.root = Node(key)
            return
        while True:
            if (key < bt.data):
                if not bt.LeftChild:
                    bt.LeftChild = Node(key)
                    return
                bt = bt.LeftChild
            elif (key > bt.data):
                if not bt.RightChild:
                    bt.RightChild = Node(key)
                    return
                bt = bt.RightChild
            else:
                bt.data = key
                return
    '''删除结点'''
    '''如果是叶子结点,则直接删除;
       若该结点有左子树,且本身是其父结点的左子树,则把该结点的左子树作为其父节点的左子树;
       若该结点有左子树,且本身是其父节点的右子树,则把该结点的左子树作为其父节点的右子树;
       若该结点有右子树,且本身是其父结点的左子树,则把该结点的右子树作为其父节点的左子树;
       若该结点有右子树,且本身是其父结点的右子树,则把该结点的右子树作为其父节点的右子树;
       若该结点既有左子树又有右子树,则用左子树中值最大的结点代替该结点,然后删除左子树中值最大的结点'''
    def DeleteNode(self,key):
        p =self.root
        f = None   # f是p的父节点
        while p:
            if p.data == key:
                break
            f = p
            if p.data > key:
                p = p.LeftChild
            if p.data < key:
                p = p.RightChild
        if not p :
            return
        if p.LeftChild == None and p.RightChild == None:
            if f.LeftChild == p:
                f.LeftChild = None
                return
            if f.RightChild == p:
                f.RightChild = None
                return
        if p.LeftChild is not None and p.RightChild == None:
            if f.LeftChild == p:
                f.LeftChild = p.LeftChild
                return
            if f.RightChild == p:
                f.RightChild = p.LeftChild
                return
        if p.LeftChild == None and p.RightChild is not None:
            if f.LeftChild == p:
                f.LeftChild = p.RightChild
                return
            if f.RightChild == p:
                f.RightChild = p.RightChild
                return
        if p.LeftChild is not None and p.RightChild is not None:
            s = p.LeftChild 
            while s.RightChild is not None:
                s = s.RightChild
            q = s.data                # 中间变量
            self.DeleteNode(s.data) 
            p.data = q
            
if __name__ == '__main__':    
    datas = [55,23,78,1,50,62] 
    tree = Tree() 
    for tdata in datas:      
        tree.add(tdata)
    print ('原中序遍历:')
    tree.InOrderStack(tree.root)
    print()
    result = tree.SearchBST(tree.root,50)
    print(result)
    tree.InsertNode(90)
    print ('插入后中序遍历:')
    tree.InOrderStack(tree.root)
    print()
    tree.DeleteNode(23)
    print ('删除后中序遍历:')
    tree.InOrderStack(tree.root)

哈希表查找

定义

哈希函数:

为某一数据元素的关键字key及其在哈希表中存储位置p之间建立一个对应关系H,即p=H(key),则称关系H为哈希函数。

哈希地址:

根据哈希函数求得某一数据元素的存储位置p,将其称作哈希地址。

哈希表:

根据设定好的哈希函数H和解决冲突的方法,将一组关键字key通过哈希函数H映射到有限的地址空间内,并将哈希函数值H(key)作为其存储位置。

构建哈希函数的方法

(1)直接定址法
思路是将关键字或者关于关键字的某个线性函数作为哈希地址。

H(key) = key
H(key) = a*key+b

该方法不会发生冲突,当关键字的分布基本连续时,可用这种方法构造哈希函数,但在实际中用的不多。
(2)除留余数法
思路是将关键字key除以一个不大于哈希长度m的数p,取其除数作为哈希地址。

H(key) = key % p

该方法的关键是选择适当的p,一般情况下,选择不大于m的最大质数。是构建哈希函数最常用的方法。
(3)数字分析法
思路是分析关键字的每一位数字,取分布均匀的若干位作为哈希地址,取的位数由哈希表的长度决定。比如m=100,则取两位。
该方法需要事先知道关键字的每一位数据的分布情况,并且关键字的位数要大于哈希表的地址位数。
(4)平方取中法
思路是取关键字进行平方运算后的中间几位作为哈希地址,所取的位数由哈希表的长度决定。
(5)折叠法
思路是将关键字分割成位数相同的几部分(最后一部分位数可能不同),然后取这几部分的叠加和(舍去最高位)作为哈希地址,分割的位数由哈希表的长度决定。
该方法适用于哈希地址位数较少,而关键字位数较多且关键字每一位的数据分布大致均匀。

哈希冲突的解决方法:

思路是替发生冲突的关键字寻找新的哈希地址,直到该哈希地址单元是空闲的。按哈希表结构的不同可以分为:

开放地址法
拉链法

开放地址法:
当在哈希地址为 p 0 p_{0} p0时发生冲突,则按照 p i = ( p 0 + d i ) p_{i} = \left ( p_{0}+d_{i} \right ) pi=(p0+di)%m 生成新的哈希地址,直到冲突解决,其中m为哈希表长度, d i d_{i} di为增量序列。根据 d i d_{i} di的不同,生成新的哈希地址的方法被分为:

线性探测法
平方探测法

线性探测法的 d i d_{i} di=1,2,3,…m-1,平方探测法的 d i d_{i} di= 1 2 1^{2} 12,- 1 2 1^{2} 12,… k 2 k^{2} k2,- k 2 k^{2} k2 (k<=m/2)。
线性探测法解决冲突简单,只要未满,一定能解决冲突,但是容易发生“聚集”现象,即哈希地址不同的关键字试图占用同一个新的地址单元。平方探测法避免了“聚集”现象,但是不一定能解决冲突,因为只探测了哈希表一半的地址空间。
拉链法:
将同义词存储在一个单链表中,哈希表的地址单元存储的是各个单链表的头指针,如果没有对应的单链表,该地址单元置为空指针。
该方法的缺点是指针需要额外的存储空间。

哈希表查找实现

开放地址法思路:
对于给定值,根据哈希函数求哈希地址,若该地址里无元素,则查找失败;若有且相等则查找成功;若有不相等则按照解决冲突的方法找新地址继续查找。

以下代码的哈希表是采用开放地址法(线性探测法)解决冲突:

'''假设哈希表长度为6'''
class HashTableList:
    def __init__(self):
        self.data = []
        self.key = [None]*6
    '''创建哈希表'''
    def CreateHashTableList(self,elements):
        for element in elements:
            self.data.append(element)
            p0 = element % 5
            if self.key[p0]== None:
                self.key[p0] = element
            else:
                for i in range(1,6):
                    p1 = (p0+i) % 6  # 线性探测法解决冲突
                    if self.key[p1]== None:
                        self.key[p1] = element
                        break
        return self.data,self.key
    '''哈希表查找'''
    def SearchHashTableList(self,key):
        iPos = -1
        addr = key % 5
        if self.key[addr] is None:
            return iPos
        elif self.key[addr] == key:
            iPos = addr
            return iPos
        elif self.key[addr] is not key:
            for i in range(1,6):
                addr1 = (addr+i) % 6    # 线性探测法解决冲突
                if self.key[addr1]== key:
                    iPos = addr1
                    return iPos
                    break
                if self.key[addr1]== None:
                    return iPos
        
if __name__ == '__main__':
    test = HashTableList()
    elements = [12,9,15,10,21]
    data,key = test.CreateHashTableList(elements)
    print("哈希表为",key)
    iPos = test.SearchHashTableList(99)
    print("位置为:%d"  %iPos) 

拉链法思路:
对于给定值根据哈希函数计算地址,若该地址里内容为None,则查找失败;否则继续查找单链表,若有相同值,则查找成功,否则查找失败。
以下代码的哈希表是采用拉链法解决冲突:

''' 假设哈希表长度为6 '''
class HashTableLinked:
    def __init__(self):
        self.key = [[None]]*6
    '''创建哈希表(利用list套list)'''
    def CreateHashTableLinked(self,elements):
        for element in elements:
            p0 = element % 5
            if self.key[p0] == [None]:
                self.key[p0]= []              # 必须先处理为空列表[]
                self.key[p0].append(element) 
            else:
                self.key[p0].append(element)
        return self.key
    '''哈希表查找(拉链法)'''
    def SearchHashTableLinked(self,key):
        iPos = -1
        addr = key % 5
        if self.key[addr] == None:
            return iPos
        for i in range(0,len(self.key[addr])):
            if self.key[addr][i] == key:
                iPos = addr
                return iPos
        return iPos
if __name__ == '__main__':
    test = HashTableLinked()
    elements = [12,9,15,10,21]
    data = test.CreateHashTableLinked(elements)
    print(data)
    iPos = test.SearchHashTableLinked(9)
    print("位置为:%d"  %iPos) 

你可能感兴趣的:(python之查找(顺序查找、折半查找、索引查找、二叉排序树查找、哈希表查找))