查找可以分为静态查找和动态查找。
定义:
在查找过程中,只是对数据元素执行查找操作,而不对其执行其他操作。
顺序查找
折半查找
索引查找
通常用线性表来表示静态查找表,线性表有顺序存储结构和链式存储结构,此处只考虑顺序结构。
顺序查找思路:
将给定值与静态查找表中数据元素的关键字逐个比较,若表中某个元素的关键字与给定值相等,则查找成功,否则查找失败。
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)