數組中的二分查找

先談談二分查找,二分查找是一種非常高效的查找算法,例題如下:
給定一個整形有序數組,如何找出某一整數是否在這個數組中,以及返回該整數所對應的下標?
此題最簡單的方法就是遍歷一遍原數組來判斷是否存在,這也是順序查找,時間複雜度爲O(n)。還可以建立一個哈希表,但是這種方法的空間複雜度爲O(n)。所以,此時最佳的算法便是二分查找,其時間複雜度爲O(logn),空間複雜度爲O(1)。
下面給出二分查找算法遞歸與非遞歸的兩種形式:

private int binarySearch(int[] nums, int left, int right, int target) {
        if (left > right) {
            return -1;
        }
        int mid = left + (right - left) / 2;//防止數值溢出
        if (nums[mid] > target) {
            return binarySearch(nums, left, mid - 1, target);
        } else if (nums[mid] < target) {
            return binarySearch(nums,mid + 1,right,target);
        }
        return mid;
    }
private int binarySearch(int[] nums, int left, int right, int target) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

也許,這並不足以證明二分查找的威力,下面擴展一下原題目。
假設按照升序排序的數組在預先未知的某個點上進行了旋轉
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。你可以假設數組中不存在重複的元素。
這個題我給出兩種解題思路,而這兩種方法都用到了二分查找。
第一種方法:二分查找加分治算法,第一次取得原數組的中間元素進行比較(中間元素恰好是目標值的情況單獨拿出來即可),不難發現總有一側的元素是有序,另外一側的元素是無序的,有序的一側直接複用二分查找即可,而無序的一側仿照剛纔思路再來一次,就這樣通過二分來實現遞歸分治來判斷目標值是否存在。
具體代碼如下:

public int search1(int[] nums, int target) {
        return find(nums, 0, nums.length - 1, target);
    }

    private int find(int[] nums, int left, int right, int target) {
        //遞歸的終止條件
        if (left > right) {
            return -1;
        }
        //判斷邊界條件
        int mid = left + (right - left) / 2;
        if (target == nums[left]) {
            return left;
        }
        if (target == nums[right]) {
            return right;
        }
        if (target == nums[mid]) {
            return mid;
        }
        if (target > nums[right] && target < nums[left]) {
            return -1;
        }
        //判斷的核心代碼
        if (nums[mid] < nums[right]) {
            if (target > nums[mid] && target < nums[right]) {
                return binarySearch(nums, mid + 1, right - 1, target);
            } else {
                return find(nums, left, mid - 1, target);
            }
        } else {
            if (target > nums[left] && target < nums[mid]) {
                return binarySearch(nums, left + 1, mid - 1, target);
            } else {
                return find(nums, mid + 1, right, target);
            }
        }
    }

    private int binarySearch(int[] nums, int left, int right, int target) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

第二種方法:二分查找加二分查找,先通過整體的二分查找得到數組的旋轉點,從而不難得出以旋轉點爲中心的左右兩側都是有序數組,進行再分別對其兩側進行二分查找來判斷目標值是否存在。
具體代碼如下:

public int search2(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int partition = findPartition(nums);
        if (partition != -1) {
            if (target == nums[partition]) {
                return partition;
            } else if (target > nums[nums.length - 1]) {
                return binarySearch(nums, 0, partition - 1, target);
            } else {
                return binarySearch(nums, partition + 1, nums.length - 1, target);
            }
        }
        return binarySearch(nums, 0, nums.length - 1, target);
    }

    private int findPartition(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //此處是通過旋轉點特殊的性質來得到旋轉點
            if (mid != right && nums[mid] > nums[mid + 1]) {
                return mid;
            }
            if (mid != left && nums[mid] < nums[mid - 1]) {
                return mid - 1;
            }
            if (nums[mid] < nums[left]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }

通過上面的題目就足以見得二分查找的威力不小。
其實就上面的題目還能進一步拓展,就是把上述數組中不存在重複的元素的條件去掉。
此時題目難度又增加了一些,因爲有重複的元素必然會影響到二分查找的性能和查找的正確性,從而一個很直接的想法就是去重,即在上述代碼的基礎上加上一段可以去掉數組重複元素的代碼即可。
具體代碼如下:

public boolean search1(int[] nums, int target) {
        return find(nums, 0, nums.length - 1, target);
    }

    private boolean find(int[] nums, int left, int right, int target) {
        if (left > right) {
            return false;
        }
        while (left < right && nums[left] == nums[left + 1]) {
            left++;
        }
        while (left < right && nums[right] == nums[right - 1]) {
            right--;
        }
        int mid = left + (right - left) / 2;
        if (target == nums[left]) {
            return true;
        }
        if (target == nums[right]) {
            return true;
        }
        if (target == nums[mid]) {
            return true;
        }
        if (target > nums[right] && target < nums[left]) {
            return false;
        }
        if (nums[mid] < nums[right]) {
            if (target > nums[mid] && target < nums[right]) {
                return binarySearch(nums, mid + 1, right - 1, target);
            } else {
                return find(nums, left, mid - 1, target);
            }
        } else {
            if (target > nums[left] && target < nums[mid]) {
                return binarySearch(nums, left + 1, mid - 1, target);
            } else {
                return find(nums, mid + 1, right, target);
            }
        }
    }

    private boolean binarySearch(int[] nums, int left, int right, int target) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                return true;
            }
        }
        return false;
    }

以上就是我對這幾個題目以及二分查找的理解,如若讀者有更好的想法或者方法願意分享,感謝留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章