Двоичное дерево в Objective-C

Я изучаю алгоритмы и структуры данных и тренируюсь. Я пытаюсь спроектировать и реализовать двоичное дерево с помощью objective-c.

До сих пор у меня были следующие классы:

  • main - для тестирования
  • Node - node дерева
  • BinaryTree - для всех методов, связанных с деревом

Один из первых методов в BinaryTree реализуемом классе я - insertNode:forRoot:.

- (void)insertNodeByRef:(Node **)node forRoot:(Node **)root{

    if (head == NULL) {
        head = *node;
    }
    // Case 2 root is null so can assign the value of the node to it
    if (root == NULL) {
        root = node;
    } else {
        if (node.data > root.data) { // to the right
            [self insertNode:node forRoot:root.right];
        } else if (node.data < root.data) { //or to the left
            [self insertNode:node forRoot:root.left];
        }
    }
}

Если интерфейс класса Node выглядит следующим образом:

@interface Node : NSObject

@property(nonatomic, assign) int data;
@property(nonatomic, strong) Node * right;
@property(nonatomic, strong) Node * left;

@end

Моя проблема в том, что я не знаю, как получить доступ к переменным-членам класса node, если я передаю node в качестве ссылки. Всякий раз, когда я пытаюсь получить доступ к свойствам node (например, данные, слева или справа), появляется следующее сообщение об ошибке:

Member reference base type 'Node *__autoreleasing *' is not a structure or union

Итак, мои вопросы: как я могу получить доступ к этим свойствам (данные, слева или справа) и использовать их для хранения либо данных int, либо ссылки на другой Node?

Надеюсь, что это имеет смысл. Спасибо!

Ответ 1

Ваш код смешивает два общих подхода к задаче, поэтому проблема. Вы также используете подход типа абстрактного типа данных (ADT), а не объектно-ориентированный, поэтому есть три подхода к рассмотрению.

В обоих подходах ADT ваше дерево представлено ссылкой на его корень, в Objective-C это, вероятно, сохраняется в переменной экземпляра:

Node *TreeRoot;

Обратите внимание также, что оба этих алгоритма используют ссылки на поле, a->b, а не ссылки на свойства, a.b - это потому, что первая ссылается на переменную, а второй алгоритм требует передачи ссылки на переменную.

Функциональный ADT: передача по значению и назначение результата

В этом подходе node вставляется в дерево и возвращается модифицированное дерево, которое назначается обратно, например. вызов верхнего уровня для вставки Node nodeToInsert будет:

TreeRoot = insertNode(nodeToInsert, TreeRoot);

и функция insertNode выглядит так:

Node *insertNode(Node *node, Node *root)
{
   if(root == nil)
   {  // empty tree - return the insert node
      return node;
   }
   else
   {  // non-empty tree, insert into left or right subtree
      if(node->data > root->data) // to the right
      {
        root->right = insertNode(node, root->right);
      }
      else if(node->data < root->data)//or to the left
      {
        root->left = insertNode(node, root->left);
      }
      // tree modified if needed, return the root
      return root;
   }
}

Обратите внимание, что в этом подходе в случае непустого (под) дерева алгоритм выполняет избыточное присваивание в переменной - назначенное значение - это то, что уже находится в переменной... Из-за этого некоторые люди предпочитают:

Процедурный ADT: Pass-by-reference

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

insertNode(nodeToInsert, &TreeRoot); // & -> pass the variable, not its value

и процедура insertNode выглядит следующим образом:

void insertNode(Node *node, Node **root)
{
   if(*root == nil)
   {  // empty tree - insert node
      *root = node;
   }
   else
   {  // non-empty tree, insert into left or right subtree
      Node *rootNode = *root;
      if(node->data > rootNode->data) // to the right
      {
        insertNode(node, &rootNode->right);
      }
      else if(node->data < rootNode->data)//or to the left
      {
        insertNode(node, &root->left);
      }
   }
}

Теперь вы можете видеть, что ваш метод представляет собой смесь двух вышеупомянутых подходов. Оба значения действительны, но поскольку вы используете Objective-C, лучше использовать третий подход:

Объектно-ориентированный ADT

Это вариация процедурного ADT - вместо передачи переменной в процедуру переменная, называемая теперь объектом, владеет методом, который обновляет себя. Выполнение этого способа означает, что вы должны протестировать пустое (вспомогательное) дерево перед тем, как сделать вызов, чтобы вставить node, в то время как предыдущие два подхода проверяются в вызове. Итак, теперь у нас есть метод в Node:

- (void) insert:(Node *)node
{
   if(node.data > self.data) // using properties, could also use fields ->
   {
      if(self.right != nil)
         [self.right insert:node];
      else
         self.right = node;
   }
   else if(node.data < rootNode.data)
   {
      if(self.left != nil)
         [self.left insert:node];
      else
         self.left = node;
   }
}

