程序设计与算法(一) 第十一周

二分查找

程序或算法的时间复杂度

  • 一个程序或算法的时间效率,也称“时间复杂度”,有时简称“复杂度”
  • 复杂度常用大的字母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:

  1. 将数组排序,复杂度是O(n×log(n))
  2. 对数组中的每个元素a[i],在数组中二分查找m-a[i],看能否找到。复杂度log(n)
    ,最坏要查找n-2次,所以查找这部分的复杂度也是O(n×log(n))
    这种解法总的复杂度是O(n×log(n))的

解法3:

  1. 将数组排序,复杂度是O(n×log(n))
  2. 查找的时候,设置两个变量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. 第1头牛放在x0
  2. 若第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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容