文件名匹配-动态规划

时间:2022-02-12 19:15:11

1.问题描述
假定正则表达式中只有如下存在:英文字母(区分大小写)、 数字、通配符“?”代表一个未知的字符,通配符“*”代表0或N个未知的字符。在某目录下的文件中寻找一个正则表达式,要求能匹配最多的文件且不能匹配任何一个不要操作的文件,在此前提下寻求长度最短的正则表达式,如果有不止一个结果则输出任意一个。本程序在视线中有如下扩展要求:(1)建立了图形界面,可以方便用户直观方便的获取结果和进行操作。
(2)对文件名匹配中的操作过程进行了优化,降低了时间复杂度。
2、源程序

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public static int maxnum; //最优正则表达式所能匹配的到操作文件数
public static int minlength; //最优正则表达式的长度
public static int newnum; //当前最优正则表达式所能匹配的操作文件数
public static char[] minexpression = new char[10] { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' };
public static char[] str = new char[10];
public static int[] pn = new int[10];

public struct calculate
{
public char ch;
public int number;
};

public static calculate[,] cha = new calculate[50, 50];

public static int[] n = new int[2];

public static string[] filename = new string[10]; //filename[10]用来存储输入的文件名称
public static int[, ,] match = new int[10, 10, 10]; //match[len][i][j]表示str[1..lenth]与filename[i][1..j]的匹配情况。

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)//[浏览]按钮
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
{
String file = ofd.FileName;
this.textBox2.Text = file;
}
}

private void button2_Click(object sender, EventArgs e)//【读取】
{
string path = this.textBox2.Text;
this.textBox1.Text = File.ReadAllText(path, Encoding.Default);//读取指定路径的文件

}
public int lineIndex(String path)
{
int Length = 0;
StreamReader FileStreamTemp = new StreamReader(path);
while ((FileStreamTemp.ReadLine()) != null)
{
Length++;
}
FileStreamTemp.Close();
return Length;
}
//逐行读取方法
private void ReadTXTLineByLine(String path)
{
System.IO.StreamReader file = new System.IO.StreamReader(path);//创建文件流,path为文本文件路径
int counter = 0;
String line = "";
String output = "";
String[] k = new String[10];
n[0] = 0; n[1] = 0; pn[0] = 0;
int lineLength = lineIndex(path);
for (int _i = 0; _i < lineLength; _i++)
{
if ((line = file.ReadLine()) != null)
{
output = line;
/****开始正式进入算法部分的设计****/
String opFileName = output.Split(' ')[0];
if (output.Split(' ')[1].Equals("+"))
{
filename[++n[0]] = opFileName;
Mysort(opFileName[0], 0);
}
else k[++n[1]] = opFileName;//这里存在问题,如果最后输入的不是-是其他字符会怎么样
}
}
for (int i = 1; i <= n[1]; i++)
filename[n[0] + i] = k[i];
Array.Clear(match, 0, 999);
for (int i = 1; i <= n[0] + n[1]; i++)
match[0, i, 0] = 1;
maxnum = 0;
minlength = 255;
}
public static void Mysort(char c, int len) //Mysort对操作文件名中出现的字符按出现频率排序储存,以加快搜索进程
{
for (int i = 1; i <= pn[len]; i++) //pn[i]表示操作文件名中出现的不同的字符的个数
if (cha[len, i].ch == c) //cha[len][i]表示当前考察的正则表达式长度为len时,操作文件名字出现频率第i高的字符。
{
cha[len, i].number++;
cha[len, 0] = cha[len, i];
int j = i;
while (cha[len, j - 1].number < cha[len, 0].number)
{
cha[len, j] = cha[len, j - 1];
j--;
}
cha[len, j] = cha[len, 0];
return;
}
cha[len, ++pn[len]].ch = c;
cha[len, pn[len]].number = 1;
}
public static int checkMatch(int len) //计算当前匹配情况
{
int i, j, t, k = 0;
newnum = 0;
//下面部分是动态规划方法动态规划方程的应用,通过动态规划方法递归地维护match数组。
//match数组定义的时候是一个String类型的二维数组,由于每个元素是string,所以度每个元素又可以通过下标访问每个字符
for (i = 1; i <= n[0]; i++)
{
for (int q = 0; q < match.GetLength(2); q++)
match[len, i, q] = 0;
if (len == 1 && str[1] == '*')
match[len, i, 0] = 1;
for (j = 1; j <= filename[i].Length; j++)
{
switch (str[len])
{
case '*':
for (t = 0; t <= j; t++)
if (match[len - 1, i, t] == 1)
{
match[len, i, j] = 1;
break;
}
break;
case '?':
match[len, i, j] = match[len - 1, i, j - 1];
break;
default:
if (str[len].Equals(filename[i][j - 1]))
match[len, i, j] = match[len - 1, i, j - 1];
break;
}
}
//通过动态规划计算match[len][i][j]结束
for (j = filename[i].Length; j >= 1; j--)
{
if (match[len, i, j] == 1)
{
k++;
if (j == filename[i].Length)
newnum++;
break;
}
}
}
if (k < maxnum || k == maxnum && len >= minlength)
return 0;
pn[len] = 0;
for (i = 1; i <= n[0]; i++)
for (j = 1; j <= filename[i].Length - 1; j++)
if (match[len, i, j] == 1)
Mysort(filename[i][j], len);
return 1;
}
public static int checkNOMatch(int len) //用于判定是否匹配非操作文件
{
int i, j, t, k;

for (k = 1; k <= len; k++)
for (i = n[0] + 1; i <= n[0] + n[1]; i++)
{
for (int p = 0; p < match.GetLength(2); p++)
{
match[k, i, p] = 0;
}
if (str[1] == '*' && k == 1)
match[k, i, 0] = 1;
for (j = 1; j <= filename[i].Length; j++)
switch (str[k])
{
case '*':
for (t = 0; t <= j; t++)
if (match[k - 1, i, t] == 1)
{
match[k, i, j] = 1;
break;
}
break;
case '?':
match[k, i, j] = match[k - 1, i, j - 1];
break;
default:
if (str[k] == filename[i][j - 1])
match[k, i, j] = match[k - 1, i, j - 1];
break;
}
}
for (i = n[0] + 1; i <= n[0] + n[1]; i++) //每次判断是否和非操作文件不匹配时都要和全部非匹配文件比较判断
if (match[len, i, filename[i].Length] == 1) //能匹配就return 0
return 0;
return 1; //不能匹配就return 1
}
public static void search(int len)
{
if ((newnum > maxnum || newnum == maxnum && len < minlength) && checkNOMatch(len) == 1)
{
maxnum = newnum;
minlength = len;
for (int i = 1; i <= minlength; i++)
minexpression[i] = str[i];
}
len++;
if (len == 1 || str[len - 1] != '*')
{
str[len] = '?';
if (checkMatch(len) == 1)
search(len);
str[len] = '*';
if (checkMatch(len) == 1)
search(len);
}
for (int i = 1; i <= pn[len - 1]; i++)
{
str[len] = cha[len - 1, i].ch;
if (checkMatch(len) == 1)
search(len);
}
}
private void button3_Click_1(object sender, EventArgs e)//【保存】
{
String path = this.textBox2.Text;
ReadTXTLineByLine(path);
search(0);
char[] a = new char[minlength];
for (int i = 1; i <= minlength; i++)
a[i - 1] = minexpression[i];
String AA = new String(a);
textBox3.Text = maxnum.ToString() + "\r\n" + AA + "\r\n";
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
}
}
}

