Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Posobie15_Wform_red1-1.doc
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
7.56 Mб
Скачать

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

Односвязные списки – стек и очередь. Реализация стека и очереди.

Динамическая структура данных состоит из узлов, включающих в себя информационную часть (данное, ради которого создается узел) и ссылочную часть (указатели на себе подобных). Динамические структуры данных классифицируются на списки, деревья и графы. Списки бывают односвязными и двусвязными. Узел односвязных списков содержит один указатель на следующий узел. Односвязные списки подразделяются на стеки, очереди, деки, циклические списки и списки прямого доступа. Двусвязные списки реализуются только как списки прямого доступа. На языке С узел односвязного списка можно задать структурой, содержащей, например, поле данных info и поле типа указатель на Node next:

struct Node

{

double info;

Node* next;

};

Стек

Логика стека - первым пришел, последним ушел. Для работы со стеком нам нужен только один указатель на первый узел (вершина стека):

struct Stack

{

Node* top;

};

Вся работа выполняется только с этим элементом. Мы добавляем новые элементы в вершину стека и удаляем элементы из вершины.

Можно выделить следующие операции над стеком:

  1. Инициализация стека:

Stack initStack()

{

Stack S = {0};

return S;

}

  1. Добавление элемента в стек:

void push(Stack &S, double inf)

{

Node* work = new Node;

work->info = inf;

work->next = S.top;

S.top = work;

}

  1. Удаление элемента из стека:

bool pop(double& inf, Stack& S)

{

if(!S.top) return false;

Node* work = S.top;

S.top = S.top->next;

inf = work->info;

delete work;

return true;

}

  1. Очистка стека:

void clear(Stack& S)

{

while(S.top)

{

Node* work = S.top;

S.top = S.top->next;

delete work;

}

}

  1. Получение данного из вершины стека:

bool get(double& inf, const Stack& S)

{

if(!S.top) return false;

inf = S.top->info;

return true;

}

  1. Проверка состояния стека:

bool empty(const Stack& S)

{

return !S.top;

}

Очередь

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

struct Queue

{

Node* top, * bottom;

};

Для работы с очередью можно выделить такие же операции, как и над стеком. Будет отличаться только реализация.

3.3 Итераторы

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

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

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

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

struct List

{

Node* top, * bottom;

};

struct Iterator

{

Node* current;

};

Для работы со списком прямого доступа с использованием итераторов можно выделить следующие операции:

  1. Инициализация списка:

List initList()

{

List L = {0, 0};

return L;

}

  1. Добавление элемента в голову списка:

void push(List &L, double inf)

{

Node *work = new Node;

work->info = inf;

work->next = L.top;

if(!L.top)

L.bottom = work;

L.top = work;

}

  1. Удаление элемента из головы списка:

bool pop(double& inf, List& L)

{

if(!L.top) return false;

Node* work = L.top;

L.top = L.top->next;

inf = work->info;

delete work;

return true;

}

  1. Очистка списка:

void clear(List& L)

{

while(L.top)

{

Node* work = L.top;

L.top = L.top->next;

delete work;

}

L.bottom = 0;

}

  1. Получение данного из головы списка:

bool get(double& inf, const List& L)

{

return L.top ? (inf = L.top->info, true) : false;

}

  1. Проверка состояния списка:

bool empty(const List& L)

{

return !L.top;

}

  1. Установка итератора в голову списка:

Iterator begin(List& L)

{

Iterator I = {L.top};

return I;

}

  1. Установка итератора в хвост списка:

Iterator end(List& L)

{

Iterator I= {L.bottom};

return I;

}

  1. Перемещение итератора на следующий узел:

bool next(Iterator& I)

{

return I.current ? (I.current = I.current->next, true) : false;

}

  1. Добавление узла после узла, на который установлен итератор:

bool add(List &L, Iterator &I, double inf)

{

if(!I.current && L.top) return false;

Node* work = new Node;

work->info = inf;

if(!L.top)

{

work->next = 0;

L.top = L.bottom = I.current = work;

}

else if(!I.current->next)

{

work->next = 0;

L.bottom = I.current->next = work;

}

else

{

work->next = I.current->next;

I.current->next = work;

}

return true;

}

  1. Удаление узла, расположенного после узла, на который установлен итератор:

bool del(double& inf, List &L, const Iterator &I)

{

if(!L.top || !I.current || !I.current->next) return false;

Node* work = I.current->next;

inf = work->info;

I.current->next = work->next;

if(!work->next)

{

L.bottom = I.current;

}

delete work;

return true;

}

  1. Получение информационной части узла, на который установлен итератор:

double get(const Iterator &I)

{

return I.current ? I.current->info : 0.;

}

  1. Получение информационной части узла следующего за тем, на который установлен итератор;

double getNext(const Iterator &I)

{

return I.current && I.current->next ? I.current->next->info : 0.;

}

  1. Установлен ли итератор на список;

bool check(const Iterator &I)

{

return I.current;

}

  1. Сравнение итераторов;

bool operator ==(const Iterator& I1, const Iterator& I2)

{

return I1.current == I2.current;

}

bool operator !=(const Iterator& I1, const Iterator& I2)

{

return I1.current != I2.current;

}

  1. Замена информационной части узла, на который установлен итератор:

bool set(Iterator& I, double inf)

{

if(!I.current) return false;

I.current->info = inf;

return true;

}

  1. Замена информационной части узла, следующего за тем, на который установлен итератор:

bool setNext(Iterator& I, double inf)

{

if(!I.current || !I.current->next) return false;

I.current->next->info = inf;

return true;

}

Обратная польская запись

Для вычисления выражений используется обратная польская запись или обратная польская нотация. Она была разработана австралийским философом и специалистом в области теории вычислительных машин Чарльзом Хэмблином в середине 1950-х на основе польской нотации, которая была предложена в 1920 году польским математиком Яном Лукасевичем.

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

  • Выражение состоит из последовательности операндов и операторов.

  • Выражение читается слева направо. Когда в выражении встречается оператор, выполняется соответствующая операция над двумя или одним (в зависимости от арности оператора) последними встретившимися перед ним операндами в порядке их записи. Результат операции заменяет в выражении последовательность её операндов и оператора, после чего выражение вычисляется дальше по тому же правилу.

  • Результатом вычисления выражения становится результат последней вычисленной операции.

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

Алгоритм:

  1. Пока в выражении есть лексемы:

    1. Берем очередную лексему;

    2. Если лексема операнд, добавляем в выходную строку;

    3. Если лексема является функцией, помещаем его в стек;

    4. Если – оператор и приоритет его меньше, либо равен приоритету оператора, находящегося на вершине стека, выталкиваем верхние операторы из стека в выходную строку до тех пор, пока не встретится оператор с меньшим приоритетом, или открывающая круглая скобка, или стек станет пустым и помещаем его в стек;

    5. Если оператор и приоритет его больше приоритета оператора на вершине стека, то просто помещаем его в стек;

    6. Если лексема является открывающей скобкой, помещаем ее в стек;

    7. Если символ является закрывающей скобкой, то до тех пор, пока верхним элементом стека не станет открывающая скобка, выталкиваем элементы из стека в выходную строку. При этом открывающая скобка удаляется из стека, но в выходную строку не добавляется. Если после этого на вершине стека оказывается функция, выталкиваем ее в выходную строку.

  2. Когда входное выражение закончилось, выталкиваем все символы из стека в выходную строку.

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