Я пытаюсь создать древовидную структуру данных AVL, которая может обрабатывать повторяющиеся ключи элементов. Я основал свой алгоритм вставки на следующем: https://www.sanfoundry.com/c-program-implement-avl-tree/#google_vignette.
Однако моя реализация avl_tree_insert
, похоже, не может обрабатывать некоторые повторяющиеся ключи, и я не знаю, почему; по сути, я определил, что вставка работает, если не выполняются все следующие условия* (*Это исходит из моего собственного понимания проверок, выполняемых моим кодом, в сочетании с выводом отладки. Это может быть ошибочная логика, как отметил по крайней мере один комментарий, чтобы узнать подробности).
left
и right
.Когда код проверяет наличие 4, вызывается вращение влево на левом узле, что приводит к сбою из-за разыменования нулевого указателя (поскольку ( *root ).left
и ( *root ).right
имеют значение null, см. 2).
Что меня смущает, так это то, как понять, как на самом деле возникает это состояние дерева, а также как его исправить; например, вызов avl_tree_insert
со случайными ключами обычно работает сотни или тысячи раз для множества различных рандомизированных состояний дерева, но затем внезапно дерево достигает состояния, подобного описанному выше, и все резко выходит из строя; это то, что я не знаю, как отладить.
Ниже приведен код тестового драйвера, который приведет к сбою при использовании операций avl_tree_insert
. Я попытался включить все соответствующие части древовидной структуры данных и алгоритм вставки ниже, опуская при этом все постороннее, а также включил некоторые операторы отладочной печати.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Adjust for your hardware.
typedef unsigned long long int u64;
typedef signed long long int i64;
typedef signed short int i16;
/** @brief Type and instance definitions for generic boolean type. */
typedef _Bool bool;
#define true ( ( bool ) 1 )
#define false ( ( bool ) 0 )
/** @brief Type definition for an AVL tree node. */
typedef struct node_t
{
i64 key;
u64 depth;
struct node_t* left;
struct node_t* right;
void* content;
}
node_t;
/** @brief Type definition for an AVL tree. */
typedef struct
{
u64 element_width;
u64 element_count;
u64 node_count;
node_t* root;
}
avl_tree_t;
// Global op count.
static u64 op_count = 0;
// Global debug flag.
static bool debug_flag;
bool
avl_tree_create
( u64 element_width
, avl_tree_t** tree_
)
{
if ( !element_width )
{
printf ( "avl_tree_create: Value of element_width argument must be non-zero.\n" );
return false;
}
if ( !tree_ )
{
printf ( "avl_tree_create: Missing argument: tree (output buffer).\n" );
return false;
}
avl_tree_t* tree = malloc ( sizeof ( avl_tree_t ) );
tree->element_width = element_width;
tree->element_count = 0;
tree->node_count = 0;
tree->root = 0;
*tree_ = tree;
return true;
}
u64
avl_tree_recompute_depth
( node_t* root
)
{
u64 left;
u64 right;
if ( !root )
{
return 0;
}
if ( !root->left )
{
left = 0;
}
else
{
left = 1 + root->left->depth;
}
if ( !root->right )
{
right = 0;
}
else
{
right = 1 + root->right->depth;
}
return ( left > right ) ? left : right;
}
i64
avl_tree_balance_factor
( node_t* root
)
{
u64 left;
u64 right;
if ( !root )
{
return 0;
}
if ( !root->left )
{
left = 0;
}
else
{
left = 1 + root->left->depth;
}
if ( !root->right )
{
right = 0;
}
else
{
right = 1 + root->right->depth;
}
return left - right;
}
node_t*
avl_tree_rotate_left
( node_t* root
)
{
// Rotate left.
node_t* right = root->right;
if ( !right ) printf ( "Failure on append #%llu. No node to the right of %i.\n" , op_count , root->key );
root->right = right->left;
right->left = root;
// Update depth.
root->depth = avl_tree_recompute_depth ( root );
right->depth = avl_tree_recompute_depth ( right );
return right;
}
node_t*
avl_tree_rotate_right
( node_t* root
)
{
// Rotate right.
node_t* left = root->left;
if ( !left ) printf ( "Failure on append #%llu. No node to the left of %i.\n" , op_count , root->key );
root->left = left->right;
left->right = root;
// Update depth.
root->depth = avl_tree_recompute_depth ( root );
left->depth = avl_tree_recompute_depth ( left );
return left;
}
node_t*
_avl_tree_insert
( avl_tree_t* tree
, node_t* root
, const void* src
, const i64 key
)
{
// CASE: End of branch.
if ( !root )
{
// Initialize a new element node.
node_t* node = malloc ( sizeof ( node_t ) + tree->element_width );
memset ( node , 0 , sizeof ( node_t ) );
( *node ).key = key;
( *node ).content = ( void* )( ( ( u64 ) node ) + sizeof ( node_t ) );
memcpy ( node->content , src , tree->element_width );
// Insert the node into the tree.
root = node;
// Update state.
tree->node_count += 1;
tree->element_count += 1;
}
// CASE: Key > root key (recurse right).
else if ( key > root->key )
{
root->right = _avl_tree_insert ( tree
, root->right
, src
, key
);
// Rebalance? Y/N
if ( avl_tree_balance_factor ( root ) < -1 )
{
if ( key <= root->right->key )
{
root->right = avl_tree_rotate_right ( root->right );
}
root = avl_tree_rotate_left ( root );
}
}
// CASE: Key <= root key (recurse left).
else
{
if ( key == root->key )
{
debug_flag = true;
printf ( "Keys match: %i, left key: %i, right key: %i\n"
, key
, ( root->left ? root->left->key : -1 )
, ( root->right ? root->right->key : -1 )
);
}
root->left = _avl_tree_insert ( tree
, root->left
, src
, key
);
// Rebalance? Y/N
if ( avl_tree_balance_factor ( root ) > 1 )
{
if ( key >= root->left->key )
{
if ( debug_flag ) printf ("Rotate left required.\n" );
root->left = avl_tree_rotate_left ( root->left );
}
root = avl_tree_rotate_right ( root );
}
}
root->depth = avl_tree_recompute_depth ( root );
return root;
}
void
avl_tree_insert
( avl_tree_t* tree
, const void* src
, const i64 key
)
{
debug_flag = false;
tree->root = _avl_tree_insert ( tree
, tree->root
, src
, key
);
}
// Test driver.
int
main
( void )
{
srand ( time ( 0 ) );
avl_tree_t* tree;
avl_tree_create ( sizeof ( i16 ) , &tree );
for ( u64 i = 0; i < 1000000; ++i )
{
i16 key;
do
{
key = rand ();
}
while ( key == -1 );
op_count += 1;
avl_tree_insert ( tree , &key , key ); // Value can just be same as key here.
}
printf ( "Num elements in tree: %llu\n" , tree->element_count );
return 0;
}
Пример вывода (-1 указывает на нулевой ключ):
Keys match: 2214, left key: -1, right key: -1
Keys match: 5145, left key: -1, right key: -1
Keys match: 7141, left key: -1, right key: -1
Keys match: 16453, left key: -1, right key: -1
Keys match: 15365, left key: -1, right key: -1
Keys match: 22779, left key: 22764, right key: 22816
Keys match: 11855, left key: -1, right key: -1
Rotate left required.
Failure on append #857. No node to the right of 11855.
// (OS crashes the program)
@trincot Возможно, именно это и является причиной проблемы. Может я тогда баланс неправильно посчитал? Я рассмотрю поближе.
Распечатайте случайное начальное число, чтобы можно было детерминированно воспроизвести сбой, когда вы его обнаружите, а затем выполнить отладку этого случая (проверить инварианты, использовать отладчик с обратным выполнением и т. д.).
не по теме, но почему вы избегаете ->
? Это затрудняет чтение кода...
По какой-то конкретной причине вы используете ( *( ( *root ).right ) ).depth
вместо root->right->depth
? Делает код неудобным для чтения. Хотя решать вам.
Макросы — корень всего зла. Я просто удалил ваши макросы (заменил на printf()
, удалил лишнюю скобку в MAX, например ( (a) )
) и вызвал __avl_tree_insert (tree, tree->root, &key, key)
непосредственно в main()
после добавления проверки malloc()
и исправил ваши ссылки на члены структуры в моем комментарии выше, и я получаю Num elements in tree: 1000000
без ошибки. Всегда компилируйте с включенными полными предупреждениями и не принимайте код, пока он не скомпилируется без предупреждения.
Я также удалил MAX из стандарта «ISO-C запрещает группы в фигурных скобках внутри выражений». Почему у вас есть для этого макрос, когда он используется только один раз, немного странно. Просто return left > right ? left : right;
из avl_tree_recompute_depth()
, вы не получаете никакой пользы от макроса в этом коде.
@DavidC.Rankin Отредактировал код для ясности, чтобы устранить эти проблемы. Эти макросы были созданы потому, что эта структура данных является частью гораздо большей библиотеки.
Рад, что у вас все заработало. Я переписал его, но @trincot уловил основную проблему, которую я полностью упустил. (это потребовало внимательного взгляда... хорошая работа - достойна одобрения). Причина, по которой макросы не одобряются, заключается в том, что расширения часто могут приводить к проблемам при добавлении кода, который использует макрос в новых настройках и т. д. (а также проблемы с соответствием стандартам, отмеченные с включенными предупреждениями). Удачи в кодировании!
@DavidC.Rankin Я ценю это. Обычно я компилирую всю библиотеку с помощью -Wall -Werror, чтобы отловить такие вещи, но я случайно забыл об этом при компиляции этого тестового кода в изолированном файле...
Если вы хотите по-настоящему углубиться, добавьте -Wextra -pedantic
, чтобы охватить больше проблем, которые вам «следует» исправить, и добавьте -Wshadow
для проблем, о которых вы должны знать и которые определенно следует исправить.
Это можно было бы воспроизвести, вставив только следующие три значения: 2, 1, 1.
Непосредственная причина проблемы заключается в том, как вы определяете необходимость двойного вращения. Условие для выполнения вращения вправо, а затем влево:
if ( key <= ( *( ( *root ).right ) ).key )
...а для зеркального футляра у вас есть:
if ( key >= ( *( ( *root ).left ) ).key )
Это не правильно. Условием двойного вращения является наличие внутреннего внука. Итак, исправьте эти два условия следующим образом:
if (root->right->left)
и
if (root->left->right)
Я не проверял ваш код после этого момента, но, по крайней мере, это было причиной проблемы, с которой вы столкнулись.
«если не будут выполнены все следующие условия»: но перечисленные вами условия не могут быть выполнены все — некоторые из них несовместимы с другими. Например, № 2 и № 3 не могут быть правдой одновременно.