📅  最后修改于: 2023-12-03 15:23:31.352000             🧑  作者: Mango
二分搜索是在已排序的数组上查找目标元素的一种高效算法,但是如果数据是存储在一个单链表中呢?本文将介绍如何在单链表上实现二分搜索。
我们先通过一段代码来实现二分搜索在数组上实现的算法,大概了解一下二分搜索的流程。
def binary_search(nums, target):
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return -1
二分搜索在数组上的思路是先找到数组中间的位置 mid,然后通过比较 mid 和目标值 target 的大小,可以排除掉数组的一半元素,从而缩小搜索范围。
但是在单链表上,我们无法直接定位到中间位置,而且在单链表上删除节点需要 O(n) 的时间复杂度,如果在搜索中反复删除节点,也会大大增加时间复杂度。所以我们需要换一种思路。
假设单链表中每个节点都存储一个整数,且单链表已经按升序排列,我们可以先获取单链表的长度,然后让 mid 指向单链表的中间节点。如果 mid 的值等于目标值 target,则直接返回 mid 的位置;否则,如果 mid 的值大于目标值 target,则说明目标值只可能在单链表左半部分,我们将 right 指向 mid 的前一个节点;否则,说明目标值只可能在单链表的右半部分,我们将 left 指向 mid 的后一个节点。
然后重复上述步骤,直到找到目标值或搜索范围缩小到空集。
下面是在 Python 中实现在单链表上二分搜索的代码实现。为了测试方便,我们手写输入一个升序排列的单链表,并实现了一个 ListNode 类表示单链表的节点。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def binary_search(head: ListNode, n: int, target: int) -> int:
left, right = 1, n # 链表节点下标从 1 开始
while left <= right:
mid = (left + right) // 2
cur = head
for _ in range(mid - 1):
cur = cur.next
if cur.val == target:
return mid
elif cur.val > target:
right = mid - 1
else:
left = mid + 1
return -1
# 手写输入一个链表
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)
head.next.next.next = ListNode(4)
head.next.next.next.next = ListNode(5)
n = 5 # 链表长度
target = 4 # 目标值
ans = binary_search(head, n, target)
print(ans) # 输出结果:4
为了和二分搜索在数组上实现的算法比较,我们设置链表节点的下标从 1 开始,因此 left 和 right 的初始值都是 1 和 n。
在每次循环中,我们需要先将 cur 指针指向链表的中间位置,查找 mid - 1 次即可(因为头节点算作第 1 个节点)。如果 cur 的值等于目标值 target,则直接返回 mid 的位置;否则,如果 cur 的值大于目标值 target,则说明目标值在链表的左半部分,我们将 right 指向 mid - 1;否则,说明目标值在链表的右半部分,我们将 left 指向 mid + 1。
在二分搜索算法中,每次循环搜索范围都会缩小一半,因此时间复杂度为 O(logn)。
另外,链表中的节点需要依次遍历才能找到中间位置,时间复杂度为 O(n),但由于题目给出的是已排序的链表,因此可以统计出链表的长度,进一步优化时间复杂度,我们可以将时间复杂度计算为 O(logn),其中 n 表示链表的长度。
本文介绍了如何在单链表上实现二分搜索算法,通过手写一个输入数据的单链表,实现了在 Python 中的代码,详细分析了算法思路和时间复杂度等方面的细节。