Итерация по карте

В этом вопросе я не спрашиваю, как это сделать, а КАК ЭТО СДЕЛАНО.
Я пытаюсь (в качестве упражнения) реализовать простую карту, и хотя у меня нет проблем с реализацией ссылок и их поведением (как найти следующее место для вставки новой ссылки и т. д.) Я застрял с проблемой, как реализовать итерацию по карте. Когда вы думаете об этом и смотрите на std::map, эта карта может возвращать начало и конец итератора. Как? Особенно конец?
Если карта — это дерево, как можно определить, какая ветвь этой карты является концом? Я просто этого не понимаю. Как перебрать карту? Начиная с вершины дерева, а потом что? Пойти и перечислить все слева? Но те узлы слева также имеют ссылки справа. Я действительно не знаю. Буду очень рад, если кто-нибудь объяснит мне это или даст ссылку, чтобы я мог об этом прочитать.


person There is nothing we can do    schedule 01.08.2010    source источник


Ответы (4)


Представление итератора вашей карты полностью зависит от вас. Я думаю, что достаточно использовать один обернутый указатель на node. Например.:

template <typename T>
struct mymapiterator
{
  typename mymap<T>::node * n;
};

Или что-то подобное. Теперь mymap::begin() может вернуть такой экземпляр итератора, что n будет указывать на крайний левый узел. mymap::end() может возвращать экземпляр с n, указывающим на корень, вероятно, или на какой-то другой специальный узел, из которого все еще можно вернуться к крайнему правому узлу, чтобы он мог удовлетворить двунаправленную итерацию от конечного итератора.

Операция перемещения между узлами (operators++() и operator--() и т. д.) заключается в обходе дерева от меньших значений к большим или наоборот. Операция, которую вы, вероятно, уже реализовали при реализации операции вставки.

person wilx    schedule 01.08.2010
comment
Вероятно, он реализовал обходы как рекурсивную функцию, которая неявно отслеживает состояние в стеке выполнения и счетчике программ — трудная часть состоит в том, как сделать это явным в языке, в котором нет сопрограмм. - person Ken Bloom; 01.08.2010

map реализован с использованием двоичного дерева поиска. Чтобы соответствовать требованиям сложности, это должно быть самобалансирующееся дерево, поэтому обычно используется красно-черное дерево, но это не влияет на то, как вы выполняете итерацию по дереву.

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

Вы можете реализовать итеративный обход по порядку. Это реализация, взятая из библиотеки древовидных контейнеров, которую я написал некоторое время назад. NodePointerT — это указатель на узел, где узел имеет left_, right_ и parent_ указатели типа NodePointerT.

// Gets the next node in an in-order traversal of the tree; returns null
// when the in-order traversal has ended
template <typename NodePointerT>
NodePointerT next_inorder_node(NodePointerT n)
{
    if (!n) { return n; }

    // If the node has a right child, we traverse the link to that child
    // then traverse as far to the left as we can:
    if (n->right_)
    {
        n = n->right_;
        while (n->left_) { n = n->left_; }
    }
    // If the node is the left node of its parent, the next node is its
    // parent node:
    else if (n->parent_ && n == n->parent_->left_)
    {
        n = n->parent_;
    }
    // Otherwise, this node is the furthest right in its subtree; we 
    // traverse up through its parents until we find a parent that was a
    // left child of a node.  The next node is that node's parent.  If 
    // we have reached the end, this will set node to null:
    else
    {
        while (n->parent_ && n == n->parent_->right_) { n = n->parent_; }
        n = n->parent_;
    }
    return n;
}

Чтобы найти первый узел для итератора begin, вам нужно найти самый левый узел в дереве. Начиная с корневого узла, следуйте указателю левого дочернего элемента, пока не встретите узел, у которого нет левого дочернего узла: это первый узел.

Для итератора end вы можете установить указатель узла так, чтобы он указывал на корневой узел или на последний узел в дереве, а затем оставить в итераторе флаг, указывающий, что это конечный итератор (is_end_ или что-то в этом роде).

person James McNellis    schedule 01.08.2010

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

map<string, int> my_map;
my_map["Hello"] = 1;
my_map["world"] = 2;
for (map<string, int>::const_iterator i = my_map.begin(); i != my_map.end(); ++i)
    cout << i->first << ": " << i->second << endl;

Как и любой другой тип итератора, итератор карты ведет себя как указатель на элемент коллекции, а для карты это std::pair, где first сопоставляется с ключом, а second сопоставляется со значением.

std::map использует внутренний бинарный поиск, когда вы вызываете его метод find() или используете оператор [], но вам никогда не понадобится прямой доступ к представлению дерева.

person tdammers    schedule 01.08.2010
comment
std::map всегда реализуется как сбалансированное бинарное дерево. - person ; 01.08.2010
comment
@Neil, обычно это реализуется как красно-черное дерево (которое сбалансировано лишь частично). - person avakar; 01.08.2010
comment
@avakar Я думаю, что большинство людей назвали бы деревья RB сбалансированными - я согласен, что они не идеально сбалансированы. - person ; 01.08.2010
comment
Этого я не знал. Я бы подумал, что это просто старый массив ключей/значений, и что бинарный поиск будет выполняться непосредственно в массиве. Однако в целях итерации он ведет себя точно так же, как обычный список, поэтому моя основная мысль остается в силе. Я отредактировал текст, чтобы он был более правильным в отношении внутренней реализации. - person tdammers; 01.08.2010
comment
вставка в массив была бы слишком медленной; требуется линейное время, чтобы переместить следующие элементы, чтобы освободить место, в то время как map::insert должно занять логарифмическое время или лучше. (Достаточно хорошо) сбалансированное дерево делает возможным как вставку, так и поиск за логарифмическое время. - person Mike Seymour; 01.08.2010

Одна большая хитрость, которую вы можете упустить, заключается в том, что итератору end() не нужно ни на что указывать. Это может быть NULL или любое другое специальное значение.

Оператор ++ устанавливает для итератора то же специальное значение, когда он проходит за конец карты. Тогда все работает.

Чтобы реализовать ++, вам может понадобиться сохранить указатели next/prev в каждом узле, или вы можете вернуться вверх по дереву, чтобы найти следующий узел, сравнив узел, который вы только что оставили, с самым правым узлом родителя, чтобы увидеть, нужно ли вам идти к этому родительскому узлу и т. д.

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

person Zan Lynx    schedule 02.08.2010