Студопедия КАТЕГОРИИ: АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Динамические структуры данных
Односвязные списки – стек и очередь. Реализация стека и очереди. Динамическая структура данных состоит из узлов, включающих в себя информационную часть (данное, ради которого создается узел) и ссылочную часть (указатели на себе подобных). Динамические структуры данных классифицируются на списки, деревья и графы. Списки бывают односвязными и двусвязными. Узел односвязных списков содержит один указатель на следующий узел. Односвязные списки подразделяются на стеки, очереди, деки, циклические списки и списки прямого доступа. Двусвязные списки реализуются только как списки прямого доступа. На языке С узел односвязного списка можно задать структурой, содержащей, например, поле данных 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; }; Для работы с очередью можно выделить такие же операции, как и над стеком. Будет отличаться только реализация.
Итераторы Итераторы - односвязные списки прямого доступа. Реализация Итератора. Обратная польская запись - алгоритм Дейкстры. Списки можно рассматривать не только как хранилище данных, но и как последовательность данных одного типа. Для организации доступа к таким данным используют указатели на узлы списка и при необходимости перемещают их по списку. В принципе, для односвязного списка можно организовать большинство алгоритмов обработки массива: поиск, удаление, вставка, группировка и сортировка. Удобство списков заключается в том, что нет необходимости заранее выделять память нужного размера и операции по вставке и удалению какого-либо узла, менее затратные. Мы заранее не знаем, со сколькими узлами нам потребуется одновременно работать, то есть сколько потребуется указателей. Это зависит от алгоритма обработки. Поэтому вспомогательный указатель лучше выделить в отдельную структуру и создавать переменные структуры по мере необходимости. Такую структуру именуют 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 году польским математиком Яном Лукасевичем. Отличительной особенностью обратной польской нотации является то, что операнды расположены перед знаком операции. В общем виде запись выглядит следующим образом:
Эдсгер Дейкстра предложил алгоритм для преобразования выражений из инфиксной нотации в обратную польскую нотацию. Алгоритм:
Мы получили выражение в обратной польской записи.
3.4. Примеры с использованием списка прямого доступа и итераторов. Рассмотрим несколько примеров с использование итераторов. Вывод списка: void printList(const List& L) { for(Iterator I = begin(L); check(I); next(I)) printf(“%7.3lf ”, get(I)); printf(“\n”); } Добавление элементов в список так, чтобы они располагались в порядке возрастания: int insertSort(List &L, double inf) { int Pos = 0; Iterator I = begin(L);
if(!check(I) || get(I) >= inf) push(L, inf); else { for(Pos = 1; I != end(L) && getNext(I) < inf; next(I), Pos++);
add(L, I, inf); }
return Pos; } Удаление повторяющихся элементов: int delElem(List &L) { int Count = 0; for(Iterator I1 = begin(L); I1 != end(L); next(I)) for(Iterator I2 = I1; I2 != end(L); ) if(get(I1) == getNext(I2)) { double temp; del(temp, L, I2); Count++; } else next(I2);
return Count; } Сортировка элементов списка: void sortList(List &L) { for(Iterator I1 = begin(L); I1 != end(L); next(I)) { double temp1 = get(I1);
for(Iterator I2 = I1; next(I2), check(I2); ) if(temp1 > get(I2)) { temp1 = get(I2); set(I2, get(I1)); set(I1, temp1); } } }
3.5. Графика. Пример построения и вывода на экран графика функции. Статистическая обработка данных и графическое представление результатов обработки в виде гистограммы и круговой диаграммы. Компоненты графики. На любом визуализируемом компоненте можно рисовать, но лучше использовать стандартный компонент pictureBox. Для рисования на компонентах используется класс Graphics, который содержит методы для рисования. Все методы рисования можно разделить на две группы: те, которые используют ручку Pen, и те, которые используют кисточку SolidBrush. Те методы, которые используют Pen, начинаются со слова Draw, а те, которые SolidBrush со слова Fill. Методы, использующие Pen:
void DrawLine(Pen^ pen, PointF p1, PointF p2).
void DrawLines(Pen^ pen, array<PointF>^ points).
void DrawPolygon(Pen^ pen, array<PointF>^ points).
void DrawRectangle(Pen^ pen, Rectangle rect).
void DrawEllipse(Pen^ pen, RectangleF rect).
void DrawArc(Pen^ pen, RectangleF rect, float startAngle, float sweepAngle).
void DrawPie(Pen^ pen, RectangleF rect, float startAngle, float sweepAngle). Методы, использующие SolidBrush:
void FillPolygon(Brush^ brush, array<PointF>^ points).
void FillRectangle(Brush^ brush, Rectangle rect).
void FillEllipse(Brush^ brush, RectangleF rect).
void FillPie(Brush^ brush, RectangleF rect, float startAngle, float sweepAngle).
Для того, чтобы что-нибудь нарисовать, надо создать объект класса Graphics и необходимые инструменты: ручки и кисточки. Объект Graphics необходимо связать c полем для рисования. Если просто рисовать на поле компонента, то это нигде не будет сохраняться, и при свертывании окна или при перекрытии компонента рисунок будет потерян. Поэтому необходимо выделять память под поле Image компонента и связывать объект класса Grapics c ним. Поскольку в этом случае мы будем рисовать на Image, то необходимо периодически обновлять поле компонента. Для этого используется метод Invalidate.
График функции. Для вывода графика функции на компонент нам необходимо задать функцию, интервал изменения аргумента функции, размеры поля компонента, для вывода графика функции. Если предполагается воспроизводить график на PictureBox, то это будет его ширина и высота. Для того, чтобы разместить график функции на поле выбранного компонента, необходимо выполнить преобразование координат, в которых задана функция ( такие координаты принято называть мировыми ), в экранные координаты или, точнее, в координаты выбранной для постороения графика компоненты Среды. Аргумент функции изменяется в задавемом пользователем интервале [a,b]. На заданном интервале функия изменяется в пределах от Ymin до Ymax. Интервал [a,b] надо сопоставить с шириной окна, в поле которого буде выводится график функции, а интервал [Ymin,Ymax] сопоставить с высотой окна. Для построения графика функции удобно использовать класс Rectangle и его свойства Width и Height, а затем связать объект Rectangle Rec с pictureBox1->ClientRectangle. Удобно сначала сформировать массив точек PointF, а затем вывести изображение графика функции на экран, используя метод DrawLines(). Для нахождения максимального и минимального значения функции на заданном интервале a,b можно использовать функции maxY() и minY(), возвращающие значение типа double. double maxY(double A, double B, double h, double (*Pf)(double)) { double Ymax = Pf(A); for(double X = A + h; X < B + h/2; X += h) if(Ymax < Pf(X)) Ymax = Pf(X);
return Ymax; }
double minY(double A, double B, double h, double (*Pf)(double)) { double Ymin = Pf(A); for(double X = A + h; X < B + h/2; X += h) if(Ymin > Pf(X)) Ymin = Pf(X);
return Ymin; } Для формирования массива точек графика в экранных координатах можно использовать функцию DrawGraph(), которая возвращает указатель на массив типа Point. Для выполнения преобразования координат необходимо найти масштабные коэффициенты по осям X и Y: масштаб по оси X – Mx определяется как отношение количества точек экрана по оси Х к интервалу изменения аргумента выводимой на экран функции; масштаб по оси Y – mY определяется как отношение размера экрана по оси Y (Rec.Height - Rec.Y) к (Ymax – Ymin).
array<Point>^ DrawGraph(Rectangle Rec, double A, double B, double (*Pf)(double)) { int Count = Rec.Width - Rec.X + 1; double h = (B - A)/(Count - 1);
double Ymax = maxY(A, B, h, Pf); double Ymin = minY(A, B, h, Pf);
double Mx = 1/h; double My = (Rec.Height - Rec.Y)/(Ymax - Ymin);
array<Point>^ mas = gcnew array<Point>(Count);
double X = A; for(int i = 0; i < Count; i++, X += h) mas[i] = Point(Rec.X + Mx*(X - A), Rec.Height - My*(Pf(X) - Ymin));
return mas; } Вызов функции DrawGraph() осуществляется из gr->DrawLines(). В примере показана передача функции DrawGraph() функции Sin(х), заданной на интервале [0., 12.56]. private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { Pen ^pen = gcnew Pen(Color::Black); Graphics^ gr = pBox->CreateGraphics(); gr->Clear(Color::White); gr->DrawLines(pen, DrawGraph(pictureBox1->ClientRectangle, 0., 12.56, Math::Sin)); } Для построения гистограммы можно использовать массив целых чисел. Так же, как и при построении графика, надо вычислить масштаб по X и по Y: Mx = W/Length, где Length – размерность массива; My = H/ElemMax, Где ElemMax – значение максимального элемента массива. Для построения круговой гистограммы можно использовать тот же массив. Сумма элементов массива соответствует полному кругу 2π. Необходимо определить угол, приходящийся на каждый элемент массива, разделив значение элемента массива на сумму элементов массива и умножив на 2π.
|
||
Последнее изменение этой страницы: 2018-06-01; просмотров: 323. stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда... |