美文网首页
二分查找

二分查找

作者: 追风骚年 | 来源:发表于2021-01-19 22:22 被阅读0次

题:二分查找中,数据存在重复项,返回数据最右边的一个数据,没有找到则返回一个合理的插入位置。

package test

import (
    "testing"
)

func TestSearch(t *testing.T) {
    t.Log(warp([]int{1, 2, 3, 3, 5}, 3))
    t.Log(warp([]int{1, 2, 3, 3, 5}, 4))
    t.Log("===")

    t.Log(warp([]int{3, 3, 3, 3, 3}, 3))
    t.Log(warp([]int{3, 3, 3, 3, 3}, 4))
    t.Log(warp([]int{3, 3, 3, 3, 3}, 2))
    t.Log("===")

    t.Log(warp([]int{3}, 3))
    t.Log(warp([]int{3}, 2))
    t.Log(warp([]int{3}, 4))
    t.Log("===")

    t.Log(warp([]int{}, 3))

}

func warp(arr []int, target int) (int, int) {
    return search(arr, target, 0, len(arr)-1)
}

func search(arr []int, target int, start int, end int) (int, int) {
    if len(arr) == 0 {
        return 0, -1
    }

    if len(arr) == 1 {
        if arr[start] == target {
            return 0, 1
        }

        if target > arr[start] {
            return start + 1, -1
        }

        return start, -1
    }

    if start < 0 || end < 0 {
        return 0, -1
    }

    if start > len(arr)-1 || end > len(arr)-1 {
        return len(arr), -1
    }

    if arr[start] == target {
        if start+1 < len(arr) && arr[start+1] == target {
            // 核心逻辑 找到需要继续往右找
            return search(arr, target, start+1, end)
        }

        return start, 1
    }

    if arr[end] == target {
        if end+1 < len(arr) && arr[end+1] == target {
            // 核心逻辑 找到需要继续往右找
            return search(arr, target, start, end+1)
        }
        return end, 1
    }

    if start == end {
        if target > arr[start] {
            return start + 1, -1
        }
        return start, -1
    }

    mid := start + (end-start)/2
    mValue := arr[mid]

    if target == mValue {
        if mid+1 < len(arr) && arr[mid+1] == target {
            // 核心逻辑 找到需要继续往右找
            return search(arr, target, mid+1, end)
        }
        return mid, 1
    }

    if target < mValue {
        // 核心逻辑 左边找
        return search(arr, target, start, mid-1)
    }
    // 核心逻辑 右边找
    return search(arr, target, mid+1, end)
}

search 返回两个值,第一个值为位置,第二个为flag,1找到,-1未找到。
核心逻辑在于找到数据时的一个处理,因为需要找到最右边的数据,所以继续向右探测一下即可。


2021年01月20日00:19:43
再三测试之后,发现

  if arr[start] == target {
        if start+1 < len(arr) && arr[start+1] == target {
            // 核心逻辑 找到需要继续往右找
            return search(arr, target, start+1, end)
        }

        return start, 1
    }

    if arr[end] == target {
        if end+1 < len(arr) && arr[end+1] == target {
            // 核心逻辑 找到需要继续往右找
            return search(arr, target, start, end+1)
        }
        return end, 1
    }

发现这一段完全是多余,本来想可以减少计算次数,反而失去二分的思想,画蛇添足了。

  if start == end {
        if target > arr[start] {
            return start + 1, -1
        }
        return start, -1
    }

这一段判断也不对,少了一个分支。
综上所述,优化了以下代码

func binarySearch(arr []int, target int) (int, int) {
    if len(arr) == 0 {
        return 0, -1
    }

    if len(arr) == 1 {
        if arr[0] == target {
            return 0, 1
        }

        if target > arr[0] {
            return 1, -1
        }
        return 0, -1
    }

    return searchNew(arr, target, 0, len(arr)-1)
}

func searchNew(arr []int, target int, start int, end int) (int, int) {

    if start < 0 || end < 0 {
        return 0, -1
    }

    if start > len(arr)-1 || end > len(arr)-1 {
        return len(arr), -1
    }

    if start == end {
        if target == arr[start] {
            return start, 1
        }

        if target > arr[start] {
            return start + 1, -1
        }
        return start, -1
    }

    mid := start + (end-start)/2
    mValue := arr[mid]

    if target == mValue {
        if mid+1 < len(arr) && arr[mid+1] == target {
            // 核心逻辑 找到需要继续往右找
            return searchNew(arr, target, mid+1, end)
        }
        return mid, 1
    }

    if target < mValue {
        // 核心逻辑 左边找
        return searchNew(arr, target, start, mid-1)
    }
    // 核心逻辑 右边找
    return searchNew(arr, target, mid+1, end)
}


func TestSearch(t *testing.T) {

    t.Log(binarySearch([]int{1, 2, 3, 3, 5}, 3))
    t.Log(binarySearch([]int{1, 2, 3, 3, 5}, 4))
    t.Log("===")

    t.Log(binarySearch([]int{3, 3, 3, 3, 3}, 3))
    t.Log(binarySearch([]int{3, 3, 3, 3, 3}, 4))
    t.Log(binarySearch([]int{3, 3, 3, 3, 3}, 2))
    t.Log("===")

    t.Log(binarySearch([]int{3}, 3))
    t.Log(binarySearch([]int{3}, 2))
    t.Log(binarySearch([]int{3}, 4))
    t.Log("===")

    t.Log(binarySearch([]int{}, 3))

    t.Log("===")
    t.Log(binarySearch([]int{1, 2, 3, 3, 3, 3, 3, 3, 5}, 3))
    t.Log(binarySearch([]int{3, 3, 3, 3, 4, 4}, 3))
}

顺便测试了一下两套算法的性能。

func generateArr() []int {
    arr := make([]int, math.MaxInt16/2)

    for i := 0; i < math.MaxInt16/2; i++ {
        if i < math.MaxInt16/5 {
            arr = append(arr, i)
        } else if i < math.MaxInt16/4 {
            arr = append(arr, math.MaxInt16/4)
        } else if i < math.MaxInt16/3 {
            arr = append(arr, math.MaxInt16/3)
        } else {
            arr = append(arr, i)
        }
    }

    return arr
}

// var benchmarkTarget = math.MaxInt16 / 3
var benchmarkTarget = math.MaxInt16/3 + 10

func BenchmarkSearchNew(b *testing.B) {
    arr := generateArr()
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        searchNew(arr, benchmarkTarget, 0, len(arr)-1)
    }
}

func BenchmarkSearchOld(b *testing.B) {
    arr := generateArr()
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        searchOld(arr, benchmarkTarget, 0, len(arr)-1)
    }
}

Benchmark 结果如下:

// benchmarkTarget = math.MaxInt16/3 + 10
BenchmarkSearchNew
BenchmarkSearchNew-8    15212474            78.2 ns/op         0 B/op          0 allocs/op
BenchmarkSearchOld
BenchmarkSearchOld-8    15368810            80.2 ns/op         0 B/op          0 allocs/op

// var benchmarkTarget = math.MaxInt16 / 3
BenchmarkSearchNew
BenchmarkSearchNew-8    13529409            74.8 ns/op         0 B/op          0 allocs/op
BenchmarkSearchOld
BenchmarkSearchOld-8       71040         15132 ns/op           0 B/op          0 allocs/op

总结:对于非重复数据,两套算法性能差不多,对于重复数据,那就天差地别了,logN 还是快。

相关文章

网友评论

      本文标题:二分查找

      本文链接:https://www.haomeiwen.com/subject/onpczktx.html