1. 程式人生 > >c++實現二分查詢

c++實現二分查詢

簡要描述

二分查詢又稱折半查詢,對排好序的陣列,每次取這個數和陣列中間的數進行比較,複雜度是O(logn):設陣列為a[n],查詢的數x,

如果x==a[n/2],則返回n/2;

如果x < a[n/2],則在a[0]a[n/2-1]中進行查詢;

如果x > a[n/2],則在a[n/2+1]a[n-1]中進行查詢;

優點是比較次數少,查詢速度快,平均效能好;其缺點是要求待查表為有序表,且插入刪除困難。

條件:查詢的陣列必須要為有序陣列。

二種實現方式

1.遞迴

/*
歸的二分查詢
arrat:陣列 , low:上界;  high:下界;  target:查詢的資料; 返回target所在陣列的下標 
*/
int binarySearch(int array[], int low, int high, int target) {
	int middle = (low + high)/2;
	if(low > high) {
		return -1;
	}
	if(target == array[middle]) {
		return middle;
	}
	if(target < array[middle]) {
		return binarySearch(array, low, middle-1, target);
	}
	if(target > array[middle]) {
		return binarySearch(array, middle+1, high, target);
	} 
}


2.非遞迴(迴圈)

/*
非遞迴的二分查詢 
arrat:陣列 , n:陣列的大小;  target:查詢的資料; 返回target所在陣列的下標 
*/
int binarySearch2(int array[], int n, int target) {
	int low = 0, high = n, middle = 0;
	while(low < high) {
		middle = (low + high)/2;
		if(target == array[middle]) {
			return middle;
		} else if(target < array[middle]) {
			high = middle;
		} else if(target > array[middle]) {
			low = middle + 1;
		}
	}
	return -1;
}


推薦使用非遞迴的方式,因為遞迴每次呼叫遞迴時有用堆疊儲存函式資料和結果。能用迴圈的儘量不用遞迴。

二分查詢的應用

還是對上一篇博文《C++如何跳出多層迴圈》中提到的抽籤問題進行分析。

上一篇博文中是進行了四重迴圈的巢狀,基時間複雜度是O(n4),資料大時其計算量會大的驚人。為便於分析,將之前程式碼帖至如下:

**
抽籤問題
解決方案,複雜度n^4 
*/ 
void  drawLots() {
   //從標準輸入讀入
   int numOfCard, sum;
   int k[MAX_N];
   cout<<"輸入numOfCard和sum"<<endl;
   cin>>numOfCard>>sum; 
   cout<<"請輸入這sum張卡片的數字"<<endl;
   for(int i=0; i<numOfCard; i++) {
cin>>k[i];
   }
   bool result = false;
   bool isBreakLoop = true;
   int _sum = 0;
   for(int a = 0; a < numOfCard && isBreakLoop; a ++) {
      for(int b = 0; b < numOfCard && isBreakLoop; b ++) {
          for(int c = 0; c < numOfCard && isBreakLoop; c++) {
              for(int d = 0; d < numOfCard && isBreakLoop; d ++) {
              	_sum = k[a] + k[b] + k[c] + k[d];
                  if(_sum == sum) {
result = true;
isBreakLoop = false;
                  }	 
              }
          }
      }
   }
   cout << "_sum:" << _sum << "  " << "sum:" << sum << endl;
   if(result){
   	cout<<"Yes"<<endl;
   } else
    cout<<"No"<<endl;
}

最內層迴圈所做事如下:

K+ kb + kc + kd = m

移項後如下:

Kd = m - (K+ kb + kc)

到第四層迴圈時,其實Kkbkc已經知道,那問題也就變成了對kd的查詢,我們可用上面講的二分查詢,複雜度就降為O(n3logn).實現如下:

降低複雜度的實現

