经典算法梳理-查找/搜索算法

数组中的元素查找

顺序查找

顺序查找

顺序查找思路比较简单,就是遍历,有就返回,所以数组有序无序无所谓,最优时间复杂度为O(1),最坏为O(n)。

# 如果只找到第一个位置
def sequentail_search_first(lis,target):  
    for ii in range(len(lis)):
        if lis[ii]==target:
            return ii
    return -1
# 想要找到所有的位置
def sequentail_search(lis,target):  
	pos=[]
    for ii in range(len(lis)):
        if lis[ii]==target:
            pos.append(ii)
    if len(pos)==0:    	
    	return -1
    else:
    	return pos

分块查找(基于顺序查找改进版本)

先把数组分成k个部分,找每一部分的最大值,记录下每一个模块第一个数的索引,比较目标值与每一个模块最大值的大小,如果比模块最大值小就有可能在当前模块,锁定分组,在分组中执行顺序查找。

# 如果只返回第一个找到的数值
def block_search_first(lis,target,block_len=10):
	index_value={}
	for ii in range(len(lis)):
		if ii%block_len==0:
			index_value[ii]=max(lis[ii:ii+block_len])
	for key,value in index_value.items():
		if target<=value:
			out=sequentail_search_first(lis[key:key+block_len],target)
			if out!=-1:
				return out
	return -1
# 如果要返回所哦有值
def block_search(lis,target,block_len=10):
    index_max={}
    for ii in range(len(lis)):
        if ii%block_len==0:
            index_max[ii]=max(lis[ii:ii+block_len])
    pos=[]
    for key,value in index_max.items():
        if value>=target:
            res=sequentail_search(lis[key:key+block_len],target)
            if res!=-1:
                pos.extend([key+jj for jj in res])
    if len(pos)==0:
        return -1
    else:
        return pos

二分法查找

有序数组二分查找

实际上就是猜数字,每次都猜中间的数字,第一轮猜最中间的,如果大了,就在右边找,如果小了就在左边找,代码上只要更新左右的位置和中间的位置,直到找到为止。要求数组比如是有序的。

def binary_search(lis,target):
    head=0
    tail=len(lis)-1
    while head<=tail:
        mid=(head+tail)//2
        if lis[mid]==target:
            return mid
        elif lis[mid]>target:
            tail=mid-1
        else:
            head=mid+1   
    return -1

平均时间复杂度为O(logn)

插值查找

是二分法的改进版本,实际上就是每次猜数不猜中间的,而是猜尽可能离目标值近的,咋做到的呢,每次猜数的位置=head+(tail-head)*((target-lis[head])/(lis[tail]-lis[head]))。

def binary_search(lis,target):
    head=0
    tail=len(lis)-1
    while head<=tail:
        mid=head+int((tail-head)*((target-lis[head])/(lis[tail]-lis[head])))
        if lis[mid]==target:
            return mid
        elif lis[mid]>target:
            tail=mid-1
        else:
            head=mid+1   
    return -1

查找成功或者失败的时间复杂度均为O(log(logn))

斐波那契查找

也是二分法的改进版本,改mid的位置。以下链接讲得很清楚。大概的思想是,先构建一个斐波那契数列,然后找刚好>=lis的长度的最小值,比如lis长度是25,那么最近的斐波那契数列是34,则用lis的最后一个数值填充lis长度到34,34的前两个数是13和21,那么先猜head+13-1为mid,看与target的大小,之后重复该过程。
斐波那契查找

哈希查找

复杂度为O(1),就是把要找的数变成dict的key,索引变成dict的value,以便于很快的找到数对应的位置。随便在leetcode上找了个case-猜数字游戏,题目如下:

你正在和你的朋友玩 猜数字(Bulls and Cows)游戏:你写下一个数字让你的朋友猜。每次他猜测后,你给他一个提示,告诉他有多少位数字和确切位置都猜对了(称为“Bulls”, 公牛),有多少位数字猜对了但是位置不对(称为“Cows”, 奶牛)。你的朋友将会根据提示继续猜,直到猜出秘密数字。
请写出一个根据秘密数字和朋友的猜测数返回提示的函数,用 A 表示公牛,用 B 表示奶牛。
请注意秘密数字和朋友的猜测数都可能含有重复数字。

