15. Дървета

  Двоични дървета.
*** Определение и свойства на дърво.
- Ацикличен насочен граф, в който всички върхове без 1 имат по 1 предшественик, а 1 връх (корен) няма предшественици.
- Единствен път до корена, ниво - брой на върховете по пътя до корена.
- Ниво на върха - брой на върховете в пътя до корена.
- Корен - ниво 0.
- Листа - върхове без наследници.
- Поддърво определено от връх - върхът и всичките му наследници, техните наследници и т.н.
- Рекурсивна същност на дърветата като структури от данни.

*** Двоично дърво.
Брой на наследниците на върховете - 0, 1 или 2;
Ляв и десен наследник.
-- Рекурсивно определение на двоично дърво:
Крайно множество от елементи (възли), което е или празно, или се състои от корен (възел), свързан с две непресичащи се двоични дървета (поддървета) - ляво и дясно поддърво.
-- Реализация с масив.
-- Реализация с указатели.
Дърво за двоично търсене:
#include <iostream>
using namespace std;

struct node
{ int info; node *pLeft, *pRight; };

class tree {
public:
   tree();
   void print() const { pr(root); }
private:
   node *root;
   void AddNode(int, node* &);
   void pr(const node *) const;
};
tree::tree()
{  root = NULL;
   int x;
   while (cin >> x, !cin.fail()) AddNode(x, root);
}
void tree::AddNode(int x, node* &p)
{  if (p == NULL)
   {  p = new node;
      p->info = x;
      p->pLeft = p->pRight = NULL;
   }
   else AddNode(x, x < p->info ? p->pLeft : p->pRight);
}
void tree::pr(const node *p)const
{  if (p)
   {  pr(p->pLeft);
      cout << p->info << " ";
      pr(p->pRight);
   }
}
int main()
{  cout << "Enter some integers to be placed in a binary tree,\n"
        << "followed by /:\n";
   tree t;
   cout << "Tree contents (in ascending order):\n";
   t.print();
   cout << endl;
   return 0;
}
***  Обхождане на структура от данни двоично дърво.
Обхождане на двоично дърво - линейно нареждане на възлите на дървото:
* корен, ляв, десен (низходящо, preorder,  клд);
* ляв, корен, десен (смесено,     inorder,    лкд);
* ляв, десен, корен (възходящо, postorder, лдк).
Пример:
Аритметични изрази и двоични дървета.
Листата са имена на променливи и константи, другите възли са аритметични операции.
(a+b/c)*(d-e*f)
1) preorder (клд) - *+a/bc-d*ef
2) inorder  (лкд)  - a+b/c*d-e*f
3) postorder(лдк) - abc/+def*-*

Претърсване на дърво за двоично търсене:
/* bintree:
   This program builds and searches a binary search
   tree and prints its contents. The program produces
   a frequency distribution of words read from a textfile.
   We can also search the tree for a given word to inquire
   how often that word occurs.
*/
#include <fstream>
#include <iomanip>
#include <ctype.h>
#include <string>
using namespace std;

struct node
{char *pWord; int count; node *pLeft, *pRight;};

class tree {
public:
   tree(ifstream &);
   ~tree() { DelTree(root); }
   void print() const { pr(root); }
   node *search() const;
   int ReadWord(istream &);
private:
   node *root;
   enum { buflen = 50 };
   char buf[buflen];
   void AddNode(node* &);
   void DelTree(node* &);
   void pr(const node *)const;
};

