//大规模字符串近似匹配程序
//程序操作描述:用户首先输入需要放入库中的文本串个数(整数),然后逐一输入放入的各个文本串,然后输入需要进行近似匹配的模式串
// 程序将从文本串库中找出近似度K最高的模式串输出并输出近似度K,接着将输出具体如果将该文本串修改为与模式串相同的详细最优操作序列
//程序使用算法类型:动态规划法(求近似度K)、回溯法(求最近似文本串)、减治法(求化归操作序列)
#include<iostream>//使用iostream头文件用于输入输出
#include<ctime>//使用ctime头文件用于计时
#include<cmath>//使用cmath头文件,用其中的绝对值函数参与运算
#include<stack>//使用stack头文件,用于使用其中的栈适配器来完成修改操作序列倒退的反向输出
#include<string>//使用string头文件,用于处理本程序中各处使用的string类的相关处理
using namespace std;
//用于求三个整数中的最小值的函数,采用内联形式和常引用传参,保证了函数效率和参数的安全性
inline int Get_Min(const int& a, const int& b, const int& c)
{
if (a <= b && a <= c)return a;
else if (b <= a && b <= c)return b;
else return c;
}
//用于对给定的模式串求出近似程度最高的文本串的函数,是本次课程设计的主要函数,采用常引用传参提高效率
void Most_Approximate_String(const string* Texts, const int* Lengths, const unsigned& number, const string& Model,const int& Max_TextLength)
{
clock_t start = clock();//记录函数的开始时间
int Model_Length = Model.length();//记录模式串的长度
int Min_K(Model_Length);//以模式串的长度作为局部最优解进行回溯查找(此处用到了回溯法,避免了蛮力法的逐一尝试)
int Most_Approximate_Position(0);//记录局部最优近似匹配文本串的下标
int** Matrix (new int* [Model_Length + 1]);//用一个二维整型数组来作为动态规划矩阵
for (int i = 0; i <= Model_Length; ++i)
{
Matrix[i] = new int[Max_TextLength + 1];//对矩阵的每行进行初始化,长度为最长的文本串的长度,这样可以避免重复定义矩阵,节约了空间并提高效率
}
for (int row = 0; row <= Model_Length; ++row)//对矩阵的首列进行初始化
{
Matrix[row][0] = row;
}
for (int col = 0; col <= Max_TextLength; ++col)//对矩阵的首行进行初始化
{
Matrix[0][col] = col;
}
//按照顺序取出库中的每一个文本串进行下面操作,通过回溯法文本串的长度进行回溯,减小遍历工作量
for (unsigned pos = 0; pos < number; ++pos)
{
int Length_Dif = Texts[pos].length() - Model_Length;
//由于两个串的近似度K一定大于等于长度之差的绝对值,因此如果一个文本串与模式串相比过长或过短,则可以忽略此次比较,提高效率
if (abs(Length_Dif) >= Min_K)continue;
else
{
//填表操作,也就是按照动态规划思想,求出该文本串对应的矩阵中各个元素的值
for (int row = 1; row <= Model_Length; ++row)
{
for (int col = 1; col <= Lengths[pos]; ++col)
{
//第一种情况:文本串与模式串在该位置的字符相同
if (Model[row - 1] == Texts[pos][col - 1])
{
Matrix[row][col] = Get_Min(Matrix[row - 1][col - 1], Matrix[row - 1][col] + 1, Matrix[row][col - 1] + 1);
}
//第二种情况:文本串与模式串在该位置的字符不同,也就是需要进行三种修改操作中的一种
else
{
Matrix[row][col] = Get_Min(Matrix[row - 1][col - 1], Matrix[row][col - 1], Matrix[row - 1][col]) + 1;
}
}
}
//每次循环完一次,即考虑是否需要更新局部最优解
if (Matrix[Model_Length][Lengths[pos]] < Min_K)
{
Min_K = Matrix[Model_Length][Lengths[pos]];
Most_Approximate_Position = pos;//更新局部最优解的同时记录该文本串在库中的下标
}
}
}
cout << "库中近似度最高的文本串为:" << Texts[Most_Approximate_Position] << " ,近似度K值为:" << Min_K << endl;//输出最近似的文本串和近似度K
cout << endl;
//上述操作找到了近似度最高的文本串,下面来求出对于该文本串的最优修改序列,使得其能够完全与模式串相同
string Text_On_Pos = Texts[Most_Approximate_Position];//根据上面记录的下标从文本串库中取出最优匹配串
int Length_On_Pos = Lengths[Most_Approximate_Position];//根据上面记录的下标记录最优匹配串的长度
//继续沿用之前定义的矩阵来求出该文本串对应的动态规划矩阵,由于方法与上面相同因此不过多解释
for (int row = 1; row <= Model_Length; ++row)
{
for (int col = 1; col <= Length_On_Pos; ++col)
{
if (Model[row - 1] == Text_On_Pos[col - 1])
{
Matrix[row][col] = Get_Min(Matrix[row - 1][col] - 1, Matrix[row][col - 1], Matrix[row - 1][col] + 1);
}
else
{
Matrix[row][col] = Get_Min(Matrix[row - 1][col - 1], Matrix[row][col - 1], Matrix[row - 1][col]) + 1;
}
}
}
//通过倒推的方法求出操作序列
int step = Min_K;
stack<string> Modify_Operations;//考虑到倒推法的操作顺序与输出顺序相反,因此使用栈这种先进后出的数据结构
string operation;//记录每一步操作
//从矩阵的最右下角开始(最右下角的元素就是最优解K),倒推走回起点
for (int row = Model_Length, col = Length_On_Pos; step >= 1 && row >= 1 && col >= 1;)
{
int Left_Up = Matrix[row-1][col-1];//记录当前元素左上角的元素的值
int Left = Matrix[row][col - 1];//记录当前元素左边的元素的值
int Up = Matrix[row - 1][col];//记录当前元素上方的元素的值
if (Left_Up <= Left && Left_Up <= Up)//第一种情况:左上角的元素为三者中最小(或最小之一),则从当前位置走向左上角
{
if (Matrix[row][col] == Matrix[row - 1][col - 1] + 1)//如果当前位置元素和左上角位置元素刚好相差1,说明此处发生了字符修改,记录修改内容
{
operation = "第" + to_string(step) + "步修改为:将文本串中下标为" +to_string (col - 1) + "的字符" + Text_On_Pos[col-1]\
+ "修改为模式串中下标为" +to_string(row - 1) + "的字符" + Model[row - 1];
step--;
Modify_Operations.push(operation);
}
row--;
col--;
}
else if (Left <= Left_Up && Left <= Up)//第二种情况:左边的元素为三者中最小,则从当前位置走向左边
{
if (Matrix[row][col] == Matrix[row][col - 1] + 1)//如果当前位置元素和左边位置元素刚好相差1,说明此处发生了字符删除,记录修改内容
{
operation = "第" + to_string(step) + "步修改为:将文本串中下标为" + to_string(col - 1) + "的字符" + Text_On_Pos[col - 1] + "删除";
step--;
Modify_Operations.push(operation);
}
col--;
}
else//第三种情况:上方的元素为三者中最小,则从当前位置走到上方
{
if (Matrix[row][col] == Matrix[row - 1][col] + 1)//如果当前位置元素和上方位置元素刚好相差1,说明此处发生了字符添加,记录修改内容
{
operation = "第" + to_string(step) + "步修改为:在文本串中下标为" + to_string(col - 1) + "处添加模式串中下标为" \
+ to_string(row - 1) + "的元素" + Model[row - 1];
step--;
Modify_Operations.push(operation);
}
row--;
}
}
if (Modify_Operations.empty())//如果栈本身为空,说明两个字符串完全相同,无需修改
{
cout << "由于两处的字符串完全相同,因此无需修改!" << endl;
}
else//如果两个字符串不同,则从操作栈中逐一取出表示操作序列的字符串并进行输出
{
cout << "使用的最优修改策略如下:" << endl;
while (!Modify_Operations.empty())
{
operation = Modify_Operations.top();
cout << operation << endl;
Modify_Operations.pop();
}
}
clock_t end = clock();//记录程序终止时间
cout << "程序结束" << endl;
cout << "本次的查找修改时间为:" << double(end - start) / CLK_TCK << "ms" << endl;//输出计算K值和输出操作序列的时间
delete[]Matrix;//最后当然要删除矩阵归还内存空间
}
int main(void)
{
unsigned TextString_Num;//用一个无符号整型变量记录需要放入库中的文本串个数
cout << "请输入需要放入库中的文本串的个数:";
cin >> TextString_Num;
string* Text_Strings (new string[TextString_Num]);//用一个堆数组存储库中的所有文本串(之所以不使用更加方便的向量容器vector是因为其效率远低于数组)
int* Texts_Length (new int[TextString_Num]);//用一个堆数组存储库中所有文本串的长度用于回溯(不使用向量的原因同上)
int Max_Length = 0;//用一个整型变量记录所有文本串的最大长度,这样可以仅仅通过一个矩阵来完成后续运算,节约了大量空间
cout << "请分别输入这些字符串:" << endl;
for (unsigned i = 0; i < TextString_Num; ++i)
{
cin >> Text_Strings[i];
Texts_Length[i] = Text_Strings[i].length();//记录每一个字符串的长度
if (Texts_Length[i] > Max_Length)//如果遍历到某处时该字符串的长度长于当前最大字符串长度,则更新当前最大字符串长度
{
Max_Length = Texts_Length[i];
}
}
string Model_String;//定义模式串
cout << "请输入需要进行近似匹配的模式串:";
cin >> Model_String;
cout << endl;
Most_Approximate_String(Text_Strings, Texts_Length, TextString_Num, Model_String,Max_Length);//使用求最优近似串的函数进行求解
//最后需要注意归还所占用的内存空间(怎么能为了提高效率就不做这件事情呢)
delete[]Text_Strings;
delete[] Texts_Length;
return 0;
}
大规模字符串的近似匹配问题(带详细注释的C++实现)
最新推荐文章于 2022-11-25 22:28:42 发布