卡在Trie树迭代器实现上

10

我需要实现一个自制的 Trie,但在迭代器部分遇到了困难。我似乎无法想出 trie 的增量方法。

希望有人能帮助我澄清这些问题。

以下是迭代器的代码:

template <typename T> class Trie<T>::IteratorPrefixe{
friend class Trie<T>;
public:
    IteratorPrefixe() : tree(NULL), currentNode(NULL), currentKey("") {};
    pair<string, T*> operator*() {return make_pair(currentKey, currentNode -> element);} ;
    IteratorPrefixe operator++()throw(runtime_error);
    void operator=(IteratorPrefixe iter) {tree = iter.tree; currentNode = iter.currentNode; currentKey = iter.currentKey;};
    bool operator==(IteratorPrefixe iter) {return tree == iter.tree && currentNode == iter.currentNode;};
    bool operator!=(IteratorPrefixe iter) {return tree != iter.tree || currentNode != iter.currentNode;};
private:
    Trie<T> * tree;
    Trie<T> * currentNode;
    string currentKey;
};

这是我的Trie树实现代码:
template <typename T> class Trie {
friend class IteratorPrefixe;
public:
    // Create a Trie<T> from the alphabet of nbletters, where nbletters must be
    // between 1 and NBLETTERSMAX inclusively
    Trie(unsigned nbletters) throw(runtime_error);

    // Add a key element of which is given in the first argument and content second argument
    // The content must be defined (different from NULL pointer)
    // The key is to be composed of valid letters (the letters between A + inclusive and exclusive nbletters
    //      Eg  if nblettres is 3, a, b and c are the only characters permitted;
    //          If nblettres is 15, only the letters between a and o inclusive are allowed.
    // Returns true if the insertion was achieved, returns false otherwise.
    bool addElement(string, T*) throw(runtime_error);
    // Deletes a key element of which is given as an argument and returns the contents of the node removed
    // The key is to be composed of letters valid (see above)
    // Can also delete at the same time the reference of the ancestors, if these ancestors are no longer used.
    // Returns NULL if the item has no delete
    T* removeElement(string cle) throw(runtime_error);
    // Find a key element of which is given as an argument and returns the associated content
    // The key is to be composed of letters valid (see above)
    // Returns NULL if the key does not exist
    T* searchElement(string cle) throw();
    // Iterator class to browse the Trie <T> in preorder mode
    class IteratorPrefixe;
    // Returns an iterator pointing to the first element
    IteratorPrefixe pbegin() throw(runtime_error);
    // Returns an iterator pointing beyond the last item
    IteratorPrefixe pend() throw();

private:
    unsigned nbLetters;
    T* element;
    vector<Trie<T> *> childs;
    Trie<T> * parent;

    // This function removes a node and its ancestors if became unnecessary. It is essentially the same work
    // as deleteElement that is how to designate remove a node that is changing. Moreover, unlike
    // deleteElement, it does not return any information on the node removed.
    void remove(Trie<T> * node) throw();

    // This function is seeking a node based on a given key. It is essentially the same work
    // searchElement but that returns a reference to the node found (or null if the node does not exist)
    // The key is to be composed of letters valid (see above)
    Trie<T>* search(string key) throw(runtime_error);
};
3个回答

7

很高兴看到Trie树仍然被教授,它们是一种重要的数据结构,经常被忽视。

你的代码可能存在设计问题,因为你应该有一个Trie类和一个Node类。从你的写法来看,每个Trie中的节点都是自己的Trie,这样做虽然可行,但会使一些事情变得复杂。

从你的问题中并不清楚你遇到的问题是什么:确定顺序还是编写实际代码?

从迭代器的名称来看,它似乎必须按前缀顺序工作。由于你的Trie存储单词,其子节点按字母组织,所以你实际上期望按字母表顺序遍历所有单词。每次递增都会带你到下一个单词。

关于你的迭代器的不变量是,在任何时候(只要它有效),它应该指向具有“终止字符”的节点,表示一个有效的单词。确定那个单词只需要沿着父链向上扫描,直到找到整个字符串。移动到下一个单词意味着进行DFS搜索:向上走一次,在后面的“兄弟”中扫描链接,查找单词,如果没有则递归向上,等等。


好答案。顺便说一下,有时候将条目以相反的顺序存储会更好,比如域名。 - Josh

2
您可能想查看我修改过的trie实现,网址如下: 具体来说,您可以看看我在comp.lang.c++.moderated上的讨论,关于如何以符合STL标准的方式为trie实现迭代器。这是一个问题,因为所有STL容器都不幸地被迫使用std::pair<>,因此迭代器必须包含值而不仅仅是对trie中单个节点的引用。

你的页面上什么都找不到? - Enno
抱歉 - 服务器已经迁移,现在该URL应该可以使用了。源代码在Mercurial上,位于http://opensource.jdkoftinoff.com/hg-oss/jdktrie,也在SVN上,位于http://opensource.jdkoftinoff.com/jdks/svn/trunk/jdktrie/trunk。 - jdkoftinoff
1
那些链接又挂了 :/ - George Hilliard
抱歉再次打扰!github更好用:https://github.com/jdkoftinoff/jdktrie/tree/master/include/jdktrie - jdkoftinoff

0
首先,所展示的代码实际上并没有描述一个 trie。相反,它似乎是一个包含每个节点中一对元素(T*unsigned)的树。你可以通过使用元组树来模拟 trie 的行为,但这只是一种约定,并非强制性规定。这也是为什么你在实现 operator++ 时遇到了困难的原因之一。
你需要做的是让每个 Trie 包含一个左右不交的 ADT,而不仅仅是原始元素。这是一种更常见于函数式语言(例如 Scala 的 Either)的抽象层次。不幸的是,C++ 的类型系统并不足够强大,无法实现如此优雅的操作。但是,这并不妨碍你这样做:
template <class L, class R>
class Either
{
public:

    Either(L *l) : left(l), right(0)
    {}

    Either(R *r) : left(0), right(r)
    {}

    L *get_left() const
    {
        return left;
    }

    R *get_right() const
    {
        return right;
    }

    bool is_left() const
    {
        return left != 0;
    }

    bool is_right() const
    {
        return right != 0;
    }

private:
    L *left;
    R *right;
};

那么你的 Trie 的数据成员应该定义如下:

private:
    Either<unsigned, T*> disjoint;

    vector<Trie<T> *> children;    // english pluralization
    Trie<T> * parent;

我在处理你的指针时有些随意,但是你应该能够理解我的意思。重要的部分是任何给定的节点都不能同时包含无符号T*

试试这个,看看是否有帮助。我认为你会发现能够轻松确定你正在遍历的是叶子节点还是分支节点将会对你的迭代尝试有很大的帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接