【问题标题】:Sorting a binary search tree on different key value根据不同的键值对二叉搜索树进行排序
【发布时间】:2010-11-19 07:18:54
【问题描述】:

假设我有一棵二叉树,其节点定义如下。


struct node
{
 int key1 ;
 int key2 ;
}

二叉搜索树是在key1的基础上创建的。现在可以根据 O(1) 空间中的 key2 重新排列二叉搜索树。虽然我可以使用指向节点的指针数组在变量空间中执行此操作。

我需要这个的实际问题是“计算文件中唯一单词的出现次数并以频率降序显示结果”。 这里,一个 BST 节点是


{
 char *word;
 int freq ;
}
BST 首先是根据单词的字母顺序创建的,最后我想要它基于频率。

我在选择数据结构(即 BST)时错了吗?

【问题讨论】:

  • 您不能按频率对 BST 进行排序,因为每个频率必须是唯一的。在您的情况下,情况并非如此。
  • Nick D:如果你看一下常见的 BST 实现,它通常被定义为“左孩子是 =”。值不必是唯一的。对于地图,是的,键必须是唯一的,但我不相信提问者真的在“将键映射到值”意义上使用键。如果我错了,那么是的,你可能是对的,他沉没了。 :(
  • 阿戈尔,我觉得你错了。在 BST 中,我们无法获得多个结果。如果我们找到一个节点,我们就会停止。我们不检查孩子。
  • 仅当您正在寻找单个值时。如果你正在按顺序走二叉树,那么你不要停下来,这样就可以了。

标签: algorithm binary-tree


【解决方案1】:

在您选择的语言中使用 HashTable (Java) 或 Dictionary (.NET) 或等效数据结构(STL 中的 hash_set 或 hash_map)将在计数阶段为您提供 O(1) 次插入,这与二叉搜索树不同插入时会在 O(log n) 到 O(n) 之间,具体取决于它是否平衡自身。如果性能真的那么重要,请确保您尝试将 HashTable 初始化为足够大的大小,这样它就不需要动态调整自身大小,这可能会很昂贵。

至于按频率列出,如果不涉及排序,我无法立即想到一个棘手的方法,即 O(n log n)。

【讨论】:

  • 您可以并行维护 2 个数据结构:一个用于快速查找的 hastable,一个用于对数据进行良好排序的映射/排序数组。但是,如果您想保持两个世界中最好的,每个添加/删除操作都需要您在 2 个单独的线程中在每个数据结构上启动它们。根据 Neeraj 的需要,它可能更难维护。
【解决方案2】:

如果您需要为字典排序输出,则 Map、BST 是很好的选择。

如果您需要混合添加、删除和查找操作,这很好。 我不认为这是你在这里的需要。您加载字典,对其进行排序,然后只在其中查找,对吗? 在这种情况下,排序数组可能是更好的容器。 (参见 Scott Meyer 的 Effective STL 中的第 23 条)。
(更新:只需考虑一个映射可能比排序数组产生更多的内存缓存未命中,因为数组在内存中获取其数据连续,并且作为映射中的每个节点都包含 2 个指向映射中其他节点的指针。当您的对象很简单并且在内存中占用的空间不多时,排序向量可能是更好的选择。我强烈建议您从 Meyer 的书中阅读该项目)

关于您正在谈论的那种排序,您将需要来自 stl 的算法: stable_sort。 这个想法是对字典进行排序,然后在频率键上使用 stable_sort() 进行排序。

它会给出类似的东西(实际上没有测试,但你明白了):

struct Node
{
char * word;
int key;
};

bool operator < (const Node& l, const Node& r)
{
    return std::string(l.word) < std::string(r.word));
}

bool freq_comp(const Node& l, const Node& r)
{
    return l.key < r.key;
}

std::vector<node> my_vector;
... // loading elements
sort(vector.begin(), vector.end());
stable_sort(vector.begin(), vector.end(), freq_comp);

【讨论】:

    【解决方案3】:

    您可以考虑的一种方法是构建两个树。一个由word 索引,一个由freq 索引。

    只要树节点包含指向数据节点的指针,您就可以通过基于word 的树访问if 来更新信息,但稍后通过freq 访问它- 基于树的输出。

    虽然,如果速度真的那么重要,我希望摆脱字符串作为键。字符串比较是出了名的慢。

    如果速度不重要,我认为最好的办法是按照 yves 的建议,根据 word 收集数据并根据 freq 重新排序。

    【讨论】:

    • 如果您的节点同时是两棵树的成员 (struct node { data; struct node *word_left, *word_right, *freq_left, *freq_right }),您甚至可以将数据保存在节点中
    【解决方案4】:

    这是我根据新键重新平衡树的建议(嗯,我有 2 条建议)。

    第一个也是更直接的方法是以某种方式调整 Heapsort 的“起泡”功能(使用 Sedgewick 的名称)。这是指向wikipedia 的链接,他们称之为“筛选”。它不是为完全不平衡的树(这是您所需要的)而设计的,但我相信它展示了树的就地重新排序的基本流程。可能有点难以理解,因为树实际上是存储在数组中而不是树中(尽管某种意义上的逻辑将其视为树) --- 不过,也许你会发现这样一个基于数组的代表是最好的!谁知道呢。

    我的更疯狂的建议是使用伸展树。我认为它们很漂亮,这是wiki link。基本上,您访问的任何元素都会“冒泡”到顶部,但它保持 BST 不变量。因此,您保持原始 Key1 用于构建初始树,但希望大多数“较高频率”值也将靠近顶部。这可能还不够(因为这意味着高频词将“靠近”树的顶部,不一定以任何方式排序),但如果你碰巧拥有或找到或制作了一棵树-平衡算法,它可能在这样的展开树上运行得更快。

    希望这会有所帮助!谢谢你的一个有趣的谜语,这对我来说听起来像是一个很好的 Haskell 项目..... :)

    【讨论】:

      【解决方案5】:

      您可以在 O(1) 空间内轻松完成此操作,但在 O(1) 时间内却不行;-)

      尽管递归地重新排列整个树直到再次排序似乎是可能的,但它可能不是很快 - 最多可能是 O(n),在实践中可能更糟。因此,一旦你完成了树并使用快速排序对这个数组进行排序(平均为 O(log n)),你可能会通过将所有节点添加到数组中来获得更好的结果。至少那是我会做的。即使很难,它也需要额外的空间,这对我来说听起来比重新布置树更有希望。

      【讨论】:

      • 但是 O(1) 空间是 fun 的方式! :D
      【解决方案6】:

      我认为您可以创建一个按freq 排序的新树,然后将所有从旧树中弹出的元素推送到那里。

      可能 O(1) 虽然可能更像O(log N),但它并不大。

      另外,我不知道你如何在 C# 中调用它,但在 Python 中,你可以使用 list,但可以通过两个不同的键就地对其进行排序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多