舍伍德(Sherwood)算法学习笔记
一.概念引入
设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多资料也没头绪,懂的朋友给指点下。