算法 in Go:Binary Search(二分查找)

发布时间 2023-06-05 22:48:47作者: QIAOPENGJUN

算法 in Go:Binary Search(二分查找)

Binary Search(二分查找)

Binary Search(二分查找)

  • 猜数
  • 1、2、3、4、5、6、7、8
  • 排好序一个集合,先从中间开始猜,根据提示就可以排除一半,在剩余的一半里,再从中间开始猜,依此类推,这就是二分查找。

Binary Search(二分查找)接收什么参数,返回什么值

  • 输入:排好序的集合
  • 如果要查找的元素在集合中:返回位置(索引)
  • 否则:返回空

Binary Search(二分查找)其它查找方式

  • 如果查找?
  • [1,2,3,4,5,...56,57,58...98,99,100]
  • 顺序的简单查找(simple search)
  • 更好的办法:从中间开始,每次都能排除一半的元素,这叫二分查找
  • [1,2,3,4,5...98,99.100],查找任何一个元素,最多只需要7步
  • [1,2,3,4,5...239998,239999,240000]
    • 二分查找,最多只需要 18步
    • 简单查找,最多需要 24 万步
  • 针对拥有 n 哥元素的已排序集合:
    • 二分查找:log2^n
    • 简单查找:n

注意

  • 二分查找只适用于已排序的集合

创建项目

~/Code/go via ? v1.20.3 via ? base
➜ mcd algorithms_binary_search

Code/go/algorithms_binary_search via ? v1.20.3 via ? base
➜ go mod init algorithms_binary_search
go: creating new go.mod: module algorithms_binary_search

Code/go/algorithms_binary_search via ? v1.20.3 via ? base
➜ c

Code/go/algorithms_binary_search via ? v1.20.3 via ? base
➜

实现算法过程中的问题

问题:

这段代码r := rand.New(rand.NewSource(time.Now().UnixNano())) v := r.Intn(10)和这段代码: v := rand.Intn(10)有什么区别?

ChatGPT解答:

这两段代码的区别在于随机数生成器的种子(seed)。

在第一段代码中,我们使用了 rand.NewSource(time.Now().UnixNano()) 作为种子,创建了一个新的随机数生成器 rtime.Now().UnixNano() 返回当前时间的纳秒表示,作为种子值。通过使用不同的种子值,我们可以得到不同的随机数序列。

然后,我们使用 r.Intn(10) 从生成器 r 中生成一个介于 0 和 9 之间的随机整数。

而在第二段代码中,我们直接调用了 rand.Intn(10),这里的 rand 是 Go 语言中的标准库 math/rand 包,它使用默认的随机数生成器。默认情况下,标准库的随机数生成器的种子值是固定的(通常为 1),因此在相同的程序运行中多次调用 rand.Intn(10) 得到的随机数序列是相同的。

因此,第一段代码使用了一个可变的种子值,每次运行程序时都会产生不同的随机数序列,而第二段代码使用了固定的种子值,多次运行得到的随机数序列是相同的。

算法实现:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	list := make([]int, 1_000_000)
	for i := 0; i < 1_000_000; i++ {
		list = append(list, i+1)
	}

	// rand.Seed(time.Now().UnixNano())
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 20; i++ {
		v := r.Intn(1_000_000-1) + 1
		fmt.Printf("针对 %d 进行二分查找:\n", v)
		idx := binarySearch(list, v)
		fmt.Printf("%d 的索引位置是:[%d]\n", v, idx)
		fmt.Println("_____________________________")
	}
}

func binarySearch(list []int, target int) int {
	low := 0
	high := len(list) - 1

	step := 0
	for {
		step = step + 1
		if low <= high {
			mid := (low + high) / 2
			guess := list[mid]
			if guess == target {
				fmt.Printf("共查找了 %d 次\n", step)
				return mid
			}
			if guess > target {
				high = mid - 1
			} else {
				low = mid + 1
			}
		}
	}
}

运行

Code/go/algorithms_binary_search via ? v1.20.3 via ? base 
➜ go run .
针对 518758 进行二分查找:
共查找了 21 次
518758 的索引位置是:[1518757]
_____________________________
针对 13067 进行二分查找:
共查找了 18 次
13067 的索引位置是:[1013066]
_____________________________
针对 186102 进行二分查找:
共查找了 18 次
186102 的索引位置是:[1186101]
_____________________________
针对 606006 进行二分查找:
共查找了 21 次
606006 的索引位置是:[1606005]
_____________________________
针对 806929 进行二分查找:
共查找了 17 次
806929 的索引位置是:[1806928]
_____________________________
针对 938525 进行二分查找:
共查找了 19 次
938525 的索引位置是:[1938524]
_____________________________
针对 718792 进行二分查找:
共查找了 21 次
718792 的索引位置是:[1718791]
_____________________________
针对 583982 进行二分查找:
共查找了 20 次
583982 的索引位置是:[1583981]
_____________________________
针对 569047 进行二分查找:
共查找了 21 次
569047 的索引位置是:[1569046]
_____________________________
针对 450770 进行二分查找:
共查找了 21 次
450770 的索引位置是:[1450769]
_____________________________
针对 503611 进行二分查找:
共查找了 19 次
503611 的索引位置是:[1503610]
_____________________________
针对 792959 进行二分查找:
共查找了 21 次
792959 的索引位置是:[1792958]
_____________________________
针对 663440 进行二分查找:
共查找了 21 次
663440 的索引位置是:[1663439]
_____________________________
针对 967010 进行二分查找:
共查找了 20 次
967010 的索引位置是:[1967009]
_____________________________
针对 631813 进行二分查找:
共查找了 20 次
631813 的索引位置是:[1631812]
_____________________________
针对 608258 进行二分查找:
共查找了 21 次
608258 的索引位置是:[1608257]
_____________________________
针对 494806 进行二分查找:
共查找了 21 次
494806 的索引位置是:[1494805]
_____________________________
针对 92081 进行二分查找:
共查找了 19 次
92081 的索引位置是:[1092080]
_____________________________
针对 615060 进行二分查找:
共查找了 21 次
615060 的索引位置是:[1615059]
_____________________________
针对 798577 进行二分查找:
共查找了 20 次
798577 的索引位置是:[1798576]
_____________________________

Code/go/algorithms_binary_search via ? v1.20.3 via ? base 
➜ 

思考:

log2^1_000_000 的对数大约等于19.93,所以说最多的次数应该是20次才对,为什么会有这么多21次?

因为二分查找取中间数,有可能不在正中间的位置,就有可能多出来一次,比如1 2 3 4 这个集合。

问题:log 以 2为底 100万的对数是多少

ChatGPT 解答:

要计算以 2 为底的 100 万的对数,可以使用对数的换底公式。

换底公式为:log_b(x) = log_c(x) / log_c(b)

在这个问题中,我们需要计算以 2 为底的 100 万的对数,可以将其转换为以 10 为底的对数:

log_2(1000000) = log_10(1000000) / log_10(2)

现在我们可以使用任何对数计算器或编程语言中的对数函数来计算这个表达式。在这里,我们将使用自然对数(以 e 为底)来计算它。

log_2(1000000) ≈ ln(1000000) / ln(2)

让我计算一下:

package main

import (
	"fmt"
	"math"
)

func main() {
	result := math.Log(1000000) / math.Log(2)
	fmt.Println(result)
}

输出结果大约为 19.931568569324174。

因此,以 2 为底的 100 万的对数约为 19.93。