Вам также необходимо изменить вызов верхнего уровня, чтобы выполнить тот же тест для пустого дерева:

if(TreeRoot != nil)
   [TreeRoot insert:nodeToInsert];
else
   TreeRoot = nodeToInsert;

И последнее замечание - если вы используете MRC, а не ARC или GC, для управления памятью вам нужно будет вставить соответствующие вызовы сохранения/выпуска.

Надеюсь, что это поможет вам разобраться.

Ответ 2

Прежде всего, не пишите свои методы, чтобы взять Node **. Это просто запутывает.

Во-вторых, подумайте о том, как это должно работать. Опишите себе, как он должен работать на довольно абстрактном уровне. Переведите это описание непосредственно в код, придумывая новые (еще не написанные!) Сообщения там, где это необходимо. Если есть шаги, которые вы еще не знаете, как это сделать, просто отбросьте их на новые сообщения, которые вы напишете позже. Я пройду через это.

Предположительно вы хотите, чтобы публичный API BinaryTree включал это сообщение:

@interface BinaryTree

- (void)insertValue:(int)value;

Итак, как вы реализуете insertValue:? Представьте, что вы - объект BinaryTree. Какое ваше высокоуровневое описание того, что вам нужно сделать, чтобы вставить значение? Вы хотите создать новый Node. Затем вы хотите вставить этот новый Node в себя. Переведите это описание непосредственно в код:

@implementation BinaryTree {
    Node *root_; // root node, or nil for an empty tree
}

- (void)insertValue:(int)value {
    Node *node = [[Node alloc] initWithData:value];
    [self insertNode:node];
}

Теперь подумайте о том, как вы вставляете. Ну, если вы пустое дерево, ваш root_ равен нулю, и вы можете просто установить его на новый node. В противном случае вы можете просто попросить root node вставить новый node под себя. Переведите это описание непосредственно в код:

- (void)insertNode:(Node *)node {
    if (root_ == nil) {
        root_ = node;
    } else {
        [root_ insertNode:node];
    }
}

Теперь притворись, что ты Node. Вас попросили вставить новый Node под себя. Как ты делаешь это? Вы должны сравнить новое значение node с вашим значением. Если новое значение node меньше вашего значения, вы хотите вставить новый node с левой стороны. В противном случае вы хотите вставить его с правой стороны. Переведите это описание непосредственно в код:

@implementation Node

- (void)insertNode:(Node *)node {
    if (node.data < self.data) {
        [self insertNodeOnLeftSide:node];
    } else {
        [self insertNodeOnRightSide:node];
    }
}

Теперь вы по-прежнему Node, и вас попросили вставить новый node с левой стороны. Как ты делаешь это? Ну, если у вас еще нет ребенка с левой стороны, просто используйте новый node как ваш левый ребенок. В противном случае вы попросите своего левого ребенка вставить новый node под себя. Переведите это описание непосредственно в код:

- (void)insertNodeOnLeftSide:(Node *)node {
    if (self.left == nil) {
        self.left = node;
    } else {
        [self.left insertNode:node];
    }
}

Я оставлю реализацию insertNodeOnRightSide: в качестве упражнения для читателя.; ^)

Ответ 3

(Node **)node является указателем на указатель объекта, поэтому node.something является недопустимым, потому что вы являетесь ссылкой далеко от объекта.

Но (*node).something будет работать.


Дополнение для комментариев:
Когда вы изначально вызываете этот метод: -(void)insertNodeByRef:(Node **)node forRoot:(Node **)root как вы его называете?
От ошибки, которую вы публикуете в своем комментарии, посмотрите на меня, что вы делаете:
Node *n = [[Node alloc] init];
[aNode insertNodeByRef:n forRoot:aRoot];

когда ваша подпись подписи вашего устройства должна быть вызвана следующим образом:
[aNode insertNodeByRef:&n forRoot:&aRoot];
Чтобы передать адрес указателя на объект.

Я говорю это, потому что ваша ошибка теперь заявляет, что вы отправляете Node * вместо Node **, которые являются двумя разными. ((Incompatible pointer types sending 'Node *' to parameter of type 'Node **') Я удаляю __autoreleasing между 2 *, это заслоняет сообщение об ошибке.)
Таким образом, другими словами, вы передаете pointer to an object, когда ваш метод запрашивает pointer TO A pointer to an object.

Ответ 4

У вашего кода, на мой взгляд, много логических ошибок. Возможно, рассмотрите вопрос о том, какой указатель на указатель должен гарантировать, что вы создаете желаемый эффект. Аналогично, вам нужно разыменовать node/root для доступа к ним в нормальном состоянии. В противном случае ошибка действительна, Node ** не является типом структуры или объединения.