posts - 200, comments - 8, trackbacks - 0, articles - 0


求两定点之间的全部路径,其根本是一个涉及到搜索和回溯的问题。我们设计算法时所关心的首要问题是:按照何种顺序搜索和回溯才能保证路径可以不重不漏地被全部找到。
 

图的存储结构:邻接矩阵。Arcs

工作结构:结点栈 mystack;

状态保存结构: 

(1) VertexStatus[]={0,0,0,1,1,}。当结点未进栈或者已经出栈,则其对应的状态为0,否则状态为1

(2) ArcStatus[][]={0,0,1,0,1..}当且仅当边的两个结点都在栈外时,边的状态才为0,否则为1

注意我们只所以设计如上结点、边两个状态存储结构,就是依据于path的定义,结点不重复,边不重复。具有边状态存储结构,也是我的算法与其他算法根本上的不同。

不失一般性,我们假设原点的编号最小为0,目标点的编号最大N。我们的问题转换成了,求最小编号的节点与最大编号的节点之间的所有路径。

Paths={}//路径集合

VertexStatus[]={0};//全部置0

ArcStatus[][]={0};////全部置0

mystack.push(0);

VertexStatus[0]=1;

While(!mystack.empty()){

  Int elem= mystack.top();//获得栈顶元素

  if(elem==N){//找到了一条路径

path=Traverse(mystack);

Paths.add(path);

VertexStatus[elem]=0;

UpdateArcStatus();//更新ArcStatus[][],使得所有两个端点都不在栈内的边的状态为0

mystack.pop();//移除栈顶元素
  }else{

i=0;
          For(;i<N;i++)
      if(VertexStatus[i]=0&&ArcStatus[elem][i]=0&&Arcs.contain(elem,i))
{

VertexStatus[i]=1;

ArcStatus[elem][i]=1;

Mystack.push(i);//入栈
    break;
  }
}
if(i=N){//该节点没有符合要求的后续节点

VertexStatus[elem]=0;

    UpdateArcStaus();////更新ArcStatus[][],使得所有两个端点都不在栈内的边的状为0

          Mystack.pop();//出栈
    }
   }
}


 

 

posted @ 2013-06-07 16:47 鑫龙 阅读(2156) | 评论 (0)编辑 收藏

