现在的位置: 首页 > 综合 > 正文

经典算法面试题解答(三)—– 最短路径、最长路径

2013年11月04日 ⁄ 综合 ⁄ 共 3768字 ⁄ 字号 评论关闭

1、最长字符连接

有n个长为m+1的字符串,如果某个字符串的最后m个字符与某个字符串的前m个字符匹配,则两个字符串可以联接,问这n个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。

分析:

将各个字符串作为一个节点,首尾链接就好比是一条边,将两个节点连接起来,于是问题就变成一个有关图的路径长度的问题。链接所得的字符串最长长度即为从图的某个节点出发所能得到的最长路径问题,与最短路径类似,可以应用弗洛伊德算法求解。对于循环,即可认为各个节点通过其他节点又回到自己,反应在路径长度上,就表示某个节点到自己节点的路径大于零(注:初始化个节点到自己的长度为零)。

弗洛伊德算法介绍

Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall算法的时间复杂度O(N^3)空间复杂度O(N^2)

Floyd-Warshall算法的原理是动态规划

D_{i,j,k}为从ij的只以(1..k)集合中的节点为中间节点的最短路径的长度。

  1. 若最短路径经过点k,则D_{i,j,k}=D_{i,k,k-1}+D_{k,j,k-1}
  2. 若最短路径不经过点k,则D_{i,j,k}=D_{i,j,k-1}

因此,D_{i,j,k}=\mbox{min}(D_{i,k,k-1}+D_{k,j,k-1},D_{i,j,k-1})

算法如下:

const int MAX = 1000000000;
int floyd(int matrix[5][5],int n)
{
 int dist[5][5];
 int path[5][5];
 for(int i=0; i<n; i++)
  for(int j=0; j<n; j++)
   dist[i][j] = matrix[i][j],path[i][j] = -1;
 for(int k=0; k<n; k++)
  for(int i=0; i<n; i++)
   for(int j=0; j<n; j++)
    if(dist[i][j]>dist[i][k]+dist[k][j])
     dist[i][j] = dist[i][k]+dist[k][j],path[i][j] = k;
 for(int i=0; i<n; i++)
 {
  for(int j=0; j<n; j++)
  {
   if(i!=j&&dist[i][j] != MAX)
   { 
    cout<<i<<"到"<<j<<"最短距离为"<<dist[i][j]<<endl;
    cout<<"路径逆序为:"<<j<<"->";
    int mid = path[i][j];
    while(mid!=-1)
     cout<<mid<<"->",mid = path[i][mid];
    cout<<i<<endl<<endl;
   }
  }
 }
 return 0;   
}
int _tmain(int argc, _TCHAR* argv[])
{

 int matrix[5][5] = {0,MAX,1,4,MAX,
  MAX,0,MAX,MAX,MAX,
  MAX,MAX,0,MAX,2,
  MAX,3,MAX,0,MAX,
  MAX,3,MAX,MAX,0};
 floyd(matrix,5);
 return 0;
}

运行如下:

问题代码如下:

void maxLengthInStrings(string text[],int nSize)
{
int dist[100][100];
int path[100][100];
memset(path,-1,sizeof(int)*100*100);
memset(dist,0,sizeof(int)*100*100);
int m = text[0].size()-1;
for(int i=0; i<nSize; i++)
{
string iText = text[i].substr(1,m);
for(int j=0; j<nSize; j++)
{
if(i!=j)
{
string jText = text[j].substr(0,m);
if(iText == jText)
dist[i][j] = 1;
else 
dist[i][j] = 0;
}
}
}
for( int k=0; k<nSize; k++)
for( int i=0; i<nSize; i++)
for( int j=0; j<nSize; j++)
{
if(dist[i][k]&&dist[k][j])
{
if(dist[i][j]<dist[i][k]+dist[k][j])
{
dist[i][j] = dist[i][k]+dist[k][j];
path[i][j] = k;
}
}
}

int max_from,max_to,max = 0;
for(int i=0; i<nSize; i++)
{
for(int j=0; j<nSize; j++)
if(max<dist[i][j])
max = dist[i][j],max_from = i,max_to = j;
}
cout<<"最长串逆序如下"<<text[max_to]<<" ";
int mid = path[max_from][max_to];
while(mid>=0)
cout<<text[mid]<<" ",mid = path[max_from][mid];
cout<<text[max_from]<<endl;
}

// test

int _tmain(int argc, _TCHAR* argv[])
{
string text[] = { "abcd",  
                  "bcde",  
                   "cdea",  
                    "deab",  
                     "eaba",  
                      "abab",  
                    "deac",  
                   "cdei",  
                  "bcdf",  
                   "cdfi",  
                    "dfic",  
                   "cdfk",  
                  "bcdg",  
};
maxLengthInStrings(text,sizeof(text)/sizeof(text[0]));
return 0;
}

 运行结果如下:

 2、.用天平(只能比较,不能称重)从一堆小球中找出其中唯一一个较轻的,
使用x 次天平,最多可以从y 个小球中找出较轻的那个,求y 与x 的关系式

 三叉树,每次分为三等分,y = 3^x

3、随机数问题

随机取样问题

随机取样问题,在《计算机程序设计艺术》(volume 2 chapter 3)中得到了详细的讲解,关于该问题的详细探讨可以翻阅该书相应章节。

随机取样问题可以分为:

1、 确定数目元素的随机取样,可以表述为:从N个元素中获得K个相异且随机的元素。

2、 不确定数目元素的随机取样,可以表述为:从一个数据流中获得k个相异且随机的元素。

其实第二个问题包含着第一个问题,我们只要探讨第二个问题即可,我直接给出下面的算法吧,首先要注意取样问题的描述:“k个”“相异”“随机”,代码如下:

T* random_sample(stream s,intk)

{

     T* rand_list=newT[k];

     inti=k;

     s.open();

     /*store first k elements in stream*/

     while(k-->=0)

           rand_list[i]=s.next();

     i=k;

     while(!s.end())

     {

           i++;

           intidx=rand(0, i-1);//create random number [0, i-1]

           if(idx<=k-1)//[0,k-1]

                rand_list[idx]=s.next();

     }

}

下面对其正确性进行证明,正确性应该保证“k个”“相异”“随机”,前两个条件显然成立,开始状态前两个状态就成立,函数执行过程中,每次替换的元素都不相同(我们认为在stream中不同位置的元素不同),对于“随机”的证明可以使用数学归纳法进行:

待证明问题,对于任意n>=k,经过上述方法后,得到的结果中任何一个元素出现的概率均为k/n。

1、当n=k时,rand_list[]中存储前k个元素,p=k/n=k/k=1成立。

2、假设当n=i时,命题成立:i个元素出现在rand_list[]中的概率为k/i。

当n=i+1时,执行上述算法,我们只关注最后一步:对于第i+1个元素的操作。

对第i+1个元素执行上述算法之前,对于前i个元素中任意一个元素,它们在rand_list[]中出现的概率为k/i,我们以k/(i+1)的概率去替换rand_list[]中的元素,前i个元素中任意一个元素被替换的概率为:

       k(1/i)* ( 1/(i+1))

前i个元素中任意一个元素最终被取到的概率:

       k/i - k(1/i)* ( 1/(i+1)) = k/(i+1)

对于第i+1个元素,它被取到的概率可以直接计算得到:

       P(i+1)=k/(i+1)

所以当n=i+1时,上述命题也成立。

下面给两个相关的题目:

1、给定一个未知长度的整数流,如何随机选取一个数

2、给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字)。如何才能从这个无穷尽的流中随机的选取1000个关键字?

抱歉!评论已关闭.