/**
抽籤問題 
解決方案,複雜度n^3 * log(n)
*/ 
void drawLots2() {
int numOfCard, sum;
int k[MAX_N];
cout<<"輸入numOfCard和sum"<<endl;
    cin>>numOfCard>>sum; 
    cout<<"請輸入這sum張卡片的數字"<<endl;
    for(int i=0; i<numOfCard; i++) {
cin>>k[i];
    }
    //對陣列進行排序 
    sort(k, k + numOfCard);
int index = -1;
    bool isBreakLoop = true;
    for(int a = 0; a < numOfCard && isBreakLoop; a ++) {
    	for(int b = 0; b < numOfCard && isBreakLoop; b ++) {
        	for(int c = 0; c < numOfCard && isBreakLoop; c++) {
        	index = binarySearch2(k, numOfCard, sum - (k[a] + k[b] + k[c]));
            	if(index >= 0) {
isBreakLoop = false;
                }
          	}
      	}
}
   if(index >= 0){
   	cout<<"Yes"<<endl;
   } else
    cout<<"No"<<endl;
}

進一步優化[O(n2logn)]

根據上一步的優化方式,我們可以進一步對內側兩層迴圈(也就是第三層和第四層)進行思考:

Kc+ kd = m - (K+ kb)

我們不能直接對Kc+ kd進行查詢,但是可以預先枚舉出K+ kb 的n2種數值並排序,再對Kc+ kd進行十分查詢。列出列舉O(n2),排序O(n2logn), 迴圈O(n2logn),所以總的複雜度降為O(n2logn),實現如下:

/**
抽籤問題 
解決方案,複雜度n^2 * log(n)
*/ 
void drawLots3() {
int numOfCard, sum;
int k[MAX_N];
cout<<"輸入numOfCard和sum"<<endl;
    cin>>numOfCard>>sum; 
    cout<<"請輸入這sum張卡片的數字"<<endl;
    for(int i=0; i<numOfCard; i++) {
cin>>k[i];
    }
    int cdNum = numOfCard*(numOfCard+1)/2;
    int cdSum[cdNum];
    int i = 0;
    for(int a=0; a<numOfCard; a++) {
    	for(int b=i; b<numOfCard; b++) {
    	cdSum[i ++] = k[a] + k[b];
    	}
    }
    //對陣列進行排序 
    sort(cdSum, cdSum + cdNum);
int index = -1;
    bool isBreakLoop = true;
    for(int a = 0; a < numOfCard && isBreakLoop; a ++) {
    	for(int b = 0; b < numOfCard && isBreakLoop; b ++) {
        	for(int c = 0; c < numOfCard && isBreakLoop; c++) {
        	index = binarySearch2(cdSum, cdNum, sum - (k[a] + k[b]));
            	if(index >= 0) {
isBreakLoop = false;
                }
          	}
      	}
}
   if(index >= 0){
   	cout<<"Yes"<<endl;
   } else
    cout<<"No"<<endl;
}

進一步思考

上面列舉K+ kb 時其實是有重複的,因為k[i] + k[j] == k[j] + k[i],去除重複值之後,K+ kb 值的個數是n(n+1)/2。至於n(n+1)/2怎麼來,可以簡單推導如下:

N     M

1     1

2      2+1

3     3+2+1

4     4+ 3+2+1

......

實現如下:

/**
抽籤問題 
解決方案,複雜度n^2 * log(n)
*/ 
void drawLots3_1() {
int numOfCard, sum;
int k[MAX_N];
cout<<"輸入numOfCard和sum"<<endl;
    cin>>numOfCard>>sum; 
    cout<<"請輸入這sum張卡片的數字"<<endl;
    for(int i=0; i<numOfCard; i++) {
cin>>k[i];
    }
    int cdNum = numOfCard*numOfCard;
    int cdSum[cdNum];
    for(int a=0; a<numOfCard; a++) {
    	for(int b=0; b<numOfCard; b++) {
    	cdSum[a*numOfCard + b] = k[a] + k[b];
    	}
    }
    //對陣列進行排序 
    sort(cdSum, cdSum + cdNum);
int index = -1;
    bool isBreakLoop = true;
    for(int a = 0; a < numOfCard && isBreakLoop; a ++) {
    	for(int b = 0; b < numOfCard && isBreakLoop; b ++) {
        	for(int c = 0; c < numOfCard && isBreakLoop; c++) {
        	index = binarySearch2(cdSum, cdNum, sum - (k[a] + k[b]));
            	if(index >= 0) {
isBreakLoop = false;
                }
          	}
      	}
}
   if(index >= 0){
   	cout<<"Yes"<<endl;
   } else
    cout<<"No"<<endl;
}