Предположительно у нас есть общий класс узлов и классов LinkedList на основе вышеизложенного, позволяющий пользователю добавлять, удалять и т. Д. Теперь, как бы мы гарантировали неизменность?
Вы гарантируете неизменность, делая каждое поле объекта readonly и гарантируя, что каждый объект, на который ссылается одно из этих полей readonly, также является неизменным объектом. Если поля все только для чтения и относятся только к другим неизменяемым данным, то, очевидно, объект будет неизменным!
Должны ли мы рекурсивно копировать все ссылки на список при вставке элемента?
Вы могли бы. Различие, которое вы получаете здесь, - это разница между неизменяемыми и постоянными. Неизменяемую структуру данных нельзя изменить. A Постоянная структура данных использует тот факт, что структура данных является неизменной для повторного использования ее частей.
Упорная неизменны связанный список особенно легко:
abstract class ImmutableList
{
public static readonly ImmutableList Empty = new EmptyList();
private ImmutableList() {}
public abstract int Head { get; }
public abstract ImmutableList Tail { get; }
public abstract bool IsEmpty { get; }
public abstract ImmutableList Add(int head);
private sealed class EmptyList : ImmutableList
{
public override int Head { get { throw new Exception(); } }
public override ImmutableList Tail { get { throw new Exception(); } }
public override bool IsEmpty { get { return true; } }
public override ImmutableList Add(int head)
{
return new List(head, this);
}
}
private sealed class List : ImmutableList
{
private readonly int head;
private readonly ImmutableList tail;
public override int Head { get { return head; } }
public override ImmutableList Tail { get { return tail; } }
public override bool IsEmpty { get { return false; } }
public override ImmutableList Add(int head)
{
return new List(head, this);
}
}
}
...
ImmutableList list1 = ImmutableList.Empty;
ImmutableList list2 = list1.Add(100);
ImmutableList list3 = list2.Add(400);
И там вы идете. Конечно, вы хотели бы добавить лучшую обработку исключений и другие методы, например методы IEnumerable<int>
. Но есть постоянный непреложный список. Каждый раз, когда вы создаете новый список, вы повторно используете содержимое существующего неизменяемого списка; list3 повторно использует содержимое списка2, что можно сделать безопасно, потому что list2 никогда не изменится.
Будет ли какой-либо из ваших ответов работать над дважды связанными списками?
Разумеется, вы можете легко составить список с двойной связью, который будет делать полную копию всей структуры данных каждый раз, но это было бы глупо; вы можете просто использовать массив и скопировать весь массив.
Создание постоянных двухсвязного списка довольно сложно, но есть способы сделать это. То, что я сделаю, - это подход к проблеме с другого направления. Вместо того, чтобы говорить «могу ли я сделать упорный двусвязный список?» спросите себя: «Каковы свойства двойного списка, который я считаю привлекательным?» Перечислите эти свойства, а затем посмотрите, можете ли вы создать постоянную структуру данных, обладающую этими свойствами.
Например, если вам понравилось, что двусвязные списки могут быть дешево расширены с любого конца, дешево разбиты пополам на два списка, а два списка могут быть объединены вместе, а оставшаяся структура - неизменный catenable deque, а не двусвязный список. Даю пример неизменного, не catenable здесь: дека
http://blogs.msdn.com/b/ericlippert/archive/2008/02/12/immutability-in-c-part-eleven-a-working-double-ended-queue.aspx
Продление его быть catenable Deque оставляется в качестве упражнения; бумага, на которую я ссылаюсь на пальцевые деревья, является хорошей для чтения.
UPDATE:
в соответствии с вышеизложенным нам нужно скопировать префикс до точки вставки. По логике непреложности, если w удаляет что-либо из префикса, мы получаем новый список, а также в суффиксе ... Почему копировать только префикс, а не суффикс?
Хорошо рассмотрим пример. Что делать, если у нас есть список (10, 20, 30, 40), и мы хотим вставить 25 в позицию 2? Поэтому мы хотим (10, 20, 25, 30, 40).
Какие части можно использовать повторно? У хвостов у нас есть (20, 30, 40), (30, 40) и (40). Ясно, что мы можем повторно использовать (30, 40).
Рисунок может помочь. Мы имеем:
10 ----> 20 ----> 30 -----> 40 -----> Empty
и мы хотим
10 ----> 20 ----> 25 -----> 30 -----> 40 -----> Empty
так давайте
| 10 ----> 20 --------------> 30 -----> 40 -----> Empty
| /
| 10 ----> 20 ----> 25 -/
Мы можем повторно использовать (30, 40), потому что часть общего для обоих списков.
UPDATE:
Можно ли предоставить код для случайного вставки и удаления, а?
Вот рекурсивный решение:
ImmutableList InsertAt(int value, int position)
{
if (position < 0)
throw new Exception();
else if (position == 0)
return this.Add(value);
else
return tail.InsertAt(value, position - 1).Add(head);
}
Вы видите, почему это работает?
Теперь, как упражнение, напишите рекурсивный DeleteAt.
Теперь, как упражнение, введите нерекурсивный InsertAt и DeleteAt. Помните, у вас есть неизменный связанный список в вашем распоряжении, поэтому вы можете использовать его в своем итеративном решении!
Большое спасибо за это. BTW, в соответствии с вышеизложенным нам нужно скопировать префикс до точки вставки. По логике непреложности, если w удаляет что-либо из префикса, мы получаем новый список, а также в суффиксе ... Почему копировать только префикс, а не суффикс? – Bober02
Можно ли также предоставить код для случайного ввода и удаления? КПП. есть ли какие-либо документы/вопросы о похожих структурах данных, таких как очереди, карты, деревья и т. д. – Bober02
Еще раз спасибо Эрик, очень ценю ваше время. Надеюсь, у вас будет одна минута, чтобы пощадить эти два простых вопроса: вы говорите, что «постоянный дважды связанный список» затруднен. Что вы подразумеваете под постоянным? Я также прокомментировал ваш другой вопрос, на котором вы написали, что вставка может быть уменьшена до log (n) с использованием блазевых деревьев. Это потому, что нам не нужно копировать левую/правую часть дерева, если мы вставляем в правое/левое поддерево? Еще раз спасибо – Bober02