Обсудим вопрос о том, как в динамической памяти можно создать структуру
данных переменного размера.
Разберем следующий пример. В процессе физического эксперимента многократно
снимаются показания прибора (допустим, термометра) и записываются в компьютерную
память для дальнейшей обработки. Заранее неизвестно, сколько будет произведено
измерений.
Если для обработки таких данных не использовать внешнюю память (файлы), то
разумно расположить их в динамической памяти. Во-первых, динамическая память
позволяет хранить больший объем информации, чем статическая. А во-вторых, в
динамической памяти эти числа можно организовать в связанный список, который не
требует предварительного указания количества чисел, подобно массиву. Что же
такое "связанный список"? Схематически он выглядит так:
Здесь Inf — информационная часть звена списка (величина
любого простого или структурированного типа, кроме файлового), Next —
указатель на следующее звено списка; First — указатель на заглавное звено
списка.
Согласно определению, список располагается в динамически распределяемой
памяти, в статической памяти хранится лишь указатель на заглавное звено.
Структура, в отличие от массива, является действительно динамической: звенья
создаются и удаляются по мере необходимости, в процессе выполнения
программы.
Для объявления списка сделано исключение: указатель на звено списка
объявляется раньше, чем само звено. В общем виде объявление выглядит так.
Type U = ^Zveno;
Zveno = Record Inf : BT; Next: U End;
Здесь BT — некоторый базовый тип элементов списка.
Если указатель ссылается только на следующее звено списка (как показано на
рисунке и в объявленной выше структуре), то такой список называют
однонаправленным, если на следующее и предыдущее звенья —
двунаправленным списком. Если указатель в последнем звене
установлен не в Nil, а ссылается на заглавное звено списка, то такой список
называется кольцевым. Кольцевыми могут быть и однонаправленные,
и двунаправленные списки.
Более подробно рассмотрим работу со связанными списками на примере
однонаправленного некольцевого списка.
Выделим типовые операции над списками:
добавление звена в начало списка;
удаление звена из начала списка;
добавление звена в произвольное место списка, отличное от начала
(например, после звена, указатель на которое задан);
удаление звена из произвольного места списка, отличного от начала
(например, после звена, указатель на которое задан);
проверка, пуст ли список;
очистка списка;
печать списка.
Реализуем выделенный набор операций в виде модуля. Подключив этот модуль,
можно решить большинство типовых задач на обработку списка. Пусть список
объявлен так, как было описано выше. Первые четыре действия сначала реализуем
отдельно, снабдив их иллюстрациями.
1. Добавление звена в начало списка
{Процедура добавления звена в начало списка; в x содержится добавляемая информация}
Procedure V_Nachalo(Var First : U; X : BT);
Var Vsp : U;
Begin
New(Vsp);
Vsp^.Inf := X;
Vsp^.Next := First; {То звено, что было заглавным, становится вторым по счёту}
First := Vsp; {Новое звено становится заглавным}
End;
2. Удаление звена из начала списка
{Процедура удаления звена из начала списка;
в x содержится информация из удалённого звена}
Procedure Iz_Nachala(Var First : U; Var X : BT);
Var Vsp : U;
Begin
Vsp := First; {Забираем ссылку на текущее заглавное звено}
First := First^.Next; {То звено, что было вторым по счёту, становится заглавным}
X := Vsp^.Inf; {Забираем информацию из удаляемого звена}
Dispose(Vsp); {Уничтожаем звено}
End;
3. Добавление звена в произвольное место списка, отличное от начала (после
звена, указатель на которое задан)
{Процедура добавления звена в список после звена,
на которое ссылается указатель Pred;
в x содержится информация для добавления}
Procedure V_Spisok(Pred : U; X : BT);
Var Vsp : U;
Begin
New(Vsp); {Создаем пустое звено}
Vsp^.Inf := X; {Заносим информацию}
Vsp^.Next := Pred^.Next; {Теперь это звено ссылается на то,
что было следом за звеном Pred}
Pred^.Next := Vsp; {Теперь новое звено встало вслед за звеном Pred}
End;
4. Удаление звена из произвольного места списка, отличного от начала
(после звена, указатель на которое задан)
{Процедура удаления звена из списка после звена,
на которое ссылается указатель Pred;
в x содержится информация из удалённого звена}
Procedure Iz_Spiska(Pred : U; Var X : BT);
Var Vsp : U;
Begin
Vsp := Pred^.Next; {Забираем ссылку на удаляемое звено}
{Удаляем звено из списка, перенаправив ссылку на следующее
за ним звено}
Pred^.Next := Pred^.Next^.Next;
X := Vsp^.Inf; {Забираем информацию из удаляемого звена}
Dispose(Vsp); {Уничтожаем звено}
End;
{Язык Pascal}
Unit Spisok;
Interface
Type BT = LongInt;
U = ^Zveno;
Zveno = Record Inf : BT; Next: U End;
Procedure V_Nachalo(Var First : U; X : BT);
Procedure Iz_Nachala(Var First : U; Var X : BT);
Procedure V_Spisok(Pred : U; X : BT);
Procedure Iz_Spiska(Pred : U; Var X : BT);
Procedure Ochistka(Var First: U);
Function Pust(First : U) : Boolean;
Procedure Print(First : U);
Implementation
Procedure V_Nachalo;
Var Vsp : U;
Begin
New(Vsp);
Vsp^.Inf := X;
Vsp^.Next := First;
First := Vsp;
End;
Procedure Iz_Nachala;
Var Vsp : U;
Begin
Vsp := First;
First := First^.Next;
X := Vsp^.Inf;
Dispose(Vsp);
End;
Procedure V_Spisok;
Var Vsp : U;
Begin
New(Vsp);
Vsp^.Inf := X;
Vsp^.Next := Pred^.Next;
Pred^.Next := Vsp;
End;
Procedure Iz_Spiska;
Var Vsp : U;
Begin
Vsp := Pred^.Next;
Pred^.Next := Pred^.Next^.Next;
X := Vsp^.Inf;
Dispose(Vsp);
End;
Procedure Ochistka;
Var Vsp : BT;
Begin
While Not Pust(First) Do Iz_Nachala(First, Vsp)
End;
Function Pust;
Begin
Pust := First = Nil
End;
Procedure Print;
Var Vsp : U;
Begin
Vsp := First;
While Vsp <> Nil Do
Begin
Write(Vsp^.Inf : 6);
Vsp := Vsp^.Next
End; WriteLn
End;
Begin
End.
Пример. Составить программу, которая на основе заданного
списка формирует два других, помещая в первый из них положительные, а во второй
— отрицательные элементы исходного списка.
При реализации алгоритма будем использовать подпрограммы разработанного
модуля. Это существенно облегчает решение задачи.
{Программа на Turbo Pascal}
Program Ex_sp_1;
Uses Spisok;
Var S1, S2, S3, V1, V2, V3 : U; A : BT; I, N : Byte;
Begin
Randomize;
N := 1 + Random(20);
S1 := Nil; A := -100 + Random(201);
V_Nachalo(S1, A); V1 := S1;
For I := 2 To N Do
Begin A := -100 + Random(201); V_Spisok(V1, A); V1 := V1^.Next End;
WriteLn('Исходный список: '); Print(S1);
V1 := s1; S2 := Nil; S3 := Nil;
While V1 <> Nil Do
Begin
If V1^.Inf > 0
Then If S2 = Nil
Then Begin V_Nachalo(S2, V1^.Inf); V2 := S2 End
Else Begin V_Spisok(V2, V1^.Inf); V2 := V2^.Next End;
If V1^.Inf < 0
Then If S3 = Nil
Then Begin V_Nachalo(s3, V1^.Inf); V3 := S3 End
Else Begin V_Spisok(V3, V1^.Inf); V3 := V3^.Next End;
V1:= V1^.Next
End;
WriteLn('Результирующий список из положительных элементов: '); Print(S2);
WriteLn('Результирующий список из отрицательных элементов: '); Print(S3);
Ochistka(S1); Ochistka(S2); Ochistka(S3);
End.