日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

n个数里找出前m个数(或者 从10亿个浮点数中找出最大的1万个)

發布時間:2023/12/10 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 n个数里找出前m个数(或者 从10亿个浮点数中找出最大的1万个) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自:http://blog.csdn.net/winsunxu/article/details/6219376點擊打開鏈接

引子
每年十一月各大IT公司都不約而同、爭后恐后地到各大高校進行全國巡回招聘。與此同時,網上也開始出現大量筆試面試題;網上流傳的題目往往都很精巧,既能讓考查基礎知識,又在平淡中隱含了廣闊的天地供優秀學生馳騁。

這兩天在網上淘到一1道筆試題目(注1),雖然真假未知,但的確是道好題,題目如下:

?????? 從10億個浮點數中找出最大的1萬個。

這是一道似易實難的題目,一般同學最容易中的陷阱就是沒有重視這個“億”字。因為有10億個單精度浮點數元素的數組在32位平臺上已經達到3.7GB之巨,在常見計算機平臺(如Win32)上聲明一個這樣的數組將導致堆棧溢出。正確的解決方法是分治法,比如每次處理100萬個數,然后再綜合起來。不過這不是本文要討論的主旨,所以本文把上題的10億改為1億,把浮點數改為整數,這樣可以直接地完成這個問題,有利于清晰地討論相關算法的優化(注2)。

不假思索
拿到這道題,馬上就會想到的方法是建立一個數組把1億個數裝起來,然后用for循環遍歷這個數組,找出最大的1萬個數來。原因很簡單,因為如果要找出最大的那個數,就是這樣解決的;而找最大的1萬個數,只是重復1萬遍而已。

template< class T >

void solution_1( T BigArr[], T ResArr[] )

{

?????? for( int i = 0; i < RES_ARR_SIZE; ++i )

?????? {

????????????? int idx = i;

????????????? for( int j = i+1; j < BIG_ARR_SIZE; ++j )

????????????? {

???????????????????? if( BigArr[j] > BigArr[idx] )

??????????????????????????? idx = j;

????????????? }

????????????? ResArr[i] = BigArr[idx];

????????????? std::swap( BigArr[idx], BigArr[i] );

?????? }

}

設BIG_ARR_SIZE = 1億,RES_ARR_SIZE = 1萬,運行以上算法已經超過40分鐘(注3),遠遠超過我們的可接受范圍。

稍作思考
從上面的代碼可以看出跟SelectSort算法的核心代碼是一樣的。因為SelectSort是一個O(n^2)的算法(solution_1的時間復雜度為O(n*m),因為solution_1沒有將整個大數組全部排序),而我們又知道排序算法可以優化到O(nlogn),那們是否可以從這方面入手使用更快的排序算法如MergeSor、QuickSort呢?但這些算法都不具備從大至小選擇最大的N個數的功能,因此只有將1億個數按從大到小用QuickSort排序,然后提取最前面的1萬個。

template< class T, class I >

void solution_2( T BigArr[], T ResArr[] )

{

?????? std::sort( BigArr, BigArr + BIG_ARR_SIZE, std::greater_equal() );

?????? memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );

}

因為STL里的sort算法使用的是QuickSort,在這里直接拿來用了,是因為不想寫一個寫一個眾人皆知的QuickSort代碼來占篇幅(而且STL的sort高度優化、速度快)。

對solution_2進行測試,運行時間是32秒,約為solution_1的1.5%的時間,已經取得了幾何數量級的進展。

深入思考
壓抑住興奮回頭再仔細看看solution_2,你將發現一個大問題,那就是在solution_2里所有的元素都排序了!而事實上只需找出最大的1萬個即可,我們不是做了很多無用功嗎?應該怎么樣來消除這些無用功?

如果你一時沒有頭緒,那就讓我慢慢引導你。首先,發掘一個事實:如果這個大數組本身已經按從大到小有序,那么數組的前1萬個元素就是結果;然后,可以假設這個大數組已經從大到小有序,并將前1萬個元素放到結果數組;再次,事實上這結果數組里放的未必是最大的一萬個,因此需要將前1萬個數字后續的元素跟結果數組的最小的元素比較,如果所有后續的元素都比結果數組的最小元素還小,那結果數組就是想要的結果,如果某一后續的元素比結果數組的最小元素大,那就用它替換結果數組里最小的數字;最后,遍歷完大數組,得到的結果數組就是想要的結果了。

