先导简介
先来看看trie树的基本介绍:
Trie树(也被称为字典树或前缀树)是一种经常用于字符串处理的树型数据结构。它特别适用于前缀匹配、搜索和插入字符串的操作。 Trie树的基本特点是每个节点代表一个字符,并且从根节点到达某个节点的路径上的字符连接起来构成一个字符串。根节点表示空字符串,每个字符的子节点按照字符顺序存储,并通过指针连接在一起。 Trie树的主要优点是可以高效地存储和检索字符串数据,尤其适用于大量字符串的前缀匹配和搜索场景。然而,Trie树的缺点是可能会占用较大的空间,因为每个节点都需要存储指向所有可能字符的指针。针对这个问题,可以使用各种优化技术,如压缩Trie、三分搜索Trie等。
下面实现的trie树,涉及以下核心技术和基本功能:
插入,删除,词频统计,串排序,模糊匹配,
文本相似度分析,前缀匹配,AC自动化,多模式串匹配
RAII技术自动析构
递归深拷贝
Modern C++编程(ranges rawStr... )
模板
双哈希等等
由C++17/20 实现
并且在最后提供源码 和 测试程序
首先导入一堆头文件

先来看看类定义

类的定义
这里Node是一个嵌套在class trie定义内的struct类型,表示trie树上的节点
Node::hasher:
using hasher = std::unordered_map< char, unique_ptr< Node > >;
hasher作为一个容器,存放每个节点的孩子(指针)域。
注意到许多多叉树的节点结构基本都是 pair< Key, pChild >[ ORDER阶数 ] 类型,比如rb-tree等等,所以hasher容器类型可以这样定义。
特别的是,为了RAII模式管理内存安全,减小手动负担,我用智能独占指针管理Node *的内存
http://std::unique_ptr - cppreference.comen.cppreference.com/w/cpp/memory/unique_ptr

Node::frequency:
frequency是optional< size_t >类型的,初始化为nullopt,表示当前节点并不是一个串的末尾。
这里需要说明相关的语法,当【frequency -> explicit operator bool() (隐式转换)为假】 或者 【frequency.has_value()->bool为假】时,说明该节点出不是任何字符串的结尾,反之,可以通过std::optional相关接口获取内部保有的std::size_t 的值,其含义为该节点的本位字符串出现的频次。
之所以这么定义,是为了避免对于一些返回值类型有二义(不一定为有效值)的函数接口,比如search,count等,大多数
情况下需要自定义return_t结构类型 : (举例) std::pair< Node *, bool >等等,比较冗余
http://std::optional - cppreference.comen.cppreference.com/w/cpp/utility/optional

Node::container:
using container = std::unordered_set< std::string >;

container在正常情况下,存储以当前位置为结尾的单个字符串(本位字符串)( 仅当frequency.has_value() == true时为有效节点, 此时container.size() == 1U)

而在trie成为AC自动机后,container需要存储所有与本位串具有最大公共后缀的字符串,为后续的多模式串匹配提供便利
failed:
参考KMP算法,在模式串pattern的迭代过程中失配的情况,利用前后缀性质回跳,即相当于Next数组
求法为BFS,再后面的程序有介绍到。
前缀匹配

这里实现起来比较简单,唯一值得注意的是:

添加图片注释,不超过 140 字(可选)
我对范围for的向前迭代容器pattern做了视图的预处理,用的语法是range-for \operator|()(管道),ranges范围算法库,callable concept等等。这一系列行为让_Char只遍历小写英文字母,方便后续处理(因为懒,有些特判不想做)
构造、析构

数据成员root已经用成员初始化列表初始化了,所以默认构造用的编译器默认提供的版本,移动构造由于unique_ptr的所有权转让,也用了默认版本。
需要注意的是拷贝构造,由于这是一个动态链表实现的树结构,需要深拷贝,而且是递归复制的“克隆”;
这里使用双哈希的方法,在DFS遍历other trie树时,在node指针树上游走同时,平行地拷贝节点。


具体实现逻辑像下面的图示:

插入字符串

