搜索

本文详细介绍了各种搜索技术,包括无序列表顺序搜索、有序列表顺序搜索、二分搜索以及散列(Hash)查找。顺序搜索在无序和有序列表中的应用和实现,以及二分搜索如何利用有序性提高效率。此外,还探讨了索引顺序表的概念,以及处理冲突的开放寻址法。最后,通过Python和C代码展示了各种搜索算法的实现。
摘要由CSDN通过智能技术生成


搜索(或查找)是指从元素集合中找到某个特定元素的算法过程。

静态查找是指只在数据元素集合中查找是否存在关键字等于某个给定关键字的数据元素。

动态查找除包括静态查找的要求外,还包括在查找过程中同时插入数据元素集合中不存在的数据元素,或者从数据元素集合中删除已存在的某个数据元素的要求。

顺序搜索

存储于列表等集合(线性表)中的数据彼此之间存在线性或顺序的关系,每个数据项的位置与其他数据项相关。从集合的一端开始,用给定数据元素的关键字逐个与顺序表中各数据元素的关键字进行比较,若在集合中查找到要查找的元素,则查找成功,函数返回数据元素所在的位置,否则查找失败。

顺序搜索又可分为无序表顺序搜索和有序表(数据元素已经排序)顺序搜索。

无序列表(顺序表)搜索

Python 实现无序表顺序搜索:

def sequentialSearch(alist, item):
    pos = 0
    found = False
    size = len(alist)
    
    while pos < size and not found:
        if alist[pos] == item:
            found = True
        else:
            pos = pos + 1;

    return found, pos

slist = [54, 26, 93, 17, 77, 31, 44, 55, 20, 65]
print(sequentialSearch(slist, 17))

执行结果:

(True, 3)

C 实现无序表顺序搜索:

#include <stdio.h>
#include "list.h"

typedef int DataType;

int seqSearch(List s, DataType item)
{
    int i = 0;
    while (i < s.size && s.items[i] != item)
    {
        i++;
        if (s.items[i] == item)
            return i;
        else
            return -1;
    }
}

有序列表(顺序表)搜索

Python 实现有序表顺序搜索:

def orderedSequentialSearch(alist, item):
    pos = 0
    found = False
    stop = False
    size = len(alist)

    while pos < size and not found and not stop:
        if alist[pos] == item:
            found = True
        else:
            if alist[pos] > item:
                stop = True
            else:
                pos = pos + 1

    return found, pos

oslist = [17, 20, 26, 31, 44, 54, 55, 65, 77, 93]
print(orderedSequentialSearch(oslist, 54))

执行结果:

(True, 5)

C 实现有序表顺序搜索:

#include <stdio.h>
#include "list.h"

int orderSeqSearch(List s, DataType item)
{
    int i = 0;
    while (i < s.size && s.items[i] < item)
    {
        i++;
        if (s.items[i] == item)
            return i;
        else
            return -1;
    }
}

二分搜索

二分搜索可以更高效的在有序表中查找目标元素。在顺序搜索时,如果第一个元素不是目标元素,最多还要比较 n-1 次。但二分搜索不是从第一个元素开始搜索列表,而是从中间的元素着手。如果这个元素就是目标元素,那就立即停止搜索;如果不是,则可以利用列表有序的特征,排除一半的元素。

Python 实现二分搜索:

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, midpoint

二分搜索的递归版本:

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

C 实现二分搜索:

int binarySearch(List s, DataType item)
{
    int low = 0;
    int high = s.size -1;
    int mid;

    while (low <= high)
    {
        mid = (low + high) / 2;
        if (s.items[mid] == item)
        {
            return mid;
        }
        else if (s.items[mid] < item)
        {
            low = mid + 1;
        }
        else if (s.items[mid] > item)
        {
            high = mid - 1;
        }
    }
    return -1;
}

索引顺序表

当顺序表中的数据元素个数非常大时,在顺序表上建立索引表可以很好的提高查找速度。

索引表与书籍目录的用途和构造方法上类同,把在其上建立索引表的顺序表称作主表。主表中存放着数据元素的全部信息,索引标准只存放主表中要查找数据元素的主关键字和索引信息。

要使索引表的查找效率,索引表必须有序,主表中的元素可以按关键字有序或按关键字分段有序。

下图展示了一个主表和一个按关键字 key 建立的索引表的结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHQ0gj54-1616748375554)(.\res\索引表结构图.png)]

key 为被索引的若干个数据元素中关键字的最大值,link 为被索引的若干个数据元素中死一个数据元素的位置编号。

