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

Слияние — это объединение двух (или более) упорядоченных последовательностей в одну при помощи циклического выбора элементов, доступных в данный момент. Слияние двух списков можно осуществить просто перестановкой указателей. Например, пусть есть два списка, показанные на рис.8.6.

Рис. 8.6. Упорядоченные списки

В случае, когда оба списка не пустые, наименьший из двух первых элементов исходных списков будет первым элементом списка-результата. Далее алгоритм слияния заключается в том, что происходит движение по тому из списков, элементы которого не больше, чем фиксированный элемент другого списка. Затем необходимо переставить указатели, то есть перецепить списки. Зафиксировать элемент того из списков, значение которого меньше, и двигаться по другому списку. И так до тех пор, пока один из списков не закончится. Оставшийся список прицепляем к его концу.

На рис.8.7 показан результат слияния списков.

Рис. 8.7. Список, полученный в результате слияния.

Алгоритм слияния списков реализован в виде функции, значение которой — указатель на список-результат. Этот алгоритм, учитывающий и два вырожденных случая: оба списка пустые и один из двух списков пустой, приведен в листинге 8.14.

Примечание. В алгоритме предполагается, что движение по одному из списков осуществляется с помощью указателя r , тогда как q указывает на элемент другого списка, с которым происходит сравнение.

Листинг 8.14. Слияние двух списков

static MyList Merge(MyNode list1, MyNode list2)

{

MyNode r;

MyList result = new MyList();

MyNode p = list1;

MyNode q = list2;

if ((p != null) && (q != null)) {

if (p.inf <= q.inf) {

result.head = p; r = p;

}

else {

result.head = q; r = q; q = p;

}

bool ok = false;

//еще не найдено место перецепления

MyNode s = r;

// s — указатель на предыдущий элемент

bool finish = false;

do {

while ((r != null) && !ok) {

//поиск места перецепления

if (r.inf > q.inf)

ok = true;

else {

s = r; r = r.next;

}

}

if (ok) { // перецепляем списки

s.next = q; s = r;

r = q; q = s; s = r; ok = false;

}

else { // дописываем список

s.next = q;

finish = true;

}

}

while (!finish);

}

else

if (p == null)

result.head = q;

else

result.head = p;

return result;

}

      1. Двусвязные и кольцевые списки

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

Листинг 8.15. Узел для двухсвязного списка

class MyNode2

{

public int inf;

public MyNode2 next;

public MyNode2 pred;

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

public MyNode2(int inf,MyNode2 next,MyNode2 pred)

{

this.inf = inf;

this.next = next; // на следующий элемент

this.pred = pred; // на предыдущий элемент

}

}

Движение по такому списку может происходить в двух направлениях: от начала к концу — с использованием указателя Next, и от конца к началу, используя указатель Pred. Пример двусвязного списка показан на рис. 8.8.

Рис. 8.8. Двусвязный список

Очевидно, что значение поля Next последнего элемента и поля Pred первого элемента линейного двусвязного списка равны null.

Добавление:

Листинг 8.16. Метод добавления

public void Add(int inf) // Add

{ // создание нового элемента

MyNode2 p = new MyNode2(inf, head, null);

if (head != null)

head.pred = p;

head = p;

count++;

}

Поиск:

Листинг 8.17. Поиск узла по значению

public MyNode2 FindNode(int inf)

{

MyNode2 p = head;

bool ok = false;

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

{

ok = p.inf == inf;

if (!ok)

p = p.next;

}

return p;

}

Включение нового элемента в двусвязный список и удаление элемента происходит и проще, и сложнее, чем в односвязный. Проще — потому что элемент двусвязного списка содержит указатели и на следующий, и на предыдущий элементы, и нет нужды, как при вставке в односвязный список, либо обменивать информационные поля местами, либо тащить, при проходе по списку, дополнительный указатель на предыдущий элемент. Сложнее — потому что требуется переприсвоить и значения указателей Next, и значения указателей Pred. На рис. 8.9 показана вставка элемента в двусвязный список. Прежние значения указателей показаны светло-серым цветом.

Рис. 8.9. Вставка элемента в двусвязный список

Для того чтобы вставить в список новый элемент после элемента p (см. рис. 8.9), необходимы следующие действия:

Листинг 78.18. Вставка после p

public void Insetr(MyNode2 p, int inf)

{

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

if (p.next != null)

p.next.pred = q;

p.next = q;

}

Удаление элемента p из двусвязного списка показано на рис. 8.10.

Рис. 8.10. Удаление элемента из двусвязного списка

Удаление производится следующими действиями:

  • связать предыдущий элемент со следующим за p;

  • связать следующий за p элемент с предыдущим.

Листинг 8.19. Удаление узла p

public void Delete(MyNode2 p)

{

if (p.next != null)

p.next.pred = p.pred;

if (p.pred != null)

p.pred.next = p.next;

else

head = p.next;

}

Если последний элемент линейного списка связать с первым посредством указателя Next, то получится кольцевой односвязный список, как показано на рис. 8.11.

Рис. 8.11. Односвязный кольцевой список

Для создания двусвязного кольцевого списка необходимо связать последний элемент с первым как по указателю Next, так и по указателю Pred. Двусвязный кольцевой список показан на рис. 8.12.

Рис. 8.12. Двусвязный кольцевой список

Разумеется, на одном из элементов кольцевого списка должен стоять указатель, и этот элемент условно считается первым.

В качестве примера использования двусвязного кольцевого списка решим следующую задачу. Дети стали в круг, взявшись за руки. Начав отсчет от первого, каждый i-тый выходит из круга, а круг смыкается. Кто останется?

Первая часть задачи — создание списка. Процедура Insert2 вставляет элемент в двусвязный список.

Листинг 8.20. Процедура Insert2 вставляет элемент в двусвязный список

public void Insert2(MyNode2 p, int e)

// вставка элемента в двусвязный список

{ // список пуст, создание первого элемента

if (head == null)

{

head = new MyNode2(e, null, null);

head.next = head;

head.pred = head; // ссылки замыкаем на себя

}

else

{

MyNode2 t = new MyNode2(e, head.next, head);

head.next.pred = t; head.next = t;

}

}

Вызвав эту процедуру K раз, получим список, содержащий K элементов. Указатель списка стоит на первом включенном в список элементе.

Вторая часть задачи – удаление i-того элемента, считая от первого. Алгоритм прост: отсчитывается заданное количество элементов, требуемый элемент удаляется, указатель на список переносится на следующий за удаленным элемент. Когда в списке останется только один элемент, его поля Pred и Next будут указывать на него самого. Метод CountDel реализует этот алгоритм.

Листинг 8.21. Удаление элемента

public MyNode2 CountDel(MyNode2 p, int step)

{

MyNode2 r = p;

do

{

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

r = r.next;

MyNode2 t = r.pred; // этот элемент будем удалять

t.pred.next = r; r.pred = t.pred; // замыкаем круг

}

while (r == r.next);

return r;

}

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