int tree::ReadWord(istream &input)
/* This function reads a word from the stream 'input'.
   It skips any leading nonalphabetic characters.
   Then the longest possible string of letters (no longer
   than buflen - 1) is read, converted to upper case and
   placed in 'buf'. Return value: success (1) or failure (0).
*/
{  char ch;
   int i;
   do
   {  input >> ch;
      if (input.fail()) return 0;
   }  while (!isalpha(ch));
   for (i=0; i<buflen-1; )
   {  buf[i++] = toupper(ch);
      input.get(ch);
      if (input.fail() || !isalpha(ch)) break;
   }
   buf[i] = '\0';
   return 1;
}
tree::tree(ifstream &input)
{  root = NULL;
   while (ReadWord(input)) AddNode(root);
}
void tree::DelTree(node* &p)
{  if (p)
   {  DelTree(p->pLeft); DelTree(p->pRight);
      delete[] p->pWord; delete p; p = NULL;
   }
}
void tree::AddNode(node* &p) // Add word in buf to tree
{  if (p == NULL)
   {  p = new node;
      p->pWord = new char[strlen(buf) + 1];
      strcpy(p->pWord, buf); p->count = 1;
      p->pLeft = p->pRight = NULL;
   }  else
   {  int code = strcmp(buf, p->pWord);
      if (code) AddNode(code < 0 ? p->pLeft : p->pRight);
      else p->count++;
   }
}
void tree::pr(const node *p)const
{  if (p)
   {  pr(p->pLeft);
      cout << setw(5) << p->count << " "
           << p->pWord << endl;
      pr(p->pRight);
   }
}
node *tree::search()const
{  node *p = root;
   for (;;)
   {  if (p == NULL) return NULL;
      int code = strcmp(buf, p->pWord);
      if (code == 0) return p;
      p = (code < 0 ? p->pLeft : p->pRight);
   }
}
int main()
{  ifstream input;
   node *ptr;
   const int NameLen=50;
   char FileName[NameLen];
   cout << "Input file: ";
   cin >> setw(NameLen) >> FileName;
   input.open(FileName, ios::in);
   if (input.fail())
   {  cout << "Cannot open input file.\n"; exit(1);   }

   tree t(input);
   cout << "Frequency distribution:\n";
   t.print();
   for (;;)
   {  cout << "\nEnter a word, or type Ctrl+Z (or Ctrl+D)"
              " to stop: ";
      if (t.ReadWord(cin) == 0) break;
// The word read by ReadWord has been placed in the buf
// class member. We now search the tree for this word:
      ptr = t.search();
      cout << "Number of occurrences: "
           << (ptr ? ptr->count : 0) << endl;
   }
   return 0;
}

Кодиране на Хъфман.
*** Алгоритъм на Хъфман:
 1. Всички букви - листа на дървото с тегла броя на срещанията на буквата в текста;
 2. Всички други възли на дървото имат тегла 0;
 3. В началото разглеждаме всички листа като отделни дървета;
 4. Намираме две дървета с най-малки претеглени дължини;
 5. Построяваме ново дърво, като създаваме нов възел (корен) с наследници - двете поддървета;
 6. Пресмятаме претеглената дължина на новото дърво (сума от претеглените дължини на двете поддървета);
 7. Отиваме на 4., ако имаме поне 2 дървета.

*** Построяване на оптимално двоично дърво по алгоритъма на Хъфман и пример за следния текст:
програмиране и структури от данни
 

п р о г а м и н е с т у к д
1 5 2 1 3 1 4 3 1 4 1 2 2 1 1

#include <iostream>
using namespace std;

struct Huf {
char id;
int wh;
Huf *left, *right;
};

struct List
{ List *next; Huf *tree; };
List *head;

void CreateList();
void WriteList();
void DelList(List *);
void AddList(Huf *);
Huf *FindDels();
void CreateTree();
void rlrootTree(Huf *);

void main()
{
 CreateList();
 WriteList();
 CreateTree();
 rlrootTree(head->tree);
}
void CreateList()
{
 const int n=13;
 char ch[n]={'Ї','°','®','Ј',' ','¬','Ё','­','±','І','і','Є','¤'};
 int   w[n]={ 1,  5,  2,  1,  3,  1,  4,  3,  1,  2,  2,  1,  1};
 List *l; Huf *h;
 head = 0;
 for (int i=0; i<n; i++)
 {
  h = new Huf;
  h->id = ch[i]; h->wh = w[i];
  h->left = 0; h->right = 0;
  l = new List;
  l->tree = h;
  l->next = head; head = l;
 }
}

void WriteList()
{
 List *l = head;
 while (l)
 {
  cout << (l->tree)->id << "  " << (l->tree)->wh << endl;
  l = l->next;
 }
}

void DelList(List *l)
{
 List *lp, *lc;
 if (l==head) {head=l->next; delete l;}
 else
 {
  lp = head; lc = lp->next;
  while (lc!=l)
  {
   lp = lc; lc = lc->next;
  }
  lp->next = lc->next; delete lc;
 }
}

void AddList(Huf *h)
{
 List *l = new List;
 l->tree = h;
 l->next = head;
 head = l;
}

Huf *FindDels()
{
 List *l = head, *sm = head;
 Huf *h;
 while (l)
 {
  if ((l->tree)->wh < (sm->tree)->wh) sm = l;
  l = l->next;
 }
 h = sm->tree;
 DelList(sm);
 return h;
}

void CreateTree()
{
 Huf *h, *h1, *h2;
 while (head->next)
 {
  h1 = FindDels();
  h2 = FindDels();
  h = new Huf;
  h->id = ' '; h->wh = (h1->wh)+(h2->wh);
  h->left = h1; h->right = h2;
  AddList(h);
 }
}

void rlrootTree(Huf *h)
{
 if (h)
 {
  rlrootTree(h->right);
  rlrootTree(h->left);
  cout << h->id << " : " << h->wh << endl;
 }
}