Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
всё для проги / Программирование на ЯВУ-Снижко / Динамические структуры данных.doc
Скачиваний:
107
Добавлен:
26.03.2015
Размер:
397.31 Кб
Скачать

Массивы и записи – основные структуры данных, так как из них можно образовывать более сложные структуры.

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

Существует, однако, много задач, которые требуют данных с более сложной структурой. Для них характерно, что в процессе вычисления изменяются не только значения переменных, но даже их структура. Поэтому такие переменные стали называть данными с динамической структурой. Естественно, что компоненты таких объектов на некотором уровне детализации представляют собой статические объекты, то есть они принадлежат к одному из основных типов данных.

Абстрактный тип данных– это математическая модель с совокупностью операторов, определенных в рамках этой модели. Простым примером АТД могут служить множества целых чисел с операторами объединения, пересечения и разности множеств.

Списки

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

В математике списокпредставляет собой последовательность элементов определенного типа. Количество элементов в спискеnназываетсядлиной списка. а1первый элементсписка, аnпоследний элементсписка. Приn=0 имеемпустой список, который не содержит элементов. Элементы в списке можно линейно упорядочить в соответствии с их позицией в списке. Поэтому можно говорить о том, что элементимеет позицию, что элементпредшествуетили элементследует за. Кроме того, постулируется существование позиции, следующей за последним элементом списка. ФункцияEND(L) будет возвращать позицию, следующую за позициейnвn-элементном спискеL. ПозицияEND(L), рассматриваемая как расстояние от начала списка, может изменяться при увеличении или уменьшении списка, в то время как другие позиции имеют неизменное расстояние от начала списка.

Для формирования абстрактного типа данных на основе математического определения списка мы должны задать множество операторов, выполняемых над объектами типа LIST(список). Однако не существует одного множества операторов, выполняемых над списками, удовлетворяющего сразу все возможные приложения. В этом разделе мы предложим одно множество операторов, а далее рассмотрим несколько структур для представления списков и напишем соответствующие процедуры для реализации типовых операторов, выполняемых над списками, в терминах этих структур данных.

Примем обозначения: L– список объектов некоторого типа,x– объект этого типа,р– позиция элемента в списке.

  1. Incert(x,p,L) – вставляет объект х в позицию р в спискеL.

  2. Locate(x,L) – возвращает позицию х в списке

  3. Retrieve(p,L) – возвращает элемент, который стоит в позиции р

  4. Delete(p,L) – удаляет элемент в позиции р из списка

  5. Next(p,L) иPrevious(p,L) – возвращают следующую и предыдущую позиции

  6. MakeNull(L) – делает список пустым и возвращает позициюEND(L)

  7. First(L) – возвращает первую позицию

  8. PrintList(L) – печатает элементы списка в порядке их расположения

Реализация:

  • с помощью массивов (выделение памяти, конечность)

  • с помощью указателей (последовательный доступ, элемент – рекурсивная структура)

  • с помощью массива записей, одно из полей которых – индекс следующего элемента (в языках, не имеющих указателей, напр. Fortran)

Характерная особенность рекурсивных структур, четко отличающая их от основных – способность изменять размер. Вследствие этого для рекурсивно определенных данных невозможно выделить память фиксированного размера, и поэтому транслятор не может сопоставить с компонентами таких переменных какие-либо адреса. Для решения этой проблемы используется метод, называемый динамическим распределением памяти, то есть память под отдельные компоненты выделяется в момент, когда они начинают существовать в процессе выполнения программы, а не во время трансляции. Транслятор в этом случае выделяет фиксированный объем памяти для хранения адреса динамически размещаемой компоненты, а не самой этой компоненты. При этом записи не обязательно будут располагаться рядом, а связываются между собой с помощью адресов, находящихся в полях-указателях, которые обычно называют ссылками. Графически такие связи изображают стрелками или ссылками.

Ссылки позволяют конструировать данные самой произвольной структуры (линейной, нелинейной, циклической и др.)

Списки бывают:

  • односвязные, двусвязные, многосвязные

  • линейные, нелинейные и кольцевые

Вот история одного человека, опубликованная в одной из цюрихских газет в июле 1922 года:

«Я женился на вдове, у которой была взрослая дочь. Мой отец, довольно часто навещавший нас, влюбился в мою падчерицу и женился на ней. Следовательно, мой отец стал моим зятем, а моя падчерица – моей матерью. Спустя несколько месяцев моя жена родила сына, который стал шурином моего отца и одновременно моим дядей. У жены моего отца, то есть моей падчерицы, тоже родился сын. Таким образом, у меня появился брат и одновременно внук. Моя жена является моей бабушкой, так как она мать моей матери. Следовательно, я муж моей жены и одновременно ее внук, другими словами, я – свой собственный дедушка».

Стек

