一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

折半查找算法

 fruitapple 2010-05-31

如果不是從一組隨機(jī)的序列里查找,而是從一組排好序的序列里找出某個(gè)元素的位置,則可以有更快的算法:

例 11.4. 折半查找

#include <stdio.h>
#define LEN 8
int a[LEN] = { 1, 2, 2, 2, 5, 6, 8, 9 };
int binarysearch(int number)
{
int mid, start = 0, end = LEN - 1;
while (start <= end) {
mid = (start + end) / 2;
if (a[mid] < number)
start = mid + 1;
else if (a[mid] > number)
end = mid - 1;
else
return mid;
}
return -1;
}
int main(void)
{
printf("%d\n", binarysearch(5));
return 0;
}

由于這個(gè)序列已經(jīng)從小到大排好序了,每次取中間的元素和待查找的元素比較,如果中間的元素比待查找的元素小,就說明“如果待查找的元素存在,一定位于序列的后半部分”,這樣可以把搜索范圍縮小到后半部分,然后再次使用這種算法迭代。這種“每次將搜索范圍縮小一半”的思想稱為折半查找(Binary Search)。思考一下,這個(gè)算法的時(shí)間復(fù)雜度是多少?

這個(gè)算法的思想很簡單,不是嗎?可是[編程珠璣]上說作者在課堂上講完這個(gè)算法的思想然后讓學(xué)生寫程序,有90%的人寫出的程序中有各種各樣的Bug,讀者不信的話可以不看書自己寫一遍試試。這個(gè)算法容易出錯(cuò)的地方很多,比如mid = (start + end) / 2;這一句,在數(shù)學(xué)概念上其實(shí)是mid = ⌊(start + end) / 2⌋,還有start = mid + 1;end = mid - 1;,如果前者寫成了start = mid;或后者寫成了end = mid;那么很可能會(huì)導(dǎo)致死循環(huán)(想一想什么情況下會(huì)死循環(huán))。

怎樣才能保證程序的正確性呢?在第 2 節(jié) “插入排序”我們講過借助Loop Invariant證明循環(huán)的正確性,binarysearch這個(gè)函數(shù)的主體也是一個(gè)循環(huán),它的Loop Invariant可以這樣描述:待查找的元素number如果存在于數(shù)組a之中,那么一定存在于a[start..end]這個(gè)范圍之間,換句話說,在這個(gè)范圍之外的數(shù)組a的元素中一定不存在number這個(gè)元素。以下為了書寫方便,我們把這句話表示成mustbe(start, end, number)??梢砸贿吙此惴ㄒ贿呑鐾评恚?/p>

 
int binarysearch(int number)
{
int mid, start = 0, end = LEN - 1;
/* 假定a是排好序的 */
/* mustbe(start, end, number),因?yàn)閍[start..end]就是整個(gè)數(shù)組a[0..LEN-1] */
while (start <= end) {
/* mustbe(start, end, number),因?yàn)橐婚_始進(jìn)入循環(huán)時(shí)是正確的,每次循環(huán)也都維護(hù)了這個(gè)條件 */
mid = (start + end) / 2;
if (a[mid] < number)
/* 既然a是排好序的,a[start..mid]應(yīng)該都比number小,所以mustbe(mid+1, end, number) */
start = mid + 1;
/* 維護(hù)了mustbe(start, end, number) */
else if (a[mid] > number)
/* 既然a是排好序的,a[mid..end]應(yīng)該都比number大,所以mustbe(start, mid-1, number) */
end = mid - 1;
/* 維護(hù)了mustbe(start, end, number) */
else
/* a[mid] == number,說明找到了 */
return mid;
}
/*
* mustbe(start, end, number)一直被循環(huán)維護(hù)著,到這里應(yīng)該仍然成立,在a[start..end]范圍之外一定不存在number,
* 但現(xiàn)在a[start..end]是空序列,在這個(gè)范圍之外的正是整個(gè)數(shù)組a,因此整個(gè)數(shù)組a中都不存在number
*/
return -1;
}