template< class T >

void solution_3( T BigArr[], T ResArr[] )

{

?????? //取最前面的一萬個

?????? memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );

?????? //標記是否發生過交換

?????? bool bExchanged = true;

?????? //遍歷后續的元素

?????? for( int i = RES_ARR_SIZE; i < BIG_ARR_SIZE; ++i )

?????? {

????????????? int idx;

????????????? //如果上一輪發生過交換

????????????? if( bExchanged )

????????????? {

???????????????????? //找出ResArr中最小的元素

???????????????????? int j;

???????????????????? for( idx = 0, j = 1; j < RES_ARR_SIZE; ++j )

???????????????????? {

??????????????????????????? if( ResArr[idx] > ResArr[j] )

?????????????????????????????????? idx = j;

???????????????????? }

????????????? }

????????????? //這個后續元素比ResArr中最小的元素大,則替換。

????????????? if( BigArr[i] > ResArr[idx] )

????????????? {

???????????????????? bExchanged = true;

???????????????????? ResArr[idx] = BigArr[i];

????????????? }

????????????? else

???????????????????? bExchanged = false;

?????? }

}

上面的代碼使用了一個布爾變量bExchanged標記是否發生過交換,這是一個前文沒有談到的優化手段——用以標記元素交換的狀態,可以大大減少查找ResArr中最小元素的次數。也對solution_3進行測試一下,結果用時2.0秒左右(不使用bExchanged則高達32分鐘),遠小于solution_2的用時。

深思熟慮
在進入下一步優化之前,分析一下solution_3的成功之處。第一、solution_3的算法只遍歷大數組一次,即它是一個O(n)的算法,而solution_1是O(n*m)的算法,solution_2是O(nlogn)的算法,可見它在本質上有著天然的優越性;第二、在solution_3中引入了bExchanged這一標志變量,從測試數據可見引入bExchanged減少了約99.99%的時間,這是一個非常大的成功。

上面這段話絕非僅僅說明了solution_3的優點,更重要的是把solution_3的主要矛盾擺上了桌面——為什么一個O(n)的算法效率會跟O(n*m)的算法差不多(不使用bExchanged)?為什么使用了bExchanged能夠減少99.99%的時間?帶著這兩個問題再次審視solution_3的代碼,發現bExchanged的引入實際上減少了如下代碼段的執行次數:

for( idx = 0, j = 1; j < RES_ARR_SIZE; ++j )

{

?????? if( ResArr[idx] > ResArr[j] )

????????????? idx = j;

}

上面的代碼段即是查找ResArr中最小元素的算法,分析它可知這是一個O(n)的算法,到此時就水落石出了!原來雖然solution_3是一個O(n)的算法,但因為內部使用的查找最小元素的算法也是O(n)的算法,所以就退化為O(n*m)的算法了。難怪不使用bExchanged使用的時間跟solution_1差不多;這也從反面證明了solution_3被上面的這一代碼段導致性能退化。使用了bExchanged之后因為減少了很多查找最小元素的代碼段執行,所以能夠節省99.99%的時間!

至此可知元兇就是查找最小元素的代碼段,但查找最小元素是必不可少的操作,在這個兩難的情況下該怎么去優化呢?答案就是保持結果數組(即ResArr)有序,那樣的話最小的元素總是最后一個,從而省去查找最小元素的時間,解決上面的問題。但這也引入了一個新的問題:保持數組有序的插入算法的時間復雜度是O(n)的,雖然在這個問題里插入的數次比例較小,但因為基數太大(1億),這一開銷仍然會令本方案得不償失。

難道就沒有辦法了嗎?記得小學解應用題時老師教導過我們如果解題沒有思路,那就多讀幾遍題目。再次審題,注意到題目并沒有要求找到的最大的1萬個數要有序(注4),這意味著可以通過如下算法來解決:

1)??????????? 將BigArr的前1萬個元素復制到ResArr并用QuickSort使ResArr有序,并定義變量MinElemIdx保存最小元素的索引,并定義變量ZoneBeginIdx保存可能發生交換的區域的最小索引;

2)??????????? 遍歷BigArr其它的元素,如果某一元素比ResArr最小元素小,則將ResArr中MinElemIdx指向的元素替換,如果ZoneBeginIdx == MinElemIdx則擴展ZoneBeginIdx;

