📜  在单链表上的二进制搜索(1)

📅  最后修改于: 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 中的代码,详细分析了算法思路和时间复杂度等方面的细节。