大话数据结构的第八章,查找,说了好大一堆查找的东西,其中每次老师会经常提到的就是二分查找还有哈希表,所以我也只想多多看看这两方面的内容~
1.经典二分查找
要解决的问题是:是在有序的序列中找到所需的数字。
中心思想是:将有序序列(假设为升序排列)中间的数字设为middle数值,左边第一个数字设为left,右边第一个数字设为right,将target与middle先做比较,若middle比target大,证明target应该出现在middle的左半段,则可以将middle右半段的序列舍弃,反之同理。
代码如下:
def binary_search(list1,target):
if list1 == None or len(list1) == 0:
return -1
left = 0
right = len(list1) - 1
while left <= right:
#这里要是没有=的话,在left=right的情况下
#如果target正好等于list1[left] = list1[right]就会正好被漏判,返回-1.
mid = int((left+right)/2)
if list1[mid] < target:
left = mid + 1
elif list1[mid] > target:
right = mid - 1
else:
return mid
return -1
此算法时间复杂度为:o(logn)(假设序列长度为n)
推导:序列长度为n,比较一次后,序列长度变为n/2,比较j次过后,序列长度变为n/(2^j),最终序列长度会变为1,所以令n/(2^j)=1,j = log2n,所以时间复杂度为o(logn)。
空间复杂度:o(1)
2.矩阵中的二分查找
问题:若给一个有序的矩阵如下所示
1 2 3 4
5 6 7 8
9 10 11 12
要在升序矩阵中查找一个目标数,利用二分查找应该怎么做?
中心思想:把矩阵转化为一个有序序列,然后再进行二分查找。
比如,此矩阵有4行4列,1这个数字的坐标为(0,0),1在数组中的index应该为0*4+0=0。
通用公式:矩阵m行n列,数字坐标为(i,j),index = i*m+j
反之,i = index/m, j = index%m
代码如下:
#矩阵二分查找
def bs(matrix,target):
if matrix == None or len(matrix) == 0:
return -1
row = len(matrix)#行数
col = len(matrix[0])#列数
left = 0
right = row*col - 1
while left <= right:
mid = (left+right)//2
#转换index为matrix的坐标
i = mid//row
j = mid%col
if matrix[i][j] < target:
left = mid + 1
elif matrix[i][j] > target:
right = mid - 1
else:
return (i,j)
return -1
时间复杂度:o(log(row*col))
空间复杂度:o(1)
3.查找一个升序序列中离目标数最近的数字。
问题:一个升序序列中,或许并没有要找的目标数字,但是需要返回一个离目标数字最近的数字。
中心思想和问题1一致,只是要将left = mid+1和right= mid-1改为left=mid和right=mid,(因为是返回离目标数最近的数字,所以mid即使和target不一样也需要留下来判断是否离target近)。但是如果使用问题1的代码,会出现死循环问题。比如在[1,2,5]中寻找离3最近的数字时,到最后会出现left=right=1,那么target永远> mid[left+right//2],程序会一直死循环下去。宏观来说就是left=right=mid时,mid又不等于target,left和right就会因为left=mid或者right=mid以及mid= (left+right)//2的语句一直卡在left=right=mid这里。为了解决这个问题,需要把代码中while的条件放宽为left
代码如下:
def closeted(list1,target):
if list1==None or len(list1)==0:
return -1
left = 0
right = len(list1)-1
while left < right - 1:
mid = (left+right)//2
if list1[mid] > target:
right = mid
elif list1[mid] < target:
left = mid
else:
return mid
return left if abs(list1[left]-target) < abs(list1[right]-target) else right
时间复杂度:o(logn)
4.如果升序序列中有重复的数值出现,比如[1,2,2,3,4],查找最早或者最后出现的2。
中心思想是在问题3的基础上加以变动,二分搜索到最后left=right-1之后,判断left和right是否等于target,查找最早出现就先判断left是否=target,查找最后出现就先判断right是否等于target。但是在判断的时候还是有点细微差别。具体见代码。
代码如下:
#寻找最后出现的target
def last(list1,target):
if len(list1) == 0 or list1 == None:
return 1
left = 0
right = len(list1) - 1
while left < right - 1:
mid = (left + right)//2
#第一条判断语句,在判断最后出现的target时,需要先确定可能重复的target在哪个区间,
比如先判断序列的左半段有没有重复的target,以免漏判。
if list1[mid] > target:
right = mid
else:
left = mid
if list1[right] == target:
return right
if list1[left] == target:
return left
return -1
寻找最先出现的target时也需要先判断可能重复的target没有在右半段出现。
时间复杂度:o(logn)
5.寻找peak值
问题:找一个序列中,比旁边两个数都大的那个数(peak)。
中心思想:利用二分查找,设置一个mid。
if array[mid] > array[mid-1] and array[mid] > array[mid+1]:
return mid
if array[mid] < array[mid+1]:证明peak出现在mid的右半段,left=mid+1
if array[mid] < array[mid-1]:证明peak出现在mid的左半段,right = mid-1
代码如下:
def peakpick(nums):
if nums == None or len(nums) == 0:
return -1
left = 0
right = len(nums) - 1
while left < right - 1:
mid = (left+right)//2
if nums[mid] > nums[mid+1] and nums[mid]>nums[mid-1]:
return mid
if nums[mid] < nums[mid+1]:
left = mid+1
if nums[mid] < nums[mid-1]:
right = mid-1
return left if nums[left]>=nums[right] else right
这段代码只能查找出一个peak值,另外的就无法查找得出。
最后一句代码的原因是在程序最后已经查找到序列最边边了,只用查找到最大值即可。
6.在旋转序列中利用二分查找查找target。
旋转序列:类似于 3,4,5,0,1,2。这样的序列。
将345分为区间1,将012分为区间2。利用二分查找。
如果array[left] < array[mid]那么array[mid]落在区间1,如果array[left]<=target if array[mid] 哈希表对于python来说就是和字典是一样的,键值对。def r(array,target):
if array == None or len(array)==0:
return -1
left = 0
right = len(array)-1
while left <= right:
mid = (left+right)//2
if array[mid] == target:
return mid
if array[mid] >= array[left]:#mid落在一区间
if array[mid] > target and target > array[left]:
right = mid - 1
else:
left = mid + 1
else:
if array[mid] < target and target < array[right]:
left = mid + 1
else:
right = mid - 1
return -1