1. 程式人生 > >演算法第六記-二分查詢

演算法第六記-二分查詢

為了準備春招,自己重新總結了一下二分查詢的各種形式,以及幾個二分查詢的面試題:

1.普通的二分查詢

int binary_find(int arr[], int length,int value)//普通的二分查詢
{
   if(!arr||length<=0)
      return -1;
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] == value)
			return mid;
		else if (arr[mid] > value)
			high = mid - 1;
		else
			low = mid + 1;
	}
	return -1;
}

2.找到第一個值等於值的下標

int binary_find_1(int arr[], int length,int value)
{//找到第一個等於value的元素下標
    if(!arr||length<=0)
      return -1;
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] > value)
		{
			high = mid - 1;
		}else if(arr[mid<value]){
			low = mid + 1;
		}
		else
		{
			if (mid == 0 || arr[mid - 1] != value)
				return mid;
			else
				high = mid - 1;
		}
	}
	return -1;
}

3.查詢最後一個值等於值的下標

int binary_find_2(int arr[], int length, int value)
{//找到最後一個等於value的元素下標
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid]<value)
		{
				low = mid + 1;
		}
		else if(arr[mid]>value)
			high = mid - 1;
		else
		{
			if (mid == length - 1 || arr[mid + 1] != value)
				return mid;
			else
				low = mid + 1;
		}
	}
	return -1;
}

4.查詢第一個值大於等於值的下標(相當於找右邊界)1,3,5,7,8,9查詢6應該返回第一個大於6的元素下標7

int binary_find_4(int arr[], int length, int value)
{//找到第一個大於等於value的元素下標
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] >=value)
		{
//如果此時mid已經是第一個元素了,那我們直接返回或者如果mid-1這個位置的元素小於value我們也返回
//因為我們的目的是找第一個大於等於value的下標,而此時arr[mid-1]已經小於value了
			if (mid == 0 || arr[mid - 1] < value)
				return mid;
			else
				high = mid - 1;
 //如果arr[mid-1]依然大於value,則我們的右邊界應改為mid-1
		}
		else
			low = mid + 1;
	}
	return -1;
}

5.查詢最後一個值小於等於值的下標(相當於找左邊界)0,1,1,2,6,9查詢3的話應該返回下標3

int binary_find_3(int arr[], int length, int value)
{//找到最後一個小於等於value的元素下標
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid]<= value)
		{
//如果此時的mid是末尾元素了,則直接返回或者mid+1這個位置的元素已經大於value我們也直接返回mid
//因為我們的目的是找最後一個小於等於value的下標,mid這個位置的元素是小於等於value我們已經知道
//那麼如果mid+1這個位置的元素是大於value的話,我們就找到了那個左邊界
			if (mid == length - 1 || arr[mid + 1] > value)
				return mid;
			else
				low = mid + 1;
          //如果此時的mid+1這個位置的元素依然是小於value,我們就繼續從右邊開始找
		}
		else
		{
			high = mid - 1;
		}
	}
	return -1;
}

6.求一個迴圈有序陣列的最小值例如(5,1,2,3,4),5 

輸出:1

int find_Min(vector<int> arr, int index1, int index2)
{
	int result = arr[index1];
	for (int i = index1 + 1; i <= index2; i++)
		if (result > arr[i])
			result = arr[i];
	return result;
}
int getMin(vector<int> arr, int n) {
	// write code here
	if (arr.empty())
		return -1;
	int index1 = 0;
	int index2 = n - 1;
	int mid = 0;//如果一開始迴圈條件就不滿足,說明就是一個有序序列,所以最小值是下標0處
	while (arr[index1] >= arr[index2])
	{
      /*如果找到那個左邊序列最大值與右邊序列最小值的邊界時,由於我們迴圈進來的條件是arr[index1]>=arr[index2],自然而然 最小值肯定是index*/
		if (index2 - index1 == 1)
		{
			mid = index2;
			break;
		}
		mid = index1 + ((index2 - index1) >> 1);
     //如果最左邊的值和中間的值和最右邊的值都相等的話,我們無法確定中間的那個值是屬於前半段有序序 
     //列還是後半段有序序列,所以此時需要採用順序查詢
    //舉例  1 0 1 1 1和 1 1 1 0 1 中間的1可以是左邊序列也可以是右邊序列
		if (arr[mid] == arr[index1] && arr[index1] == arr[index2])
			return find_Min(arr, index1, index2);
     //此時是在左邊界比右邊界大的前提下(迴圈條件就是如此),同時中間值比左邊界還大,自然最小值不 //會出現在左半邊,所以我們修改左邊界 繼續從右半邊序列開始找
		if (arr[mid] >= arr[index1])
			index1 = mid;
		else if (arr[mid] <= arr[index2])
			index2 = mid;
     //此時是左邊界比右邊界大的前提下,同時中間值比左邊界小,那麼如果我們的中間值比右邊界還小的話
    //說明左邊有序序列的最大值與右邊有序序列最小值的交界處此時應該是在mid左邊,我們修改右邊界,從 
    //mid左邊的序列開始繼續找
}
	return arr[mid];
}

7.有一個有序陣列ARR,其中不含有重複元素,請找到滿足ARR [I] ==我條件的最左的位置。如果所有位置上的數都不滿足條件,返回-1。

給定有序陣列arr及它的大小n,請返回所求求值。

測試樣例:

[-1,0,2,3],4  返回:2
int findPos(vector<int> arr, int n) {
        if (arr[0] > n - 1 || arr[n - 1] < 0)
		return -1;
	int left = 0;
	int right = n - 1;
	int mid;
	int res = -1;
	while (left <= right)
	{
     //如果當前值比下標還小,說明左半邊不可能有arr[i]=i的出現(在沒有重複值的前提下)
		int mid = (left + right) / 2;
		if (arr[mid]<mid)
		{
			left = mid+1;
		}
		else if (arr[mid] > mid)//如果當前值比下標大,右半邊肯定也是延續的,所以我們看左半邊
		{
			right = mid-1;
		}
		else
		{
			if(mid==0||arr[mid-1]!=mid-1)
                          return mid;
            else
                right=mid-1;
		}
	}
	return res;
    }

8.連結串列可以使用二分查詢麼?

答:可以使用,但是查詢效率就達不到O(logn)時間了因為指定一個位置,連結串列必須從頭開始走,不像陣列那樣具有隨機定位的特性,所以每次我們找中間位置時必須從頭走到那個位置,而不是像陣列一樣直接定位到中間。唯一的難點在於如何找到連結串列的中間點,這裡就要運用到了一個額外的技巧——快慢指標。快慢指標常常用於判斷連結串列中是否有環,簡單來說就是有兩個指標,快指標每次走兩個節點,慢指標每次走一個節點,當快指標走到連結串列盡頭時,慢指標才走到了連結串列的一半,此時就是我們想要的中間節點。那麼連結串列使用二分查詢效率會退化到多少呢?答案是O(N)!因為二分第一次要走的n / 2,第二次N / 4,第三次N / 8。根據等比數列前Ñ項和計算最終是線性級數。

9.計算一個數的平方根(使其精度在小數點後六位)

float sqrt(int x)
{
    double low=0;
    double mid=x/2;
    double high=x;
   while(abs(mid*mid-x)>0.000001)
    {
       if(mid*mid<x)
         low=mid;
       else
         high=mid;
      mid=(low+high)/2;
     } 
     return mid;
}