Страница 34 из 60
Использование связанного списка для организации разреженного массива
При реализации разреженного массива с помощью связанного списка формируется запись, которая содержит информационные поля для каждого элемента массива и поле позиции элемента в массиве, а также указатели на предыдущий и следующий элементы списка. Все записи в списке упорядочены по индексу массива. Доступ к элемен- там массива осуществляется путем прохода по связям элементов списка. Например, может использоваться следующая запись для создания разреженного массива:
str128 = string[128]; str9 = string[9];
CellPointer = ^cell;
cell = record CellName: str9; {содержит название ячейки} formula: str128; {содержит формулу} next: CellPointer; {указатель на следующую запись} prior: CellPointer; {указатель на предыдущую запись} end;
В этом примере поле "CellName" содержит строку с названием ячейки, например, А1, В34 или Z19. Строка "formula" содержит фор- мулу для каждой ячейки электронной таблицы. Ниже приводятся нес- колько примерных программ, использующих разреженные матрицы на базе связанного списка. (Следует помнить, что имеется много спо- собов реализации электронных таблиц. К приводимым ниже структурам данных и программам следует относиться только как к примерам ис- пользования таких методов). Для указателей начала и конца связан- ного списка используются следующие глобальные переменные:
start, last: CellPointer;
Когда вы вводите формулу в ячейку типичной электронной таб- лицы, вы фактически создаете новый элемент разреженной матрицы. Если электронная таблица строится на базе связанного списка, то вставка нового элемента будет производится с помощью функции "DLS _Store", которая рассматривается в главе 2. (Поскольку Паскаль позволяет создавать независимые функции указанная функция может использоваться фактически без всяких изменений). В приводимом примере список сортируется по названию ячейки (т.е. А12 предшест- вует А13 и т.д.)
{ упорядоченная вставка элементов в список и установка указателя на начало списка } function DLS_Store(info, start: CellPointer; var last: CellPointer): CellPointer; var old, top: CellPointer; done: boolean; begin top := start; old := nil; done := FALSE;
if start = nil then begin { первый элемент списка } info^.next := nil; last := info; info^.prior :=nil; DLS_Store := info; end else begin while (start<>nil) and (not done) do begin if start^.CellName < info^.CellName then begin old := start; start := start^.next; end else begin { вставка в середину } if old <>nil then begin old^.next := info; info^.next := start; start^.prior := info; info^.prior := old; DLS_Store := top; { сохранение начала } done := TRUE; end else begin info^.next := start;{новый первый элемент } info^.prior := nil; DLS_Store := info; done := TRUE; end; end; end; { конец цикла } if not done then begin last^.next := info; info^.next := nil; info^.prior := last; last := info; DLS_Store := top; { сохранение начала } end; end; end; { конец функции DLS_Store }
Для удаления элемента из электронной таблицы необходимо уда- лить соответствующую запись из списка и возвратить занимаемую им память системе с помощью функции Dispose. Функция DL_Delete уда- ляет ячейку из списка по заданному названию:
{ удаление ячейки из списка } function DL_Delete(start: CellPointer; key str9): CellPointer; var temp, temp2: CellPointer; done: boolean; begin if start^.CellName=key then begin { первый элемент в списке } DL_Delete := start^.next; if temp^.next <> nil then begin temp := start^.next; temp^.prior := nil; end; Dispose(start); end else begin done := FALSE; temp := start^.next; temp2 := start; while (temp<>nil) and (not done) do begin if temp^.CellName=key then begin temp2^.next := temp^.next; if temp^.next<>nil then temp^.next^.prior := temp2;
done := TRUE; last := temp^.prior; Dispose(temp); end else begin temp2 := temp; temp := temp^.next; end; end; DL_Delete := start; { начало не изменяется } if not done then WriteLn('not found'); end; end; { конец процедуры удаления элемента из списка }
Функция Find позволяет обратиться к любой конкретной ячейке. Эта функция имеет важное значение, так как многие формулы элект- ронной таблицы имеют ссылки на другие ячейки и нужно эти ячейки найти, чтобы обновить их значения. В этой функции используется линейный поиск и, как показано в главе 2, среднее число операций при линейном поиске равно n/2, где n является числом элементов в списке. Кроме того, значительные потери будут из-за того, что каждая ячейка может содержать ссылки на другие ячейки и тогда потребуется выполнить поиск этих ячеек. Ниже дается пример функ- ции Find (см.след.стр.).
Создание, поддержка и обработка разряженных массивов имеет один большой недостаток, когда такой массив строится на базе свя- занного списка. Этот недостаток заключается в необходимости ис- пользовать линейный поиск каждой ячейки списка. Без
{ найти конкретную ячейку и установить указатель на нее } function Find(cell: CellPointer): CellPointer; var c: CellPointer; begin c := start; while c<>nil do begin if c^.CellName=cell^.CellName then find:=c else c := c^.next; end; WriteLn('cell not found'); Find:=nil; end; { конец процедуры поиска }
дополнительной информации, которая требует дополнительного расхо- да памяти, нельзя обеспечить двоичный поиск ячейки. Даже процеду- ра вставки использует линейный поиск для нахождения соответствую- щего места для вставки новой ячейки в отсортированный список. Эти проблемы можно решить, используя для построения разреженного мас- сива двоичное дерево.
|