Представление разреженного массива в виде двоичного дерева

Содержание

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

Чтобы использовать двоичное дерево для реализации электронной таблицы, необходимо изменить структуру cell следующим образом:

struct cell {
  char cell_name[9];  /* имя ячейки, напр., A1, B34 */
  char  formula[128]; /* данные, напр., 10/B2 */
  struct cell *left;  /* указатель на левое поддерево */
  struct cell *right; /* указатель на правое поддерево */
} list_entry;

Функцию street() из главы 22 можно модифицировать так, чтобы она строила дерево на основании имени ячейки. В следующем коде предполагается, что параметр n является указателем на вставляемый элемент дерева.

struct cell *stree(
        struct cell *root,
        struct cell *r,
        struct cell *n)
{
  if(!r) {    /* первая вершина поддерева */
    n->left = NULL;
    n->right = NULL;
    if(!root) return n;  /* первый вход в дерево */
    if(strcmp(n->cell_name, root->cell_name) < 0)
      root->left = n;
    else
      root->right = n;
    return n;
  }

  if(strcmp(r->cell_name, n->cell_name) <= 0)
    stree(r, r->right, n);
  else
    stree(r, r->left, n);

  return root;
}

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

Чтобы удалить ячейку электронной таблицы, можно воспользоваться показанной ниже модифицированной функцией dtree(), принимающей в качестве ключа имя ячейки:

struct cell *dtree(
        struct cell *root,
        char *key)
{
  struct cell *p, *p2;

  if(!root) return root; /* элемент не найден */

  if(!strcmp(root->cell_name, key)) { /* удаление корня */
    /* это означает пустое дерево */
    if(root->left == root->right){
      free(root);
      return NULL;
    }
    /* или если одно из поддеревьев пустое */
    else if(root->left == NULL) {
      p = root->right;
      free(root);
      return p;
    }
    else if(root->right == NULL) {
      p = root->left;
      free(root);
      return p;
    }
    /* или если оба поддерева непустые */
    else {
      p2 = root->right;
      p = root->right;
      while(p->left) p = p->left;
      p->left = root->left;
      free(root);
      return p2;
    }
  }
  if(strcmp(root->cell_name, key)<=0)
    root->right = dtree(root->right, key);
  else root->left = dtree(root->left, key);
  return root;
}

Наконец, для быстрого поиска ячейки электронной таблицы по ее имени можно воспользоваться модифицированной версией функции search().

struct cell *search_tree(
        struct cell *root,
        char *key)
{
  if(!root) return root;  /* пустое дерево */
  while(strcmp(root->cell_name, key)) {
    if(strcmp(root->cell_name, key) <= 0)
      root = root->right;
    else root = root->left;
    if(root == NULL) break;
  }
  return root;
}

Анализ метода представления в виде двоичного дерева


Применение двоичного дерева значительно уменьшает время вставки и поиска элементов по сравнению со связанным списком. Следует помнить, что последовательный поиск требует в среднем n/2 сравнений, где n — количество элементов списка. По сравнению с этим двоичный поиск требует только log2n сравнений (если дерево сбалансировано). Кроме того, двоичные деревья так же экономно расходуют память, как и связанные списки. Тем не менее, в некоторых ситуациях есть лучшие альтернативы, чем двоичные деревья.