Спонсор: Microsoft
Опубликован: 02.02.2011 | Доступ: свободный | Студентов: 2242 / 306 | Оценка: 4.43 / 3.57 | Длительность: 33:06:00
Специальности: Программист
Лекция 33:

Решение задач на динамические структуры данных

< Лекция 32 || Лекция 33: 123 || Лекция 34 >

Деки

Дек является особым видом очереди.

Дек (англ. deque – аббревиатура от double-ended queue, двухсторонняя очередь) – это структура данных, представляющая собой последовательность элементов, в которой можно добавлять и удалять в произвольном порядке элементы с двух сторон ( рис. 32.3). Первый и последний элементы дека соответствуют входу и выходу дека.

Дек и его организация

Рис. 32.3. Дек и его организация

Частные случаи дека – это ограниченные деки:

  • дек с ограниченным входом – из конца дека можно только извлекать элементы;
  • дек с ограниченным выходом – в конец дека можно только добавлять элементы.

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

Однако применительно к деку целесообразно говорить не о начале и конце как в очереди, а о левом и правом конце.

Описание элементов дека аналогично описанию элементов линейного двунаправленного списка. Поэтому объявим дек через объявление линейного двунаправленного списка:

  • создание дека;
  • печать (просмотр) дека;
  • добавление элемента в левый конец дека;
  • добавление элемента в правый конец дека;
  • извлечение элемента из левого конца дека;
  • извлечение элемента из правого конца дека;
  • проверка пустоты дека;
  • очистка дека.

Реализацию этих операций приведем в виде соответствующих функций, которые, в свою очередь, используют функции операций с линейным двунаправленным списком.

//создание дека
void Make_Deque(int n, Deque* End_Deque){
  Make_Double_List(n,&(End_Deque->Begin),NULL);
  Double_List *ptr; //вспомогательный указатель
  ptr = End_Deque->Begin;
  while (ptr->Next != NULL){
    ptr = ptr->Next;
  }
  End_Deque->End = ptr;
}

//печать дека
void Print_Deque(Deque* Begin_Deque){
  Print_Double_List(Begin_Deque->Begin);  
}

//добавление элемента в правый конец дека
void Add_Right_Item_Deque(int NewElem, Deque* End_Deque){
  End_Deque->End =Insert_Item_Double_List(End_Deque->End,2,NewElem);
  End_Deque->End = End_Deque->End->Next;
}

//добавление элемента в левый конец дека
void Add_Left_Item_Deque(int NewElem, Deque* Begin_Deque){
  Begin_Deque->Begin =
    Insert_Item_Double_List(Begin_Deque->Begin, 1, NewElem);
}

//извлечение элемента из левого конца дека
int Extract_Left_Item_Deque(Deque* Begin_Deque){
  int NewElem = NULL;
  if (Begin_Deque->Begin != NULL) {
   NewElem = Begin_Deque->Begin->Data;
   Begin_Deque->Begin=Delete_Item_Double_List(Begin_Deque->Begin,0);
   //удаляем вершину
  }
  return NewElem;
}

//извлечение элемента из правого конца дека
int Extract_Right_Item_Deque(Deque* End_Deque){
  int NewElem = NULL;
  if (End_Deque->End != NULL) {
    NewElem = End_Deque->End->Data;
    Delete_Item_Double_List(End_Deque->End, 1); 
    //удаляем вершину
    }
  return NewElem;
}

//проверка пустоты очереди
bool Empty_Deque(Deque* Begin_Deque){
  return Empty_Double_List(Begin_Deque->Begin);  
}

//очистка очереди
void Clear_Deque(Deque* Begin_Deque){
  Delete_Double_List(Begin_Deque->Begin);  
}
Листинг 3.

Красно-черные деревья

Бинарные деревья работают лучше всего, когда они сбалансированы, когда длина пути от корня до любого из листьев находится в определенных пределах, связанных с числом вершин. Красно-черные деревья являются одним из способов балансировки деревьев. Название происходит от стандартной раскраски узлов таких деревьев в красный и черный цвета. Цвета вершин используются при балансировке дерева.

Красно-черное дерево ( Red-Black-Tree, RB-Tree ) – это бинарное дерево со следующими свойствами ( рис. 32.4):

  • каждая вершина должна быть окрашена либо в черный, либо в красный цвет;
  • корень дерева должен быть черным;
  • листья дерева должны быть черными и объявляться как NIL-вершины (NIL-узлы, то есть "виртуальные" узлы, наследники узлов, которые обычно называют листьями; на них "указывают" NULL указатели);
  • каждый красный узел должен иметь черного предка;
  • на всех ветвях дерева, ведущих от его корня к листьям, число черных вершин одинаково.
