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