插入字符串操作和前面的前缀匹配的实现几乎一样。
只是在这里略有不同:

删除字符串

删除操作更是简单,只需要复用前缀匹配的接口,检查是否为目标字符串,为真就直接把frequency数据域赋空。
查找字符串(透明接口)

无需多言。
统计字符串:

也是类似的实现。
前缀匹配(透明接口)

AC自动机
接下来介绍AC自动机
AC自动机基于字典树(Trie)和有限状态机的思想,具有高效的匹配速度和较低的内存消耗。
AC自动机的构建过程包括以下步骤:
构建Trie树:将所有模式串逐个插入到Trie树中,并为每个节点设置指向父节点的指针和指向匹配失败时应该跳转的节点的指针。
构建失败指针:使用广度优先搜索(BFS)的方式,从根节点开始遍历Trie树,依次为每个节点设置失败指针(也称为跳转指针),用于在匹配失败时快速跳转到下一个可能的匹配位置。
匹配操作:对输入的主串进行遍历,从根节点开始根据字符不断向下匹配。如果当前节点无法匹配当前字符,就根据失败指针跳转到下一个位置,直到遍历完主串。
在匹配过程中,如果某个节点表示一个模式串的结束位置,则可以根据需要记录或处理匹配结果。
AC自动机的特点是可以同时匹配多个模式串,并且具有线性的匹配时间复杂度。它在字符串搜索、关键词过滤、词法分析等场景中有广泛的应用。
从上述介绍可以知道,这里首先需要提供一个对外的接口,使得一个正常trie对象转化为AC自动机,链接failed指针,扩展出多模式匹配的功能
下面我将对比经典的单模式匹配KMP算法来介绍AC自动机
初始化
首先是AC自动机的初始化

可以看到,这个功能模块的整体样式是标准的BFS

而在BFS执行过程中,产生的副作用就是通过动态规划思想,来更新failed失配指针,学习过KMP之后就不会陌生。
这里提供一下传统KMP Next数组的求法,以供对比,可以看到,二者的实现逻辑几乎一样。

添加图片注释,不超过 140 字(可选)

值得注意的是,最后一行代码,通过拷贝父节点构造一个临时对象,并调用子节点的unordered_set::merge来合并父节点中container的所有的公共子串,
是典型C++的swap© idiom
多模式串匹配

添加图片注释,不超过 140 字(可选)
前面说过 container::pattern在trie成为AC自动机后,用来存储那些具有和’本位字符串‘的最大公共后缀的串
代码前半部分是正常匹配,像是search,prefix_matching那部分的实现

由于AC自动机是KMP算法在trie树上的应用,所以模式匹配失败进行回跳那部分的代码也是高度重合


最后 std::views::copy( patterns, std::ostream_iterator<>(std::cout,"|"))最子串集合做遍历并打印
这里使用了一些c++17 20或以上版本的新特性,ranges范围算法库,views视图,iterator迭代器接口和标签等,并不是必须的,只是有些场景Modern cpp更灵活和简洁
Iterator library - cppreference.com
Standard library header <ranges> (C++20) - cppreference.com
字符串排序

这是一种非比较排序,和计数排序,基数排序等一样,通过DFS递归遍历trie树,根据树上游走的时间戳来排序。
字符串排序的排序规则是字典序,属于多关键字排序,和基数排序一样,带来了快速排序的可能性。
这里的sorted是一个透明公开的接口,调用了私有的辅助函数sortedHelper

如图所示,整体架构就是dfs,在函数开头判断是否遇到有效节点(恰好为某个模式串结尾),为真则收集该字串到结果vector中。这里的递归是自然有限的,不需要设置出口。
for循环中 _Char 从 a 到 z 保证了字典序的从小到大顺序

模糊匹配


这里的函数调用关系是public版本调用private版本( helper 辅助函数 )。我们直接看辅助函数的实现,从代码层次上看,这只是一个标准的dfs。
根据下面dfs的框架:

