<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go面試中如果這樣寫二分查找!

          共 5591字,需瀏覽 12分鐘

           ·

          2021-07-04 13:05

          前言

          今天與大家分享一下Go標準庫sort.Search是如何實現(xiàn)二分查找的,為什么突然想到分享這個函數(shù)呢。起因是這周在刷leetcode的一道題時,需要使用到二分查找,最開始是自己實現(xiàn)了一版,后面就想Go語言標準庫這么全,應該會有這個封裝,果然被我找到了,順便看了一下他是怎么實現(xiàn)的,感覺挺有意思的,就趕緊來分享一下。

          什么是二分查找

          以下來自百度百科:

          二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結構,而且表中元素按關鍵字有序排列。

          總結一下,使用二分查找必須要符合兩個要求:

          • 必須采用順序存儲結構
          • 必須按關鍵字大小有序排列

          我們來舉個例子,就很容易理解了,就拿我和我女朋友的聊天內容做個例子吧:

          我家寶寶每次買一件新衣服,就會習慣性的問我一句,小松子,來猜一猜我這次花了多少錢?

          小松子:50?

          臭寶:你埋汰我呢?欠打!

          小松子:500?

          臭寶:你當我是富婆呢?能不能有點智商!

          小松子:250?

          臭寶:我感覺你在罵我,可我還沒有證據。少啦,少啦!?。?/p>

          小松子:哎呀,好難猜呀,290?

          臭寶:啊!你這臭男人,就是在罵我!氣死啦,氣死啦!多啦,多啦!

          小松子:難道是260?

          臭寶:哎呦,挺厲害呀,竟然猜對了!

          后面對話內容就省略啦.....

          這里我只需要5次就成功猜出來了,這就是二分查找的思想,每一次猜測,我們都選取一段整數(shù)范圍的中位數(shù),根據條件幫我們逐步縮小范圍,每一次都以讓剩下的選擇范圍縮小一半,效率提高。

          • 二分查找的時間復雜度

          二分查找每次把搜索區(qū)域減少一半,時間復雜度為O(logn)。(n代表集合中元素的個數(shù))。

          空間復雜度為O(1)。

          自己實現(xiàn)一個二分查找

          二分算法的實現(xiàn)還是比較簡單的,可以分兩種方式實現(xiàn):遞歸和非遞歸方式實現(xiàn),示例代碼如下:

          非遞歸方式實現(xiàn):

          // 二分查找非遞歸實現(xiàn)
          func binarySearch(target int64, nums []int64) int {
             left := 0
             right := len(nums) - 1
             for left <= right {
                mid := left + (right - left) / 2
                if target == nums[mid] {
                   return mid
                }
                if target > nums[mid] {
                   left = mid + 1
                   continue
                }
                if target < nums[mid] {
                   right = mid - 1
                   continue
                }
             }
             return -1
          }

          總體思路很簡單,每次我們都選取一段整數(shù)范圍的中位數(shù),然后根據條件讓剩下的選擇范圍縮小一半。這里有一個要注意的點就是我們在獲取中位數(shù)時使用的寫法是mid := left + (right - left) / 2,而不是mid = (left +right)/2的寫法,這是因為后者有可能會造成位數(shù)的溢出,也就會導致結果出問題。

          遞歸方式實現(xiàn):

          func Search(nums []int64, target int64) int {
           return binarySearchRecursive(target, nums, 0len(nums))
          }

          func binarySearchRecursive(target int64, nums []int64, left, right int) int {
           if left > right {
            return -1
           }

           mid := left + (right - left) / 2

           if target == nums[mid] {
            return mid
           }
           if nums[mid] < target {
            return binarySearchRecursive(target, nums, mid+1, right)
           }
           if nums[mid] > target {

            return binarySearchRecursive(target, nums, left, mid-1)
           }
           return -1

          }

          Go標準庫是如何實現(xiàn)二分查找的?

          我們先看一下標準庫中的代碼實現(xiàn):

          func Search(n int, f func(int) boolint {
           // Define f(-1) == false and f(n) == true.
           // Invariant: f(i-1) == false, f(j) == true.
           i, j := 0, n
           for i < j {
            h := int(uint(i+j) >> 1// avoid overflow when computing h
            // i ≤ h < j
            if !f(h) {
             i = h + 1 // preserves f(i-1) == false
            } else {
             j = h // preserves f(j) == true
            }
           }
           // i == j, f(i-1) == false, and f(j) (= f(i)) == true  =>  answer is i.
           return i
          }

          初一看,與我們上面的實現(xiàn)一點也不同呢,我們來分析一下。

          入參n就是代表要查找序列的長度,入參f就是我們自定義的條件。這段代碼很短,大概思路就是:

          • 定義好這段序列的開始、結尾的位置
          • 使用位移操作獲取中位數(shù),這樣能更好的避免溢出
          • 然后根據我們傳入的條件判斷是否符合條件,逐漸縮小范圍

          這段代碼與我實現(xiàn)的不同在于,它并不是在用戶傳入的比較函數(shù)f返回true就結束查找,而是繼續(xù)在當前[i, j)區(qū)間的前半段查找,并且,當ffalse時,也不比較當前元素與要查找的元素的大小關系,而是直接在后半段查找。所以for循環(huán)退出的唯一條件就是i>=j,如果我們這樣使用,就會出現(xiàn)問題:

          func main() {
           nums := []int64{1234567}
           fmt.Println(sort.Search(len(nums), func(i int) bool{
            return nums[i] == 1
           }))
          }

          運行結果竟然是7,而不是1,如果我們把條件改成return nums[i] >=1,運行結果就對了。這是因為我們傳入的條件并不是讓用戶確認目標條件,這里的思想是讓我們逐步縮小范圍,通過這個條件,我們每次都可以縮小范圍,說的有點饒,就上面的代碼舉個例子?,F(xiàn)在是一個升序數(shù)組,我們要找的數(shù)值是1,我們傳入的條件是return nums[i]>=1,第一進入函數(shù)Search,我們獲取中的中位數(shù)h3,當前元素是大于目標數(shù)值的,所以我們只能在前半段查找,就是這樣不斷縮小范圍,找到我們最終的那個數(shù)值,如果當前序列中沒有我們要找的目標數(shù)值,那么就會返回我們可以插入的位置,也就是最后一位元素的坐標+1的位置。

          這個邏輯說實話,我也是第一次接觸,仔細思考了一下,這種實現(xiàn)還是有一些優(yōu)點的:

          • 使用移位操作,避免因為i+j太大而造成的溢出
          • 如果我們查找序列中有多個元素相等時,且我們要找的元素就是這個時,我們總會找到下標最小的那個元素
          • 如果我們沒找到要找的目標元素時,返回的下標是我們可插入的位置,我們在進行數(shù)據插入時,依然可以保證數(shù)據的有序

          注意:使用sort.Search時,入參條件是根據要查找的序列是升序序列還是降序序列來決定的,如果是升序序列,則傳入的條件應該是>=目標元素值,如果是降序序列,則傳入的條件應該是<=目標元素值

          解析int(uint(i+j) >> 1)這段代碼

          這里我想單獨解析一下這段代碼,因為很少見,所以可以當作一個知識點記一下。這里使用到的是移位操作,通過向右移動一位,正好可以得到/2的結果。具體什么原因呢,我畫了一個圖,手工畫的,看完你就懂了:

          懂了吧,兄弟們!移位實現(xiàn)要比乘除發(fā)的效率高很多,我們在平常開發(fā)中可以使用這種方式來提升效率。

          這里還有一個點就是使用uint數(shù)據類型,因為uint的數(shù)據范圍是2^3204294967295。使用uint可以避免因為i+j太大而造成的溢出。

          總結

          好啦,今天的文章到這里就結束了,最后我想說的是,沒事大家可以專研一下Go標準庫中的一些方法是怎樣實現(xiàn)的,好的思想我們要借鑒過來。如果我們在面試中使用這種方式寫出二分查找,那得到的offer的幾率不就又增加了嘛~。


          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關注公眾號 「polarisxu」,回復 ebook 獲?。贿€可以回復「進群」,和數(shù)萬 Gopher 交流學習。

          瀏覽 142
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  超碰人人澡 | 欧美日韩视频在线观看一区 | 做爱视频免费网站 | 青娱乐免费精品 | 五月天亚洲综合 |