По сути, двоичное дерево — это просто видоизмененный двусвязный список. Его основное преимущество заключается в возможности быстрого поиска. Именно благодаря этому удается очень быстро выполнять вставки и затрачивать совсем немного времени на доступ к элементам. (Ведь двоичные деревья идеально подходят для приложений, в которых требуется структура связанного списка, в которой поиск должен занимать совсем немного времени.)
Чтобы использовать двоичное дерево для реализации электронной таблицы, необходимо изменить структуру 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 сравнений (если дерево сбалансировано). Кроме того, двоичные деревья так же экономно расходуют память, как и связанные списки. Тем не менее, в некоторых ситуациях есть лучшие альтернативы, чем двоичные деревья.