python数据结构第5篇--搜索(草稿)

本文探讨了三种搜索方法:顺序搜索、有序顺序搜索和二分搜索,其中二分搜索在有序列表中表现出较高的效率。此外,介绍了散列的概念,包括散列函数设计、冲突处理(如开放定址法和链接法)以及散列表的实现,强调了散列在实现O(1)查找时间复杂度中的作用。散列函数的选择和冲突解决策略对散列表性能至关重要。
摘要由CSDN通过智能技术生成

5.2 搜索

5.2.1 顺序搜索

def sequetialSearch(alist,item):
    index=0
    found=False

    while index<len(alist) and not found:
        if alist[index]==item:
            found=True 
        else:
            index=index+1

    return found 

对于无序列表,是比较次数为:
在这里插入图片描述
假设列表中的元素按升序排列。如果存在目标元素,那么它出现在n个位置中任意一个位置的可能性仍然一样大,因此比较次数与在无序列表中相同。

不过,如果不存在目标元素,那么搜索效率就会提高。


def orderedSequetialSearch(alist,item):
    index=0 
    found=False
    stop=False 

    while pos<len(alist) and not found and not stop:
        if alist[index]==item:
            found=True 

        else: 
            if alist[pos]>item:
                stop=True
            else:
                pos=pos+1
    return found 

在这里插入图片描述
5.2.2 二分搜索

还可以进一步利用列表有序这个有利条件
进行二分搜索,这是一种分治的思想

两种情况下,都是针对一个更小的列表递归调用二分搜索函数,所以可写成下面的递归版本

alist=[1,4,5,6,7,8,9,10]
item=11

[8,9,10]


def binarySearch(alist,item):
    if len(alist)==0:
        return False
    else:
        minpoint=len(alist)//2
        if alist[minpoint]==item:
            return True 
        else:
            if item<alist[minpoint]:
                return binarySearch(alist[:minpoint],item)
            else:

                return binarySearch(alist[minpoint+1:],item)
                

注意python列表的这个特性:

a=[10]
print(a[1:])
返回值不报错,而是返回一个[]空列表
print(a[1])会报错

时间复杂度分析:

在这里插入图片描述比较到就剩一个元素时,程序结束
此时
在这里插入图片描述2的i次方等于n,所以,i=logn,二分搜索算法的时间复杂度是O(logn)

注意:
实际上在Python中,切片操作的时间复杂度是O(k)。这意味着若采用切片操作,那么二分搜索算法的时间复杂度不是严格的对数阶。所幸,通过在传入列表时带上头和尾的下标,可以弥补这一点。

实际使用中,如果先排序,在二分搜索也是有时间开销的,所以实际中要根据实际情况,综合考虑是使用顺序搜索还是先排序在使用二分搜索.

5.2.3 散列

通过散列构建一个查找时间复杂度为O(1)的数据结构。

散列表是元素集合,其中的元素以一种便于查找的方式存储。散列表中的每个位置通常被称为槽,其中可以存储一个元素。

用列表实现一个散列表:

在这里插入图片描述
散列函数将散列表中的元素与其所属位置对应起来

对散列表中的任一元素,散列函数返回一个介于0和m -1之间的整数

设计一个散列函数(取余函数):

h(item) = item%11

用散列函数计算出每个元素的散列值

在这里插入图片描述
将元素按照散列值放入散列表:

在这里插入图片描述为了统计目前散列表的占用率,使用载荷因子
在这里插入图片描述

搜索目标元素时,仅需使用散列函数计算出该元素的槽编号,并查看对应的槽中是否有值。

因为计算散列值并找到相应位置所需的时间是固定的,所以搜索操作的时间复杂度是O(1)。

不过,还要考虑一种情况,散列冲突,也叫散列碰撞,就是两个元素有相同的散列值的时候该怎么办?

再次详细的定义一下散列表的相关概念!

1.散列函数

完美散列函数:可以确保将不同元素映射到不同的槽,但是,给定任意一个元素集合,没有系统化方法来保证散列函数是完美的

我们的目标是创建这样一个散列函数:冲突数最少,计算方便,元素均匀分布于散列表中.

常见散列函数方法:

取余函数法

折叠法(取余函数法的优化):
折叠法先将元素切成等长的部分(最后一部分的长度可能不同),然后将这些部分相加,用这个和得到散列值.

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

计算字符串的散列值:

在这里插入图片描述在这里插入图片描述

def hash(astring,tablesize):
    sum=0
    for pos in range(len(astring)):
        sum=sum+ord(astring[pos])

    return sum%tablesize

这种算法,异序词得到的散列值是完全相同的.

可以用位置权重的方法来重写这个算法:
在这里插入图片描述

def hash(astring,tablesize):
    sum=0
    for pos in range(len(astring)):
        sum=sum+ord(astring[pos])*(pos+1)

    return sum%tablesize

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策略

再散列(rehash)泛指在发生冲突后寻找另一个槽的过程

开放定址法及扩展线性探测的方式可以用公式表达:

rehash(pos) = (pos +skip)%sizeoftable
(skip)的大小要能保证表中所有的槽最终都访问到,常常建议散列表的大小为素数

平方探测是线性探测的一个变体

如果第一个散列值是h,后续的散列值就是h+1、h+4、h+9、h+16,…

链接法

允许散列表中的同一个位置上存在多个元素。发生冲突时,元素仍然被插入其散列值对应的槽中。不过,随着同一个位置上的元素越来越多,搜索变得越来越困.

链接法的优点是,平均算来,每个槽的元素不多,因此搜索可能更高效。

3.实现映射抽象数据类型

字典是存储键-值对的数据类型。键用来查找关联的值,这个概念常常被称作映射。

映射抽象数据类型定义如下:

  • 它是将键和值关联起来的无序集合
  • 键是不重复的
  • 键和值之间是一一对应的关系

实现一个散列表数据结构:


class HashTable:
    def __init__(self):
        self.size=11
        #散列表大小
        #但选用一个素数很重要,这样做可以尽可能地提高冲突处理算法的效率。
        self.slots=[None]*self.size
        #保存键的列表
        self.data=[None]*self.size 
        #保存值的列表



    def put(self,key,data):
    """put(key, val)往映射中加入一个新的键-值对。如果键已经存在,就用新值替换旧值。"""
        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))

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



    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):
        self.put(key.data)

4.分析散列搜索算法

在分析散列表的使用情况时,最重要的信息就是载荷因子λ。从概念上来说,如果λ很小,那么发生冲突的概率就很小,元素也就很有可能各就各位。如果λ很大,则意味着散列表很拥挤,发生冲突的概率也就很大。因此,冲突解决起来会更难,找到空槽所需的比较次数会更多。若采用链接法,冲突越多,每条链上的元素也越多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值