注意這個(gè)算法有一個(gè)非常重要的前提--a是排好序的。缺了這個(gè)前提,“如果a[mid] < number,那么a[start..mid]應(yīng)該都比number”這一步推理就不能成立,這個(gè)函數(shù)就不能正確地完成查找。從更普遍的意義上說,函數(shù)的調(diào)用者(Caller)和函數(shù)的實(shí)現(xiàn)者(Callee,被調(diào)用者)之間訂立了一個(gè)契約(Contract),在調(diào)用函數(shù)之前,Caller要為Callee提供某些條件,比如確保a是排好序的,確保a[start..end]都是有效的數(shù)組元素而沒有訪問越界,這稱為Precondition,然后Callee對(duì)一些Invariant進(jìn)行維護(hù)(Maintenance),這些Invariant保證了Callee在函數(shù)返回時(shí)能夠?qū)aller盡到某些義務(wù),比如確保“如果number在數(shù)組a中存在,一定能找出來并返回它的位置,如果number在數(shù)組a中不存在,一定能返回-1”,這稱為Postcondition。如果每個(gè)函數(shù)的文檔都非常清楚地記錄了Precondition、Maintenance和Postcondition是什么,那么每個(gè)函數(shù)都可以獨(dú)立編寫和測試,整個(gè)系統(tǒng)就會(huì)易于維護(hù)。這種編程思想是由Eiffel語言的設(shè)計(jì)者Bertrand Meyer提出來的,稱為Design by Contract(DbC)。

測試一個(gè)函數(shù)是否正確需要把Precondition、Maintenance和Postcondition這三方面都測試到,比如binarysearch這個(gè)函數(shù),即使它寫得非常正確,既維護(hù)了Invariant也保證了Postcondition,如果調(diào)用它的Caller沒有保證Precondition,最后的結(jié)果也還是錯(cuò)的。我們編寫幾個(gè)測試用的Predicate函數(shù),然后把相關(guān)的測試插入到binarysearch函數(shù)中:

例 11.5. 帶有測試代碼的折半查找

 
#include <stdio.h>
#include <assert.h>
#define LEN 8
int a[LEN] = { 1, 2, 2, 2, 5, 6, 8, 9 };
int is_sorted(void)
{
int i;
for (i = 1; i < LEN; i++)
if (a[i-1] > a[i])
return 0;
return 1;
}
int mustbe(int start, int end, int number)
{
int i;
for (i = 0; i < start; i++)
if (a[i] == number)
return 0;
for (i = end+1; i < LEN; i++)
if (a[i] == number)
return 0;
return 1;
}
int contains(int n)
{
int i;
for (i = 0; i < LEN; i++)
if (a[i] == n)
return 1;
return 0;
}
int binarysearch(int number)
{
int mid, start = 0, end = LEN - 1;
assert(is_sorted()); /* Precondition */
while (start <= end) {
assert(mustbe(start, end, number)); /* Maintenance */
mid = (start + end) / 2;
if (a[mid] < number)
start = mid + 1;
else if (a[mid] > number)
end = mid - 1;
else {
assert(mid >= start && mid <= end
&& a[mid] == number); /* Postcondition 1 */
return mid;
}
}
assert(!contains(number)); /* Postcondition 2 */
return -1;
}
int main(void)
{
printf("%d\n", binarysearch(5));
return 0;
}
 

 

assert是頭文件assert.h中的一個(gè)宏定義,執(zhí)行到assert(is_sorted())這句時(shí),如果is_sorted()返回值為真,則當(dāng)什么事都沒發(fā)生過,繼續(xù)往下執(zhí)行,如果is_sorted()返回值為假(例如把數(shù)組的排列順序改一改),則報(bào)錯(cuò)退出程序:

main: main.c:33: binarysearch: Assertion `is_sorted()' failed.
Aborted

在代碼中適當(dāng)?shù)牡胤绞褂脭嘌裕ˋssertion)可以有效地幫助我們測試程序。也許有人會(huì)問:我們用幾個(gè)測試函數(shù)來測試binarysearch,那么這幾個(gè)測試函數(shù)又用什么來測試呢?在實(shí)際工作中我們要測試的代碼絕不會(huì)像binarysearch這么簡單,而我們編寫的測試函數(shù)往往都很簡單,比較容易保證正確性,也就是用簡單的、不容易出錯(cuò)的代碼去測試復(fù)雜的、容易出錯(cuò)的代碼。

測試代碼只在開發(fā)和調(diào)試時(shí)有用,如果正式發(fā)布(Release)的軟件也要運(yùn)行這些測試代碼就會(huì)嚴(yán)重影響性能了,如果在包含assert.h之前定義一個(gè)NDEBUG宏(表示No Debug),就可以禁用assert.h中的assert宏定義,這樣代碼中的所有assert測試都不起作用了:

#define NDEBUG
#include <stdio.h>
#include <assert.h>
...

注意NDEBUG和我們以前使用的宏定義有點(diǎn)不同,例如#define N 20N定義為20,在預(yù)處理時(shí)把代碼中所有的標(biāo)識(shí)符N替換成20,而#define NDEBUGNDEBUG定義為空,在預(yù)處理時(shí)把代碼中所有的標(biāo)識(shí)符NDEBUG替換成空。這樣的宏定義主要是為了用#ifdef等預(yù)處理指示測試它定義過沒有,而不是為了做替換,所以定義成什么值都無所謂,一般定義成空就足夠了。

還有另一種辦法,不必修改源文件,在編譯命令行加上選項(xiàng)-DNDEBUG就相當(dāng)于在源文件開頭定義了NDEBUG宏。宏定義和預(yù)處理到第 21 章 預(yù)處理再詳細(xì)解釋,在第 4 節(jié) “其它預(yù)處理特性”將給出assert.h一種實(shí)現(xiàn)。

1、本節(jié)的折半查找算法有一個(gè)特點(diǎn):如果待查找的元素在數(shù)組中有多個(gè)則返回其中任意一個(gè),以本節(jié)定義的數(shù)組int a[8] = { 1, 2, 2, 2, 5, 6, 8, 9 };為例,如果調(diào)用binarysearch(2)則返回3,即a[3],而有些場合下要求這樣的查找返回a[1],也就是說,如果待查找的元素在數(shù)組中有多個(gè)則返回第一個(gè)。請(qǐng)修改折半查找算法實(shí)現(xiàn)這一特性。

2、編寫一個(gè)函數(shù)double mysqrt(double y);y的正平方根,參數(shù)y是正實(shí)數(shù)。我們用折半查找來找這個(gè)平方根,在從0到y之間必定有一個(gè)取值是y的平方根,如果我們查找的數(shù)xy的平方根小,則x2<y,如果我們查找的數(shù)xy的平方根大,則x2>y,我們可以據(jù)此縮小查找范圍,當(dāng)我們查找的數(shù)足夠準(zhǔn)確時(shí)(比如滿足|x2-y|<0.001),就可以認(rèn)為找到了y的平方根。思考一下這個(gè)算法需要迭代多少次?迭代次數(shù)的多少由什么因素決定?

3、編寫一個(gè)函數(shù)double mypow(double x, int n);xn次方,參數(shù)n是正整數(shù)。最簡單的算法是:

double product = 1;
for (i = 0; i < n; i++)
product *= x;

這個(gè)算法的時(shí)間復(fù)雜度是Θ(n)。其實(shí)有更好的辦法,比如mypow(x, 8),第一次循環(huán)算出x·x=x2,第二次循環(huán)算出x2·x2=x4,第三次循環(huán)算出4·x4=x8。這樣只需要三次循環(huán),時(shí)間復(fù)雜度是Θ(lgn)。思考一下如果n不是2的整數(shù)次冪應(yīng)該怎么處理。請(qǐng)分別用遞歸和循環(huán)實(shí)現(xiàn)這個(gè)算法。

從以上幾題可以看出,折半查找的思想有非常廣泛的應(yīng)用,不僅限于從一組排好序的元素中找出某個(gè)元素的位置,還可以解決很多類似的問題。[編程珠璣]對(duì)于折半查找的各種應(yīng)用和優(yōu)化技巧有非常詳細(xì)的介紹。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    男人的天堂的视频东京热| 欧美av人人妻av人人爽蜜桃| 欧美国产亚洲一区二区三区| 老司机精品福利视频在线播放 | 日本理论片午夜在线观看| 大香蕉精品视频一区二区| 国产一级内片内射免费看| 亚洲最新av在线观看| 欧美高潮喷吹一区二区| 久久99亚洲小姐精品综合| 亚洲中文在线男人的天堂| 一二区不卡不卡在线观看| 色综合视频一区二区观看| 欧美人妻一区二区三区| 亚洲伦理中文字幕在线观看| 欧美日韩国产黑人一区| 夜夜嗨激情五月天精品| 国产三级不卡在线观看视频| 国产欧美韩日一区二区三区| 欧美欧美日韩综合一区| 亚洲五月婷婷中文字幕| 熟女免费视频一区二区| 日本三区不卡高清更新二区| 欧美91精品国产自产| 欧美精品一区二区三区白虎| 欧美日韩一区二区午夜| 午夜久久精品福利视频| 千仞雪下面好爽好紧好湿全文| 欧美黑人在线一区二区| 国产三级欧美三级日韩三级| 国产成人一区二区三区久久| 日韩综合国产欧美一区| 日韩偷拍精品一区二区三区| 亚洲国产精品久久琪琪| 好吊色欧美一区二区三区顽频| 黄片美女在线免费观看| 绝望的校花花间淫事2| 中文字幕日韩无套内射| 国产精品刮毛视频不卡| 日韩精品毛片视频免费看| 夜夜嗨激情五月天精品|