Красно-черное дерево

Рис. 32.4. Красно-черное дерево

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

Над красно-черными деревьями можно выполнять все те же основные операции, что и над бинарными деревьями.

Приведем функции следующих операций над красно-черными деревьями: создание дерева, печать (просмотр) дерева, обход дерева, проверка пустоты дерева и удаление дерева.

//создание красно-черного дерева
void Make_RBTree(RBTree** Node, int n){
  int Data;
  while (n > 0) {
    cout << "Введите значение ";
        cin >> Data;
    Insert_Node(Node, Data);
    n--;
  }
}

//добавление узла в красно-черное дерево
void Insert_Node(RBTree** Node,int Data) {
  RBTree **Curent, *Parent, *New_Node;
  Curent = Node;
  Parent = NIL;
  // Поиск местоположения
  while (*Curent != NIL) {
    Parent = (*Curent);
    Curent = Data < (*Curent)->Data ? &((*Curent)->Left) : &((*Curent)->Right);
  }
  // Создание нового узла
  New_Node = new RBTree();
  New_Node->Data = Data;
  New_Node->Parent = Parent;
  New_Node->Left = NIL;
  New_Node->Right = NIL;
  New_Node->color = RED;
  // Вставка элемента в дерево
  if(Parent != NIL){
    if (Data < Parent->Data) Parent->Left = New_Node;
    else Parent->Right = New_Node;
  }
  else (*Curent) = New_Node;
  Insert_Fixup(Node, New_Node);
} 

// Поддержка баланса дерева после вставки нового элемента
void Insert_Fixup(RBTree** Node,RBTree* New_Node){
  RBTree* Current = New_Node;
  // Проверка свойств дерева
  while (Current != *(Node) && Current->Parent->color == RED){
    // если есть нарушение
    if (Current->Parent == Current->Parent->Parent->Left) {
      RBTree *ptr = Current->Parent->Parent->Right;
      if (ptr->color == RED) {
        Current->Parent->color = BLACK;
        ptr->color = BLACK;
        Current->Parent->Parent->color = RED;
        Current = Current->Parent->Parent;
      }
      else {
        if (Current == Current->Parent->Right) {
          // сделать Current левым потомком
          Current = Current->Parent;
          Rotate_Left(Node,Current);
        }
        // перекрасить и повернуть
        Current->Parent->color = BLACK;
        Current->Parent->Parent->color = RED;
        Rotate_Right(Node,Current->Parent->Parent);
      }
    }
    else {
      RBTree *ptr = Current->Parent->Parent->Left;
      if (ptr->color == RED) {
        Current->Parent->color = BLACK;
        ptr->color = BLACK;
        Current->Parent->Parent->color = RED;
        Current = Current->Parent->Parent;
      }
      else {
        if (Current == Current->Parent->Left) {
          Current = Current->Parent;
          Rotate_Right(Node,Current);
        }
        Current->Parent->color = BLACK;
        Current->Parent->Parent->color = RED;
        Rotate_Left(Node,Current->Parent->Parent);
      }
    }
  }
  (*Node)->color = BLACK;
}

//поворот узла Current влево
void Rotate_Left(RBTree** Node,RBTree *Current) {
  RBTree *ptr = Current->Right;
  Current->Right = ptr->Left;
  if (ptr->Left != NIL) ptr->Left->Parent = Current;
  if (ptr != NIL) ptr->Parent = Current->Parent;
  if (Current->Parent != NIL) {
    if (Current == Current->Parent->Left)
      Current->Parent->Left = ptr;
    else
      Current->Parent->Right = ptr;
    }
  else {
    (*Node) = ptr; 
  }
  ptr->Left = Current;
  if (Current != NIL) Current->Parent = ptr;
}

//поворот узла Current вправо
void Rotate_Right(RBTree** Node,RBTree *Current) {
  RBTree *ptr = Current->Left;
  Current->Left = ptr->Right;
  if (ptr->Right != NIL) ptr->Right->Parent = Current;
  if (ptr != NIL) ptr->Parent = Current->Parent;
  if (Current->Parent != NIL) {
    if (Current == Current->Parent->Right)
      Current->Parent->Right = ptr;
    else
      Current->Parent->Left = ptr;
  }
  else {
    (*Node) = ptr;
  }
  ptr->Right = Current;
  if (Current != NIL) Current->Parent = ptr;
}

