python数据结构与算法分析--搜索

本文参考于《python数据结构与算法分析》
顺序搜索:依次按照顺序进行访问。
在这里插入图片描述

#无序搜索
from datetime import datetime
def sequentialSearch(alist, item):
    pos = 0
    found = False
    starttime = datetime.timestamp(datetime.now())
    while pos < len(alist) and not found:
        if alist[pos] == item:
            found = True

        else:
            pos += 1
    endtime = datetime.timestamp(datetime.now())
    print(endtime - starttime)
    return found
#测试
print(sequentialSearch([54, 26, 93, 17, 77, 31, 44, 55, 20, 65], 20))
    
#结果
True
1.5020370483398438e-05

在这里插入图片描述

#有序搜索
def orderSequentialSearch(alist, item):
    pos = 0
    found = False
    stop = False
    starttime = datetime.timestamp(datetime.now())
    while pos < len(alist) and not stop and not found:
        if alist[pos] == item:
            found = True
        else:
            if alist[pos] > item:
                stop = True
            else:
                pos += 1
    endtime = datetime.timestamp(datetime.now())
    print(endtime - starttime)
    return found
#测试
print(orderSequentialSearch([17, 20, 26, 31, 44, 54,
                                 55, 65, 77, 93], 20))
#结果
1.6927719116210938e-05
True

在这里插入图片描述
二分查找:首先这个表必须是有序列表,然后从数据中间的元素开始查起,如果小于这个数,重复此步骤在左边进行查找,如果大于这个数,重复此步骤在右边进行查找。
在这里插入图片描述

#有序二分查找
def binarySearch(alist, item):
    first = 0
    last  = len(alist) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            found = True
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1
    return found
#递归版本
def binarySearchRec(alist, item):
    if len(alist) == 0:
        return False
    else:
        midpoint = len(alist) // 2
        if alist[midpoint] == item:
            return True
        else:
            if item < alist[midpoint]:
                return binarySearchRec(alist[:midpoint], item)
            else:
                return binarySearchRec(alist[midpoint+1:],item)

在这里插入图片描述
散列表:是元素集合,其中的元素以一种便于查找的方法存储。散列表中的每个位置通常被称为槽,其中可以存储一个元素。
在这里插入图片描述
散列函数:将散列表中的元素与其所属位置对应起来。对散列表中的任一元素,散列函数返回 一个介于 0 和 m – 1 之间的整数。假设有一个由整数元素 54、26、93、17、77 和 31 构成的集 合。首先来看第一个散列函数,它有时被称作“取余函数”,即用一个元素除以表的大小,并将 得到的余数作为散列值(h(item) = item%11)。表 5-4 给出了所有示例元素的散列值。取余函 数是一个很常见的散列函数,这是因为结果必须在槽编号范围内。(散列表函数一定要简单高效,如果计算量比二分或者搜索还要久,就没有意义了)
在这里插入图片描述
只有当每个元素的散列值不同时,这个技巧才有用。如果集合中的下 一个元素是 44,它的散列值是 0(44%11==0),而 77 的散列值也是 0,这就有问题了。散列函 数会将两个元素都放入同一个槽,这种情况被称作冲突。给定一个元素集合,能将每个元素映射到不同的槽,这种散列函数称作完美散列函数。我们的目标是创建这样一个散列函数:冲突数最少,计算方便,元素均匀分布于散列表中。有多种常见的方法来扩展取余函数,下面介绍其中的几种。

折叠法:先将元素切成等长的部分(最后一部分的长度可能不同),然后将这些部分相加,得到 散列值。假设元素是电话号码 436-555-4601,以 2 位为一组进行切分,得到 43、65、55、46 和 01。将这些数字相加后,得到 210。假设散列表有 11 个槽,接着需要用 210 除以 11,并保留余数 1。 所以,电话号码 436-555-4601 被映射到散列表中的 1 号槽。有些折叠法更进一步,在加总前每 隔一个数反转一次。就本例而言,反转后的结果是:43+56+55+64+01=219,219%11=10。

平方取中法:先将元素取平方,然后提取中间几位数。如 果元素是 44,先计算 442=1936,然后提取中间两位 93,继续进行取余的步骤,得到 5(93%11)。

在这里插入图片描述
处理冲突:当两个元素被分到同一个槽中时,必须通过一种系统化方法在 散列表中安置第二个元素。这个过程被称为处理冲突。前文说过,如果散列函数是完美的,冲突 就永远不会发生。然而,这个前提往往不成立,因此处理冲突是散列计算的重点。

一种方法是在散列表中找到另一个空槽,用于放置引起冲突的元素。简单的做法是从起初的 散列值开始,顺序遍历散列表,直到找到一个空槽。注意,为了遍历散列表,可能需要往回检查 第一个槽。这个过程被称为开放定址法,它尝试在散列表中寻找下一个空槽或地址。由于是逐个 访问槽,因此这个做法被称作线性探测。

整数集合(54, 26, 93, 17, 77, 31, 44, 55, 20)。经过取余散列函数处理后的结果。当我们尝试把 44 放入 0 号槽时,就会产生冲突。采用线性探测,依次检查每个槽,直到找到一个空槽,在本例中 即为 1 号槽。
在这里插入图片描述