类比得出,模糊匹配函数的出口判断现在是否到达了模式串末尾,为真则尝试收集本位字符串到结果集。
而向下递归的方向取决Char的取值,这里我设置模糊匹配的占位符为 ' * ';
![]()
添加图片注释,不超过 140 字(可选)
那么当Char为占位符时,它可以代表26个字母(或者别的字符集,我这里约定是小写字母)中的任何一个,
所以做遍历并向下递归;

如果Char是正常字符,那么需要做判断,当前位置的节点是否有下一个节点为Char的链接,即children.count(Char)非负

文本相似度分析
两个文本的相似度的指标有很多,常见的有
词袋分析(词频重复率),词向量余弦(词的相对位置抽象空间化),LCS(子串,子序列),Jaccard相似度分析(单词集合的对称差与最小全集的比值),编辑距离等等
我这里只采用了词袋分析和LCS最大公共子序列长度分析的方法。
具体实现是通过 std::istringstream流对象分割字符串string到单词序列vector< string >
LCS的求解是经典的动态规划
词频统计,直接调用已经实现好的trie::count( std::string_view )接口
由于没什么难度,这里直接贴代码
template < std::size_t N, std::size_t M, typename Ty >
using matrix = std::array< std::array< Ty, M >, N >;
static constexpr std::size_t LENGTH_LIMIT { 64 };
enum state : char { SAME_AS = 0, LHS, RHS } ;
template < std::size_t N >
void GetLCString( matrix< N, N, state > &record,
std::size_t i, std::size_t j,
const std::vector< std::string >& dest,
std::vector< std::size_t >& indices ){
if( !i || !j ) return;
if( record[i][j] == SAME_AS ){
indices.push_back( i );
GetLCString( record, i-1, j-1, dest, indices );
}else if( record[i][j] == LHS )
GetLCString( record, i-1, j, dest, indices );
else
GetLCString( record, i, j-1, dest, indices );
}
template < typename Ty > concept arthmetic
= ( std::integral< Ty >
|| std::floating_point< Ty > );
template < typename Ty, typename Tx > concept comparable
= ( requires( Tx v1, Ty v2) {
{ v1 < v2 }->std::convertible_to< bool >;
} );
template < arthmetic Ty, arthmetic Tx >
requires comparable< Ty, Tx >
inline constexpr auto
min( Ty&& v1, Tx&& v2 ){
return v2 < v1 ? v2 : v1;
}
template < arthmetic Ty, arthmetic ...Args >
requires (comparable< Ty, Args > && ...)
constexpr auto
min( Ty&& first, Args&&...rest ){
if constexpr ( sizeof...( Args ) == 1 ) return min( first, rest...);
return min( std::forward< Ty >( first ),
min( std::forward< Args >( rest )... ) );
}
void similarity_analysis( const std::string& Str1, const std::string& Str2 ){
std::istringstream s1( Str1 );
std::istringstream s2( Str2 );
std::vector< std::string > words1;
std::vector< std::string > words2;
std::string buffer;
while( s1 >> buffer ) words1.emplace_back( std::move( buffer ) );
while( s2 >> buffer ) words2.emplace_back( std::move( buffer ) );
std::size_t length1 { words1.size() };
std::size_t length2 { words2.size() };
matrix< LENGTH_LIMIT, LENGTH_LIMIT, std::size_t > dp;
matrix< LENGTH_LIMIT, LENGTH_LIMIT, state > record;
for( auto i { 1U }; i < length1; ++i )
for( auto j { 1U }; j < length2; ++j ){
if( words1[i] == words2[j] ){
dp[i][j] = dp[i-1][j-1] + 1;
record[i][j] = SAME_AS;
}else if( dp[i][j-1] <= dp[i-1][j]){
dp[i][j] = dp[i-1][j];
record[i][j] = LHS;
}else{
dp[i][j] = dp[i][j-1];
record[i][j] = RHS;
}
}
std::vector< std::size_t > indices;
indices.reserve( length1 << 1U | 1U );
GetLCString( record, length1 - 1, length2 - 1, words1, indices );
std::size_t common_length { dp[ length1 - 1 ][ length2 - 1 ] };
std::cout << "Longest common subword sequence:\n";
std::cout << "1 length:\t" << common_length << '\n';
puts("2 sequence:");
namespace std_v = std::views;
for( const auto& Word :
indices | std_v::reverse | std_v::transform(
[&]< typename Value>( Value index ){
return words1[index];
} ) )
std::cout << Word << "...";
endl( std::cout );
std::size_t N { ( int )Str1.length() };
std::size_t M { ( int )Str2.length() };
for(auto& dimenssion : dp ) dimenssion.fill(0);
for( auto i {0U}; i <= N; ++i ) dp[i][0] = i;
for( auto j {0U}; j <= M; ++j ) dp[0][j] = j;
for( auto i {1U}; i <= N; ++i ) for( auto j {1U}; j <= M; ++j )
dp[i][j] = min(dp[i-1][j]+1,
dp[i][j-1]+1,
dp[i-1][j-1]+(std::size_t)(!(Str1[i-1] == Str2[j-1])));
std::cout << "minimun edit distance between Str1 and Str2:\t"
<< dp[N][M] << '\n';
trie T;
for( const auto& W : words1 )
T.insert( W );
std::size_t repeated { 0U };
for( const auto& W : words2 )
repeated += T.count( W );
std::puts("-----------------------");
std::cout << "repeated words:\t" << repeated << '\n';
}
测试程序
写一个数据结构,相关的demo测试是必不可少的
下面是简单的测试样例,用的宏架构控制编译分支,可阅读一下以了解函数接口的调用规则
之后有对应控制台输出结果
#define SIMILARITY_ANALYSIS
int main( void ){
#ifdef INSERT_ERASE_COUNT
trie T;
std::puts(R"(insert "hello")");
T.insert("hello");
std::puts("-----------------");
std::puts(R"(insert "hello")");
T.insert("hello");
std::puts("-----------------");
std::puts(R"(search "hello"...result:)");
std::cout << std::boolalpha << T.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(erase "hello")");
T.erase("hello");
std::puts("-----------------");
std::puts(R"(search "hello"...result:)" );
std::cout << std::boolalpha << T.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(insert "world" for 10 times)");
auto counter { 10 };
while( counter -- ) T.insert("world");
std::puts("-----------------");
std::puts(R"(check any string starts with "wor"...result:)");
std::cout << std::boolalpha << T.hasAnyStartsWith("wor");
endl( std::cout );
std::puts("-----------------");
std::cout << R"(count of "hello":)" "\t\n"
<< T.count("world");
endl( std::cout );
#elif defined MULTI_PATTERN_MATCHING
trie T;
std::puts(R"(insert "he")");
T.insert("he");
std::puts("-----------------");
std::puts(R"(insert "ell")");
T.insert("ell");
std::puts("-----------------");
std::puts(R"(insert "lowor")");
T.insert("lowor");
std::puts("-----------------");
std::puts(R"(insert "rd")");
T.insert("rd");
std::puts("-----------------");
std::puts("Building MultiPatternMatching with KMP Algo...");
T.AhoCorasickAutomatonBuilder();
std::puts("-----------------");
std::puts(R"("Multi pattern matching with string: "helloword")" );
T.MultiPatternMatching("helloword");
#elif defined DEEP_COPY
trie T1;
std::puts(R"(T1 insert "hello")");
T1.insert("hello");
std::puts("-----------------");
std::puts(R"(T1 insert "world")");
T1.insert("world");
std::puts("-----------------");
std::puts("T1(T2) copy construct called...");
trie T2( T1 );
std::puts("-----------------");
std::puts(R"(T2 search "hello"...result:)" );
std::cout << std::boolalpha << T2.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(T2 search "world"...result:)" );
std::cout << std::boolalpha << T2.search("world");
endl( std::cout );
std::puts("-----------------");
#elif defined SORT
trie T;
T.insert("fsd");
T.insert("set");
T.insert("iowe");
T.insert("aosid");
std::puts("insert: fsd set iowe aosid");
puts("----------------------");
std::puts("sorted result:");
std::vector< std::string > sorted_result;
T.sorted( sorted_result );
namespace std_r = std::ranges;
std_r::copy( sorted_result,
std::ostream_iterator< std::string >( std::cout, "\t" ) );
endl( std::cout );
#elif defined FUZZY_PATTERN_MATCHING
trie T;
T.insert("bear");
T.insert("baar");
T.insert("beer");
std::puts("insert: bear baar beer");
std::vector< std::string > result;
std::puts("fuzzy pattern matching with b**r ");
T.fuzzyPatternMatching( "b**r", result );
namespace std_r = std::ranges;
std_r::copy( result,
std::ostream_iterator< std::string >( std::cout, "\t" ) );
endl( std::cout );
#elif defined SIMILARITY_ANALYSIS
std::string Str1 { " Str1: I always love eating ice the cream so much " };
std::string Str2 { " Str2: I love so much eating the cream ice "};
std::cout << Str1; endl( std::cout );
std::cout << Str2; endl( std::cout );
std::puts("------------------------");
similarity_analysis( Str1, Str2 );
#endif
return 0;
}
插入 删除 统计函数的测试结果