有的时候需要在主表上再建立若干个次关键字索引表,一般方法是:先在主表上建立了一个与主表项完全相同,但只包含索引关键字和该数据元素在主表中位置信息的索引表,再在这样的索引表上建立索引表(把与主表项完全相同,但只包含索引关键字和该数据在元素在主表中位置信息的索引表称作完全索引表)。所以当数据元素个数非常庞大时,可以按照建立索引表的方法对索引表在建立索引表,这样的索引表称为二级索引表,二级以上的索引结构称作多级索引结构。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LQoCdKA-1616748375559)(.\res\带完全索引表的索引表结构图.png)]

等长索引表:索引表中的每个索引项对应主表中的数据元素个数是相等的。

不等长索引表:索引表中不同的索引项和主表中数据元素的个数的对应关系是不同的。不等长索引表结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iV6jDDSb-1616748375562)(.\res\不等长索引表结构图.png)]

索引顺序表上的查找过程,包括在索引表上的查找和在主表上的查找两部分。

假设索引表的长度为 m,主表中每个子表的长度为 s,并假设在索引表上和主表上均采用顺序表查找算法,则索引表上查找算法的平均查找长度为:
A S L = m + 1 2 + s + 1 2 = m + s 2 ASL = \frac{m + 1}{2} + \frac{s + 1}{2} = \frac{m + s}{2} ASL=2m+1+2s+1=2m+s

散列(Hash)

散列表是元素集合,其中的元素以一种便于查找的方式存储。散列表中的每个位置通常被称为,其中可以存储一个元素。槽用一个从 0 开始的整数标记,例如 0 号槽、1 号槽、2 号槽。初始情形下,散列表中没有元素,每个人槽都是空的。

散列函数将散列表中的元素与其所属位置对应起来。

取余散列函数: h(item) = item%n,n 为散列表的大小

举个例子:

假设有一个集合 [54, 26, 93, 17, 77, 31],散列表大小为 11,利用取余函数计算散列值。

元素散列值
5410
264
935
176
770
319

计算除散列值后,将每个元素插入到相应的位置,散列表大小为 11,则存在 11 个槽,但元素散列值只有 6 个,所以只会有 6 个槽被占用。占用率被称为载荷因子
λ = 元 素 个 数 散 列 表 大 小 λ = \frac{元素个数}{散列表大小} λ=
在本例中:
λ = 6 11 λ = \frac{6}{11} λ=116
那么此时的散列表内容为:

012345678910
77NoneNoneNone269317NoneNone3154

搜索目标元素时,仅需使用散列函数计算出该元素的编号,并查看对应的槽中是否有值。因为计算散列值并找到相应位置所需的时间是固定的,所以搜索操作的时间复杂度是 O(1)

给定一个元素集合,能将每个元素映射到不同的槽,这种散列函数被称作完美散列函数。创建一个理想的散列函数,要求冲突数最少,计算方便,元素均匀分布于散列表中。我们可以使用折叠法和平方取中放来扩展取余函数。那么当两个元素的散列值相同呢?

冲突

散列函数将两个元素都放入同一个槽,这种情况叫“冲突”,也叫“碰撞”。

处理冲突

当两个元素被分到同一个槽时,必须通过一种系统方法在散列函数表中安置第二个元素。这个过程被称为处理冲突。

开放定制法:从起始散列值开始,顺序遍历列表,直到找到一个空槽来放置冲突元素,由于时逐个访问槽,因此这个方法被称作线性探测。线性探测有个缺点,就是会使散列表中的元素聚集现象,也就是说,如果一个槽发生太多冲突,线性探测会填满其附近的槽,而这会影响到后续插入的元素。解决这个问题,可以使用再散列和平方探测法。

Python 实现散列表,映射抽象数据类型:

class HashTable:
    def __init__(self):
        self.size = 11
        self.slots = [None] * self.size
        self.data = [None] * self.size

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

    def rehash(self, oldhash, size):
        return (oldhash + 1)%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))

                if self.slots[nextslot] == None:
                    self.slots[nextslot] = key
                    self.data[nextslot] =data
                else:
                    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, data):
        self.put(key,data)

h = HashTable()
h[54] = "cat"
h[26] = "dog"
h[93] = "lion"
h[17] = "tiger"
h[77] = "bird"
h[31] = "cow"
h[44] = "goat"
h[55] = "pig"
h[20] = "chicken"

print(h.slots)
print(h.data)
h[20] = "duck"
print(h.slots)
print(h.data)

执行结果:

[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'duck', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二流人物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值