【问题标题】:algorithm for making optimise string from sub strings从子字符串中优化字符串的算法
【发布时间】:2015-02-13 14:33:45
【问题描述】:

假设我有一个子字符串的集合,例如:

string a = {"cat","sensitive","ate","energy","tense"}

那么输出应该如下:

catensesensitivenergy

我该怎么做?

【问题讨论】:

  • 用什么语言?这只是一个理论问题(例如,你会在功课中找到的那种),语言并不重要吗?
  • 构造字符串的规则是什么?子串的最大重叠?
  • 要非常具体的语言并不重要。
  • 我们可以使用母语 c.要求算法背后的原因是应该没有或最少使用函数,我们可以使用stdin.h和stdio.h函数
  • 输出应该是catensensitivenergy吗?

标签: c++ c string algorithm


【解决方案1】:

分解问题,看看我们得到了什么。仅从两个字符串开始。我们必须检查一个字符串的哪个后缀是另一个字符串的最长前缀。这为我们提供了最佳连接的顺序。

现在,有了一组n字,我们怎么办?我们首先构建一个包含每个单词(每个单词的键)的 trie。如果一个词与另一个词重复,我们可以在构建前缀树时轻松地标记它。

我快速实现了一个常规的 Trie。你可以找到它here

我们拥有构建一个有向图的工具,将不同的词联系起来,无论第一个词的后缀是第二个词的前缀。边的权重就是后缀的长度。

为此,对于输入集的每个单词w,我们必须看看我们可以使用后缀w到达哪些单词:

  • 我们使用后缀沿着树向下走。我们最终会进入一个节点(或不是)。
  • 从这个节点,如果它存在,我们扫描剩余的子树,看看哪些词是 可用的。
    • 如果给定的长度为 l 的后缀产生与 词的前缀w',然后我们添加一条边w → w',权重length(w') - l
    • 如果这样的边缘已经存在,我们只需更新权重以保持最低

从那里开始,图就设置好了,我们必须找到通过每个顶点(例如单词)只运行一次的最短路径。如果图表完整,则为Traveling Salesman Problem。大多数情况下,图表并不完整。

不过,这仍然是一个 NP 难题。用更多的“技术”术语来说,手头的问题是找到the shortest hamiltonian path of a digraph

注意:给定一条哈密顿路径(如果存在),成本为 C,起始顶点(单词)为 W,超字符串长度由下式给出:

L = LW + C

注意:如果两个词没有后缀将它们链接到另一个词,则图不连通,不存在哈密顿路径。

【讨论】:

    【解决方案2】:

    这个问题被称为最短的普通超弦问题,它是 NP 难的,所以如果你需要一个精确的解决方案,那么尝试所有可能性并选择最好的解决方案是再好不过的了。

    一种可能的指数解决方案是生成输入字符串的所有排列,为每个排列贪婪地找到最短的公共超串(排列指定字符串的顺序,并且可以证明对于固定顺序的贪婪算法总是正确工作) 并选择最佳的。

    【讨论】:

    • @AndyG 超序列和超字符串实际上是不同的东西。
    • “所以你最好的办法就是尝试所有的可能性并选择最好的那个。”这是对 NP-hard 含义的过度简化。通常可以很好地击败完全详尽的解决方案,尽管不是在可证明的多项式时间内,例如用于分解整数,这很可能是一个 NP 难题,但已知的解决方案可证明比试除法快得多。
    • 更具体地说,手头的问题是 NP 难题,因为它简化为在有向图中找到最短的哈密顿路径
    【解决方案3】:

    使用 user2040251 建议:

    #include <string>
    #include <iostream>
    #include <algorithm>
    
    std::string merge_strings( const std::vector< std::string > & pool )
    {
        std::string retval;
        for( auto s : pool )
            if( retval.empty() )
                retval.append( s );
            else if( std::search( retval.begin(), retval.end(), s.begin(), s.end() ) == retval.end() )
            {
                size_t len = std::min( retval.size(), s.size() ); 
                for( ; len; --len )
                    if( retval.substr( retval.size() - len ) == s.substr( 0, len ) )
                    {
                        retval.append( s.substr( len ) );
                        break;
                    }
                if( !len )
                    retval.append( s );
            }
        return retval;           
    }
    
    std::string shortest_common_supersequence( std::vector< std::string > & pool )
    {
        std::sort( pool.begin(), pool.end() );
    
        std::string buffer;
        std::string best_reduction = merge_strings( pool );
        while( std::next_permutation( pool.begin(), pool.end() ) )
        {
            buffer = merge_strings( pool );
            if( buffer.size() < best_reduction.size() )
                best_reduction = buffer;
        }
        return best_reduction;
    }
    
    
    int main( int argc, char ** argv )
    {
        std::vector< std::string > a{"cat","sensitive","ate","energy","tense"};
        std::vector< std::string > b{"cat","sensitive","ate","energy","tense","sit"};
        std::vector< std::string > c{"personal","ate","energy","tense","gyroscope"};
        std::cout << "best a --> \"" << shortest_common_supersequence( a ) << "\"\n";
        std::cout << "best b --> \"" << shortest_common_supersequence( b ) << "\"\n";
        std::cout << "best c --> \"" << shortest_common_supersequence( c ) << "\"\n";
    
        return 0;
    }
    

    输出:

    best a --> "catensensitivenergy"
    best b --> "catensensitivenergy"
    best c --> "atensenergyroscopersonal"
    

    【讨论】:

    • 通过检查每个排列你不是最理想的吗?大多数时候,单词的基础图表并不完整,这让我们的生活更轻松:)
    猜你喜欢
    • 2021-11-08
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多