添加图片注释,不超过 140 字(可选)
AC自动机 | 多模式匹配测试

添加图片注释,不超过 140 字(可选)
多叉树的递归深拷贝测试

添加图片注释,不超过 140 字(可选)
串的非比较排序测试

添加图片注释,不超过 140 字(可选)
模糊匹配测试

添加图片注释,不超过 140 字(可选)
文本相似度分析测试

项目完整代码
#include <iostream>
#include <unordered_map>
#include <memory>
#include <unordered_set>
#include <string_view>
#include <string>
#include <optional>
#include <ranges>
#include <cctype>
#include <queue>
#include <iterator>
#include <algorithm>
#include <sstream>
#include <array>
#include <concepts>
#include <functional>
class trie{
struct Node{
using hasher =
std::unordered_map< char,
std::unique_ptr< Node > >;
using container =
std::unordered_set< std::string >;
std::optional< std::size_t >
frequency { std::nullopt };
hasher children;
container patterns;
Node *failed { nullptr };
};
private:
std::unique_ptr< Node > root { new Node };
Node *prefix_pattern_matching( const std::string_view& pattern ) const{
namespace std_v = std::views;
Node *ptr { root.get() };
for( const auto& _Char :
pattern | std_v::filter(
[]< typename Value >( Value _Char )
{ return std::isalpha( _Char ); } )
| std_v::transform(
[]< typename Value >( Value _Char )
{ return std::tolower( _Char ); } ) ){
if( ! ptr->children.count( _Char ) )
return static_cast< Node * >( nullptr );
ptr = ptr->children[ _Char ].get();
}
return ptr;
}
void hasherConstor( Node *o,
std::unordered_map< Node *, Node * >& hasher ){
Node *clone { new Node };
clone->patterns = o->patterns;
clone->frequency = o->frequency;
hasher[ o ] = clone;
for( auto & [ _Char, child ] : o->children )
hasherConstor( child.get(), hasher );
}
void hasherMapper( Node *o,
std::unordered_map< Node *, Node * >& hasher ){
hasher[o]->failed = hasher[o->failed];
for( auto& [ _Char, child ] : o->children ){
hasher[ o ]->children[ _Char ].reset(
hasher[ o->children[ _Char ].get() ] );
hasherMapper( child.get(), hasher );
}
}
void sortedHelper( Node * o, std::vector< std::string >& result, std::string Str ) const{
if( o->frequency ) result.emplace_back( std::move( Str ) );
for( auto _Char { 'a' }; _Char <= 'z'; ++_Char)
if( o->children.count( _Char ) )
sortedHelper( o->children[ _Char ].get(),
result,
Str + _Char );
}
void fuzzyPatternMatchingHelper( Node *o, std::size_t index,
const std::string_view& fussy,
std::vector< std::string >& result, std::string Str ) const {
if( index == fussy.length() ){
if( o->frequency ) result.emplace_back( std::move( Str ) );
return;
}
const auto& _Char { fussy[index] };
if( _Char == PLACE_HOLDER ){
for( auto _Alpha { 'a' }; _Alpha <= 'z'; ++_Alpha ){
if( o->children.count( _Alpha) )
fuzzyPatternMatchingHelper(
o->children[ _Alpha ].get(),
index + 1,
fussy,
result,
Str + _Alpha);
}
}else if( o->children.count( _Char ) )
fuzzyPatternMatchingHelper(
o->children[ _Char ].get(),
index + 1,
fussy,
result,
Str + _Char);
}
public:
explicit trie( void ) noexcept = default;
explicit trie( trie&& ) noexcept = default;
~trie( void ) noexcept{ root.release(); }
explicit trie( const trie& other ) {
std::unordered_map< Node *, Node * > hasher;
hasherConstor( other.root.get(), hasher );
hasherMapper( other.root.get(), hasher );
root.reset( hasher[ other.root.get() ] );
}
void erase( const std::string_view& Str ){
auto ptr { prefix_pattern_matching( Str ) };
if( ptr && ptr->frequency )
ptr->frequency = std::nullopt;
}
void insert( const std::string_view& Str ){
namespace std_v = std::views;
Node *ptr { root.get() };
for( const auto& _Char :
Str | std_v::filter(
[]< typename Value >( Value _Char )
{ return std::isalpha( _Char ); } )
| std_v::transform(
[]< typename Value >( Value _Char )
{ return std::tolower( _Char ); } ) ){
if( ! ptr->children.count( _Char ) )
ptr->children[ _Char ] = std::make_unique< Node >();
ptr = ptr->children[ _Char ].get();
}
if( ! ptr->frequency ){
ptr->frequency = 1;
ptr->patterns.emplace( Str );
}
else ++*( ptr->frequency );
}
inline bool search( const std::string_view& Str ) const{
auto ptr { prefix_pattern_matching( Str ) };
return static_cast< bool >( ptr && ptr->frequency );
}
inline std::size_t count( const std::string_view& Str ) const{
return prefix_pattern_matching( Str )->frequency.value_or( 0U );
}
bool hasAnyStartsWith( const std::string_view& Str ) const{
return static_cast< bool >( prefix_pattern_matching( Str ) );
}
void AhoCorasickAutomatonBuilder( void ){
std::queue< Node * > Q;
root->failed = root.get();
for( auto& [ _Char, child ] : root->children ){
child->failed = root.get();
Q.push( child.get() );
}
while( !Q.empty() ){
Node *current { Q.front() };
Q.pop();
for( auto& [ _Char, child ] : current->children ){
Q.push( child.get() );
Node *jumper { current->failed };
while( !( jumper == root.get() )
&& !( jumper->children.count( _Char ) ) )
jumper = jumper->failed;
child->failed =
jumper->children.count( _Char )
? jumper->children[ _Char ].get()
: root.get();
child->patterns.merge(
Node::container( child->failed->patterns ) );
}
}
}
void MultiPatternMatching( const std::string_view& Str ) const{
namespace std_r = std::ranges;
Node *current { root.get() };
for( const auto& _Char : Str ){
while( !( current == root.get() )
&& !( current->children.count( _Char ) ) )
current = current->failed;
if( current->children.count( _Char ) )
current = current->children[ _Char ].get();
if( !current->patterns.empty() )
std_r::copy( current->patterns,
std::ostream_iterator< std::string >
( std::cout, "|" ) );
}
}
void sorted( std::vector< std::string >& result ) const {
using namespace std::literals::string_literals;
sortedHelper( root.get(), result, ""s );
}
static constexpr char PLACE_HOLDER { '*' };
void fuzzyPatternMatching( const std::string_view& fuzzy,
std::vector< std::string >& result ) const{
using namespace std::literals::string_literals;
fuzzyPatternMatchingHelper( root.get(), 0U, fuzzy, result, ""s );
}
};
template < std::size_t N, std::size_t M, typename Ty >
using matrix = std::array< std::array< Ty, M >, N >;
static constexpr std::size_t LENGTH_LIMIT { 64 };
enum state : char { SAME_AS = 0, LHS, RHS } ;
template < std::size_t N >
void GetLCString( matrix< N, N, state > &record,
std::size_t i, std::size_t j,
const std::vector< std::string >& dest,
std::vector< std::size_t >& indices ){
if( !i || !j ) return;
if( record[i][j] == SAME_AS ){
indices.push_back( i );
GetLCString( record, i-1, j-1, dest, indices );
}else if( record[i][j] == LHS )
GetLCString( record, i-1, j, dest, indices );
else
GetLCString( record, i, j-1, dest, indices );
}
template < typename Ty > concept arthmetic
= ( std::integral< Ty >
|| std::floating_point< Ty > );
template < typename Ty, typename Tx > concept comparable
= ( requires( Tx v1, Ty v2) {
{ v1 < v2 }->std::convertible_to< bool >;
} );
template < arthmetic Ty, arthmetic Tx >
requires comparable< Ty, Tx >
inline constexpr auto
min( Ty&& v1, Tx&& v2 ){
return v2 < v1 ? v2 : v1;
}
template < arthmetic Ty, arthmetic ...Args >
requires (comparable< Ty, Args > && ...)
constexpr auto
min( Ty&& first, Args&&...rest ){
if constexpr ( sizeof...( Args ) == 1 ) return min( first, rest...);
return min( std::forward< Ty >( first ),
min( std::forward< Args >( rest )... ) );
}
void similarity_analysis( const std::string& Str1, const std::string& Str2 ){
std::istringstream s1( Str1 );
std::istringstream s2( Str2 );
std::vector< std::string > words1;
std::vector< std::string > words2;
std::string buffer;
while( s1 >> buffer ) words1.emplace_back( std::move( buffer ) );
while( s2 >> buffer ) words2.emplace_back( std::move( buffer ) );
std::size_t length1 { words1.size() };
std::size_t length2 { words2.size() };
matrix< LENGTH_LIMIT, LENGTH_LIMIT, std::size_t > dp;
matrix< LENGTH_LIMIT, LENGTH_LIMIT, state > record;
for( auto i { 1U }; i < length1; ++i )
for( auto j { 1U }; j < length2; ++j ){
if( words1[i] == words2[j] ){
dp[i][j] = dp[i-1][j-1] + 1;
record[i][j] = SAME_AS;
}else if( dp[i][j-1] <= dp[i-1][j]){
dp[i][j] = dp[i-1][j];
record[i][j] = LHS;
}else{
dp[i][j] = dp[i][j-1];
record[i][j] = RHS;
}
}
std::vector< std::size_t > indices;
indices.reserve( length1 << 1U | 1U );
GetLCString( record, length1 - 1, length2 - 1, words1, indices );
std::size_t common_length { dp[ length1 - 1 ][ length2 - 1 ] };
std::cout << "Longest common subword sequence:\n";
std::cout << "1 length:\t" << common_length << '\n';
puts("2 sequence:");
namespace std_v = std::views;
for( const auto& Word :
indices | std_v::reverse | std_v::transform(
[&]< typename Value>( Value index ){
return words1[index];
} ) )
std::cout << Word << "...";
endl( std::cout );
std::size_t N { ( int )Str1.length() };
std::size_t M { ( int )Str2.length() };
for(auto& dimenssion : dp ) dimenssion.fill(0);
for( auto i {0U}; i <= N; ++i ) dp[i][0] = i;
for( auto j {0U}; j <= M; ++j ) dp[0][j] = j;
for( auto i {1U}; i <= N; ++i ) for( auto j {1U}; j <= M; ++j )
dp[i][j] = min(dp[i-1][j]+1,
dp[i][j-1]+1,
dp[i-1][j-1]+(std::size_t)(!(Str1[i-1] == Str2[j-1])));
std::cout << "minimun edit distance between Str1 and Str2:\t"
<< dp[N][M] << '\n';
trie T;
for( const auto& W : words1 )
T.insert( W );
std::size_t repeated { 0U };
for( const auto& W : words2 )
repeated += T.count( W );
std::puts("-----------------------");
std::cout << "repeated words:\t" << repeated << '\n';
}
#define SIMILARITY_ANALYSIS
int main( void ){
#ifdef INSERT_ERASE_COUNT
trie T;
std::puts(R"(insert "hello")");
T.insert("hello");
std::puts("-----------------");
std::puts(R"(insert "hello")");
T.insert("hello");
std::puts("-----------------");
std::puts(R"(search "hello"...result:)");
std::cout << std::boolalpha << T.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(erase "hello")");
T.erase("hello");
std::puts("-----------------");
std::puts(R"(search "hello"...result:)" );
std::cout << std::boolalpha << T.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(insert "world" for 10 times)");
auto counter { 10 };
while( counter -- ) T.insert("world");
std::puts("-----------------");
std::puts(R"(check any string starts with "wor"...result:)");
std::cout << std::boolalpha << T.hasAnyStartsWith("wor");
endl( std::cout );
std::puts("-----------------");
std::cout << R"(count of "hello":)" "\t\n"
<< T.count("world");
endl( std::cout );
#elif defined MULTI_PATTERN_MATCHING
trie T;
std::puts(R"(insert "he")");
T.insert("he");
std::puts("-----------------");
std::puts(R"(insert "ell")");
T.insert("ell");
std::puts("-----------------");
std::puts(R"(insert "lowor")");
T.insert("lowor");
std::puts("-----------------");
std::puts(R"(insert "rd")");
T.insert("rd");
std::puts("-----------------");
std::puts("Building MultiPatternMatching with KMP Algo...");
T.AhoCorasickAutomatonBuilder();
std::puts("-----------------");
std::puts(R"("Multi pattern matching with string: "helloword")" );
T.MultiPatternMatching("helloword");
#elif defined DEEP_COPY
trie T1;
std::puts(R"(T1 insert "hello")");
T1.insert("hello");
std::puts("-----------------");
std::puts(R"(T1 insert "world")");
T1.insert("world");
std::puts("-----------------");
std::puts("T1(T2) copy construct called...");
trie T2( T1 );
std::puts("-----------------");
std::puts(R"(T2 search "hello"...result:)" );
std::cout << std::boolalpha << T2.search("hello");
endl( std::cout );
std::puts("-----------------");
std::puts(R"(T2 search "world"...result:)" );
std::cout << std::boolalpha << T2.search("world");
endl( std::cout );
std::puts("-----------------");
#elif defined SORT
trie T;
T.insert("fsd");
T.insert("set");
T.insert("iowe");
T.insert("aosid");
std::puts("insert: fsd set iowe aosid");
puts("----------------------");
std::puts("sorted result:");
std::vector< std::string > sorted_result;
T.sorted( sorted_result );
namespace std_r = std::ranges;
std_r::copy( sorted_result,
std::ostream_iterator< std::string >( std::cout, "\t" ) );
endl( std::cout );
#elif defined FUZZY_PATTERN_MATCHING
trie T;
T.insert("bear");
T.insert("baar");
T.insert("beer");
std::puts("insert: bear baar beer");
std::vector< std::string > result;
std::puts("fuzzy pattern matching with b**r ");
T.fuzzyPatternMatching( "b**r", result );
namespace std_r = std::ranges;
std_r::copy( result,
std::ostream_iterator< std::string >( std::cout, "\t" ) );
endl( std::cout );
#elif defined SIMILARITY_ANALYSIS
std::string Str1 { " Str1: I always love eating ice the cream so much " };
std::string Str2 { " Str2: I love so much eating the cream ice "};
std::cout << Str1; endl( std::cout );
std::cout << Str2; endl( std::cout );
std::puts("------------------------");
similarity_analysis( Str1, Str2 );
#endif
return 0;
}
本文主要介绍了用C++17/20实现的Trie树,它是用于字符串处理的树型数据结构,适用于前缀匹配等操作。文中涉及插入、删除、词频统计等功能,还介绍了AC自动机,可同时匹配多模式串。此外,还包括字符串排序、模糊匹配、文本相似度分析等内容,并提供了测试程序和完整代码。
475

被折叠的 条评论
为什么被折叠?



