美文网首页
二分查找

二分查找

作者: 追风骚年 | 来源:发表于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