《盘点那些秀你一脸的秒天秒地算法》(3)
斐波那契之美
斐波那契數(shù)列(Fibonacci sequence),又稱黃金分割數(shù)列、因數(shù)學(xué)家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數(shù)列”。
這個(gè)數(shù)列就是1、1、2、3、5、8、13、21、34、……
在數(shù)學(xué)上,斐波那契數(shù)列以如下被以遞推的方法定義:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在現(xiàn)代物理、準(zhǔn)晶體結(jié)構(gòu)、化學(xué)等領(lǐng)域,斐波納契數(shù)列都有直接的應(yīng)用,為此,美國數(shù)學(xué)會(huì)從1963年起出版了以《斐波納契數(shù)列季刊》為名的一份數(shù)學(xué)雜志,用于專門刊載這方面的研究成果。
但是斐波那契的知識(shí)不止于此,它還有很多驚艷到我的地方,下面我們就一起了解一下:
真的不禁感嘆:真是一個(gè)神奇的數(shù)列啊
桶排序
我們都知道,基于比較的排序,時(shí)間的極限就是O(NlogN),從來沒有哪個(gè)排序可以突破這個(gè)界限,如速度最快的快速排序,想法新穎的堆排序,分治的歸并排序。
但是,如果我們的排序根本就不基于比較,那就可能突破這個(gè)時(shí)間。
桶排序不是基于比較的排序方法,只需對(duì)號(hào)入座。將相應(yīng)的數(shù)字放進(jìn)相應(yīng)編號(hào)的桶即可。
當(dāng)要被排序的數(shù)組內(nèi)的數(shù)值是均勻分配的時(shí)候,桶排序使用線性時(shí)間o(n)
對(duì)于海量有范圍數(shù)據(jù)十分適合,比如全國高考成績(jī)排序,公司年齡排序等等。
l=list(map(int,input().split(" "))) n=max(l)-min(l) p=[0]*(n+1)#為了省空間 for i in l:p[i-min(l)]=1#去重排序,做標(biāo)記即可 for i in range(n):if p[i]==1:#判斷是否出現(xiàn)過print(i+min(l),end=" ")當(dāng)然,基數(shù)排序是桶排序的改良版,也是非常好的排序方法,具體操作是:把數(shù)字的每一位都按桶排序排一次,因?yàn)橥芭判蚴欠€(wěn)定的排序,因此從個(gè)位進(jìn)行桶排序,然后十位進(jìn)行桶排序。。。直到最高位,排序結(jié)束。
這樣極大的弱化了桶排序范圍有限的弱點(diǎn),比如范圍1-100000需要準(zhǔn)備100000個(gè)銅,而基數(shù)排序只要10個(gè)銅就夠了(因?yàn)橐还簿褪畟€(gè)數(shù)字。)。
想出這個(gè)排序的人也是個(gè)天才啊。。。選擇合適的場(chǎng)合使用覺得有很好的效果。
快速排序
快速排序(Quicksort)是對(duì)冒泡排序的一種改進(jìn)。
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序,整個(gè)排序過程可以遞歸進(jìn)行,以此達(dá)到整個(gè)數(shù)據(jù)變成有序序列。
分三區(qū)優(yōu)化1:
在使用partition-exchange排序算法時(shí),如快速排序算法,我們會(huì)遇到一些問題,比如重復(fù)元素太多,降低了效率,在每次遞歸中,左邊部分是空的(沒有元素比關(guān)鍵元素小),而右邊部分只能一個(gè)一個(gè)遞減移動(dòng)。結(jié)果導(dǎo)致耗費(fèi)了二次方時(shí)間來排序相等元素。這時(shí)我們可以多分一個(gè)區(qū),即,小于區(qū),等于區(qū),大于區(qū)。(傳統(tǒng)快排為小于區(qū)和大于區(qū))
下面我們通過一個(gè)經(jīng)典例題來練習(xí)這種思想。
荷蘭國旗問題
”荷蘭國旗難題“是計(jì)算機(jī)科學(xué)中的一個(gè)程序難題,它是由Edsger Dijkstra提出的。荷蘭國旗是由紅、白、藍(lán)三色組成的。
現(xiàn)在有若干個(gè)紅、白、藍(lán)三種顏色的球隨機(jī)排列成一條直線。現(xiàn)在我們的任務(wù)是把這些球按照紅、白、藍(lán)排序。
樣例輸入
3
BBRRWBWRRR
RRRWWRWRB
RBRW
樣例輸出
RRRRRWWBBB
RRRRRWWWB
RRWB
思路:
現(xiàn)在我們的思路就是把未排序時(shí)前部和后部分別排在數(shù)組的前面和后面,那么中部自然就排好了。
設(shè)置兩個(gè)標(biāo)志位head指向數(shù)組開頭,tail指向數(shù)組末尾,now從頭開始遍歷:
(1)如果遍歷到的位置為1,那么它一定是屬于前部,于是就和head交換值,然后head++,now++;
(2)如果遍歷到的位置為2,說明屬于中部,now++;
(3)如果遍歷到的位置為3,說明屬于后部,于是就和tail交換值,然而,如果此時(shí)交換后now指向的值屬于前部,那么就執(zhí)行(1),tail--;
廢話不多說,上代碼。
#include<iostream> #include<algorithm> using namespace std;const int maxn = 100 + 5;int n; string str; int main(){cin>>n;while(n--){cin>>str;int len=str.size();int now=0,ans=0;int head=0,tail=len-1;while(now<=tail){if(str[now]=='R'){swap(str[head],str[now]);head++;now++;}else if(str[now]=='W'){now++;}else{swap(str[now],str[tail]);tail--;}}cout<<str<<endl;}return 0; }快排分三區(qū)以后降低了遞歸規(guī)模,避免了最差情況,性能得到改進(jìn)。
但是還是存在退化情況:
隨機(jī)化快排優(yōu)化2:
快速排序的最壞情況基于每次劃分對(duì)主元的選擇。基本的快速排序選取第一個(gè)元素作為主元。這樣在數(shù)組已經(jīng)有序的情況下,每次劃分將得到最壞的結(jié)果。比如1 2 3 4 5,每次取第一個(gè)元素,就退化為了O(N^2)。一種比較常見的優(yōu)化方法是隨機(jī)化算法,即隨機(jī)選取一個(gè)元素作為主元。
這種情況下雖然最壞情況仍然是O(n^2),但最壞情況不再依賴于輸入數(shù)據(jù),而是由于隨機(jī)函數(shù)取值不佳。實(shí)際上,隨機(jī)化快速排序得到理論最壞情況的可能性僅為1/(2^n)。所以隨機(jī)化快速排序可以對(duì)于絕大多數(shù)輸入數(shù)據(jù)達(dá)到O(nlogn)的期望時(shí)間復(fù)雜度。
?
BFPRT
我們以前寫過快排的改進(jìn)求前k大或前k小,但是快排不可避免地存在退化問題,即使我們用了隨機(jī)數(shù)等優(yōu)化,最差情況不可避免的退化到了O(N^2),而BFPRT就解決了這個(gè)問題,主要的思想精華就是怎么選取劃分值。
我們知道,經(jīng)典快排是選第一個(gè)數(shù)進(jìn)行劃分。而改進(jìn)快排是隨機(jī)選取一個(gè)數(shù)進(jìn)行劃分,從概率上避免了基本有序情況的退化。而BFPRT算法選劃分值的規(guī)則比較特殊,保證了遞歸最小的縮減規(guī)模也會(huì)比較大,而不是每次縮小一個(gè)數(shù)。
這個(gè)劃分值如何劃分就是重點(diǎn)。
如何讓選取的點(diǎn)無論如何都不會(huì)太差。
1、將n個(gè)元素劃分為n/5個(gè)組,每組5個(gè)元素
2、對(duì)每組排序,找到n/5個(gè)組中每一組的中位數(shù);?
3、對(duì)于找到的所有中位數(shù),調(diào)用BFPRT算法求出它們的中位數(shù),作為劃分值。
下面說明為什么這樣找劃分值。
我們先把數(shù)每五個(gè)分為一組。
同一列為一組。
排序之后,第三行就是各組的中位數(shù)。
我們把第三行的數(shù)構(gòu)成一個(gè)數(shù)列,遞歸找,找到中位數(shù)。
這個(gè)黑色框?yàn)槭裁凑业暮芎谩?/p>
因?yàn)樗欢ū華3、B3大,而A3、B3、C3又在自己的組內(nèi)比兩個(gè)數(shù)要大。
我們看最差情況:求算其它的數(shù)都比c3大,我們也能在25個(gè)數(shù)中縮小九個(gè)數(shù)的規(guī)模。大約3/10.
我們就做到了最差情況固定遞減規(guī)模,而不是可能縮小的很少。
下面代碼實(shí)現(xiàn):
public class BFPRT { //前k小public static int[] getMinKNumsByBFPRT(int[] arr, int k) {if (k < 1 || k > arr.length) {return arr;}int minKth = getMinKthByBFPRT(arr, k);int[] res = new int[k];int index = 0;for (int i = 0; i != arr.length; i++) {if (arr[i] < minKth) {res[index++] = arr[i];}}for (; index != res.length; index++) {res[index] = minKth;}return res;} //第k小public static int getMinKthByBFPRT(int[] arr, int K) {int[] copyArr = copyArray(arr);return select(copyArr, 0, copyArr.length - 1, K - 1);}public static int[] copyArray(int[] arr) {int[] res = new int[arr.length];for (int i = 0; i != res.length; i++) {res[i] = arr[i];}return res;} //給定一個(gè)數(shù)組和范圍,求第i小的數(shù)public static int select(int[] arr, int begin, int end, int i) {if (begin == end) {return arr[begin];}int pivot = medianOfMedians(arr, begin, end);//劃分值int[] pivotRange = partition(arr, begin, end, pivot);if (i >= pivotRange[0] && i <= pivotRange[1]) {return arr[i];} else if (i < pivotRange[0]) {return select(arr, begin, pivotRange[0] - 1, i);} else {return select(arr, pivotRange[1] + 1, end, i);}} //在begin end范圍內(nèi)進(jìn)行操作public static int medianOfMedians(int[] arr, int begin, int end) {int num = end - begin + 1;int offset = num % 5 == 0 ? 0 : 1;//最后一組的情況int[] mArr = new int[num / 5 + offset];//中位數(shù)組成的數(shù)組for (int i = 0; i < mArr.length; i++) {int beginI = begin + i * 5;int endI = beginI + 4;mArr[i] = getMedian(arr, beginI, Math.min(end, endI));}return select(mArr, 0, mArr.length - 1, mArr.length / 2);//只不過i等于長(zhǎng)度一半,用來求中位數(shù)} //經(jīng)典partition過程public static int[] partition(int[] arr, int begin, int end, int pivotValue) {int small = begin - 1;int cur = begin;int big = end + 1;while (cur != big) {if (arr[cur] < pivotValue) {swap(arr, ++small, cur++);} else if (arr[cur] > pivotValue) {swap(arr, cur, --big);} else {cur++;}}int[] range = new int[2];range[0] = small + 1;range[1] = big - 1;return range;} //五個(gè)數(shù)排序,返回中位數(shù)public static int getMedian(int[] arr, int begin, int end) {insertionSort(arr, begin, end);int sum = end + begin;int mid = (sum / 2) + (sum % 2);return arr[mid];} //手寫排序public static void insertionSort(int[] arr, int begin, int end) {for (int i = begin + 1; i != end + 1; i++) {for (int j = i; j != begin; j--) {if (arr[j - 1] > arr[j]) {swap(arr, j - 1, j);} else {break;}}}} //交換值public static void swap(int[] arr, int index1, int index2) {int tmp = arr[index1];arr[index1] = arr[index2];arr[index2] = tmp;} //打印public static void printArray(int[] arr) {for (int i = 0; i != arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}public static void main(String[] args) {int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };// sorted : { 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 7, 9, 9, 9 }printArray(getMinKNumsByBFPRT(arr, 10));} }這個(gè)辦法解決了最差的退化情況,在一大堆數(shù)中求其前k大或前k小的問題,簡(jiǎn)稱TOP-K問題。目前解決TOP-K問題最有效的算法即是BFPRT算法,其又稱為中位數(shù)的中位數(shù)算法,該算法由Blum、Floyd、Pratt、Rivest、Tarjan提出?,讓我們永遠(yuǎn)記住這群偉大的人。
總結(jié)
以上是生活随笔為你收集整理的《盘点那些秀你一脸的秒天秒地算法》(3)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数组名与指向数组的指针之间的联系与区别【
- 下一篇: leetcode 152 乘积最大子序列