Давайте вручную вычислим LCS для случая строк BEGIN/FINISH. Мы получим матрицу 6x7 (мы будем учитывать пустые подстроки, поэтому индексация должна начинаться с 0). Вместо того, чтобы рекурсивно заполнять матрицу (все эти рекурсивные вызовы трудно поддерживать в упорядоченном виде), итеративно вычислим все ячейки слева направо и сверху вниз. Вычисление ячеек первой строки и первого столбца не представляет сложности: они все являются нулями. Почему? Да потому, что наиболее длинная общая последовательность пустой и любой другой строки равна нулевой строке. С этого момента можно начать определение LCS для ячейки (1,1) или двух строк B и F. Два последних символа этих односимвольных строк не совпадают. Следовательно, длина LCS равна максимальной из предшествующих ячеек, расположенных к северу и к западу от данной. Обе эти ячейки нулевые, поэтому их максимальное значение и, следовательно, значение этой ячейки равно нулю. Ячейка (1,2) соответствует строкам B и F1. Ее значение также рано нулю. Ячейка (2,1) соответствует строкам BE и F: длина LCS снова равна 0. Продолжая подобные вычисления, можно заполнить все 42 ячейки матрицы. Обратите внимание на ячейки, соответствующие совпадающим символам: именно в них длина LCS возрастает. Конечный результат показан в таблице 12.1.
Таблица 12.1. Матрица LCS для строк BEGIN и FINISH
_ _ F I N I S H
_ 0 0 0 0 0 0 0
B 0 0 0 0 0 0 0
E 0 0 0 0 0 0 0
G 0 0 0 0 0 0 0
I 0 0 1 1 1 1 1
N 0 0 1 2 2 2 2
Записать этот процесс выполнения действий вручную в виде кода не особенно трудно. Чтобы облегчить задачу начинающим программистам, я решил вначале создать класс матричного кеша. Внутри этого класса матрица хранится в объекте TList из TLists, причем ведущий объект TList представляет строки в матрице, а ведомый TLists - ячейки в столбцах отдельной строки. Кроме того, класс матрицы специфичен для решаемой задачи. Было бы излишним разрабатывать, кодировать и использовать общий класс матрицы. Код реализации класса матрицы показан в листинге 12.22.
Листинг 12.22. Класс матрицы для реализации алгоритма определения LCS
type
TtdLCSDir = (ldNorth, ldNorthWest, ldWest);
PtdLCSData = ^TtdLCSData;
TtdLCSData = packed record
ldLen : integer;
ldPrev : TtdLCSDir;
end;
type
TtdLCSMatrix = class private
FCols : integer;
FMatrix : TList;
FRows : integer;
protected
function mxGetItem(aRow, aCol : integer): PtdLCSData;
procedure mxSetItem(aRow, aCol : integer;
aValue : PtdLCSData);
public
constructor Create(aRowCount, aColCount : integer);
destructor Destroy; override;
procedure Clear;
property Items [aRow, aCol : integer] : PtdLCSData
read mxGetItem write mxSetItem;
default;
property RowCount : integer read FRows;
property ColCount : integer read FCols;
end;
constructor TtdLCSMatrix.Create(aRowCount, aColCount : integer);
var
Row : integer;
ColList : TList;
begin
{создать производный объект}
inherited Create;
{выполнить простую проверку}
Assert ((aRowCount > 0) and (aColCount > 0),
' TtdLCSMatrix.Create: Invalid Row or column count');
FRows := aRowCount;
FCols := aColCount;
{создать матрицу: она будет матрицей TList матриц TLists, упорядоченных по строкам}
FMatrix := TList.Create;
FMatrix.Count := aRowCount;
for Row := 0 to pred(aRowCount) do
begin
ColList := TList.Create;
ColList.Count := aColCount;
TList(FMatrix.List^[Row]) := ColList;
end;
end;
destructor TtdLCSMatrix.Destroy;
var
Row : integer;
begin
{уничтожить матрицу}
if (matrix <> nil) then begin
Clear;
for Row := 0 to pred(FRows) do
TList(FMatrix.List^[Row]).Free;
FMatrix.Free;
end;
{уничтожить производный объект}
inherited Destroy;
end;
procedure TtdLCSMatrix.Clear;
var
Row, Col : integer;
ColList : TList;
begin
for Row := 0 to pred(FRows) do
begin
ColList := TList(FMatrix.List^[Row]);
if (ColList <> nil) then
for Col := 0 to pred(FCols) do
begin
if (ColList.List^[Col] <> nil) then
Dispose(PtdLCSData(ColList.List^[Col]));
ColList.List^[Col] :=nil;
end;
end;
end;
function TtdLCSMatrix.mxGetItem(aRow, aCol : integer): PtdLCSData;
begin
if not ((0 <= aRow) and (aRow < RowCount) and (0 <= aCol) and (aCol < ColCount)) then
raise Exception.Create(
'TtdLCSMatrix.mxGetItem: Row or column index out of bounds');
Result := PtdLCSData(TList(FMatrix.List^[aRow]).List^[aCol]);
end;
procedure TtdLCSMatrix.mxSetItem(aRow, aCol : integer;
aValue : PtdLCSData);
begin
if not ((0 <= aRow) and (aRow < RowCount) and (0 <= aCol) and (aCol < ColCount)) then
raise Exception.Create(
'TtdLCSMatrix.mxSetItem: Row or column index out of bounds');
TList(Matrix.List^[aRow]).List^[aCol] := aValue;
end;
Следующий шаг заключается в создании класса, который реализует алгоритм вычисления LCS для строк. Код интерфейса и выполнения служебных функций класса TtdStringLCS приведен в листинге 12.23.
Листинг 12.23. Класс TtdStringLCS
type
TtdStringLCS = class private
FFromStr : string;
FMatrix : TtdLCSMatrix;
FToStr : string;
protected
procedure slFillMatrix;
function slGetCell(aFromInx, aToInx : integer): integer;
procedure slWriteChange(var F : System.Text;
aFromInx, aToInx : integer);
public
constructor Create(const aFromStr, aToStr : string);
destructor Destroy; override;
procedure WriteChanges(const aFileName : string;
end;
constructor TtdStringLCS.Create(const aFromStr, aToStr : string);
begin
{создать производный объект}
inherited Create;
{сохранить строки}
FFromStr := aFromStr;
FToStr :=aToStr;
{создать матрицу}
Читать дальше