考虑n位二进制数,有多少个数中不存在两个相邻的1。例如,3位数中有5个数符合这一要求:000、001、010、100、101。 
1、试找出其中的规律 
2、请给出完整代码实现(参数输入代码可略) 
3、试证明你找到的规律是正确的 
1、 答:规律为2 n/2+2n-n/2-1。 
2、 代码如下 
#include<cmath> 
using namespace std; 
int Func(int n) { 
  if(n<0) {  return -1; } 
  if(n==0) {  return 0; } 
  return pow(2,(n/2))+pow(2,((n-(n/2)))-1;
 
3、 证明(构造法) 
答: 
1列出n位由1组成的2进制数a,111。。。。111;
2奇数位都用0取代得到数b,010。。。。010; 
3偶数位也用0取代得到数c,101。。。。101; 
4数b和数c中的任何一个1都可以被0取代,或不被取代,而构造出满足要求的数,且每个1的取代具有独立性。 
5数b可以构造出2n/2个满足要求的数,同时数c可以构造出2n-n/2个满足要求的数。 
6数b和数a都可以构造出全由0组成二进制数,因此统计存在一个重复。 
7规律为2 n/2+2n-n/2-1;

-------------------------------------------------------------------------------------------------
n位的二进制,比如3位:000 001 010 100 101
什么规律呢,假设n位二进制有f(n)种不相邻的组合,
从最左边的位置开始放,可以放0,也可以放1。
如果最左边放0,那么将不影响第二位的放置,从第二位放起,即有f(n-1)种。
如果最左边放1,那么第二位则只能放0,从第三位放起,即有f(n-2)种。
所以f(n)=f(n-1)+f(n-2)种。
另外f(1)=2(因为可以放0或者1),f(2)=3(因为可以是00,01,10)。




posted @ 2013-06-06 21:57 鑫龙 阅读(196) | 评论 (0)编辑 收藏

转自:http://blog.csdn.net/superhackerzhang/article/details/6410670 

以SGI的STL为例

 

sort有两种重载形式

 

template <class RandomAccessIterator>

void sort(RandomAccessIterator first, RandomAccessIterator last);  
template <class RandomAccessIterator, class StrictWeakOrdering>
void sort(RandomAccessIterator first, RandomAccessIterator last,StrictWeakOrdering comp);
这两种形式都要求形随机访问迭代器,因此只能用于容器vector或deque,这里我们只以第一种为例进行讲解
在数据量大时,采用Quick Sort,分段递归排序,一旦分段后的数据量小于门限值时,为避免递归的额外开销,便采用Insertion sort。 
如果递归的层次过深,还会使用Heap Sort.
对于以下的以__开头的命名函数,表示其为被内部的其它函数调用,而不能被用户直接调用。
首先来看插入排序
template <class _RandomAccessIter> 
void __insertion_sort(_RandomAccessIter __first, _RandomAccessIter __last) {   
if (__first == __last) return;    
for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)     
__linear_insert(__first, __i, __VALUE_TYPE(__first)); }
其所调用的 __linear_insert如下示:
template <class _RandomAccessIter, class _Tp>
inline void __linear_insert(_RandomAccessIter __first, 
                            _RandomAccessIter __last, _Tp*) {
  _Tp __val = *__last;
  if (__val < *__first) {
    copy_backward(__first, __last, __last + 1);
    *__first = __val;
  }else
    __unguarded_linear_insert(__last, __val);
}
这里需要对其进行一下说明,通常情况下在进行插入排序时,既要进行大小的比较,又要对边界进行控制,经过上面的改进后,但只需要进行大小的比较便可,这就是代码的高明这处。
首先对要插入有序部分的元素__val与队首的最小元素 *__first进行比较,如果__val < *__first,则只__first与 __last之间的元素向后移一个位置,然后将__val插入队首。
如果__val >= *__first,则说明__val在将要新生成的有序队列中不是最小,因此,在下面的while中不用进行界限控制,只比较元素的大小即可。
template <class _RandomAccessIter, class _Tp> 
void __unguarded_linear_insert(_RandomAccessIter __last, _Tp __val) {
_RandomAccessIter __next = __last;
--__next;
while (__val < *__next) {
*__last = *__next;
__last = __next;
--__next;
}
*__last = __val;
}
在STL中对避免快排时每次都选择最小或最大的元素做轴,使用以下函数选择一个三点中值。
template <class _Tp> 
inline const _Tp& __median(const _Tp& __a, const _Tp& __b, const _Tp& __c) {
__STL_REQUIRES(_Tp, _LessThanComparable);
if (__a < __b)
if (__b < __c) return __b;
else if (__a < __c) return __c;
else return __a;
else if (__a < __c) return __a;
else if (__b < __c) return __c;
else return __b;
}
下面是快排中所要使用的分割函数。对[first,last)区间的元素进行分割,使用得中轴右边的元素大于等于中轴,左边的元素小于等于中轴,并返回中轴所在位置。
template <class _RandomAccessIter, class _Tp> 
_RandomAccessIter __unguarded_partition(_RandomAccessIter __first,_RandomAccessIter __last,_Tp __pivot) {
while (true) {
while (*__first < __pivot) ++__first;
--__last;
while (__pivot < *__last) --__last;
if (!(__first < __last))
return __first;
iter_swap(__first, __last);
++__first;
}
}
sort 代码如下
template <class _RandomAccessIter> 
inline void sort(_RandomAccessIter __first, _RandomAccessIter __last) {
if (__first != __last) {
__introsort_loop(__first, __last,
__VALUE_TYPE(__first),
__lg(__last - __first) * 2);
__final_insertion_sort(__first, __last);
}
}
基中__lg(n)用来找出2^k<=n的最大k值,用以控控制递归的深度。
template <class _Size> 
inline _Size __lg(_Size __n) {
_Size __k;
for (__k = 0; __n != 1; __n >>= 1) ++__k;
return __k;
}
下面的是用来对区间使用快排,以达到部分有序状态。
template <class _RandomAccessIter, class _Tp, class _Size> 
void __introsort_loop(_RandomAccessIter __first,
_RandomAccessIter __last, _Tp*,
_Size __depth_limit) {
while (__last - __first > __stl_threshold) {//__stl_threshold在这里用前面定义的常量16,当区间多于16个元素时,才有必要使用快排。
if (__depth_limit == 0) {//当递归的层次过深时,使用堆排序。
partial_sort(__first, __last, __last);
return;
}
--__depth_limit;
_RandomAccessIter __cut = __unguarded_partition(__first, __last,
_Tp(__median(*__first,
*(__first + (__last - __first)/2),
*(__last - 1))));//使用分割函数,反回中轴
__introsort_loop(__cut, __last, (_Tp*) 0, __depth_limit);//对右半部分进行递归排序
__last = __cut;//使尾指针指向中轴,即要对左半部分排序
}
}
最后使用插入排序对各部分进行排序。
template <class _RandomAccessIter> 
void __final_insertion_sort(_RandomAccessIter __first,
_RandomAccessIter __last) {
if (__last - __first > __stl_threshold) {
__insertion_sort(__first, __first + __stl_threshold);
__unguarded_insertion_sort(__first + __stl_threshold, __last);
} else __insertion_sort(__first, __last);
}
些函数先判断元素个数是否大于16,如是大于,则先用__insertion_sort()对16个元素的子序列排序,再用__unguarded_insertion_sort()对
其余的排序。否则直接用__insertion_sort()排序。
template <class _RandomAccessIter> 
inline void __unguarded_insertion_sort(_RandomAccessIter __first,
_RandomAccessIter __last) {
__unguarded_insertion_sort_aux(__first, __last, __VALUE_TYPE(__first));
}

template <class _RandomAccessIter, class _Tp, class _Compare>
void __unguarded_insertion_sort_aux(_RandomAccessIter __first,
_RandomAccessIter __last,
_Tp*, _Compare __comp) {
for (_RandomAccessIter __i = __first; __i != __last; ++__i)
__unguarded_linear_insert(__i, _Tp(*__i), __comp);
}
template <class _RandomAccessIter, class _Tp> 
void __unguarded_linear_insert(_RandomAccessIter __last, _Tp __val) {
_RandomAccessIter __next = __last;
--__next;
while (__val < *__next) {
*__last = *__next;
__last = __next;
--__next;
}
*__last = __val;
}
ps:个人感觉__final_insertion_sort()中区分区间大小是多此一举,因为最终其调用的都是__unguarded_linear_insert(),其并没用针对不
同的大小区间采用明显不用的算法。

posted @ 2013-06-01 18:53 鑫龙 阅读(386) | 评论 (0)编辑 收藏

     摘要:                第二十八~二十九章:最大连续乘积子串、字符串编辑距离前言    时间转瞬即逝,一转眼,又有4个多月没来更新blog了,过去4个月都在干啥呢?对的,今2013年元旦和朋友利用业余时间一起搭了个方便朋友们找工作的编程面试算法论坛:为学论坛http://www.51weixu...  阅读全文

posted @ 2013-05-28 22:18 鑫龙 阅读(295) | 评论 (0)编辑 收藏

第二十七章:不改变正负数之间相对顺序重新排列数组.时间O(N),空间O(1)


前言

    在这篇文章:九月腾讯,创新工场,淘宝等公司最新面试十三题第5题(一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的正负数之间相对顺序),自从去年九月收录了此题至今,一直未曾看到令人满意的答案,为何呢?

    因为一般达不到题目所要求的:时间复杂度O(N),空间O(1),且保证原来正负数之间的相对位置不变。本编程艺术系列第27章就来阐述这个问题,若有任何漏洞,欢迎随时不吝指正。谢谢。


重新排列使负数排在正数前面

原题是这样的:

一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的正负数之间相对顺序。
比如: input: 1,7,-5,9,-12,15 ,ans: -5,-12,1,7,9,15 。且要求时间复杂度O(N),空间O(1) 。

    OK,下面咱们就来试着一步一步解这道题,如下5种思路(从复杂度O(N^2)到O(N*logN),从不符合题目条件到一步步趋近于条件)):

  1. 最简单的,如果不考虑时间复杂度,最简单的思路是从头扫描这个数组,每碰到一个正数时,拿出这个数字,并把位于这个数字后面的所有数字往前挪动一位。挪完之后在数组的末尾有一个空位,这时把该正数放入这个空位。由于碰到一个正,需要移动O(n)个数字,因此总的时间复杂度是O(n2)。
  2. 既然题目要求的是把负数放在数组的前半部分,正数放在数组的后半部分,因此所有的负数应该位于正数的前面。也就是说我们在扫描这个数组的时候,如果发现有正数出现在负数的前面,我们可以交换他们的顺序,交换之后就符合要求了。
    因此我们可以维护两个指针,第一个指针初始化为数组的第一个数字,它只向后移动;第二个指针初始化为数组的最后一个数字,它只向前移动。在两个指针相遇之前,第一个指针总是位于第二个指针的前面。如果第一个指针指向的数字是正而第二个指针指向的数字是负数,我们就交换这两个数字。
    但遗憾的是上述方法改变了原来正负数之间的相对顺序。所以,咱们得另寻良策
  3. 首先,定义这样一个过程为“翻转”:(a1,a2,...,am,b1,b2,...,bn) --> (b1,b2,...,bn,a1,a2,...,am)。其次,对于待处理的未排序整数数组,从头到尾进行扫描,寻找(正正...正负...负负)串;每找到这样一个串,则计数器加1;若计数为奇数,则对当前串做一个“翻转”;反复扫描,直到再也找不到(正正...正负...负负)串。

    此思路来自朋友胡果果,空间复杂度虽为O(1),但其时间复杂度O(N*logN)。更多具体细节参看原文:http://qing.weibo.com/1570303725/5d98eeed33000hcb.html。故,不符合题目要求,继续寻找。

  4. 我们可以这样,设置一个起始点j, 一个翻转点k,一个终止点L,从右侧起,起始点在第一个出现的负数, 翻转点在起始点后第一个出现的正数,终止点在翻转点后出现的第一个负数(或结束)。

    如果无翻转点, 则不操作,如果有翻转点, 则待终止点出现后, 做翻转, 即ab => ba 这样的操作。翻转后, 负数串一定在左侧, 然后从负数串的右侧开始记录起始点, 继续往下找下一个翻转点。

    例子中的就是(下划线代表要交换顺序的两个数字):

    1, 7, -5, 9-12, 15  
    第一次翻转: 1, 7, -5, -12,9, 15   =>  1, -12, -5, 7, 9, 15
    第二次翻转: -5, -12, 1, 7, 9, 15

    此思路2果真解决了么?NO,用下面这个例子试一下,我们就能立马看出了漏洞:
    1, 7, -5, -6, 9-12, 15(此种情况未能处理)
    7 -5 -6 -12 9 15
    -12 -5 -6 7 9 15
    -6 -12 -5 1 7 9 15   (此时,正负数之间的相对顺序已经改变,本应该是-5,-6,-12,而现在是-6 -12 -5)
  5. 看来这个问题的确有点麻烦,不过我们最终貌似还是找到了另外一种解决办法,正如朋友超越神所说的:从后往前扫描,遇到负数,开始记录负数区间,然后遇到正数,记录前面的正数区间,然后把整个负数区间与前面的正数区间进行交换,交换区间但保序的算法类似(a,bc->bc,a)的字符串原地翻转算法。交换完之后要继续向前一直扫描下去,每次碰到负数区间在正数区间后面,就翻转区间。下面,将详细阐述此思路4。

