- •Введение
- •Создание проекта в среде Windows Form
- •1.1. Описание среды Windows Form
- •1.2. Разработка проекта
- •1.3 Сохранение, сборка и выполнение проекта
- •Примеры использования визуальных компонентов среды Windows Form
- •2.1. Визуальные компоненты Label, TextBox и Button
- •2.2. Визуальные компоненты ListBox, ComboBox, RadioButton и GroupBox.
- •2.3. Матрица ячеек DataGridView
- •Материалы для самостоятельной проработки
- •3.1. Краткое введение в объектно-ориентированное программирование (ооп)
- •3.2 Динамические структуры данных
- •3.3 Итераторы
- •3.4. Примеры с использованием списка прямого доступа и итераторов.
- •3.6. Реализация «движения» по однородному и сложному фону.
- •3.7. Сложное движение каркасных моделей.
- •Типовые задания для лабораторных работ
- •Контрольные вопросы
- •Список рекомендуемой литературы
- •Оглавление
3.2 Динамические структуры данных
Односвязные списки – стек и очередь. Реализация стека и очереди.
Динамическая структура данных состоит из узлов, включающих в себя информационную часть (данное, ради которого создается узел) и ссылочную часть (указатели на себе подобных). Динамические структуры данных классифицируются на списки, деревья и графы. Списки бывают односвязными и двусвязными. Узел односвязных списков содержит один указатель на следующий узел. Односвязные списки подразделяются на стеки, очереди, деки, циклические списки и списки прямого доступа. Двусвязные списки реализуются только как списки прямого доступа. На языке С узел односвязного списка можно задать структурой, содержащей, например, поле данных info и поле типа указатель на Node next:
struct Node
{
double info;
Node* next;
};
Стек
Логика стека - первым пришел, последним ушел. Для работы со стеком нам нужен только один указатель на первый узел (вершина стека):
struct Stack
{
Node* top;
};
Вся работа выполняется только с этим элементом. Мы добавляем новые элементы в вершину стека и удаляем элементы из вершины.
Можно выделить следующие операции над стеком:
Инициализация стека:
Stack initStack()
{
Stack S = {0};
return S;
}
Добавление элемента в стек:
void push(Stack &S, double inf)
{
Node* work = new Node;
work->info = inf;
work->next = S.top;
S.top = work;
}
Удаление элемента из стека:
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;
}
Очистка стека:
void clear(Stack& S)
{
while(S.top)
{
Node* work = S.top;
S.top = S.top->next;
delete work;
}
}
Получение данного из вершины стека:
bool get(double& inf, const Stack& S)
{
if(!S.top) return false;
inf = S.top->info;
return true;
}
Проверка состояния стека:
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;
};
Для работы со списком прямого доступа с использованием итераторов можно выделить следующие операции:
Инициализация списка:
List initList()
{
List L = {0, 0};
return L;
}
Добавление элемента в голову списка:
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;
}
Удаление элемента из головы списка:
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;
}
Очистка списка:
void clear(List& L)
{
while(L.top)
{
Node* work = L.top;
L.top = L.top->next;
delete work;
}
L.bottom = 0;
}
Получение данного из головы списка:
bool get(double& inf, const List& L)
{
return L.top ? (inf = L.top->info, true) : false;
}
Проверка состояния списка:
bool empty(const List& L)
{
return !L.top;
}
Установка итератора в голову списка:
Iterator begin(List& L)
{
Iterator I = {L.top};
return I;
}
Установка итератора в хвост списка:
Iterator end(List& L)
{
Iterator I= {L.bottom};
return I;
}
Перемещение итератора на следующий узел:
bool next(Iterator& I)
{
return I.current ? (I.current = I.current->next, true) : false;
}
Добавление узла после узла, на который установлен итератор:
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;
}
Удаление узла, расположенного после узла, на который установлен итератор:
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;
}
Получение информационной части узла, на который установлен итератор:
double get(const Iterator &I)
{
return I.current ? I.current->info : 0.;
}
Получение информационной части узла следующего за тем, на который установлен итератор;
double getNext(const Iterator &I)
{
return I.current && I.current->next ? I.current->next->info : 0.;
}
Установлен ли итератор на список;
bool check(const Iterator &I)
{
return I.current;
}
Сравнение итераторов;
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;
}
Замена информационной части узла, на который установлен итератор:
bool set(Iterator& I, double inf)
{
if(!I.current) return false;
I.current->info = inf;
return true;
}
Замена информационной части узла, следующего за тем, на который установлен итератор:
bool setNext(Iterator& I, double inf)
{
if(!I.current || !I.current->next) return false;
I.current->next->info = inf;
return true;
}
Обратная польская запись
Для вычисления выражений используется обратная польская запись или обратная польская нотация. Она была разработана австралийским философом и специалистом в области теории вычислительных машин Чарльзом Хэмблином в середине 1950-х на основе польской нотации, которая была предложена в 1920 году польским математиком Яном Лукасевичем.
Отличительной особенностью обратной польской нотации является то, что операнды расположены перед знаком операции. В общем виде запись выглядит следующим образом:
Выражение состоит из последовательности операндов и операторов.
Выражение читается слева направо. Когда в выражении встречается оператор, выполняется соответствующая операция над двумя или одним (в зависимости от арности оператора) последними встретившимися перед ним операндами в порядке их записи. Результат операции заменяет в выражении последовательность её операндов и оператора, после чего выражение вычисляется дальше по тому же правилу.
Результатом вычисления выражения становится результат последней вычисленной операции.
Эдсгер Дейкстра предложил алгоритм для преобразования выражений из инфиксной нотации в обратную польскую нотацию.
Алгоритм:
Пока в выражении есть лексемы:
Берем очередную лексему;
Если лексема операнд, добавляем в выходную строку;
Если лексема является функцией, помещаем его в стек;
Если – оператор и приоритет его меньше, либо равен приоритету оператора, находящегося на вершине стека, выталкиваем верхние операторы из стека в выходную строку до тех пор, пока не встретится оператор с меньшим приоритетом, или открывающая круглая скобка, или стек станет пустым и помещаем его в стек;
Если оператор и приоритет его больше приоритета оператора на вершине стека, то просто помещаем его в стек;
Если лексема является открывающей скобкой, помещаем ее в стек;
Если символ является закрывающей скобкой, то до тех пор, пока верхним элементом стека не станет открывающая скобка, выталкиваем элементы из стека в выходную строку. При этом открывающая скобка удаляется из стека, но в выходную строку не добавляется. Если после этого на вершине стека оказывается функция, выталкиваем ее в выходную строку.
Когда входное выражение закончилось, выталкиваем все символы из стека в выходную строку.
Мы получили выражение в обратной польской записи.
