ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 11.12.2025

Просмотров: 3182

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Вот как выглядит вывод программы, если указать неверное имя каталога х:

Каталог "х" неверен

Could not find a part of the path "C:\C#Programs\LoopThroughFiles\bin\Debug\x".

Больше файлов нет

Нажмите <Enter> для завершения программы...

Не впечатляет?...

Написание собственного класса коллекции: связанный список

Я из тех учителей, которые по старинке считают, что сначала следует освоить табли­ цу умножения, а уж потом давать ученику калькулятор. Так что сейчас вы пройдете сквозь дебри создания собственной коллекции, перед тем как познакомиться со встроен­ ными коллекциями, о которых упоминалось в главе 15, "Обобщенное программирова­ ние". Здесь будут рассмотрены все "болты и гайки", из которых состоит класс коллек­ ции, и как все они объединяются в одно целое.

Одним из наиболее распространенных видов контейнеров после массива является связанный список, каждый объект которого указывает на предыдущий и последующий элементы списка, т.е. объекты, составляющие список, оказываются соединены в цепочку. Вы используете ссылки на объекты для объединения отдельных узлов в цепь. В каждом та­ ком узле содержатся дополнительные данные, указывающие на следующий узел в цепи. Отдельная переменная, обычно называющаяся ссылкой на голову списка, указывает на первый объект в списке, в то время как хвост списка указывает на его последний элемент.

Односвязные списки содержат узлы, связанные только с узлами, следующими за ними. По такому списку можно пройти только в одном направлении, следуя связям между узлами. Дважды связанный список содержит узлы, которые ука­ зывают как на последующий, так и на предыдущий узлы. По таким спискам можно проходить в обоих направлениях.

Связанный список по сравнению с массивом обладает рядом преимуществ и недос­ татков.

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

Точно так же можно легко удалить элемент из связанного списка.

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

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

Глава 20. Работа с коллекциями

451


Связанные списки идеально подходят для хранения последовательностей данных, особенно если программа не знает заранее их точное количество (тем не менее следует серьезно подумать о возможном применении обобщенного класса List<T>, который был описан в главе 15, "Обобщенное программирование". Если вам нужен именно свя­ занный список, можно воспользоваться встроенным связанным списком из С# 2.0, а не тем, который разрабатывается в данном разделе. Обратитесь к справочной системе за информацией о пространстве имен System. Collections .Generic).

Другие пространства имен коллекций, которыми вы можете захотеть воспользовать­ с я — System. Collections и System. Collections . Specialized. Поищите информацию о них в справочной системе, но в первую очередь следует искать подходя­ щую коллекцию именно в пространстве имен System. Collections . Generic.

Пример связанного списка

Приведенная далее демонстрационная программа иллюстрирует создание

ииспользование связанного списка.

//LinkedListContainer - демонстрация "самодельного"

//связанного списка. Этот контейнер реализует интерфейс

//IEnumerable для поддержки таких операторов, как foreach.

//Этот пример включает также итератор, который реализует

//интерфейс IEnumerator

using System;

using System.Collections;

namespace LinkedListContainer

{

//LLNode - каждый LLNode образует узел списка. Каждый

//узел LLNode содержит ссылку на целевые данные,

//встроенные в список

public class LLNode

{

//Это данные, которые хранятся в узле списка internal object linkedData = null;

//Указатели на следующий и предыдущий узлы в списке internal LLNode forward = null; // Следующий узел internal LLNode backward = null; // Предыдущий узел

internal LLNode(object linkedData)

{

this.linkedData = linkedData;

}

// Получение данных, хранящихся в узле public object Data

{

 

get

 

{

 

return

linkedData;

}

 

}

 

452

Часть VII. Дополнительные главы



}

II LinkedList - реализация дважды связанного списка public class LinkedList : IEnumerable

{

//Концы связанного списка. Спецификатор internal

//позволяет итераторам обращаться к ним непосредственно internal LLNode head = null; // Начало списка

internal LLNode tail = null; // Конец списка

public IEnumerator GetEnumerator()

return new LinkedListlterator(this);

// AddObject - добавление объекта в конец списка public LLNode AddObject(object objectToAdd)

{

return AddObject(tail, objectToAdd);

}

// AddObjectдобавление объекта в список public LLNode AddObject(LLNode previousNode,

object objectToAdd)

{

//Создание нового узла с добавляемым объектом LLNode newNode = new LLNode(objectToAdd);

//Начнем с простейшего случая — пустого списка.

if (head == null && tail == null)

{

// ...теперь в нем один элемент

head = newNode; tail = newNode;

return newNode;

}

// Добавляем ли мы новый узел в средину списка? if (previousNode != null &&

previousNode.forward != null)

{

// Просто изменяем указатели

LLNode nextNode = previousNode.forward;

//Указатель на следующий узел newNode.forward = nextNode; previousNode.forward = newNode;

//Указатель на предыдущий узел nextNode.backward = newNode; newNode.backward = previousNode;

return newNode;

}

// Добавление в начало списка? if (previousNode == null)

Глава 20. Работа с коллекциями

453


{

// Делаем его головой списка LLNode nextNode = head; newNode.forward = nextNode; nextNode.backward = newNode; head = newNode;

return newNode;

}

// Добавление в конец списка newNode.backward = previousNode; previousNode.forward = newNode; tail = newNode;

return newNode;

}

// RemoveObject - удаление объекта из списка public void RemoveObject(LLNode currentNode)

{

// Получаем соседей

удаляемого узла

LLNode

previousNode

=

currentNode.backward;

LLNode

nextNode

=

currentNode.forward;

//Обнуляем указатели удаляемого объекта currentNode.forward = currentNode.backward = null;

//Был ли это последний элемент списка?

if (head == currentNode && tail == currentNode)

head = tail = null; return;

// Это узел в средине списка?

if (head != currentNode && tail != currentNode)

previousNode.forward = nextNode; nextNode.backward = previousNode; return;

// Это узел в начале списка?

if (head •== currentNode && tail != currentNode)

head = nextNode; nextNode.backward = null; return;

// Это узел в конце списка...

tail = previousNode; previousNode.forward = null;

// LinkedListlterator - дает приложению доступ к спискам

454

Часть VII. Дополнительные главы