Стек– это специальный вид списка, в котором все вставки и удаления выполняются только на одном конце, называемом вершиной (top). Часто для их определения используют аббревиатуруLIFO–lastinfirstout. Абстрактные типы данных семействаSTACKобычно используют следующие 5 операторов:

  1. MakeNull(S) – делает стек пустым

  2. Top(S) – возвращает элемент с вершины стека (Top(S) =Retrieve(First(L),L))

  3. Pop(S) – удаляет элемент с вершины стека (Delete(First(S))

  4. Push(х,S) – вставляет элемент х в вершину стека (Insert(x,First(S),S)

  5. Empty(S) – возвращает значение «Истина», если стек пуст.

Реализация:

  • с помощью массивов (дно – последний элемент массива)

  • с помощью указателей (элемент – рекурсивная структура)

Примерорганизации стека с помощью массива:

#include <iostream.h>

#include <conio.h>

#define MAXLENGTH 10

struct Stack

{

int top;

int elements[MAXLENGTH];

};

void MakeNull (Stack *pstack); //задание вершины на дне //(пустой стек)

bool Empty (Stack *pstack); //стек пуст

int Top (Stack *pstack); //элемент с вершины

void Pop (Stack *pstack); //удаление элемента

void Push (int x, Stack *pstack); //добавление элемента

int main()

{

Stack stack;

int i;

MakeNull(&stack);

for (i=1; i<6; i++)

Push (i, &stack);

while (!Empty(&stack))

{

cout<<Top(&stack)<<' ';

Pop (&stack);

}

getch();

return 0;

}

void MakeNull (Stack *pstack)

{

pstack->top = MAXLENGTH;

}

bool Empty(Stack *pstack)

{

if (pstack->top > MAXLENGTH-1)

return true;

else

return false;

}

int Top (Stack *pstack)

{

if (Empty (pstack))

{

cout<<"Стек пуст\n";

return 0;

}

else

return pstack->elements[pstack->top];

}

void Pop (Stack *pstack)

{

if (Empty (pstack))

{

cout<<" Стек пуст \n";

return;

}

else pstack->top++;

}

void Push (int x, Stack *pstack)

{

if (pstack->top == 0)

{

cout<<" Стек пуст \n";

return;

}

else

{

pstack->top--;

pstack->elements[pstack->top] = x;

}

}

Примерорганизации стека с помощью указателей

#include <iostream.h>

#include <conio.h>

#include <stdlib.h>

struct Element

{

int element;

Element *next;

};

void MakeNull (Element * &pstack); //обнуление (пустой стек)

int Empty (Element *pstack); //стек пустой

int Top (Element *pstack); //текущий элемент

void Pop (Element * &pstack); //удаление элемента

void Push (int x, Element * &pstack); //добавление элемента

int main()

{

Element *stack; //указатель на вершину

int i;

MakeNull(stack);

for (i=1; i<6; i++)

Push (i, stack);

while (!Empty(stack))

{

cout<<Top(stack)<<' ';

Pop (stack);

}

getch();

return 0;

}

void MakeNull (Element * &pstack)

{

pstack = NULL;

}

int Empty(Element * pstack)

{

if (pstack==NULL)

return 1;

else

return 0;

}

int Top (Element * pstack)

{

if (Empty (pstack))

{

cout<<"Стек пуст\n";

return 0;

}

else

return pstack->element;

}

void Pop (Element * &pstack)

{

Element *del;

if (Empty (pstack))

{

cout<<" Стек пуст \n";

return;

}

else

{

del = pstack;

pstack = pstack->next;

delete del;

}

}

void Push (int x, Element * &pstack)

{

Element *ins;

ins = new Element;

ins->element = x;

ins->next = pstack;

pstack = ins;

}

Очередь

Очередь(queue) – это специальный вид списка, где элементы вставляются с одного конца, называемого задним (rear), а удаляются с другого – переднего (front). Часто для их определения используют аббревиатуруFIFO–firstinfirstout. Операторы, выполняемые над очередями, аналогичны операторам стеков. Существенное отличие между ними состоит в том, что вставка новых элементов осуществляется в конец списка, а не в начало. Кроме того, различна устоявшаяся терминология для стеков и очередей. ОчередиQUEUEобычно используют следующие 5 операторов:

  1. MakeNull(Q) – делает очередь пустой

  2. Front(Q) – возвращает первый элемент очереди (Front(Q) =Retrieve(First(L),L))

  3. EnQueue(х,Q) – вставляет элемент х в конец очереди (Insert(x,End(L),L)

  4. DeQueue(Q) – удаляет первый элемент очереди (Delete(First(L))

  5. Empty(Q) – возвращает значение «Истина», если очередь пуста.

Реализация:

  • с помощью указателей (элемент – рекурсивная структура). Используются указатели на начало и на конец очереди;

  • с помощью «циклических» массивов (в линейном массиве удаление первого элемента влечет за собой сдвиг всех элементов, в «циклическом» нет). Чтобы избежать проблем с определением, пуста очередь или, напротив, полностью заполнена, максимальное количество элементов в очереди должно быть MAXLENGTH-1.

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

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

Примерреализации очереди с помощью указателей

#include <iostream.h>

#include <conio.h>

struct Element

{

int element;

Element *next;

};

struct Queue

{

Element *front, *rear;

};

void MakeNull (Queue * &pqueue);

int Empty (Queue * pqueue);

int Front (Queue * pqueue);

void EnQueue (int x, Queue * &pqueue);

void DeQueue (Queue * &pqueue);

int main()

{

Queue *queue; //указатель на очередь

int i;

MakeNull(queue);

for (i=1; i<6; i++)

EnQueue (i, queue);

while (!Empty(queue))

{

cout<<Front(queue)<<' ';

DeQueue (queue);

}

getch();

return 0;

}

void MakeNull (Queue * &pqueue)

{

pqueue->rear = pqueue->front = new Element;

pqueue->front->next = NULL;

}

int Empty(Queue * pqueue)

{

if (pqueue->front == pqueue->rear)

return 1;

else

return 0;

}

int Front (Queue * pqueue)

{

if (Empty (pqueue))

{

cout<<"Очередь пуста\n";

return 0;

}

else

return pqueue->front->element;

}

void EnQueue (int x, Queue * &pqueue)

{

pqueue->rear->next = new Element;

pqueue->rear->element = x;

pqueue->rear = pqueue->rear->next;

pqueue->rear->next = NULL;

}

void DeQueue (Queue * &pqueue)

{

Element *del;

if (Empty (pqueue))

{

cout<<"Очередь пуста\n";

return;

}

else

{

del = pqueue->front;

pqueue->front = pqueue->front->next;

delete del;

}

}

Примерреализации очереди с помощью «циклического» массива

#include <iostream.h>

#include <conio.h>

#define MAXLENGTH 10

struct Queue

{

int front, rear;

int elements[MAXLENGTH];

};

void MakeNull (Queue *pqueue);

bool Empty (Queue *pqueue);

int Front (Queue *pqueue);

void EnQueue (int x, Queue *pqueue);

void DeQueue (Queue *pqueue);

int main()

{

Queue queue;

int i;

MakeNull(&queue);

for (i=1; i<6; i++)

EnQueue (i, &queue);

while (!Empty(&queue))

{

cout<<Front(&queue)<<' ';

DeQueue (&queue);

}

getch();

return 0;

}

int addone (int i)

{

return (i+1) % MAXLENGTH; //добавление 1 к i с учетом //циклического массива

}

void MakeNull (Queue *pqueue)

{

pqueue->front = 0;

pqueue->rear = MAXLENGTH-1;

}

bool Empty(Queue *pqueue)

{

if (addone(pqueue->rear) == pqueue->front)

return true;

else

return false;

}

int Front (Queue *pqueue)

{

if (Empty (pqueue))

{

cout<<"Очередь пуста\n";

return 0;

}

else

return pqueue->elements[pqueue->front];

}

void EnQueue (int x, Queue *pqueue)

{

if (addone(addone(pqueue->rear)) == pqueue->front)

{

cout<<"Очередь заполнена\n";

return;

}

else

{

pqueue->rear = addone(pqueue->rear);

pqueue->elements[pqueue->rear] = x;

}

}

void DeQueue (Queue *pqueue)

{

if (Empty (pqueue))

{

cout<<"Очередь пуста\n";

return;

}

else pqueue->front = addone(pqueue->front);

}

Примерреализации некоторых операторов, выполняемых над списками, при представлении списков с помощью указателей

//файл list.h (заголовки)

typedef <тип> elementtype;

struct Element

{

elementtype element;

Element * next;

};

typedef Element * list;

typedef Element * position;

position End (list l);

void Insert (elementtype x, position p);

void Delete (position p);

void MakeNull (list & l);

void сhange (position p); //меняет местами 2 соседних элемента //списка, следующих за р

//файл list.cpp (реализация)

#include "list.h"

position End (list l)

{

position q = l;

while (q->next)

q = q->next;

return q;

}

void Insert (elementtype x, position p)

{

position temp = p->next;

p->next = new Element;

p->next->element = x;

p->next->next = temp;

}

void Delete (position p)

{

position temp = p->next;

p->next = p->next->next;

delete temp;

}

void MakeNull (list & l)

{

l = new Element;

l->next = NULL;

}

void change (position p)

{

position temp = p->next;

p->next->next = temp->next->next;

p->next = temp->next;

}

Примерудаления элемента из двусвязного списка

typedef int elementtype;

struct Element

{

elementtype element;

Element * previous, * next;

};

typedef Element * position;

voidDelete(positionp)//p - указатель на удаляемый элемент

{

if (p->previous) //удаление ячейки, которая не

//является первой

p->previous->next=p->next;

if (p->next) //удаление ячейки, которая не

//является последней

p->next->previous = p->previous;

delete p;

}

Соседние файлы в папке Программирование на ЯВУ-Снижко