3)??????????? 重新在ZoneBeginIdx至RES_ARR_SIZE元素段中尋找最小元素,并用MinElemIdx保存其它索引;

4)??????????? 重復2)直至遍歷完所有BigArr的元素。

依上算法,寫代碼如下:

template< class T, class I >

void solution_4( T BigArr[], T ResArr[] )

{

?????? //取最前面的一萬個

?????? memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );

?????? //排序

?????? std::sort( ResArr, ResArr + RES_ARR_SIZE, std::greater_equal() );

?????? //最小元素索引

?????? unsigned int MinElemIdx = RES_ARR_SIZE - 1;

?????? //可能產生交換的區域的最小索引

?????? unsigned int ZoneBeginIdx = MinElemIdx;

?????? //遍歷后續的元素

?????? for( unsigned int i = RES_ARR_SIZE; i < BIG_ARR_SIZE; ++i )

?????? {????

????????????? //這個后續元素比ResArr中最小的元素大,則替換。

????????????? if( BigArr[i] > ResArr[MinElemIdx] )

????????????? {

???????????????????? ResArr[MinElemIdx] = BigArr[i];

???????????????????? if( MinElemIdx == ZoneBeginIdx )

??????????????????????????? --ZoneBeginIdx;

???????????????????? //查找最小元素

???????????????????? unsigned int idx = ZoneBeginIdx;

???????????????????? unsigned int j = idx + 1;

???????????????????? for( ; j < RES_ARR_SIZE; ++j )

???????????????????? {

??????????????????????????? if( ResArr[idx] > ResArr[j] )

?????????????????????????????????? idx = j;

???????????????????? }

???????????????????? MinElemIdx = idx;

????????????? }

?????? }

}

經過測試,同樣情況下solution_4用時約1.8秒,較solution_3效率略高,總算不負一番努力。


因為受到經濟危機的影響,我在 bokee.com 的博客可能隨時出現無法訪問的情況;因此將2005年到2006年間在 bokee.com 撰寫的博客文章全部遷移到 csdn 博客中來,本文正是其中一篇遷移的文章。

聲明:本文最初發表于《電腦編程技巧與維護》2006年第5期,版本所有,如蒙轉載,敬請連此聲明一起轉載,否則追究侵權責任。

從一道筆試題談算法優化(下)