示例 1:
输入: secret = “1807”, guess = “7810”
输出: “1A3B”
解释: 1 公牛和 3 奶牛。公牛是 8,奶牛是 0, 1 和 7。
示例 2:
输入: secret = “1123”, guess = “0111”
输出: “1A1B”
解释: 朋友猜测数中的第一个 1 是公牛,第二个或第三个 1 可被视为奶牛。

这个题目返回的A为匹配的,B为不匹配但是有包含的,前面的数为A和B的数目,比如1A3B就是1个匹配的3个不匹配但是包含的。
思路是这样:先创建了一个dict,里面key为每一位数字,value为每个数字对应可能的位置的列表。如果发现A就计数,然后要删掉这个位置。以上操作结束后,如果发现B,就计数并且删掉对应数字的一个位置即可。

class Solution(object):
    def getHint(self, secret, guess):
        """
        :type secret: str
        :type guess: str
        :rtype: str
        """
        dict_secret={}
        set_secret=set(list(str(secret)))
        for ii in set_secret:
            dict_secret[ii]=[]  
        for jj in range(len(str(secret))):
            dict_secret[str(secret)[jj]].append(jj)
        A=0
        B=0
        for ii in range(len(str(secret))):
            if str(secret)[ii]==str(guess)[ii]:
                A+=1
                dict_secret[str(secret)[ii]].remove(ii) 
        for ii in range(len(str(secret))):
            if str(secret)[ii]!=str(guess)[ii] and str(guess)[ii] in str(secret):
                if len(dict_secret[str(guess)[ii]])!=0:
                    B+=1
                    dict_secret[str(guess)[ii]].pop(0)  
        return str(A)+"A"+str(B)+"B"

在这里插入图片描述
结果不是很好,只是简单的写一下怎么用到哈希表的。

基本图形搜索算法

广度优先搜索(BFS)

用来解决最短路径问题的查找算法。比如下图是一个有向图的问题,如何用BFS找到两点之间的最短路径。
在这里插入图片描述
A只能到B,C,D;B只能到E,C只能到E,F以此类推,先想办法描述图,咋描述呢,如下:

graph={}
graph["A"]=["B","C","D"]
graph["B"]=["E"]
graph["C"]=["E","F"]
graph["D"]=["G"]
graph["E"]=[]
graph["F"]=["G"]
graph["G"]=[]

然后我们先来解决第一个问题,能否到达的问题,比如A点能否到达G点。

def BFS1(graph,start,end):
	queue=[start]
	while queue:
		current_point=queue.pop(0) # 返回当前所在点,并从队列里面删掉
		for next_point in graph[current_point]: #遍历当前点可到达的点
			if next_point==end:
				return True
			else: # 如果当前点不可到达目的地,那就全部收进队列,并且一个一个找他们的下一个点能不能到达目的地
				queue.append(next_point)
	return False

上面的代码其实很容易读懂,那现在如果要知道能到达的路径都有啥,最短是啥?咋整?那其实就是记录下走过的点,BFS算法可以保证找到的第一个路径为最短路径。

def BFS2(graph,start,end):
    queue=[(start,start)] ## 多了一个start记录path
    while queue:
        (current_point,path)=queue.pop(0)
        for next_point in graph[current_point]:
            if next_point==end:
            	return "最短步数为"+str(len(path.replace("->","")))+"  最短路径为"+ path+"->"+next_point
            else:
                queue.append((next_point,path+"->"+next_point))
    return "无法到达目的地"

深度优先搜索

与广度优先搜索的区别是优先死磕一个路径一直走到最深(BFS是一层一层的找),可以保证最先找到最远的路径。只需要把队列改成栈即可。

def DFS(graph, start, end):
    stack = [(start, [start])]
    while stack:
        (current_point, path) = stack.pop()
        for next_point in set(graph[current_point]) - set(path):
            if next_point == end:
                return path + [next_point]
            else:
                stack.append((next_point, path + [next_point]))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值