查找——二分查找

  二分查找也叫作折半查找,查找的對象是已經排好序的序列(通常默認爲升序)框架

  讓咱們來看看原理:顧名思義,就是先將中間數和目標key比較,若是相等則返回其索引,不然把序列分紅兩半,根據大小判斷所查找的key在哪一半中,對這一半序列再重複上述步驟,直到找到目標key或查找完序列。
spa

通常的二分查找

  被查找的序列arr中無重複的元素,在此序列中查找目標數target。設計

  被查找的序列示例:int[] arr1 = { 1, 2, 4, 5, 8, 13, 19, 20, 33, 38, 40, 48, 88 }。指針

public static int binarySearch(int[] arr, int target) { if (arr == null || arr.length == 0) { return -1; }
   // 肯定別查找的區間
int left = 0; int right = arr.length - 1;
   // 開始查找【left,right】區間
while (left <= right) {
     // 找到中間數的索引
int mid = (left + right) / 2;
     // 判斷 中間數 和 target 的大小
if (arr[mid] == target) {     // 若相等,則返回其索引 return mid; } else if (arr[mid] > target) {  // 若大於,肯定前半段區間 right = mid - 1; } else if (arr[mid] < target) {  // 若小於,肯定後半段區間 left = mid + 1; } }
   // 若是未查找到,則返回-1
return -1; }

  第一步,初始化原序列,left指向索引爲0的位置,即此區間的第一個元素right指向索引爲arr.length-1的位置,即此區間最後一個元素,也就是說這個區間是閉區間,[left,right]區間內的元素都是被查的元素。code

  第二步找到中間元素的索引mid,判斷中間元數和目標數target的值是否相等對象

  • 若中間數等於target,說明已經找到了目標數了查找成功,直接返回其索引mid;
  • 若中間數小於target,說明要查找的數在後半段,即[mid+1,right],因此將mid+1值賦給left,肯定下次要查找的區間,重複第二步;
  • 若中間數大於target,說明要查找的數在前半段,即[left,mid-1],因此將mid-1值賦給right,肯定下次要查找的區間,重複第二步;

   第三步當查找到left==right時是最後一個區間,即[left,left],此區間只有一個元素,即下標爲left的元素。若是此元素是目標數查找成功返回其索引若是不是,會從新肯定left或right的值使得left>right查找完畢退出循環,返回-1blog

有重複元素的二分查找

  被查找的序列arr中有重複的元素,在此序列中查找目標數target。索引

  被查找的序列示例:int[] arr1 = { 1, 2, 5, 5, 5, 13, 19, 33, 33, 38, 40, 48, 48 }。
get

1.尋找左側邊界的二分查找

  顧名思義,就是當序列中被查找的數有多個時,找到最左邊的那個數的位置,而後返回其索引。固然,若是被查找的數只有一個,找到並返回其索引就行了,不然返回-1。class

public static int binarySearch1(int[] arr, int target) { if (arr == null || arr.length == 0) { return -1; } int left = 0; int right = arr.length;               // 右側未閉合
    while (left < right) {                // left==right時,退出循環
        int mid = (left + right) / 2; if (arr[mid] == target) {         // 找到時,right向右側靠攏,記住此位置
            right = mid; }else if (arr[mid] < target) {    // 小於時,左側閉合,向右靠攏
            left = mid + 1; }else if (arr[mid] > target) {    // 大於時,右側不閉合,向左靠攏
            right = mid; } } if (left == arr.length) {            // 若是target大於全部元素,返回-1
        return -1; } return arr[left] == target ? left : -1; }

   大框架並無發生變化,改變的地方須要說一下:

  首先,while的條件變了, left <= righ 變成了 left < right ,當left == right時退出循環,爲何要變呢?由於咱們此次所查找區間是左閉右開的,[left,right),left增加到left == right時,所查找的區間已查找完畢,這時咱們就要退出循環了。爲何要這樣設計?此中妙處,請往下看。

   if (left == arr.length) { return -1; } 這段代碼是幹啥的?這是當咱們要查找的數大於此序列的全部數時,left會一直增長到left == right,而此時的right並未改變,就是arr.length。這段代碼是爲了處理這這種狀況

  當中間數不是target時,調整下次要查詢的區間的範圍,你們應該能夠理解,由於這是左閉右開的區間。

  當中間數等於target時, right = mid; ,怎麼回事?找到 target 時不要當即返回,而是縮小「搜索區間」的上界 right,在區間 [left, mid) 中繼續搜索,即不斷向左收縮,達到鎖定左側邊界的目的。固然,若是此時right鎖定的就是左邊界,left會一直向右收縮,直到left == right。

  最後的 return arr[left] == target ? left : -1; 是爲了應對target小於全部數時的狀況和是否找到target時的狀況。

 2.尋找右側邊界的二分搜索

  與上面的相反,就是當序列中被查找的數有多個時,找到最右邊的那個數的位置,而後返回其索引。固然,若是被查找的數只有一個,找到並返回其索引就行了,不然返回-1。

public static int binarySearch2(int[] arr, int target) { if (arr == null || arr.length == 0) { return -1; } int left = 0; int right = arr.length; while (left < right) { int mid = (left + right) / 2; if (arr[mid] == target) { left = mid+1; }else if (arr[mid] < target) { left = mid+1; }else if (arr[mid] > target) { right = mid; } } if (left == 0) { return -1; } return arr[left-1] == target ? (left-1) : -1; }

  當咱們查找左邊界的時候,咱們是用right指針來鎖定左側邊界的;同理,當咱們查找右邊界的時候,咱們用left來鎖定右邊界,但有一點小區別,由於查找區間是左閉右開的,left指向的元素在查找範圍內,所以咱們用left的前一個元素來鎖定右邊界

  這就是爲何當中間元素等於target時,咱們要 left = mid+1; 呢,left的前一個元素最終會鎖定的右邊界,這也就對應了後面的return語句中爲何是left - 1,由於left - 1指向的最右邊的target元素

   if (left == 0) { return -1; } 這段代碼是爲了應對當target小於全部元素時的狀況,由於後面有left-1,沒有這條語句的話會形成索引越界的狀況。

總結一下

  二分查找有一個實現框架:

public static int binarySearch(int[] arr, int target) { if (arr == null || arr.length == 0) { return -1; } int left = 0; int right = --------; while (--------) { int mid = (left + right) / 2; if (arr[mid] == target) { --------; } else if (arr[mid] > target) { right = --------; } else if (arr[mid] < target) { left = --------; } } return --------; }

  當咱們實現時根據題目的具體要求,來調整框架中所填部分的值。

  通常的二分查找最爲簡單;當有重複數字時,查找相對複雜一些,本文中只提到了查找左右邊界的狀況,但日常咱們還會遇到一些變種的二分查找狀況,好比:查找最後一個等於或者小於key的元素查找最後一個小於key的元素查找第一個等於或者大於key的元素查找第一個大於key的元素等。雖然變化不少,但萬變不離其宗!讀者只要理解查找的原理和每一步的過程,將其融會貫通,即可攻無不克。

相關文章
相關標籤/搜索