3、算法概要设计

由于在目录下的文件名限定,很难确定一种能直接寻找最优正则表达式的解析方法,在这种情况下,可以采用回溯法。总体思路是:从空串出发,从可选字符集中挑选字符式(本题中可选的字符分为三种:分别是?、*和字符)填入当前正则式,所填字符要使得构造的正则式不能匹配任何非匹配的操作文件。在构造过程中计算当前正则式匹配的待操作文件数,并更新当前最短且最优匹配的正则表达式。在搜索了正则表达式所有可能形式后,便得出匹配的待操作文件数最多且长度最短的一个正则表达式。
算法整体程序流程图如图1所示:
图1 算法整体流程图

(1)首先通过递归构造正则表达式:从空串出发,从可选字符集中挑选字符式(本题中可选的字符分为三种:分别是?、*和字符)填入当前正则式。对每种正表达式需要判定其匹配的情况。
(2)判定正则式的匹配通过DP思想实现:
其DP方程如图2所示:
文件名匹配-动态规划

其中match[length,I,j]:= str[1..lenth]与filename[i][1..j]的匹配情况.即当当前构造的正则式长度为length时与第i行待匹配的操作文件前j个字符串的匹配情况。
match[length,I,j]=1表示匹配,match[length,I,j]=0表示不匹配。match[length,I,j]=1即匹配情况根据当前正则式的第length个(即当前最后一个)正则式字符的取值分为三种情况:
①当str[length]=’?’时,只要正则式去掉最后一个符号前面的部分与待匹配文件名去掉最后一个字符的部分匹配,则正则式加上最后一个字符和待匹配文件名加上最后一个字符一定匹配。
②当str[length]=filename[i][j]时,即当正则式最后一个字符与待匹配文件名最后一个字符相同时,情形与①中完全相同。正则式加上最后一个字符和待匹配文件名加上最后一个字符一定匹配。
③当 str[length]=’‘时,只要正则表达式去掉最后一个’‘的情况下与该行字符串前面存在匹配的,正则式加上最后一个‘*‘和待匹配文件名加上若干字符一定匹配。即可以向前推进好几位。
(3)创新部分:为了加快搜索进程,对操作文件名中出现的字符按出现频率排序储存。对于任一字符,它在待操作文件中出现的频率越高,则它对增加匹配文件数的作用愈大,覆盖的功能越显著。因此,借助贪心算法的思想。待操作文件名序列中出现的所有字符按其频率递减的次序排序。当选择字符加入最优正则式序列时,总能保证先选择所有字符中出现频率最好的字符加入正则式,从而保证了选择加入正则式的字符对增加匹配文件数的作用最大,最容易找到最优的正则式。