1. 程式人生 > >[C++]二分查詢法

[C++]二分查詢法

引用:https://baike.baidu.com/item/%E4%BA%8C%E5%88%86%E6%B3%95/1364267?fr=aladdin


二分法

對於區間\boldsymbol{[a,b]}上連續不斷且\boldsymbol{f(a)\cdot f(b)< 0}的函式\boldsymbol{y=f(x)},通過不斷地把函式\boldsymbol{f(x)}的零點所在的區間一分為二,使區間的兩個端點逐步逼近零點,進而得到零點近似值的方法叫二分法。

定義

二分法(Bisection method) 即一分為二的方法. 設\boldsymbol{[a,b]}為R的閉區間. 逐次二分法就是造出如下的區間序列\boldsymbol{([an,bn]):a0=a}\boldsymbol{b0=b},且對任一自然數\boldsymbol{n}\boldsymbol{[an+1,bn+1]}或者等於\boldsymbol{[an,cn]},或者等於\boldsymbol{[cn,bn]},其中\boldsymbol{cn}表示\boldsymbol{[an,bn]}的中點。

典型演算法

演算法:當資料量很大適宜採用該方法。採用二分法查詢時,資料需是排好序的。

基本思想:假設資料是按升序排序的,對於給定值\boldsymbol{key},從序列的中間位置k開始比較,

如果當前位置\boldsymbol{arr[k]}值等於\boldsymbol{key},則查詢成功;

\boldsymbol{key}小於當前位置值\boldsymbol{arr[k]},則在數列的前半段中查詢,\boldsymbol{arr[low,mid-1]}

\boldsymbol{key}大於當前位置值\boldsymbol{arr[k]},則在數列的後半段中繼續查詢\boldsymbol{arr[mid+1,high]}

直到找到為止,時間複雜度:\boldsymbol{O(log(n))} 

求法

給定精確度ξ\boldsymbol{\xi},用二分法求函式\boldsymbol{f(x)}零點近似值的步驟如下:

1 確定區間\boldsymbol{[a,b]},驗證\boldsymbol{f(a)·f(b)<0},給定精確度\boldsymbol{\xi}.

2 求區間\boldsymbol{(a,b)}的中點\boldsymbol{c}.

3 計算\boldsymbol{f(c)}.

(1) 若\boldsymbol{f(c)=0},則\boldsymbol{c}就是函式的零點;

(2) 若\boldsymbol{f(a)·f(c)<0},則令\boldsymbol{b=c};

(3) 若\boldsymbol{f(c)·f(b)<0},則令\boldsymbol{a=c}.

(4) 判斷是否達到精確度\boldsymbol{\xi}:即若\boldsymbol{|a-b|<\xi},則得到零點近似值\boldsymbol{a}(或\boldsymbol{b}),否則重複2-4.


例題

Oj Url:http://noi.openjudge.cn/ch0111/02/

             http://noi.openjudge.cn/ch0111/01/

01:二分法求函式的零點

總時間限制: 1000ms

記憶體限制: 65536kB

 

描述

有函式:\boldsymbol{f(x) = x5 - 15 * x4+ 85 * x3- 225 * x2+ 274 * x - 121}

已知 \boldsymbol{f(1.5)>0} , \boldsymbol{f(2.4)<0}

且方程 \boldsymbol{f(x)=0} 在區間 \boldsymbol{[1.5,2,4]} 有且只有一個根,請用二分法求出該根。

 

輸入    無

輸出    該方程在區間 \boldsymbol{[1.5,2,4]} 中的根。要求四捨五入到小數點後6位。

 

樣例輸入    無

樣例輸出    不提供

程式程式碼

#include <iostream>
#include <iomanip>
#include <cmath>
typedef double db;
inline bool determine(const db mid) {
	db f = pow(mid, 5) - 15.0 * pow(mid, 4) + 85.0 * pow(mid, 3) - 225.0 * pow(mid, 2) + 274.0 * mid - 121.0;
	return f > 0.0;	
}; 
int main(int argc, char *argv[]) {
	std::ios::sync_with_stdio(false);
	db left = 1.5, right = 2.4, mid;
	while (right - left > 1e-8) {
		mid = left + (right - left) / 2.0;
		if (determine(mid)) left = mid;
		else right = mid;	
	};
	std::cout << std::fixed << std::setprecision(6) << mid << std::endl;
	return 0;	
};  

 02:查詢最接近的元素

總時間限制: 1000ms

記憶體限制: 65536kB

 

描述

在一個非降序列中,查詢與給定值最接近的元素。

 

輸入

第一行包含一個整數\boldsymbol{n},為非降序列長度。\boldsymbol{1 <= n <= 100000}
第二行包含\boldsymbol{n}個整數,為非降序列各元素。所有元素的大小均在\boldsymbol{0-1,000,000,000}之間。
第三行包含一個整數\boldsymbol{m},為要詢問的給定值個數。\boldsymbol{1 <= m <= 10000}
接下來m行,每行一個整數,為要詢問最接近元素的給定值。所有給定值的大小均在\boldsymbol{0-1,000,000,000}之間。

輸出

\boldsymbol{m}行,每行一個整數,為最接近相應給定值的元素值,保持輸入順序。若有多個值滿足條件,輸出最小的一個。

 

樣例輸入

3
2 5 8
2
10
5

樣例輸出

8
5

程式程式碼

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int INF = 0x3ffffff;
const int MAX_SIZE = 1000001;
int array[MAX_SIZE] = {};
template <typename _Tp>
inline void quicksort(_Tp *arr, int start, int end) {
	int i = start, j = end; _Tp pivot = arr[start];
	if (i >= j) return;
	while (i != j) {
		while (i < j && arr[j] > pivot) --j;
		while (i < j && arr[i] < pivot) ++i;
		if (i < j) swap(arr[i], arr[j]);	
	};	
	swap(arr[i], arr[start]);
	quicksort(arr, start, i - 1);
	quicksort(arr, i + 1, end);
};
int main(int argc, char *argv[]) {
	int n; cin >> n;
    for (int i = 0; i < n; ++i) scanf("%d", array + i);
	int m; cin >> m;
    while (m--) {
        int bf, answer, left = 0, mid, right = n - 1; cin >> bf;
        if (bf <= array[0]) {cout << array[0] << endl; continue;};
        if (bf >= array[n - 1]) {cout << array[n - 1] << endl; continue;};
        while (left <= right) {
            mid = left + (right - left) / 2;
            if(array[mid] <= bf) left = mid + 1;
            else right = mid - 1;
        };
        if (abs(array[left] - bf) < abs(array[right] - bf)) answer = left;
        else answer = right;	
        cout << array[answer] << endl;
    };
    return 0;
};