Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsia_12_Spiski_steki_ocheredi.docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
410.98 Кб
Скачать
  1. Списки, стеки, очереди

    1. Динамические структуры данных

Многие задачи требуют более сложных, чем линейная, структур. Даже для линейных структур желательно иметь переменный размер и легкость вставки и удаления любого элемента структуры. Такие структуры изменяются во время выполнения программы, поэтому они называются динамическими.

Существуют некоторые близкие аналогии между методами структурирования алгоритмов и методами структурирования данных. Сравнение этих методов приведет нас к пониманию динамических структур и работы с ними.

Элементарным, неструктурированным оператором является оператор присваивания. Ему соответствует скалярный тип данных. Оба они являются простейшими строительными блоками для составных операторов и типов данных. Простейшие структуры, получаемые с помощью перечисления, или следования, – это составной оператор и структура. Оба состоят из небольшого количества компонент, которые могут различаться. Если все компоненты одинаковы, их не нужно выписывать отдельно: для того, чтобы описать повторения, число которых известно и конечно, пользуются оператором цикла с параметром for и массивом. Выбор из двух или более вариантов выражается операторами if или swith и соответственно записью с вариантами. И, наконец, повторение неизвестное количество раз выражается оператором цикла с предусловием while или с постусловием do. Соответствующая структура данных – последовательность (файл).

Существует ли структура данных, которая подобным же образом соответствует оператору процедуры? Разумеется, наиболее интересная по сравнению с другими операторами особенность процедур – это возможность рекурсии. Значения типа данных, который можно назвать рекурсивным, должны содержать одну или более компонент того же типа, что и все значение, по аналогии с процедурой, содержащей один или более вызовов самой себя. Как и в процедурах, в таких определениях типов рекурсия может быть прямой или косвенной.

Аналогии между методами структурирования алгоритмов и методами структурирования данных

Алгоритмы

Данные

A = B

скалярный тип: int, double, …

{} – блок

structur

for

int [ ]

while, do

file

Рекурсия

?

Характерная особенность рекурсивных структур, которая отличает их от основных структур (массивов, записей), – их способность изменять размер. Поэтому для рекурсивно определенных структур невозможно установить фиксированный размер памяти, и поэтому компилятор не может приписать такой переменной определенного адреса. Для решения этой проблемы чаще всего применяется метод динамического распределения памяти, то есть выделения памяти для отдельных переменных в тот момент, когда они появляются во время выполнения программы, а не во время компиляции. Во время компиляции выделяется фиксированный объем памяти для хранения адреса динамической переменной, а не самой переменной.

Поскольку нам необходимо динамически создавать структуры из произвольного числа элементов, эти элементы нужно связывать друг с другом. Поэтому каждый элемент динамической структуры должен содержать один или несколько адресов тех переменных, с которыми он связан (на которые, как говорят, он указывает или ссылается).

Явное использование ссылок позволяет строить более разнообразные структуры, чем те, которые можно задать лишь с помощью рекурсивных определений. Следовательно, нужно ввести типы данных, значениями которых являются указатели (ссылки) на другие данные. Введем рекурсивный класс узла:

Листинг 8.1. Рекурсивный класс узла

class MyNode

{

public int inf;

public MyNode next;

public MyNode(int inf, MyNode next)

// конструктор

{

this.inf = inf;

this.next = next;

}

}

В этом классе определены два поля: поле inf, которое содержит информацию и может быть любого типа; поле next того же самого типа что и сам узел. То есть поле next показывает на такой же элемент, что и сам узел. Конструктор MyNode() принимает значения полей.

На основе этого класса создадим класс динамического списка MyList, который содержит два поля: поле head всегда показывающее на начало списка и поле count, содержащее информацию о количестве элементов списка.

Листинг 8.2. Класс динамического списка

class MyList

{

public MyNode head; // голова списка

public int count; // число элементов

public MyList() // Конструктор

public void Add(int inf) // Add

public void Printer() // Вывод списка

public MyNode FindNode(int val)// Поиск узла

public void Delete(int index)

//Удалить по индексу

public void Insert(int index, int val)

// Вставить по индексу

public void AddSort(int inf)

}

Список элементов показан на рис. 8.1.

Рис. 8.1. Линейный список

Кроме этого класс MyList содержит некоторое количество методов.

Конструктор MyList() обнуляет число элементов count и направляет голову head на null:

Листинг 8.3. Конструктор

public MyList() // Конструктор

{

head = null;

count = 0;

}

Метод Add() создает новый элемент p, у которого поле next показывает на head, перемещает head на p и увеличивает count.

Листинг 8.4. Метод Add()

public void Add(int inf) // Add

{

MyNode p = new MyNode(inf, head);

head = p;

count++;

}

Метод Printer() ставит p на начало списка head, выводит на экран p.inf и перемещает p на следующий элемент.

Листинг 8.5. Метод Printer()

public void Printer() // Вывод списка

{

MyNode p = head;

do

{

Console.WriteLine("{0}", p.inf);

p = p.next;

}

while (p != null);

}

Метод FindNode()

Листинг 8.6. Метод FindNode()

public MyNode FindNode(int val) // Поиск узла

{

MyNode p = head;

bool ok = false;

while ((p != null) && !ok)

{

ok = p.inf == val;

if (!ok)

p = p.next;

}

return p;

}

Метод Delete(). Удаление элемента из списка должно состоять из двух действий. Первое – исключение элемента из списка, то есть изменение ссылок.

Листинг 8.7. Метод Delete()

public void Delete(int index) // Удалить по индексу

{

if (index != 0)

{

MyNode p = head;

for (int i = 0; i < index - 1; i++)

p = p.next;

if (p.next != null)

p.next = p.next.next;

}

else

head = head.next;

count--;

}

Это показано на рис. 8.2.

Рис. 8.2. Удаление элемента из списка

Метод Insert()

Листинг 8.8. Метод Insert()

public void Insert(int index,int val)

// Вставить по индексу

{

if (index != 0)

{

MyNode p = head;

for (int i = 0; i < index; i++)

p = p.next;

MyNode q = new MyNode(val, p.next);

p.next = q;

}

else

{

MyNode q = new MyNode(val, head);

head = q;

}

count++;

}

Результат показан на рис. 8.3.

Рис. 8.3. Включение в список после заданного элемента.

Если требуется включение перед элементом, а не после него, то кажется, что однонаправленность списка препятствует этому, поскольку нет доступа к предыдущему элементу. Однако простой прием позволяет решить эту проблему. Прием заключается в следующем: новый элемент в действительности вставляется после p, но затем происходит обмен значениями между новым элементом и p. С учетом того, что значение нового элемента – val, это выполняется операторами

MyNode q = new MyNode(p.inf, p.next);

p.inf = val;

p.next = q;

и показано на рис. 8.4.

Рис. 8.4. Включение в список перед заданным элементом.

Код метода Main() программы с демонстрацией методов класса MyList приведен ниже:

Листинг 8.9. Код метода Main()

static void Main(string[] args)

{

Console.WriteLine("Init");

MyList list = new MyList();

for (int i = 1; i <= 10; i++)

list.Add(i);

list.Printer();

Console.ReadKey();

Console.WriteLine();

Console.WriteLine("FindNode(5)={0}",

list.FindNode(5).inf);

Console.ReadKey();

Console.WriteLine();

list.Delete(9);

Console.WriteLine("Delete(9)");

list.Printer();

Console.ReadKey();

Console.WriteLine();

Console.WriteLine("Insert(5,55)");

list.Insert(5, 55);

list.Printer();

Console.ReadKey();

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]