《编程之美》——最短摘要的生成

问题:
(简化)输入两个字符串,一个表示用户输入的搜索关键词,另一个表示一篇网页的内容。对于搜索关键词和网页分别进行自动分词后,用户输入的搜索关键词和网页内容的两个字符串变为两个词语序列。所求摘要即为网页序列中包含搜索关键词序列的最短的子串。

(网页序列包含搜索关键词序列里面的所有词语,但是不要求顺序相同,最短摘要是网页字符串中所有满足这样要求的最短子串。)

分析与解法:
假设W数组是经过网页分词之后的结果,其中W[0], W[1],…, W[n]为一些已经分好的词语;Q数组是用户输入的搜索关键词,其中Q[0], Q[1],…, Q[m]为所有输入的搜索关键词。

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

【解法一】

  • 从W数组的第一个位置开始查找出一段包含所有关键词数组Q的序列,计算当前的最短长度,并更新结果序列;
  • 对目标数组W进行遍历,从第二个位置开始,重新查找包含所有关键词数组Q的序列,同样计算出其最短长度,以及更新包含所有关键词的结果序列,然后求出最短距离;
  • 依次操作下去,一直到遍历至目标数组W的最后一个位置为止。

时间复杂度是O(m*n^2)。

【解法二】

第一种解法中有大量的重复计算,对其进行优化。第一次扫描的时候,假设需要包含所有的关键词,从第一个位置w0处将扫描到w6处

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

下次扫描,先把第一个被扫描的位置挪到q0处

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

然后把第一个被扫描的位置继续往后面移动一格,这样包含的序列中将减少了关键词q0。那么便可以把第二个扫描位置往后移,这样就可以找到下一个包含所有关键词的序列。即从w4扫描到w9处,便包含了q1,q0

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

这样,问题就和第一次扫描时碰到的情况一样了。依次扫描下去,在w中找出所有包含q的序列,并且找出其中的最小值,就可得到最终的结果。

时间复杂度是O(m+n)。

书中代码如下,

int nTargetLen = N + 1; // 设置目标长度为总长度+1  
int pBegin = 0; // 初始指针  
int pEnd = 0; // 结束指针  
int nLen = N; // 目标数组的长度为N  
int nAbstractBegin = 0; // 目标摘要的起始地址  
int nAbstractEnd = 0; // 目标摘要的结束地址  

while(true) 
{  
    // 假设未包含所有的关键词,并且后面的指针没有越界,往后移动指针  
 while(!isAllExisted() && pEnd < nLen) 
    {  
        pEnd++;  
    }  

    // 假设找到一段包含所有关键词信息的字符串  
 while(isAllExisted()) 
    {  
        if(pEnd – pBegin < nTargetLen) 
        {  
            nTargetLen = pEnd – pBegin;  
            nAbstractBegin = pBegin;  
            nAbstractEnd = pEnd – 1; 
        }  
        pBegin++;  
    }  
    if(pEnd >= N) 
        Break;  
}

全部代码实现如下,

#include <stdio.h>  
#include <string>  
#include <map>  
class KeyWordChecker {  
public:  
  KeyWordChecker() : current_state_(false) {}

  void AddKeyWord(const std::string& word) {  
    if (keywords_.find(word) == keywords_.end()) {//如果该字符不存在        
      keywords_[word] = 0;  
    }  
  }  

  bool IsContainAllKeyWord(const std::string& word, bool add) {  
    if (keywords_.find(word) == keywords_.end()) {  
      return current_state_;  
    }

    if (add) {
      keywords_[word]++;//每个关键字出现的次数  
    } else {  
      keywords_[word]--;  
    }  

    std::map<std::string, int>::iterator begin = keywords_.begin();  

    int counter = 0;//总的关键字出现的次数  

    while (begin != keywords_.end()) {  
      if (begin->second > 0) {  
        counter++;  
      } else {  
        break;  
      }  
      begin++;  
    }  

    if (counter == keywords_.size()) {  
      current_state_ = true;  
      return true;  
    } else {  
      current_state_ = false;  
      return false;  
    }  
  }  

private:  
  std::map<std::string, int> keywords_;//存储每个关键字出现的次数  
  bool current_state_;//指示是否包含关键字  
};  

std::string GenerateSnippet(const std::string& content, KeyWordChecker* keyword_checker) {  
  int begin = 0;//摘要起始位置  
  int end = 0;//摘要结束位置  
  std::string snippet;//摘要内容  
  int min_length = content.size();//当前最短长度  
  while (end < content.size()) {  
    if (!keyword_checker->IsContainAllKeyWord(std::string(1, content[end])//将该处的字符生成为字符串
        , true)) {  
       end++;  
       continue;  
    }  
    while (begin <= end && keyword_checker->IsContainAllKeyWord(std::string(1, content[begin]), false)) {  
      begin++;  
    }  
    if (end - begin + 1 < min_length) {  
      snippet = content.substr(begin, end - begin + 1);  
      min_length = end - begin + 1;  
    }  
    end++;  
    begin++;  
  }    
  return snippet;  
} 

int main(int argc, char** argv) {  
  std::string content = "abbbbbcaaadebmmmmdcfg";  
  KeyWordChecker keyword_checker;  
  keyword_checker.AddKeyWord("b");  
  keyword_checker.AddKeyWord("d");  
  std::string snippet = GenerateSnippet(content, &keyword_checker);  
  printf("snippet:%s\n", snippet.c_str());  
}

文章参考以下博文:
http://blog.csdn.net/bertzhang/article/details/7278728

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值