思路5之区间翻转

    其实上述思路5非常简单,既然单个翻转无法解决问题,那么咱们可以区间翻转阿。什么叫区间翻转?不知读者朋友们是否还记得本blog之前曾经整理过这样一道题,微软面试100题系列第10题,如下:

10、翻转句子中单词的顺序。
题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。例如输入“I am a student.”,则输出“student. a am I”。而此题可以在O(N)的时间复杂度内解决

    由于本题需要翻转句子,我们先颠倒句子中的所有字符。这时,不但翻转了句子中单词的顺序,而且单词内字符也被翻转了。我们再颠倒每个单词内的字符。由于单词内的字符被翻转两次,因此顺序仍然和输入时的顺序保持一致。
    以上面的输入为例:翻转“I am a student.”中所有字符得到“.tneduts a ma I”,再翻转每个单词中字符的顺序得到“students. a am I”,正是符合要求的输出(编码实现,可以参看此文:http://zhedahht.blog.163.com/blog/static/254111742007289205219/)。

    对的,上述思路3就是这个意思,单词翻转便相当于于区间翻转,既如此,咱们来验证下上述思路2中那个测试用例,如下:

1, 7, -5, -6, 9-12, 15
1 7 -5 -6 -12 9 15
-12 -6 -5 7 1 9 15   (借用单词翻转的方法,先逐个数字翻转,后正负数整体原地翻转)
-5 -6 -12 1 7 9 15


思路5再次被质疑

    但是,我还想再问,问题至此被解决了么?真的被KO了么?NO,咱们来看这样一种情况,正如威士忌所说:

看看这个数据,+-+-+-+-+------+,假如Nminus 等于 n/2,由于前面都是+-+-+-,区间交换需要 n/2/2 = n/4次,每次交换是 T(2*(Nminus + Nplus)) >= T(n),n/4 * T(n) = T(n*n/4)=O(n^2)。

    还有一种更坏的情况,就是+-+-+-+-+------+这种数据可能,后面一大堆的负数,前面正负交替。所以,咱们的美梦再次破灭,路漫漫其修远兮,此题仍然未找到一个完全解决了的方案。

posted @ 2013-05-28 17:50 鑫龙 阅读(508) | 评论 (0)编辑 收藏

第二十五章:二分查找实现(Jon Bentley:90%程序员无法正确实现)

作者:July
出处:结构之法算法之道

引言

    Jon Bentley:90%以上的程序员无法正确无误的写出二分查找代码。也许很多人都早已听说过这句话,但我还是想引用《编程珠玑》上的如下几段文字: 

二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过log(2)N次比较。 
多数程序员都觉得只要理解了上面的描述,写出代码就不难了;但事实并非如此。如果你不认同这一点,最好的办法就是放下书本,自己动手写一写。试试吧。 
我在贝尔实验室和IBM的时候都出过这道考题。那些专业的程序员有几个小时的时间,可以用他们选择的语言把上面的描述写出来;写出高级伪代码也可以。考试结束后,差不多所有程序员都认为自己写出了正确的程序。于是,我们花了半个钟头来看他们编写的代码经过测试用例验证的结果。几次课,一百多人的结果相差无几:90%的程序员写的程序中有bug(我并不认为没有bug的代码就正确)。 
我很惊讶:在足够的时间内,只有大约10%的专业程序员可以把这个小程序写对。但写不对这个小程序的还不止这些人:高德纳在《计算机程序设计的艺术 第3卷 排序和查找》第6.2.1节的“历史与参考文献”部分指出,虽然早在1946年就有人将二分查找的方法公诸于世,但直到1962年才有人写出没有bug的二分查找程序。——乔恩·本特利,《编程珠玑(第1版)》第35-36页。

    你能正确无误的写出二分查找代码么?不妨一试。

二分查找代码

     二分查找的原理想必不用多解释了,不过有一点必须提醒读者的是,二分查找是针对的排好序的数组。OK,纸上读来终觉浅,觉知此事要躬行。我先来写一份,下面是我写的一份二分查找的实现(之前去某一家公司面试也曾被叫当场实现二分查找,不过结果可能跟你一样,当时就未能完整无误写出),有任何问题或错误,恳请不吝指正:

//二分查找V0.1实现版  
//copyright@2011 July  
//随时欢迎读者找bug,email:zhoulei0907@yahoo.cn。  
  
//首先要把握下面几个要点:  
//right=n-1 => while(left <= right) => right=middle-1;  
//right=n   => while(left <  right) => right=middle;  
//middle的计算不能写在while循环外,否则无法得到更新。  
  
int binary_search(int array[],int n,int value)  
{  
    
int left=0;  
    
int right=n-1;  
    
//如果这里是int right = n 的话,那么下面有两处地方需要修改,以保证一一对应:  
    
//1、下面循环的条件则是while(left < right)  
    
//2、循环内当array[middle]>value 的时候,right = mid  
  
    
while (left<=right)          //循环条件,适时而变  
    {  
        
int middle=left + ((right-left)>>1);  //防止溢出,移位也更高效。同时,每次循环都需要更新。  
  
        
if (array[middle]>value)  
        {  
            right 
=middle-1;   //right赋值,适时而变  
        }   
        
else if(array[middle]<value)  
        {  
            left
=middle+1;  
        }  
        
else  
            
return middle;    
        
//可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多  
        
//如果每次循环都判断一下是否相等,将耗费时间  
    }  
    
return -1;  
}  


posted @ 2013-05-28 16:41 鑫龙 阅读(179) | 评论 (0)编辑 收藏

     摘要:   第二十三、四章:杨氏矩阵查找,倒排索引关键词Hash不重复编码实践作者:July、yansha。编程艺术室出品。出处:结构之法算法之道。前言    本文阐述两个问题,第二十三章是杨氏矩阵查找问题,第二十四章是有关倒排索引中关键词Hash编码的问题,主要要解决不重复以及追加的功能,同时也是经典算法研究系列十一、从头到尾彻底解析Hash表算法之续。  &nb...  阅读全文

posted @ 2013-05-28 16:39 鑫龙 阅读(272) | 评论 (0)编辑 收藏

题目:一个单入口单出口的有向无环图中,要求在某些地方插入一些节点使得任何一条由起点到终点所经历的节点数相同,类似于下面的图,要求给出算法描述并分析时间复杂度。

如上图所示,节点A到C有两条路径,ABC这条路径经过了一个节点,而AC路径经过了0个节点,我们的算法所要做的事就是要在AC路径中间加入一个节点,然后ABC路径和ADC路径都经过了一个节点。

算法是这样的:对于每个节点维护一组信息,包括节点的层数(起始节点到该节点的路径长度,起始节点设为0)以及生成该长度的父节点,相对于右图,节点6维护的不经处理的信息就是:层数2来自节点3和节点4;节点7维护的不经处理的信息就是:层数3来自节点5和节点6以及层数2来自节点4;节点8的不经处理的信息是:层数4来自节点7,层数3来自节点7,层数2来自节点4。我们算法所要做的事就是最终使每个节点需要维护的层信息变为一个,即无论从那条路径到该节点,该节点所处的层数都是固定的。算法如下:
1、初始化起始节点的层数信息
2、从起始节点开始遍历每条路径,遇到每个节点生成一个维护信息
(1)如果此节点不存在维护信息,创建之;
(2)如果该节点存在维护信息,有两种情况:
(a)如果生成的维护信息的层数和原来已有的维护信息的层数是相同的,则合并这两个维护信息,比如对于例子中的图,节点5原来的维护信息为“层数3来自节点2”,然后从节点3到节点5生成的维护信息为“层数3来自节点3”,由于层数相同,我们可以将其合并为“层数3来自节点2和节点3”;
(b)如果生成的维护信息的层数和原来节点的维护信息的层数不一致,我们需要比较那一个的层数较大:
a.如果原来维护信息的层数较大,此时,我们只需要在生成此维护信息的节点与此节点之间插入一个新的节点,然后生成新节点的维护信息,然后从新节点开始(2)过程
b.如果新生成的维护信息的层数较大,将新生成的节点信息存入此节点,然后我们需要在生成原来维护信息的所有节点和此节点之间插入新节点,并且需要从所有的新插入节点开始(2)过程



posted @ 2013-05-26 10:40 鑫龙 阅读(222) | 评论 (0)编辑 收藏

题目:

已知一个函数rand7()能够生成1-7的随机数,请给出一个函数,该函数能够生成1-10的随机数。


思路:

假如已知一个函数能够生成1-49的随机数,那么如何以此生成1-10的随机数呢?


解法:

该解法基于一种叫做拒绝采样的方法。主要思想是只要产生一个目标范围内的随机数,则直接返回。如果产生的随机数不在目标范围内,则丢弃该值,重新取样。由于目标范围内的数字被选中的概率相等,这样一个均匀的分布生成了。

显然rand7至少需要执行2次,否则产生不了1-10的数字。通过运行rand7两次,可以生成1-49的整数,

   1  2  3  4  5  6  7 1  1  2  3  4  5  6  7 2  8  9 10  1  2  3  4 3  5  6  7  8  9 10  1 4  2  3  4  5  6  7  8 5  9 10  1  2  3  4  5 6  6  7  8  9 10  *  * 7  *  *  *  *  *  *  *
由于49不是10的倍数,所以我们需要丢弃一些值,我们想要的数字范围为1-40,不在此范围则丢弃并重新取样。

代码:

  1. int rand10() {  
  2.   int row, col, idx;  
  3.   do {  
  4.     row = rand7();  
  5.     col = rand7();  
  6.     idx = col + (row-1)*7;  
  7.   } while (idx > 40);  
  8.   return 1 + (idx-1)%10;  
  9. }  

由于row范围为1-7,col范围为1-7,这样idx值范围为1-49。大于40的值被丢弃,这样剩下1-40范围内的数字,通过取模返回。下面计算一下得到一个满足1-40范围的数需要进行取样的次数的期望值:

E(# calls to rand7) = 2 * (40/49) +                       4 * (9/49) * (40/49) +                       6 * (9/49)2 * (40/49) +                       ...                                             =  2k * (9/49)k-1 * (40/49)                       k=1                      = (80/49) / (1 - 9/49)2                     = 2.45
优化:

上面的方法大概需要2.45次调用rand7函数才能得到1个1-10范围的数,下面可以进行再度优化。

对于大于40的数,我们不必马上丢弃,可以对41-49的数减去40可得到1-9的随机数,而rand7可生成1-7的随机数,这样可以生成1-63的随机数。对于1-60我们可以直接返回,而61-63则丢弃,这样需要丢弃的数只有3个,相比前面的9个,效率有所提高。而对于61-63的数,减去60后为1-3,rand7产生1-7,这样可以再度利用产生1-21的数,对于1-20我们则直接返回,对于21则丢弃。这时,丢弃的数就只有1个了,优化又进一步。当然这里面对rand7的调用次数也是增加了的。代码如下:

  1. int rand10Imp() {  
  2.   int a, b, idx;  
  3.   while (true) {  
  4.     a = rand7();  
  5.     b = rand7();  
  6.     idx = b + (a-1)*7;  
  7.     if (idx <= 40)  
  8.       return 1 + (idx-1)%10;  
  9.     a = idx-40;  
  10.     b = rand7();  
  11.     // get uniform dist from 1 - 63  
  12.     idx = b + (a-1)*7;  
  13.     if (idx <= 60)  
  14.       return 1 + (idx-1)%10;  
  15.     a = idx-60;  
  16.     b = rand7();  
  17.     // get uniform dist from 1-21  
  18.     idx = b + (a-1)*7;  
  19.     if (idx <= 20)  
  20.       return 1 + (idx-1)%10;  
  21.   }  
  22. }  
下面计算下优化后方法的调用rand7函数的期望次数:

E(# calls to rand7) = 2 * (40/49) +                       3 * (9/49) * (60/63) +                       4 * (9/49) * (3/63) * (20/21) +                         (9/49) * (3/63) * (1/21) *                       [ 6 * (40/49) +                         7 * (9/49) * (60/63) +                         8 * (9/49) * (3/63) * (20/21) ] +                        ((9/49) * (3/63) * (1/21))2 *                       [ 10 * (40/49) +                         11 * (9/49) * (60/63) +                         12 * (9/49) * (3/63) * (20/21) ] +                       ...                      = 2.2123
这里期望次数为2.21,比起未优化的2.45次减少了大概10%。

posted @ 2013-05-20 22:55 鑫龙 阅读(3955) | 评论 (0)编辑 收藏

     摘要: 第十六~第二十章:全排列,跳台阶,奇偶排序,第一个只出现一次等问题作者:July、2011.10.16。出处:http://blog.csdn.net/v_JULY_v。引言    最近这几天闲职在家,一忙着投简历,二为准备面试而搜集整理各种面试题。故常常关注个人所建的Algorithms1-14群内朋友关于笔试,面试,宣讲会,offer,薪资的讨论以及在群内发布的各种笔/面试...  阅读全文

posted @ 2013-05-16 16:42 鑫龙 阅读(406) | 评论 (0)编辑 收藏

仅列出标题
共20页: 1 2 3 4 5 6 7 8 9 Last