二分查找
程序或算法的時間復雜度
- 一個程序或算法的時間效率,也稱“時間復雜度”,有時簡稱“復雜度”
- 復雜度常用大的字母O和小寫字母n來表示,比如O(n),O(n2)等。n代表問題的
規模 - 時間復雜度是用算法運行過程中,某種時間固定的操作需要被執行的次數和n
的關系來度量的。在無序數列中查找某個數,復雜度是O(n) - 計算復雜度的時候,只統計執行次數最多的(n足夠大時)那種固定操作的次數
。比如某個算法需要執行加法n2次,除法n次,那么就記其復雜度是O(n2)的。
插入排序
void InsertionSort(int a[] ,int size)
{
for(int i = 1;i < size; ++i ) {
//a[i]是最左的無序元素,每次循環將a[i]放到合適位置
for(int j = 0; j < i; ++j)
if( a[j]>a[i]) {
//要把a[i]放到位置j,原下標j到 i-1的元素都往后移一個位子
int tmp = a[i];
for(int k = i; k > j; --k)
a[k] = a[k-1];
a[j] = tmp;
break;
}
}
} //復雜度O(n2)
如果復雜度是多個n的函數之和,則只關心隨n的增長增長得最快的那個函數
O(n3+n2
) => O(n3
)
O(2n+n3
) => O(2n
)
O(n! + 3n
) => O(n!)常數復雜度:O(1) 時間(操作次數)和問題的規模無關
對數復雜度:O(log(n))
線性復雜度:O(n)
多項式復雜度:O(n
k )指數復雜度:O(an )
階乘復雜度:O(n! )
復雜度有“平均復雜度”和“最壞復雜度”兩種。
兩者可能一致,也可能不一致在無序數列中查找某個數(順序查找) O(n)
平面上有n個點,要求出任意兩點之間的距離 O(n2)
插入排序、選擇排序、冒泡排序 O(n2)
快速排序 O( n*log(n))
二分查找 O(log(n))
二分查找
- A心里想一個1-1000之間的數,B來猜,可以問問題,A只能回答是或否。
怎么猜才能問的問題次數最少?是1嗎?是2嗎?.......是999嗎? 平均要問500次大于500嗎?大于750嗎?大于625嗎? ......每次縮小猜測范圍到上次的一半,只需要 10次
二分查找函數
- 寫一個函數BinarySeach,在包含size個元素的、從小到大排序的int數組a里查找元素p,如果找到,則返回元素下標,如果找不到,則返回-1。要求復雜度O(log(n))
int BinarySearch(int a[],int size,int p)
{
int L = 0; //查找區間的左端點
int R = size - 1; //查找區間的右端點
while( L <= R) { //如果查找區間不為空就繼續查找
int mid = L+(R-L)/2; //取查找區間正中元素的下標
if( p == a[mid] )
return mid;
else if( p > a[mid])
L = mid + 1; //設置新的查找區間的左端點
else
R = mid - 1; //設置新的查找區間的右端點
}
return -1;
} //復雜度O(log(n))
- 寫一個函數LowerBound,在包含size個元素的、從小到大排序的int數組a里查找比給定整數p小的,下標最大的元素。找到則返回其下標,找不到則返回-1
int LowerBound(int a[],int size,int p) //復雜度O(log(n))
{
int L = 0; //查找區間的左端點
int R = size - 1; //查找區間的右端點
int lastPos = -1; //到目前為止找到的最優解
while( L <= R) { //如果查找區間不為空就繼續查找
int mid = L+(R-L)/2; //取查找區間正中元素的下標
if(a[mid]>= p)
R = mid - 1;
else {
lastPos = mid;
L = mid+1;
}
}
return lastPos;
}
- 注意:
int mid = (L+R)/2; //取查找區間正中元素的下標 - 為了防止 (L+R)過大溢出:
int mid = L+(R-L)/2;
二分法求方程的根
求下面方程的一個根:f(x) = x3-5x2+10^x-80 = 0
若求出的根是a,則要求 |f(a)| <= 10^(-6)
- 解法:對f(x)求導,得f'(x)=3x^2-10x+10。由一元二次方程求根公式知方程
f'(x)= 0 無解,因此f'(x)恒大于0。故f(x)是單調遞增的。易知 f(0) < 0且
f(100)>0,所以區間[0,100]內必然有且只有一個根。由于f(x)在[0,100]內是
單調的,所以可以用二分的辦法在區間[0,100]中尋找根。
二分法求方程的根
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
double EPS = 1e-6;
double f(double x) { return x*x*x - 5*x*x + 10*x - 80; }
int main() {
double root, x1 = 0, x2 = 100,y;
root = x1+(x2-x1)/2;
int triedTimes = 1; //記錄一共嘗試多少次,對求根來說不是必須的
y = f(root);
while( fabs(y) > EPS) {
if( y > 0 ) x2 = root;
else x1 = root;
root = x1+(x2 - x1)/2;
y = f(root);
triedTimes ++;
}
printf("%.8f\n",root);
printf("%d",triedTimes);
return 0;
}
例題1
輸入n ( n<= 100,000)個整數,找出其中的兩個數,它們之和等于整數m(假定
肯定有解)。題中所有整數都能用 int 表示
解法1:用兩重循環,枚舉所有的取數方法,復雜度是O(n2)的。
for(int i = 0;i < n-1; ++i)
for(int j = i + 1; j < n; ++j)
if( a[i]+a[j] == m)
break;
100,0002 = 100億,在各種OJ上提交或參加各種程序設計競賽,這樣的復雜度都會超時
!
解法2:
- 將數組排序,復雜度是O(n×log(n))
- 對數組中的每個元素a[i],在數組中二分查找m-a[i],看能否找到。復雜度log(n)
,最壞要查找n-2次,所以查找這部分的復雜度也是O(n×log(n))
這種解法總的復雜度是O(n×log(n))的
解法3:
- 將數組排序,復雜度是O(n×log(n))
- 查找的時候,設置兩個變量i和j,i初值是0,j初值是n-1.看a[i]+a[j],如果大于m,
就讓j減1,如果小于m,就讓i加1,直至a[i]+a[j]=m。
這種解法總的復雜度是O(n×log(n))的。
例題2 百練 2456:Aggressive cows
http://bailian.openjudge.cn/practice/2456
農夫 John 建造了一座很長的畜欄,它包括N (2≤N≤100,000)個隔間,這
些小隔間的位置為x0
,...,xN-1 (0≤xi≤1,000,000,000,均為整數,各不相同).
John的C (2≤C≤N)頭牛每頭分到一個隔間。牛都希望互相離得遠點省得
互相打擾。怎樣才能使任意兩頭牛之間的最小距離盡可能的大,這個最
大的最小距離是多少呢?
- 解法1:
先得到排序后的隔間坐標 x0,...,xN-1
從1,000,000,000/C到1依次嘗試這個“最大的最近距離”D, 找到的
第一個可行的就是答案。
嘗試方法:
- 第1頭牛放在x0
- 若第k頭牛放在xi ,則找到xi+1到xN-1中第一個位于[xi+D, 1,000,000,000]中的Xj
第k+1頭牛放在Xj。找不到這樣的Xj,則 D=D-1,轉 1)再試
若所有牛都能放下,則D即答案
復雜度 1,000,000,000/C *N,即 1,000,000,000, 超時!
- 解法2:
先得到排序后的隔間坐標 x0,...,xN-1
在[L,R]內用二分法嘗試“最大最近距離”D = (L+R)/2 (L,R初值為
[1, 1,000,000,000/C]
若D可行,則記住該D,然后在新[L,R]中繼續嘗試(L= D+1)
若D不可行,則在新[L,R]中繼續嘗試(R= D-1)
復雜度 log(1,000,000,000/C) * N