作者:賴勇浩(http://blog.csdn.net/lanphaday)

苦想冥思

這次優化從solution_4產生的輸出來入手。把solution_4的輸出寫到文件,查看后發現數組基本無序了。這說明在程序運行一定時間后,頻繁的替換幾乎將原本有序的結果數組全部換血。結果數組被替換的元素越多,查找最小元素要遍歷的范圍就越大,當被替換的元素個數接近結果數組的大小時,solution_4就退化成solution_3。因為solution_4很快退化也就直接導致它的效率沒有本質上的提高。

找出了原因,就應該找出一個解決的辦法。通過上面的分析,知道solution_3和solution_4最消耗時間的是查找最小元素這一操作,將它減少(或去除)才有可能從本質上提高效率。這樣思路又回到保持結果數組有序這一條老路上來。在上一節我們談到保持數組有序的插入算法將帶來大量的元素移動,頻繁的插入操作將使這一方法在效率上得不償失。有沒有辦法讓元素移動去掉呢?答案也是有的——那就是使用鏈表。這時新的問題又來了,鏈表因為是非隨機存取數據結構,插入前尋找位置的算法又是O(n)的。解決新的問題的答案是使用AVL樹,但AVL樹雖然插入和查找都是O(logn),可是需要在插入后進行調整保持平衡,這又是一個耗費大量時間的操作。分析到現在,發現我們像進了迷宮,左沖右突都找不到突破口。

現在請靜下來想一想,如果思考結果沒有跳出上面這個怪圈,那我不幸地告訴你:你被我誤導了。這個故意的誤導是要告誡大家:進行算法優化必須時刻保持自己頭腦清醒,否則時刻都有可能陷入這樣的迷宮當中。現在跳出這個怪圈重新思考,根據前文的分析,可知目標是減少(或去除)查找最小元素的操作次數(或查找時間),途徑是讓ResArr保持有序,難點在于給ResArr排序太費時。反過來想一想,是否需要時刻保持ResArr有序?答案為否,因為當查找最小元素需要遍歷的范圍較小時,速度還是很快的,這樣就犯不著在每替換一個元素的時候都排序一次,而僅需要在無序元素較多的時候適時地排序即可(即保持查找最小元素要遍歷的范圍較小)。這個思想有用嗎?寫代碼來測試一下:

template< class T, class I >

void solution_5( T BigArr[], T ResArr[] )

{

?????? //同solution_4,略

?????? //這個后續元素比ResArr中最小的元素大,則替換。

?????? if( BigArr[i] > ResArr[MinElemIdx] )

?????? {

????????????? ResArr[MinElemIdx] = BigArr[i];

????????????? if( MinElemIdx == ZoneBeginIdx )

???????????????????? --ZoneBeginIdx;

????????????? //太多雜亂元素的時候排序

????????????? if( ZoneBeginIdx < 9400 )

????????????? {

???????????????????? std::sort( ResArr, ResArr + RES_ARR_SIZE, std::greater() );

???????????????????? ZoneBeginIdx = MinElemIdx = RES_ARR_SIZE - 1;

???????????????????? continue;

????????????? }

?????? //同solution_4,略

}

代碼中的9400是經過試驗得出的最好數值,即在有600個元素無序的時候進行一次排序。測試的結果令人驚喜,用時僅400毫秒左右,約為solution_4的五分之一,這也證明了上述思想是正確的。

殫思極慮

腳步永遠向前,在取得solution_5這樣的成果之后,仍然有必要分析和優化它。對這一看似已經完美的算法進行下一次優化要從哪里著手?這時候要借助于性能剖分工具了,常用的有Intel的VTune以及Microsoft Visual C++自帶的profile等。使用MS profile對solution_5分析產生的報告如下(略去一些無關數據):

????????? Func???????????? Func+Child?????????? Hit

??????? Time?? %???????? Time????? %????? Count? Function

---------------------------------------------------------

????? 37.718?? 1.0???? 3835.317? 99.5??????? 1 _main (algo.obj)

???? 111.900?? 2.9???? 3220.082? 83.6??????? 1 solution_5(int * ...

?????? 0.000?? 0.0???? 3074.063? 79.8????? 112 _STL::sort(int *,...

?????? ……

可以發現sort函數的調用用去了將近80%的時間,這表明sort函數是問題所在,優化應該從這里著手。但正如前文所說,STL的sort已經高度優化速度很快了,再對他作優化是極難的;而且sort函數里又調用了其它STL內部函數,如蛛絲般牽來繞去,讀得懂已經不是一般人可完成的了,優化從何談起?

我們不能左右天氣,但我們可以左右心情;我們不能修改sort函數,但我們可以控制sort的調用。再看看solution_5里對sort的調用有沒有什么蛛絲馬跡可尋:

?????? std::sort( ResArr, ResArr + RES_ARR_SIZE, std::greater() );

這個調用是把結果數組ResArr重新排序一遍。需要把整個ResArr完全重新排序嗎?答案是需要的,但可以不使用這個方法。因為ResArr里的元素絕大部分是有序的(結合上文可知前面94%的元素都有序),待排序的只是6%。只要把這600個數據重新排序然后將前后兩個有序數組歸并為一個有序數組即可(歸并算法的時間復雜度為O(n+m)),將因為排序的數據量較少而大大節約時間。寫代碼如下:

template< class T, class I >

void solution_6( T BigArr[], T ResArr[] )

{

?????? //同solution_5,略

?????? //太多雜亂元素的時候排序

?????? if( ZoneBeginIdx < 9400 )

?????? {

????????????? std::sort( ResArr + 9400, ResArr + RES_ARR_SIZE, std::greater() );

????????????? std::merge(ResArr, ResArr + 9400, ResArr + 9400, ResArr + RES_ARR_SIZE, BigArr, std::greater() );

????????????? memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );

?????? //同solution_5,略

}

經測試,solutio_6的運行時間為250毫秒左右,比solution_5快了將近一半,通過profile分析報告計算sort函數和merge函數的占用時間總計約為執行時間的19.6%,遠小于solution_5的占用時間。

結束語

一番努力之后,終于將一個原來需要近一個小時才能解決的問題用250毫秒完成,文章到這里要完結,不過上述算法仍有可優化的余地,這就要讀者朋友自己去挖掘了。我希望看到這篇文章的人不僅僅是贊嘆算法的奇妙,更希望能夠學會算法優化的方法和技巧。對于算法優化的方法,我總結如下(僅供參考及拋磚引玉之用):

l???????? 不斷地否定自己的方法[全文]

l???????? 減少重復計算[solution_3];

l???????? 不要做沒要求你做的事[solution_3];

l???????? 深化對需求的理解[solution_4];

l???????? 溫故而知新,多重讀自己的算法代碼[solution_4];

l???????? 從程序的輸出(或者中間結果)里找突破[solution_5];

l???????? 時刻保持頭腦清醒,常常跳出習慣的框框[solution_5];

l???????? 善于使用工具[solution_6];

l???????? 養成解決一個問題思考多個方案的習慣[全文]。

最后要講的一點就是STL里提供了一個可以直接完成這一問題的算法——nth_element。經測試,nth_element在大數組比較小的時候速度比以上算法都要快,但在大數組尺寸為1億的時候所用的時間為1.3秒左右,是solution_6運行時間的5倍。原因在于nth_elenemt的實現方法跟本文介紹的算法大不相同,有興趣的朋友可以去閱讀其源碼。建議大家在一般情況下使用STL的nth_element,它在數量為十萬級的時候仍有極好的性能。


總結

以上是生活随笔為你收集整理的n个数里找出前m个数(或者 从10亿个浮点数中找出最大的1万个)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 免费看国产曰批40分钟粉红裤头 | 91禁国产网站 | 偷拍第一页| 欧美一区二区三区色 | 亚洲无人区码一码二码三码 | 国产一级精品毛片 | www国产成人 | 中文字幕无产乱码 | 国产精品入口66mio男同 | 国产啊v在线 | 665566综合网 | 国产乱子伦农村叉叉叉 | 99在线精品观看 | 欧美高清视频一区二区 | 欧美播放 | 日本中文字幕在线 | 久久婷婷国产麻豆91天堂 | 亚洲国产精品一区二区三区 | 日本成人动漫在线观看 | 免费大片黄在线观看视频网站 | www.国产黄色 | 精品视频专区 | 午夜看片福利 | 91久久精品一区二区 | 蜜桃在线一区二区三区 | 女人高潮被爽到呻吟在线观看 | 欧美在线观看成人 | 日韩欧美麻豆 | 国产淫片av片久久久久久 | 亚洲欧洲另类 | 久久伊人操| 久久激情婷婷 | 亚欧美精品 | 公侵犯一区二区三区四区中文字幕 | 精品久久伊人 | 免费av电影网站 | 欧美在线一卡 | 国产肥老妇视频 | 久草操| 国产成人精品无码免费看夜聊软件 | av影片在线看 | 国产视频中文字幕 | 一级aaaa毛片 | 国产一区二区三区色淫影院 | 美女自拍偷拍 | 韩国毛片一区二区三区 | 岛国二区三区 | 国产xxxx在线 | 欧美黄色免费视频 | 免费观看的av | 国产精品秘 | 久久久久久久久99精品 | 人人曰| 国产大奶在线观看 | 丁香花高清在线 | 一本一道波多野结衣一区二区 | 国产成人一区二区在线 | 亚洲av永久无码精品三区在线 | 狠狠a| 老司机一区二区三区 | 男女一进一出视频 | 一区二区三区伦理 | 国产吞精囗交久久久 | 无码人妻一区二区三区在线视频 | 欧美日韩欧美日韩在线观看视频 | 黄色91视频| 2023天天操| 手机在线看a | 人妻精品一区二区在线 | 亚洲一区中文字幕在线观看 | 国产农村乱对白刺激视频 | 15p亚洲| 天天操夜夜添 | 免费观看全黄做爰的视频 | 琪琪午夜伦理影院7777 | 成年人网站黄色 | 蜜桃视频一区二区在线观看 | 性——交——性——乱免费的 | 69堂视频| 久久久久免费视频 | 国产精品无码专区av在线播放 | 亚洲国产果冻传媒av在线观看 | 中文字幕一区二区三区乱码人妻 | 久久作爱视频 | 我和我的太阳泰剧在线观看泰剧 | 麻豆changesxxx国产 | 国产亚洲视频在线观看 | 欧美粗大猛烈 | 国产黄色av片| 久久国产免费观看 | 黄色在线视频观看 | 亚洲人成无码www久久久 | 久久99精品久久久久子伦 | 欧美性生交xxxxx久久久 | 青苹果av | 播播网色播播 | 999精品在线观看 | 国产精品一卡二卡三卡 | 色婷婷狠狠 |