一.概念引入
設A是一個確定性算法,當它的輸入實例為x時所需的計算時間記為tA(x)。設Xn是算法A的輸入規模為n的實例的全體,則當問題的輸入規模為n時,算法A所需的平均時間為
。這顯然不能排除存在x∈Xn使得的
可能性。希望獲得一個隨機化算法B,使得對問題的輸入規模為n的每一個實例均有
。這就是舍伍德算法設計的基本思想。當s(n)與tA(n)相比可忽略時,舍伍德算法可獲得很好的平均性能。
概率算法的一個特點是對同一實例多次運用同一概率算法結果可能同。舍伍德算法(O(sqrt(n)),綜合了線性表和線性鏈表的優點)總能求的問題的一個正確解,當一個確定性算法在最壞情況和平均情況下差別較大時可在這個確定性算法中引入隨機性將之改造成一個舍伍德算法;引入隨機性不是為了消除最壞,而是為了減少最壞和特定實例的關聯性。比如線性表a的查找若是找10(獨一無二),如果在a[0]則為O(1),若是最后一個則O(n),可見時間與輸入實例有關,此時可引入隨機性將之改造成一個舍伍德算法。
有時候無法直接把確定性算法改造為舍伍德算法,這時候對輸入洗牌。
下面是洗牌算法源代碼:
import java.util.Random; public class Shuffle { public static void main(String[] args) { int a[] = new int[]{1,2,4,5,8}; /* * Collections.shuffle(list)參數只能是list */ myShuffle(a); for(int i:a) { //犯了個低級錯誤,輸出了a[i],結果數組下標越界異常 System.out.print(i+" "); } System.out.println(); } private static void myShuffle(int[] a) { int len = a.length; for(int i=0; i<len; i++) { Random r = new Random(); //直接Random.nextInt(len)提示靜態方法里無法引用 int j = r.nextInt(len); //Collections.swap(list,i,j)必須是list類型 if(i!=j) {//原來沒加這個條件 int temp = a[i]; a[i] = a[j]; a[j] = temp; } } } }
二.舍伍德思想解決迅雷2010年校招--發牌
問題描述:52張撲克牌分發給4人,每人13張,要求保證隨機性。已有隨機整數生成函數rand(),但開銷較大。請編寫函數實現void deal(int a[],int b[],int c[],int d[]),撲克牌用序號0-51表示,分別存在大小為13的a,b,c,d四個數組中,要求盡可能高效。
三.舍伍德思想快拍算法解決第k小問題import java.util.Random; public class Poker { /* * 這道題確實不怎么明白,直接初始化后洗牌算法不得了 * 不過解答只是替換nextInt,直接線性同余了,交換次數也減少了 * 交換次數是隨機產生的 */ //為方便swap和deal函數使用 static int array[] = new int[52]; public static void main(String[] args) { for(int i=0; i<array.length; i++) { array[i] = i; } int a[] = new int[13]; int b[] = new int[13]; int c[] = new int[13]; int d[] = new int[13]; deal(a,b,c,d); //這樣輸出方便 for(int i=0; i<13; i++) { System.out.println(a[i]+" "+b[i]+" "+c[i] + " "+d[i]); } } private static void deal(int[] a, int[] b, int[] c, int[] d) { int m = 10; int p = 31;//需要素數 int q = 10; Random r = new Random(); int num = r.nextInt(52);//循環次數 for(int i=0; i<num; i++) { m = m*p + q; m = m%(51-i); int j = 51 - m; if(i!=j) { swap(array,i,j); } } for(int i=0; i<13; i++) { a[i] = array[i]; b[i] = array[i+13]; c[i] = array[i+26]; d[i] = array[i+39]; } } private static void swap(int[] a, int i, int j) { //交換是正確的 int temp = a[i]; a[i] = a[j]; a[j] = temp; } }import java.util.Arrays; import java.util.Random; /* * 目前還不知道找不到咋辦? * 這是個愚蠢的問題,肯定找得到,因為是找第k個 * 只需要判斷k的合法性,在selectK函數外部 */ public class SherwoodQsort { /** *舍伍德思想快拍算法解決第k小問題(就是正所第k個) *記得看算法導論時提出一個算法是維護一個k大小數組,掃描原有數組不斷插入排序,最后第k個元素就是所求 *這樣等於說是求出了前k小,並不僅僅是第k小,肯定效率不如下面。 */ public static void main(String[] args) { //注意:數組a的最后一項表示最大值 int a[] = new int[]{1,8,4,9,0,32,45,27,6,5,65535}; int b[] = new int[a.length]; b = Arrays.copyOf(a, a.length); //Collections.sort只對list可用 Arrays.sort(b); System.out.print("待排序數組排序后為:"); for(int i:b) { System.out.print(i+" "); } System.out.println(); int k = 5; //注意:沒把數組a的最后一項算進去 int ans = selectK(a,0,a.length-1,k); System.out.print("第k(5)個數組元素是:"+ans+"\n"); } private static int selectK(int[] a, int left, int right, int k) { //注意right=a.length-1,沒把數組a的最后一項算進去 while(true) {//更新left,right,k的值,直到找到為止 if(left>=right) { return a[left]; } //隨機選擇划分項,注意right=a.length-1,沒把數組a的最后一項算進去 int r = createRandom(left,right); int i = left; /* * 注意:數組a的最后一項65535表示最大值,是特地加上去的 * 產生的r最多是a.length-1(因為right=a.length-1) * 這樣下面的j=r+1才絕對不會越界,大多是這么處理的 */ int j = right+1;//right=a.length-1,就是數組最大項65535 if(i!=r) { //注意是和r交換 swap(a,a[i],a[r]); } //實際上是partion函數,由於需要返回p和j,就不單獨寫了 int p = a[left];//雖然初試i=left,但下標不可是i while(true) { //直到左邊小於划分項,右邊大於為止 while(a[++i]<p); while(a[--j]>p); //寫在交換之前 if(i>=j) { break; } swap(a,i,j); } //交換,完成一次划分 a[left] = a[j]; a[j] = p; int t = j-left+1; if(t==k) { return p;//或者a[j] }else if(t>k) {//在左邊 right = j - 1; }else { /* * 原來這個順序錯了,結果一直數組下標越界 */ k = k - t; left = j+1; } } } private static void swap(int[] a, int i, int j) { //交換是正確的 int temp = a[i]; a[i] = a[j]; a[j] = temp; } private static int createRandom(int left, int right) { Random r = new Random(); return r.nextInt(right-left+1) + left; } }
四.舍伍德隨機化思想搜索有序表
- 問題描述
用兩個數組來表示所給的含有n個元素的有序集S。用value[0:n]存儲有序集中的元素,link[0:n]存儲有序集中元素在數組value中位置的指針(實際上使用數組模擬鏈表)。link[0]指向有序集中的第一個元素,集value[link[0]]是集合中的最小元素。一般地,如果value[i]是所給有序集S中的第k個元素,則value[link[i]]是S中第k+1個元素。S中元素的有序性表現為,對於任意1<=i<=n有value[i]<=value[link[i]]。對於集合S中的最大元素value[k]有,link[k]=0且value[0]是一個大數。
- 舉例
- 搜索思想
隨機抽取數組元素若干次,從較接近搜索元素x的位置開始做順序搜索。
import java.util.Random; public class SearchK { public static void main(String[] args) { int value[] = new int[]{65535,2,3,13,1,5,21,8}; int link[] = new int[]{4,2,5,6,1,7,0,3}; //查看是否存在元素k int k = 13; boolean flag = searchK(value,link,k); System.out.println("元素K(13)是否找到:"+flag); } private static boolean searchK(int[] value, int[] link, int x) { int max = value[1]; int m = (int)Math.sqrt(value.length-1); Random r = new Random(); int index = 1;//這個初始化本是為了不讓編譯器報錯(未初始化) for(int i=0; i<m; i++) { //隨機產生元素位置,加1是為了不取到value[0] int j = r.nextInt(value.length-1) + 1; int y = value[j]; /* * 不明白max作用 * value[index]一定小於x,所以下面才可以順序搜索 */ if(max<y&&y<x) { max = y; index = j; } } //順序搜索 while(value[link[index]]<x) { index = link[index]; } return value[link[index]]==x; } } /* *不懂,n個元素 * if(!searchK(value,link,x)) { value[++n] = x; link[n] = link[index]; link[index] = n; } //刪除集合中指定元素 template<class Type> void OrderedList<Type>::Delete(Type k) { int index; if(searchK(value,link,x)) { int p = link[index]; if(p == n) { link[index] = link[p]; } else { if(link[p]!=n) { int q = SearchLast(); link[q] = p; link[index] = link[p]; } value[p] = value[n];//刪除元素由最大元素來填補 link[p] = link[n]; } n--; } } */五.舍伍德算法解決跳躍表問題
舍伍德型算法的設計思想還可用於設計高效的數據結構,提高有序鏈表效率的一個技巧是在有序鏈表的部分結點處增設附加指針以提高其搜索性能。在增設附加指針的有序鏈表中搜索一個元素時,可借助於附加指針跳過鏈表中若干結點,加快搜索速度。這種增加了向前附加指針的有序鏈表稱為跳躍表。
有空寫吧……
六.隨機抽題算法
共有n道題,要求以相同概率隨機抽取m道不重復試題。可以編號為1到n,圍成一圈,每次抽取round,並出圈,下次再選到時候忽略如此直到選好了m題;不過若是n比較大會占用比較多的時間,先分析出圈的影響,round出圈后小於round的編號不變,大於的編號減一;對於已經抽到的題目(共k道),存入數組並由小到大排好序,再次選擇roundk+1,如果大於等於round1則roundk+1加1,一直進行比較到roundk,不過這樣可能會死循環,可以在中間判斷,如果和roundi相等則加一過后如果小於roundi+1,則則直接插入已選題數組,否則和roundi+2比較,如此進行。
七.總結
很多問題還是沒鬧明白,主要是資料太少,我查萬方和維普數據庫總共找到不超過10篇介紹舍伍德算法的,其中大部分都是泛泛而談。
遺留問題:搜索有序表時怎么初始化link數組?value[0]為什么搞個無窮大?初試找index為什么是sqrt(n)?查了n多資料也沒頭緒,懂的朋友給指點下。