//печать красно-черного дерева
void Print_RBTree(RBTree* Node, int l){
  int i;
  if (Node != NIL) {
    Print_RBTree(Node->Right, l+1);
    for (i=0; i< l; i++) cout << "    ";
    if (Node->color == RED) 
      SetConsoleTextAttribute(hStd,FOREGROUND_RED);
    cprintf ("%4ld", Node->Data);
    SetConsoleTextAttribute(hStd,atr);
    Print_RBTree(Node->Left, l+1);
  }
  else cout << endl;
}

//прямой обход красно-черного дерева
void PreOrder_RBTree(RBTree* Node){
  if (Node != NIL) {
    printf ("%3ld",Node->Data);
    PreOrder_RBTree(Node->Left);
    PreOrder_RBTree(Node->Right);
  }
}

//обратный обход красно-черного дерева
void PostOrder_RBTree(RBTree* Node){
  if (Node != NIL) {
    PostOrder_RBTree(Node->Left);
    PostOrder_RBTree(Node->Right);
    printf ("%3ld",Node->Data);
  }
}

//симметричный обход красно-черного дерева
void SymmetricOrder_RBTree(RBTree* Node){
  if (Node != NIL) {
    PostOrder_RBTree(Node->Left);
    printf ("%3ld",Node->Data);
    PostOrder_RBTree(Node->Right);
  }
}

//проверка пустоты красно-черного дерева
bool Empty_RBTree(RBTree* Node){
  return ( Node == NIL ? true : false );
}

//освобождение памяти, выделенной под красно-черное дерево
void Delete_RBTree(RBTree* Node){
  if (Node != NIL) {
    Delete_RBTree(Node->Left);
    Delete_RBTree(Node->Right);
    delete(Node);
  }
}
Листинг 4.

Ключевые термины

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

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

Дек с ограниченным входом – это дек, из конца которого можно только извлекать элементы;

Дек с ограниченным выходом – это дек, в конец которого можно только добавлять элементы.

Красно-черное дерево (Red-Black-Tree, RB-Tree) – это бинарное дерево со следующими свойствами:

  • каждая вершина должна быть окрашена либо в черный, либо в красный цвет;
  • корень дерева должен быть черным;
  • листья дерева должны быть черными и объявляться как NIL-вершины;
  • каждый красный узел должен иметь черного предка;
  • на всех ветвях дерева, ведущих от его корня к листьям, число черных вершин одинаково.

Черная высота дерева – это количество черных вершин на ветви красно-черного дерева от корня до листа.

Краткие итоги

  1. Особенности указателей в языке С++ позволяют строить динамические структуры памяти на основе статически объявленных переменных или на смеси статических и динамических переменных.
  2. Циклический (кольцевой) список является структурой данных, представляющей собой последовательность элементов, последний элемент которой содержит указатель на первый элемент списка, а первый (в случае двунаправленного списка) – на последний.
  3. Основными операциями с циклическим списком являются: создание списка; печать (просмотр) списка; вставка элемента в список; удаление элемента из списка; поиск элемента в списке; проверка пустоты списка; удаление списка.
  4. Дек является структурой данных, представляющей собой последовательность элементов, в которой можно добавлять и удалять в произвольном порядке элементы с двух сторон. Первый и последний элементы дека соответствуют входу и выходу дека.
  5. Частные случаи дека – это ограниченные деки.
  6. Основными операциями с деком являются: создание дека; печать (просмотр) дека; добавление элемента в левый конец дека; добавление элемента в правый конец дека; извлечение элемента из левого конца дека; извлечение элемента из правого конца дека; проверка пустоты дека; очистка дека.
  7. Красно-черные деревья являются одним из способов балансировки деревьев, что определяется свойствами данной структуры.
  8. Над красно-черными деревьями можно выполнять все те же основные операции, что и над бинарными деревьями.
  9. При вставке/удалении элемента необходима поддержка баланса дерева через проверку и перекрашивание узлов при необходимости.
< Лекция 32 || Лекция 33: 123 || Лекция 34 >
Денис Курбатов
Денис Курбатов
Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!

Дмитрий Карпов
Дмитрий Карпов
Россия, Нижний Новгород
Сергей Злобин
Сергей Злобин
Россия, Подольск