同理,55 应该被放入 0 号槽,但是为了避免冲突,必须被放入 2 号槽。集合中的最后一个 元素是 20,它的散列值对应 9 号槽。因为 9 号槽中已有元素,所以开始线性探测,依次访问 10 号槽、0 号槽、1 号槽和 2 号槽,最后找到空的 3 号槽。
一旦利用开放定址法和线性探测构建出散列表,即可使用同样的方法来搜索元素。假设要查 找元素 93,它的散列值是 5。查看 5 号槽,发现槽中的元素就是 93,因此返回 True。如果要查 找的是 20,又会如何呢?20 的散列值是 9,而 9 号槽中的元素是 31。因为可能有冲突,所以不 能直接返回 False,而是应该从 10 号槽开始进行顺序搜索,直到找到元素 20 或者遇到空槽。
线性探测有个缺点,那就是会使散列表中的元素出现聚集现象。也就是说,如果一个槽发生 太多冲突,线性探测会填满其附近的槽,而这会影响到后续插入的元素。在尝试插入元素 20 时, 要越过数个散列值为 0 的元素才能找到一个空槽。图 5-9 展示了这种聚集现象。
在这里插入图片描述
要避免元素聚集,一种方法是扩展线性探测,不再依次顺序查找空槽,而是跳过一些槽,这 样做能使引起冲突的元素分布得更均匀。采用“加 3”探测策略处理冲突后的元素 分布情况。发生冲突时,为了找到空槽,该策略每次跳两个槽。
在这里插入图片描述
再散列泛指在发生冲突后寻找另一个槽的过程。采用线性探测时,再散列函数是 newhashvalue = rehash(oldhashvalue),并且 rehash(pos) = (pos + 1)%sizeoftable。 “加 3”探测策略的再散列函数可以定义为 rehash(pos) = (pos + 3)%sizeoftable。也就是说,可以将再散列函数定义为 rehash(pos) = (pos + skip)%sizeoftable。注意,“跨 步”(skip)的大小要能保证表中所有的槽最终都被访问到,否则就会浪费槽资源。要保证这一 点,常常建议散列表的大小为素数,这就是本例选用 11 的原因。

平方探测是线性探测的一个变体,它不采用固定的跨步大小,而是通过再散列函数递增散列 值。如果第一个散列值是 h,后续的散列值就是 h+1、h+4、h+9、h+16,等等。换句话说,平方 探测的跨步大小是一系列完全平方数。图 5-11 展示了采用平方探测处理后的结果。

在这里插入图片描述
另一种处理冲突的方法是让每个槽有一个指向元素集合(或链表)的引用。链接法允许散列 表中的同一个位置上存在多个元素。发生冲突时,元素仍然被插入其散列值对应的槽中。不过, 随着同一个位置上的元素越来越多,搜索变得越来越困难。采用链接法解决冲突 后的结果。
在这里插入图片描述

实现映射抽象数据类型:字典是最有用的 Python 集合之一。字典是存储键–值对的数据类型。键用来查找关联的值,这个概念常常被称作映射。

  • Map()创建一个空的映射,它返回一个空的映射集合。
  • put(key, val)往映射中加入一个新的键–值对。如果键已经存在,就用值替换旧值。
  • get()根据key拿数据
class HashTable:
    def __init__(self):
        self.size = 11
        self.slots = [None] * self.size #存储键
        self.data = [None] * self.size  #存储数据

    def put(self, key, data):
        hashvalue = self.hashfunction(key, len(self.slots))

        if self.slots[hashvalue] == None:
            self.slots[hashvalue] = key
            self.data[hashvalue] = data

        else:
            if self.slots[hashvalue] == key:
                self.data[hashvalue] = data #替换

            else:
                nextslot = self.rehash(hashvalue, len(self.slots))
                while self.slots[nextslot] != None and self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot, len(self.slots))

                nextslot = self.rehash(nextslot, len(self.slots))
                if self.slots[nextslot] == None:
                    self.slots[nextslot] = key
                    self.data[nextslot] = data

                else:
                    self.data[nextslot] = data

    def hashfunction(self, key, size):
        return key % size

    def rehash(self, oldhash, size):
        return (oldhash+1)%size

    def get(self, key):
        startslot = self.hashfunction(key, len(self.slots))

        data = None
        stop = False
        found = False
        position = startslot

        while self.slots[position] != None and not found and not stop:
            if self.slots[position] == key:
                found = True
                data = self.data[position]

            else:
                position = self.rehash(position, len(self.slots))
                if position == startslot:
                    stop = True
        return data

    #额外的字典功能,可以通过[]方法进行访问。

    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, data):
        self.put(key, data)
#测试
testhash = HashTable()
testhash[54] = "cat"
testhash[26] = "dog"
#结果
[None, None, None, None, 26, None, None, None, None, None, 54]
[None, None, None, None, 'dog', None, None, None, None, None, 'cat']
#测试
testhash[54] = "new_cat"
#结果
[None, None, None, None, 26, None, None, None, None, None, 54]
[None, None, None, None, 'dog', None, None, None, None, None, 'new_cat']

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值