Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


Двухсвязные линейные списки




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

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

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

 

 

Рассмотрим особенности работы с двухсвязным списком.

1. Как и в случае с односвязным списком, создадим две структуры. Описание этих структур расположим в начале программы (до main() и других функций). Вот возможная реализация структур:

struct Data

{

int a; // данные

};

struct DList

{

Data d;

DList *prev; // указатель на предшествующий элемент

DList *next; // указатель на последующий элемент

};

2. Затем создаём два указателя:

DList *Begin = NULL; // Начало списка

DList *End = NULL; // Конец списка

3. Теперь можно формировать какой-то начальный список. Делаем это по аналогии с тем, как делали при работе с односвязными списками. Начнём формировать список из трёх чисел 3, 5 и 1:

// Создаём первый элемент

DList *t = new DList;

t->d.a = 3;

t->prev = NULL;

t->next = NULL;

 

// Настроим на него оба указателя

Begin = t;

End = t;

//++++++++++++++++++++++++следующая пара+++++++++++++++

// Создаём второй элемент

t->next = new DList;

DList *p = t;

t = t->next;

t->prev = p;

t->d.a = 5;

t->next = NULL;

End = t;

 

// Создаём третий элемент

t->next = new DList;

p = t;

t = t->next;

t->prev = p;

t->d.a = 1;

t->next = NULL;

End = t;

Всё, список из трёх чисел создан. Приведём хотя бы некоторые действия с этим списком.

1. Обход списка в прямом направлении и его вывод на экран монитора:

void print_forward(DList *Begin)

{

DList *p = Begin;

cout << "Spisok (forward):" << endl;


while(p)

{

cout << p->d.a << endl;

p = p->next;

}

}

Возможный вызов функции:

print_forward(Begin);

Как видим, полученная реализация по сути является копией функции print() для печати односвязного списка.

2. Обход списка в обратном направлении и его вывод на экран монитора:

void print_back(DList *End)

{

DList *p = End;

cout << "Spisok (back):" << endl;

while(p)

{

cout << p->d.a << endl;

p = p->prev;

}

}

Возможный вызов функции:

print_back(End);

И здесь сходство большое. Важно обратить внимание на оператор, за счет которого выполняется движение назад:

p = p->prev;

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

Кольцевой список

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

Схема кольцевого списка представлена на рисунке ниже (используем те же данные, что и для ранее рассмотренного односвязного списка, т.е. список из чисел 3, 5, 1, 9):

Используем те же структуры (Data и List), что применяли для односвязного списка. Точно так же определяем указатель на начало будущего списка:

List *u = NULL;

и так же делаем начальное заполнение списка. Но в конце, после того, как для нашего примера мы занесли число 9 в список, требуется «замкнуть» список на начало:

x->next = u;

В итоге будет получен кольцевой список.

Операции для кольцевого списка можно выполнять те же, что и для обычного списка, но здесь будет присутствовать одна особенность: список замкнут, поэтому проверка того факта, что достигнут конец цикла, выполняется по другому. Покажем это на примере распечатки кольцевого cписка:

void printC(List *u)

{

if(u!= NULL)

{

List *p = u;

cout << "Kolcevoj Spisok:" << endl;

cout << p->d.a << endl;

p = p->next;

while(p!= u)

{

cout << p->d.a << endl;

p = p->next;

}

}

else

cout << "Spisok pust." << endl;

}

Возможный вызов функции:

printC(u);

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

Другие операции для кольцевого списка разработайте самостоятельно.

Стеки

Определение стека

Стек — динамическая структура данных, для которой выполняется правило: последним пришёл — первым ушёл. Таким образом, и дополнение новых данных, и извлечение их из стека всегда выполняется с одной стороны.

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

Операции со стеком

Рассмотрим основные и дополнительные действия со стеком. Для работы используем структуры Data и Stack:

struct Data

{

int a;

};

 

struct Stack

{

Data d;

Stack *next;

};

В программе определяем указатель на начало будущего стека:

Stack *u = NULL;

После этого можно начать работать со стеком.

1. Добавление в стек. Функцию добавления назовём Push() — по аналогии с командой ассемблера push, которая заносит целое число в программный стек.

void Push(Stack **u, Data &x)

{

Stack *t = new Stack; // Память под новый элемент

t->d.a = x.a; // Заполнение полей

t->next = *u; // Подключаем новый элемент к имеющимся

*u = t; // Перенастройка вершины

}

Обратиться к функции можно так:

Push(&u,x);

где x — объект типа Data.

2. Извлечение из стека. Здесь снова аналогия с ассемблером (команда pop выталкивает целое число из стека).

bool Pop(Stack **u, Data &x)

{

if(*u == NULL)

{

cout << "Pustoj Stack" << endl;

return false;

}

Stack *t = *u;

x.a = t->d.a; // Получаем значения полей на вершине стека

*u = t->next; // Перенастройка вершины

delete t; // Удаление ненужного элемента

return true;

}

Пример вызова функции:

Data y;

if(Pop(&u, x))

{

y = x;

cout << "y=" << y.a << endl;

}

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

bool Read(Stack **u, Data &x)

{

if(*u == NULL)

{

cout << "Pustoj Stack" << endl;

return false;

}

Stack *t = *u;

x.a = t->d.a; // Заполнение полей

return true;

}

Использование функции Read() может быть аналогичным использованию Pop().

Очереди

Определение очереди

Очередь — это линейная динамическая структура данных, для которой выполняется правило: добавление новых данных возможно только в конец этой структуры, а удаление (извлечение) — только с начала. В англоязычной литературе этот принцип называется FIFO (First Input — First Output, т.е. первый пришёл — первый ушёл).

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

Как не трудно понять, очередь — это линейный список, для которого определены всего две основные операции: добавление в конец и извлечение с начала. Значит, удобно иметь два указателя: на начало и конец этой динамической структуры. Но списки бывают односвязные и двухсвязные. Какой использовать? Подойдёт только двухсвязный список. В этом можно будет убедиться при рассмотрении основных алгоритмов для работы с очередью.

На рисунке ниже показано графическое представление очереди. Как и в предыдущих темах, очередь будем строить из целых чисел, например: 3, 5, 1.

Для работы используем структуры Data (данные) и Queue (очередь):

struct Data

{

int a;

};

struct Queue

{

Data d;

Queue *prev; // указатель на предшествующий элемент

Queue *next; // указатель на последующий элемент

};

Затем в программе определяем два указателя:

Queue *Begin = NULL; // Начало очереди

Queue *End = NULL; // Конец очереди

После этого можно начать работать с очередью.





Поделиться с друзьями:


Дата добавления: 2016-09-06; Мы поможем в написании ваших работ!; просмотров: 841 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Надо любить жизнь больше, чем смысл жизни. © Федор Достоевский
==> читать все изречения...

2376 - | 2051 -


© 2015-2025 lektsii.org - Контакты - Последнее добавление

Ген: 0.011 с.