参考:《大话数据结构》
读书笔记参考:https://www.cnblogs.com/feixuelove1009/p/6148357.html
目录
顺序查找
二分查找,插值查找,斐波那契查找
实现代码和测试如下:
import random,time
class Search:
def sequentialSearch(self, array, key):
# 顺序查找
for i in range(len(array)):
if array[i] == key:
return i
return None
def binarySearch(self, array, key):
# 有序表查找——折半查找
left = 0
right = len(array) - 1
while left < right:
if key >= array[left] and key <= array[right]:
mid = (left + right) // 2
if array[mid] > key:
right = mid
elif array[mid] < key:
left = mid
else:
return mid
continue
def interpolationSearch(self, array, key):
# 有序表查找——插值查找
left = 0
right = len(array) - 1
while left < right:
if key >= array[left] and key <= array[right]:
mid = left + int((right - left) * (key - array[left]) / (array[right] - array[left]))
if array[mid] > key:
right = mid
elif array[mid] < key:
left = mid
else:
return mid
def fibonacciSearch(self, array, key):
# 有序表查找——斐波那契查找
fibonacci_list = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,233, 377, 610, 987,
1597, 2584, 4181, 6765,10946, 17711, 28657, 46368]
n = len(array)
for i in range(len(fibonacci_list)):
if fibonacci_list[i] >= n:
ind = i
break
if fibonacci_list[ind] > n:
array.extend([array[-1]] * (fibonacci_list[ind] - n))
left = 0
right = fibonacci_list[ind] - 1
while left < right and ind - 1 >= 0 and ind - 2 >= 0:
mid = left + fibonacci_list[ind - 1]
if array[mid] < key:
left = mid
ind -= 2
elif array[mid] > key:
right = mid
ind -= 1
else:
return mid
if __name__ == '__main__':
list = [random.randint(0,999) for i in range(10000)]
s = Search()
key = random.randint(0,999)
start = time.clock()
index = s.sequentialSearch(list, key)
end = time.clock()
print('顺序查找 ', end - start, '\n','结果: ', key == list[index])
list1 = sorted(list.copy())
start = time.clock()
index = s.binarySearch(list1, key)
end = time.clock()
print('二分查找 ', end - start, '\n', '结果: ', key == list1[index])
list2 = sorted(list.copy())
start = time.clock()
index = s.interpolationSearch(list2, key)
end = time.clock()
print('插值查找 ', end - start, '\n', '结果: ', key == list2[index])
list3 = sorted(list.copy())
start = time.clock()
index = s.fibonacciSearch(list3, key)
end = time.clock()
print('Fibonacci查找 ', end - start, '\n', '结果: ', key == list3[index])
对于海量的无序数据,为了提高查找速度,一般会为其构造索引表。
索引就是把一个关键字与它相对应的记录进行关联的过程。
一个索引由若干个索引项构成,每个索引项至少包含关键字和其对应的记录在存储器中的位置等信息。
索引按照结构可以分为:线性索引、树形索引和多级索引。
线性索引:将索引项的集合通过线性结构来组织,也叫索引表。
线性索引可分为:稠密索引、分块索引和倒排索引
稠密索引(索引有序,存储无序)
稠密索引指的是在线性索引中,为数据集合中的每个记录都建立一个索引项。
这其实就相当于给无序的集合,建立了一张有序的线性表。其索引项一定是按照关键码进行有序的排列。
这也相当于把查找过程中需要的排序工作给提前做了。
分块索引(块内无序,块间有序)——图书馆的书架有编号,书架上无序
给大量的无序数据集合进行分块处理,使得块内无序,块与块之间有序。
这其实是有序查找和无序查找的一种中间状态或者说妥协状态。因为数据量过大,建立完整的稠密索引耗时耗力,占用资源过多;但如果不做任何排序或者索引,那么遍历的查找也无法接受,只能折中,做一定程度的排序或索引。
分块索引的效率比遍历查找的O(n)要高一些,但与二分查找的O(logn)还是要差不少。
倒排索引——购物网站的物品标签
不是由记录来确定属性值,而是由属性值来确定记录的位置,这种被称为倒排索引。其中记录号表存储具有相同次关键字的所有记录的地址或引用(可以是指向记录的指针或该记录的主关键字)。
倒排索引是最基础的搜索引擎索引技术。
具体参见《大话数据结构》
import random
class BiTNode():
def __init__(self, x, left = None, right = None):
self.val = x
self.lchild = left
self.rchild = right
class BinarySortTree():
def __init__(self):
self._root = None
def is_empty(self):
return self._root is None
def search(self, key):
bt = self._root
while bt:
if bt.val < key:
bt = bt.rchild
elif bt.val > key:
bt = bt.lchild
else:
return bt.val
return None
def insert(self, key):
bt = self._root
# 空树插入的第一步
if not bt:
self._root = BiTNode(key)
return
while True:
if bt.val < key:
if bt.rchild is None:
bt.rchild = BiTNode(key)
return
bt = bt.rchild
else:
if bt.lchild is None:
bt.lchild = BiTNode(key)
return
bt = bt.lchild
def delete(self, key):
parent_node, cur_node = None, self._root
if not self._root:
print('The tree is empty!')
return
while cur_node and cur_node.val != key:
parent_node = cur_node
if key < cur_node.val:
cur_node = cur_node.lchild
else:
cur_node = cur_node.rchild
if not cur_node:
print('No such kty!')
return
if not cur_node.lchild:
# cur_node左子树不存在,直接将cur_node的右子树接入parent_node
if parent_node is None:
self._root = cur_node.rchild
elif parent_node.lchild is cur_node:
parent_node.lchild = cur_node.rchild
else:
parent_node.rchild = cur_node.rchild
# 查找cur_node的左子树的最右节点,将cur_node的右子树链接为该节点的右子树
right_node = cur_node.lchild
while right_node.rchild:
right_node = right_node.rchild
right_node.rchild = cur_node.rchild
# 将新的左子树接入parent_node
if parent_node is None:
self._root = cur_node.lchild
elif parent_node.lchild is cur_node:
parent_node.lchild = cur_node.lchild
else:
parent_node.rchild = cur_node.lchild
def __iter__(self):
data = []
node = self._root
while node or data:
while node:
data.append(node)
node = node.lchild
node = data.pop()
yield node.val
node = node.rchild
if __name__ == '__main__':
list = [random.randint(1,9) for i in range(20)]
bst = BinarySortTree()
for i in list:
bst.insert(i)
for j in bst:
print(j)
bst.delete(1)
for j in bst:
print(j)
二叉排序树最差情况下深度等于n,使得查找性能极差;调整节点使得平衡因子(balance factor)绝对值小于等于1。
详见《大话数据结构》
多路查找树(muitl-way search tree):其每一个节点的孩子可以多于两个,且每一个结点处可以存储多个元素。
2-3树
2-3树:每个结点都具有2个孩子,或者3个孩子,或者没有孩子。(不存在只有一个孩子的情况)
一个2结点包含一个元素和两个孩子(或者没有孩子,不能只有一个孩子)。与二叉排序树类似,其左子树包含的元素都小于该元素,右子树包含的元素都大于该元素。
一个3结点包含两个元素和三个孩子(或者没有孩子,不能只有一个或两个孩子)。
2-3树中所有的叶子都必须在同一层次上。
详见《大话数据结构》
红黑树(补充)
参考:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html
2-3-4树
略。
B树
注:B-tree就是指的B树(B-tree)
维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树。与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作。B-tree算法减少定位记录时所经历的中间过程,从而加快存取速度。普遍运用在数据库和文件系统。”
B树是一种平衡的多路查找树。节点最大的孩子数目称为B树的阶(order):
2-3树是3阶B树,2-3-4是4阶B树。B树的数据结构主要用在内存和外部存储器的数据交互中。
参考:http://www.cnblogs.com/yangecnu/p/Introduce-B-Tree-and-B-Plus-Tree.html
B+树
为了解决B树的所有元素遍历等基本问题,在原有的结构基础上,加入新的元素组织方式后,形成了B+树。
B+树是应文件系统所需而出现的一种B树的变形树,严格意义上将,它已经不是最基本的树了。
B+树中,出现在分支节点中的元素会被当做他们在该分支节点位置的中序后继者(叶子节点)中再次列出。另外,每一个叶子节点都会保存一个指向后一叶子节点的指针。
所有的叶子节点包含全部的关键字的信息,及相关指针,叶子节点本身依关键字的大小自小到大顺序链接
B+树的结构特别适合带有范围的查找。比如查找年龄在20~30岁之间的人。
散列表:所有的元素之间没有任何关系。元素的存储位置,是利用元素的关键字通过某个函数直接计算出来的。这个一一对应的关系函数称为散列函数或Hash函数。采用散列技术将记录存储在一块连续的存储空间中,称为散列表或哈希表(Hash Table)。关键字对应的存储位置,称为散列地址。
散列表是一种面向查找的存储结构。它最适合求解的问题是查找与给定值相等的记录。但是对于某个关键字能对应很多记录的情况就不适用,比如查找所有的“男”性。也不适合范围查找,比如查找年龄20~30之间的人。排序、最大、最小等也不合适。
因此,散列表通常用于关键字不重复的数据结构。比如python的字典数据类型。
设计出一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题。
但是,一般散列函数都面临着冲突的问题。
冲突:两个不同的关键字,通过散列函数计算后结果却相同的现象。
散列函数的构造方法
好的散列函数:计算简单、散列地址分布均匀
例如取关键字的某个线性函数为散列函数:f(key) = a*key + b (a,b为常数)
抽取关键字里的数字,根据数字的特点进行地址分配
将关键字的数字求平方,再截取部分
将关键字的数字分割后分别计算,再合并计算,一种玩弄数字的手段。
最为常见的方法之一。
对于表长为m的数据集合,散列公式为:f(key) = key mod p (p<=m)
mod:取模(求余数)
该方法最关键的是p的选择,而且数据量较大的时候,冲突是必然的。一般会选择接近m的质数。
选择一个随机数,取关键字的随机函数值为它的散列地址。
f(key) = random(key)
总结,实际情况下根据不同的数据特性采用不同的散列方法,考虑下面一些主要问题:
计算散列地址所需的时间
关键字的长度
散列表的大小
关键字的分布情况
记录查找的频率