【问题标题】:Why is insertion into my tree faster on sorted input than random input?为什么在排序输入上插入我的树比随机输入更快?
【发布时间】:2011-01-27 02:54:06
【问题描述】:

现在我一直听说从随机选择的数据构建二叉搜索树比从有序数据构建更快,这仅仅是因为有序数据需要显式重新平衡以将树高度保持在最低水平。

最近我实现了一个不可变的treap,这是一种特殊的二叉搜索树,它使用随机化来保持自身相对平衡。与我的预期相反,我发现我可以始终如一地从有序数据中构建一个比无序数据快 2 倍并且通常更好地平衡 - 我不知道为什么。

这是我的trap实现:

这是一个测试程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace ConsoleApplication1
{

    class Program
    {
        static Random rnd = new Random();
        const int ITERATION_COUNT = 20;

        static void Main(string[] args)
        {
            List<double> rndTimes = new List<double>();
            List<double> orderedTimes = new List<double>();

            rndTimes.Add(TimeIt(50, RandomInsert));
            rndTimes.Add(TimeIt(100, RandomInsert));
            rndTimes.Add(TimeIt(200, RandomInsert));
            rndTimes.Add(TimeIt(400, RandomInsert));
            rndTimes.Add(TimeIt(800, RandomInsert));
            rndTimes.Add(TimeIt(1000, RandomInsert));
            rndTimes.Add(TimeIt(2000, RandomInsert));
            rndTimes.Add(TimeIt(4000, RandomInsert));
            rndTimes.Add(TimeIt(8000, RandomInsert));
            rndTimes.Add(TimeIt(16000, RandomInsert));
            rndTimes.Add(TimeIt(32000, RandomInsert));
            rndTimes.Add(TimeIt(64000, RandomInsert));
            rndTimes.Add(TimeIt(128000, RandomInsert));
            string rndTimesAsString = string.Join("\n", rndTimes.Select(x => x.ToString()).ToArray());

            orderedTimes.Add(TimeIt(50, OrderedInsert));
            orderedTimes.Add(TimeIt(100, OrderedInsert));
            orderedTimes.Add(TimeIt(200, OrderedInsert));
            orderedTimes.Add(TimeIt(400, OrderedInsert));
            orderedTimes.Add(TimeIt(800, OrderedInsert));
            orderedTimes.Add(TimeIt(1000, OrderedInsert));
            orderedTimes.Add(TimeIt(2000, OrderedInsert));
            orderedTimes.Add(TimeIt(4000, OrderedInsert));
            orderedTimes.Add(TimeIt(8000, OrderedInsert));
            orderedTimes.Add(TimeIt(16000, OrderedInsert));
            orderedTimes.Add(TimeIt(32000, OrderedInsert));
            orderedTimes.Add(TimeIt(64000, OrderedInsert));
            orderedTimes.Add(TimeIt(128000, OrderedInsert));
            string orderedTimesAsString = string.Join("\n", orderedTimes.Select(x => x.ToString()).ToArray());

            Console.WriteLine("Done");
        }

        static double TimeIt(int insertCount, Action<int> f)
        {
            Console.WriteLine("TimeIt({0}, {1})", insertCount, f.Method.Name);

            List<double> times = new List<double>();
            for (int i = 0; i < ITERATION_COUNT; i++)
            {
                Stopwatch sw = Stopwatch.StartNew();
                f(insertCount);
                sw.Stop();
                times.Add(sw.Elapsed.TotalMilliseconds);
            }

            return times.Average();
        }

        static void RandomInsert(int insertCount)
        {
            Treap<double> tree = new Treap<double>((x, y) => x.CompareTo(y));
            for (int i = 0; i < insertCount; i++)
            {
                tree = tree.Insert(rnd.NextDouble());
            }
        }

        static void OrderedInsert(int insertCount)
        {
            Treap<double> tree = new Treap<double>((x, y) => x.CompareTo(y));
            for(int i = 0; i < insertCount; i++)
            {
                tree = tree.Insert(i + rnd.NextDouble());
            }
        }
    }
}

这是一个比较随机和有序插入时间(以毫秒为单位)的图表:

Insertions         Random          Ordered         RandomTime / OrderedTime
50                 1.031665        0.261585        3.94
100                0.544345        1.377155        0.4
200                1.268320        0.734570        1.73
400                2.765555        1.639150        1.69
800                6.089700        3.558350        1.71
1000               7.855150        4.704190        1.67
2000               17.852000       12.554065       1.42
4000               40.157340       22.474445       1.79
8000               88.375430       48.364265       1.83
16000              197.524000      109.082200      1.81
32000              459.277050      238.154405      1.93
64000              1055.508875     512.020310      2.06
128000             2481.694230     1107.980425     2.24

我在代码中看不到任何使有序输入渐近地比无序输入更快的东西,所以我无法解释其中的区别。

为什么从有序输入构建一个陷阱比随机输入要快得多?

【问题讨论】:

  • 测量标准差可能会很有趣,所以也许你得到一个很好的平均值,但随机波动较小。
  • 我对treap和avl或rb-tree的比较感兴趣:)
  • 添加计数器来计算更改树结构的频率会很有趣。您可以将其与计算值进行比较。
  • 您是如何使用一些特殊工具或自己编写的来测试您的性能的?
  • 这意味着要从无序输入构建一个treap,我们可能会分批处理它,使用快速排序算法对每个批次进行排序,并使用treap 合并而不是插入。批处理应该由读取访问分隔:因此,我们用一对(treap,未刷新的插入)表示treap,并在每次读取操作之前刷新插入。顺便说一句,可能有明确的算法可以更快地从排序的输入构建陷阱。

标签: c# performance data-structures treap


【解决方案1】:

试试这个:treap 上的数据库。

http://code.google.com/p/treapdb/

【讨论】:

    【解决方案2】:

    Aaronaught 做了一个非常体面的工作来解释这一点。

    对于这两种特殊情况,我发现在插入路径长度方面更容易掌握。

    对于随机输入,您的插入路径会下降到叶子之一,并且路径的长度(即旋转次数)受树的高度限制。

    在排序的情况下,你走在treap的right spine上,边界是脊椎的长度,小于或等于高度。

    由于您沿插入路径旋转节点并且在这种情况下您的插入路径是脊椎,因此这些旋转将始终缩短脊椎(这将导致下一次插入时插入路径更短,因为插入路径只是脊柱等)

    编辑:对于随机情况,插入路径要长 1.75 倍。

    【讨论】:

    • 这也是很重要的一点。我记得昨天挖掘的其中一件事是,树干的高度比整棵树的高度短很多 - 一棵高度为 30 的树可能有一个短至 15 或甚至 10 个节点。所以这实际上是对性能的三重打击 - 重新平衡的频率较低并且成本较低并且每个插入的查找时间更短!我不确定哪个因素是主导因素,但它们一起肯定会产生重大影响。
    • @Aaronaught:@Juliet 找不到更复杂的数据结构来分析。 :-) 这个野兽具有三合一:二叉树的属性,二叉堆的属性,整体被一些概率论所激发(这是没有分析现实世界硬件上的运行时特性)。我敢打赌他们会用它来折磨 CS 学生。哦,哦。是的,他们这样做了:ocw.mit.edu/NR/rdonlyres/…xD
    • @Aaronaught:你关于主导因素的问题真的困扰着我,所以我更新了答案。 ;-)
    • @Aaronaught:问题是,这个陷阱是不可变的,所以时间包括每次改造后的重建成本。这不是必须考虑到所有关于性能的推理吗?
    • @Mike Dunlavey:不变性肯定使它比等效的可变结构更昂贵,但它应该是一个恒定的开销——不应该对排序插入和随机插入产生任何重大影响,因为它是相同的两种方式的节点数。
    【解决方案3】:

    您只看到大约 2 倍的差异。除非你已经从这段代码中调整了日光,否则这基本上是在噪音中。大多数编写良好的程序,尤其是那些涉及数据结构的程序,很容易有比这更大的改进空间。 Here's an example.

    我刚刚运行了您的代码并拍摄了一些堆栈快照。这是我看到的:

    随机插入:

    1 Insert:64 -> HeapifyLeft:81 -> RotateRight:150
    1 Insert:64 -> Make:43 ->Treap:35
    1 Insert:68 -> Make:43
    

    有序插入:

    1 Insert:61
    1 OrderedInsert:224
    1 Insert:68 -> Make:43
    1 Insert:68 -> HeapifyRight:90 -> RotateLeft:107
    1 Insert:68
    1 Insert:68 -> Insert:55 -> IsEmpty.get:51
    

    这是一个相当少的样本数量,但它表明在随机输入的情况下,Make(第 43 行)正在消耗更多的时间。就是这段代码:

        private Treap<T> Make(Treap<T> left, T value, Treap<T> right, int priority)
        {
            return new Treap<T>(Comparer, left, value, right, priority);
        }
    

    然后,我拍摄了 20 个随机插入代码的堆栈快照,以更好地了解它在做什么:

    1 Insert:61
    4 Insert:64
    3 Insert:68
    2 Insert:68 -> Make:43
    1 Insert:64 -> Make:43
    1 Insert:68 -> Insert:57 -> Make:48 -> Make:43
    2 Insert:68 -> Insert:55
    1 Insert:64 -> Insert:55
    1 Insert:64 -> HeapifyLeft:81 -> RotateRight:150
    1 Insert:64 -> Make:43 -> Treap:35
    1 Insert:68 -> HeapifyRight:90 -> RotateLeft:107 -> IsEmpty.get:51
    1 Insert:68 -> HeapifyRight:88
    1 Insert:61 -> AnonymousMethod:214
    

    这揭示了一些信息。
    25% 的时间花在 Make:43 或其被调用者上。
    15% 的时间花在该行上,而不是在公认的例程上,换句话说,new 创建一个新节点。
    90% 的时间花在 Insert:64 和 68 行(调用 Make 和 heapify。
    10% 的时间花在 RotateLeft 和 Right。
    15% 的时间花在 Heapify 或其被调用者上。

    我也做了相当多的单步处理(在源代码级别),并开始怀疑,由于树是不可变的,它会花费大量时间来创建新节点,因为它不想改变旧的。然后旧的被垃圾回收,因为没有人再引用它们了。

    这一定是低效的。

    我仍然没有回答你为什么插入有序数字比随机生成的数字更快的问题,但这并不让我感到惊讶,因为树是不可变的。

    我认为你不能指望任何关于树算法的性能推理很容易转移到不可变树,因为树深处最轻微的变化会导致它在返回的路上被重建,在@ 987654327@-ing 和垃圾回收。

    【讨论】:

      【解决方案4】:

      存在自平衡树以修复与非随机分布数据相关的问题。根据定义,它们牺牲了一些最佳情况的性能,以极大地提高与非平衡 BST 相关的最坏情况的性能,特别是排序输入的性能。

      您实际上是在想这个问题,因为随机数据与有序数据的较慢插入是任何平衡树的特征。在 AVL 上尝试一下,您会看到相同的结果。

      Cameron 的想法是正确的,取消了优先级检查以强制执行最坏的情况。如果你这样做并检测你的树,这样你就可以看到每个插入发生了多少重新平衡,实际上发生了什么变得非常明显。插入排序后的数据时,树总是向左旋转,根的右孩子总是空的。由于插入节点没有子节点并且不会发生递归,因此插入总是会导致恰好一次重新平衡。另一方面,当您在随机数据上运行它时,几乎立即您开始看到每次插入都发生了多次重新平衡,在最小的情况下(50 次插入)多达 5 或 6 次,因为它发生在子树上好吧。

      重新启用优先级检查后,不仅由于更多节点被推入左子树(它们永远不会出现,因为那里没有发生插入),重新平衡通常更便宜,而且它们也不太可能发生。为什么?因为在treap中,高优先级节点浮动到顶部,并且不断的左旋转(不伴随右旋转)开始将所有高优先级节点也推入左子树。结果是由于概率分布不均匀,重新平衡发生的频率降低了。

      如果您检测再平衡代码,您会发现这是真的;对于排序和随机输入,您最终会得到几乎相同数量的左旋转,但随机输入也给出相同数量的右旋转,这总共是两倍。这应该不足为奇 - 高斯输入应该导致旋转的高斯分布。您还会看到,排序后的输入只有大约 60-70% 的顶级重新平衡,这可能 令人惊讶,再次,这是由于排序后的输入混淆了自然优先级分配。

      您还可以通过检查插入循环末尾的完整树来验证这一点。在随机输入的情况下,优先级往往会按级别线性降低;使用排序后的输入,优先级往往会保持很高,直到您从底部到达一两个级别。

      希望我在解释这一点方面做得不错……如果其中任何内容过于模糊,请告诉我。

      【讨论】:

      • +1, +answer:这实际上很有意义,我很感激。也感谢其他所有人:)
      【解决方案5】:

      我运行了您的代码,我认为这与旋转次数有关。在有序输入期间,旋转次数是最优的,树永远不必旋转回来。

      在随机输入期间,树必须执行更多的旋转,因为它可能必须来回旋转。

      要真正找出答案,我必须为每次运行的左右旋转次数添加计数器。你可以自己做这个。

      更新:

      我在 rotateleft 和 rotateright 上设置了断点。在有序输入期间,从不使用rotateright。在随机输入期间,两者都被击中,在我看来,它们的使用频率更高。

      更新 2:

      我在 50 项有序运行中添加了一些输出(为了清楚起见,用整数代替),以了解更多信息:

      TimeIt(50, OrderedInsert)
      LastValue = 0, Top.Value = 0, Right.Count = 0, Left.Count = 0
      RotateLeft @value=0
      LastValue = 1, Top.Value = 1, Right.Count = 0, Left.Count = 1
      LastValue = 2, Top.Value = 1, Right.Count = 1, Left.Count = 1
      LastValue = 3, Top.Value = 1, Right.Count = 2, Left.Count = 1
      RotateLeft @value=3
      RotateLeft @value=2
      RotateLeft @value=1
      LastValue = 4, Top.Value = 4, Right.Count = 0, Left.Count = 4
      LastValue = 5, Top.Value = 4, Right.Count = 1, Left.Count = 4
      LastValue = 6, Top.Value = 4, Right.Count = 2, Left.Count = 4
      RotateLeft @value=6
      LastValue = 7, Top.Value = 4, Right.Count = 3, Left.Count = 4
      LastValue = 8, Top.Value = 4, Right.Count = 4, Left.Count = 4
      RotateLeft @value=8
      RotateLeft @value=7
      LastValue = 9, Top.Value = 4, Right.Count = 5, Left.Count = 4
      LastValue = 10, Top.Value = 4, Right.Count = 6, Left.Count = 4
      RotateLeft @value=10
      RotateLeft @value=9
      RotateLeft @value=5
      RotateLeft @value=4
      LastValue = 11, Top.Value = 11, Right.Count = 0, Left.Count = 11
      LastValue = 12, Top.Value = 11, Right.Count = 1, Left.Count = 11
      RotateLeft @value=12
      LastValue = 13, Top.Value = 11, Right.Count = 2, Left.Count = 11
      RotateLeft @value=13
      LastValue = 14, Top.Value = 11, Right.Count = 3, Left.Count = 11
      LastValue = 15, Top.Value = 11, Right.Count = 4, Left.Count = 11
      RotateLeft @value=15
      RotateLeft @value=14
      LastValue = 16, Top.Value = 11, Right.Count = 5, Left.Count = 11
      LastValue = 17, Top.Value = 11, Right.Count = 6, Left.Count = 11
      RotateLeft @value=17
      LastValue = 18, Top.Value = 11, Right.Count = 7, Left.Count = 11
      LastValue = 19, Top.Value = 11, Right.Count = 8, Left.Count = 11
      RotateLeft @value=19
      LastValue = 20, Top.Value = 11, Right.Count = 9, Left.Count = 11
      LastValue = 21, Top.Value = 11, Right.Count = 10, Left.Count = 11
      RotateLeft @value=21
      LastValue = 22, Top.Value = 11, Right.Count = 11, Left.Count = 11
      RotateLeft @value=22
      RotateLeft @value=20
      RotateLeft @value=18
      LastValue = 23, Top.Value = 11, Right.Count = 12, Left.Count = 11
      LastValue = 24, Top.Value = 11, Right.Count = 13, Left.Count = 11
      LastValue = 25, Top.Value = 11, Right.Count = 14, Left.Count = 11
      RotateLeft @value=25
      RotateLeft @value=24
      LastValue = 26, Top.Value = 11, Right.Count = 15, Left.Count = 11
      LastValue = 27, Top.Value = 11, Right.Count = 16, Left.Count = 11
      RotateLeft @value=27
      LastValue = 28, Top.Value = 11, Right.Count = 17, Left.Count = 11
      RotateLeft @value=28
      RotateLeft @value=26
      RotateLeft @value=23
      RotateLeft @value=16
      RotateLeft @value=11
      LastValue = 29, Top.Value = 29, Right.Count = 0, Left.Count = 29
      LastValue = 30, Top.Value = 29, Right.Count = 1, Left.Count = 29
      LastValue = 31, Top.Value = 29, Right.Count = 2, Left.Count = 29
      LastValue = 32, Top.Value = 29, Right.Count = 3, Left.Count = 29
      RotateLeft @value=32
      RotateLeft @value=31
      LastValue = 33, Top.Value = 29, Right.Count = 4, Left.Count = 29
      RotateLeft @value=33
      RotateLeft @value=30
      LastValue = 34, Top.Value = 29, Right.Count = 5, Left.Count = 29
      RotateLeft @value=34
      LastValue = 35, Top.Value = 29, Right.Count = 6, Left.Count = 29
      LastValue = 36, Top.Value = 29, Right.Count = 7, Left.Count = 29
      LastValue = 37, Top.Value = 29, Right.Count = 8, Left.Count = 29
      RotateLeft @value=37
      LastValue = 38, Top.Value = 29, Right.Count = 9, Left.Count = 29
      LastValue = 39, Top.Value = 29, Right.Count = 10, Left.Count = 29
      RotateLeft @value=39
      LastValue = 40, Top.Value = 29, Right.Count = 11, Left.Count = 29
      RotateLeft @value=40
      RotateLeft @value=38
      RotateLeft @value=36
      LastValue = 41, Top.Value = 29, Right.Count = 12, Left.Count = 29
      LastValue = 42, Top.Value = 29, Right.Count = 13, Left.Count = 29
      RotateLeft @value=42
      LastValue = 43, Top.Value = 29, Right.Count = 14, Left.Count = 29
      LastValue = 44, Top.Value = 29, Right.Count = 15, Left.Count = 29
      RotateLeft @value=44
      LastValue = 45, Top.Value = 29, Right.Count = 16, Left.Count = 29
      LastValue = 46, Top.Value = 29, Right.Count = 17, Left.Count = 29
      RotateLeft @value=46
      RotateLeft @value=45
      LastValue = 47, Top.Value = 29, Right.Count = 18, Left.Count = 29
      LastValue = 48, Top.Value = 29, Right.Count = 19, Left.Count = 29
      LastValue = 49, Top.Value = 29, Right.Count = 20, Left.Count = 29
      

      排序的项目总是自然地添加到树的右侧。当右侧比左侧大时,会发生左旋转。 Rotateright 永远不会发生。每次树翻倍时,都会大致选择一个新的顶部节点。优先级值的随机性让它有点抖动,所以在这次运行中它变为 0、1、4、11、29。

      随机运行揭示了一些有趣的事情:

      TimeIt(50, RandomInsert)
      LastValue = 0,748661640914465, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 0
      LastValue = 0,669427539533669, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 1
      RotateRight @value=0,669427539533669
      LastValue = 0,318363281115127, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 2
      RotateRight @value=0,669427539533669
      LastValue = 0,33133987678743, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 3
      RotateLeft @value=0,748661640914465
      LastValue = 0,955126694382693, Top.Value = 0,955126694382693, Right.Count = 0, Left.Count = 4
      RotateRight @value=0,669427539533669
      RotateLeft @value=0,33133987678743
      RotateLeft @value=0,318363281115127
      RotateRight @value=0,748661640914465
      RotateRight @value=0,955126694382693
      LastValue = 0,641024029180884, Top.Value = 0,641024029180884, Right.Count = 3, Left.Count = 2
      LastValue = 0,20709771951991, Top.Value = 0,641024029180884, Right.Count = 3, Left.Count = 3
      LastValue = 0,830862050331599, Top.Value = 0,641024029180884, Right.Count = 4, Left.Count = 3
      RotateRight @value=0,20709771951991
      RotateRight @value=0,318363281115127
      LastValue = 0,203250563798123, Top.Value = 0,641024029180884, Right.Count = 4, Left.Count = 4
      RotateLeft @value=0,669427539533669
      RotateRight @value=0,748661640914465
      RotateRight @value=0,955126694382693
      LastValue = 0,701743399585478, Top.Value = 0,641024029180884, Right.Count = 5, Left.Count = 4
      RotateLeft @value=0,669427539533669
      RotateRight @value=0,701743399585478
      RotateLeft @value=0,641024029180884
      LastValue = 0,675667521858433, Top.Value = 0,675667521858433, Right.Count = 4, Left.Count = 6
      RotateLeft @value=0,33133987678743
      RotateLeft @value=0,318363281115127
      RotateLeft @value=0,203250563798123
      LastValue = 0,531275219531392, Top.Value = 0,675667521858433, Right.Count = 4, Left.Count = 7
      RotateRight @value=0,748661640914465
      RotateRight @value=0,955126694382693
      RotateLeft @value=0,701743399585478
      LastValue = 0,704049674190604, Top.Value = 0,675667521858433, Right.Count = 5, Left.Count = 7
      RotateRight @value=0,203250563798123
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      LastValue = 0,161392807104342, Top.Value = 0,161392807104342, Right.Count = 13, Left.Count = 0
      RotateRight @value=0,203250563798123
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      RotateLeft @value=0,161392807104342
      LastValue = 0,167598206162266, Top.Value = 0,167598206162266, Right.Count = 13, Left.Count = 1
      LastValue = 0,154996359793002, Top.Value = 0,167598206162266, Right.Count = 13, Left.Count = 2
      RotateLeft @value=0,33133987678743
      LastValue = 0,431767346538495, Top.Value = 0,167598206162266, Right.Count = 14, Left.Count = 2
      RotateRight @value=0,203250563798123
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      RotateLeft @value=0,167598206162266
      LastValue = 0,173774613614089, Top.Value = 0,173774613614089, Right.Count = 14, Left.Count = 3
      RotateRight @value=0,830862050331599
      LastValue = 0,76559642412029, Top.Value = 0,173774613614089, Right.Count = 15, Left.Count = 3
      RotateRight @value=0,76559642412029
      RotateLeft @value=0,748661640914465
      RotateRight @value=0,955126694382693
      RotateLeft @value=0,704049674190604
      RotateLeft @value=0,675667521858433
      LastValue = 0,75742144871383, Top.Value = 0,173774613614089, Right.Count = 16, Left.Count = 3
      LastValue = 0,346844367844446, Top.Value = 0,173774613614089, Right.Count = 17, Left.Count = 3
      RotateRight @value=0,830862050331599
      LastValue = 0,787565814232251, Top.Value = 0,173774613614089, Right.Count = 18, Left.Count = 3
      LastValue = 0,734950566540915, Top.Value = 0,173774613614089, Right.Count = 19, Left.Count = 3
      RotateLeft @value=0,20709771951991
      RotateRight @value=0,318363281115127
      RotateLeft @value=0,203250563798123
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      RotateRight @value=0,75742144871383
      RotateLeft @value=0,173774613614089
      LastValue = 0,236504829598826, Top.Value = 0,236504829598826, Right.Count = 17, Left.Count = 6
      RotateLeft @value=0,830862050331599
      RotateLeft @value=0,787565814232251
      RotateLeft @value=0,76559642412029
      RotateRight @value=0,955126694382693
      LastValue = 0,895606500048007, Top.Value = 0,236504829598826, Right.Count = 18, Left.Count = 6
      LastValue = 0,599106418713511, Top.Value = 0,236504829598826, Right.Count = 19, Left.Count = 6
      LastValue = 0,8182332901369, Top.Value = 0,236504829598826, Right.Count = 20, Left.Count = 6
      RotateRight @value=0,734950566540915
      LastValue = 0,704216948572647, Top.Value = 0,236504829598826, Right.Count = 21, Left.Count = 6
      RotateLeft @value=0,346844367844446
      RotateLeft @value=0,33133987678743
      RotateRight @value=0,431767346538495
      RotateLeft @value=0,318363281115127
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      RotateRight @value=0,75742144871383
      LastValue = 0,379157059536854, Top.Value = 0,236504829598826, Right.Count = 22, Left.Count = 6
      RotateLeft @value=0,431767346538495
      LastValue = 0,46832062046431, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 6
      RotateRight @value=0,154996359793002
      LastValue = 0,0999000217299443, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 7
      RotateLeft @value=0,20709771951991
      LastValue = 0,229543754006524, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 8
      RotateRight @value=0,8182332901369
      LastValue = 0,80358425984326, Top.Value = 0,236504829598826, Right.Count = 24, Left.Count = 8
      RotateRight @value=0,318363281115127
      LastValue = 0,259324726769386, Top.Value = 0,236504829598826, Right.Count = 25, Left.Count = 8
      RotateRight @value=0,318363281115127
      LastValue = 0,307835293145774, Top.Value = 0,236504829598826, Right.Count = 26, Left.Count = 8
      RotateLeft @value=0,431767346538495
      LastValue = 0,453910283024381, Top.Value = 0,236504829598826, Right.Count = 27, Left.Count = 8
      RotateLeft @value=0,830862050331599
      LastValue = 0,868997387527021, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 8
      RotateLeft @value=0,20709771951991
      RotateRight @value=0,229543754006524
      RotateLeft @value=0,203250563798123
      LastValue = 0,218358597354199, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 9
      RotateRight @value=0,0999000217299443
      RotateRight @value=0,161392807104342
      LastValue = 0,0642934488431986, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 10
      RotateRight @value=0,154996359793002
      RotateLeft @value=0,0999000217299443
      LastValue = 0,148295871982489, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 11
      LastValue = 0,217621828065078, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 12
      RotateRight @value=0,599106418713511
      LastValue = 0,553135806020878, Top.Value = 0,236504829598826, Right.Count = 29, Left.Count = 12
      LastValue = 0,982277666210326, Top.Value = 0,236504829598826, Right.Count = 30, Left.Count = 12
      RotateRight @value=0,8182332901369
      LastValue = 0,803671114520948, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 12
      RotateRight @value=0,203250563798123
      RotateRight @value=0,218358597354199
      LastValue = 0,19310415405459, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 13
      LastValue = 0,0133136604043253, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 14
      RotateLeft @value=0,46832062046431
      RotateRight @value=0,531275219531392
      RotateRight @value=0,641024029180884
      RotateRight @value=0,675667521858433
      RotateRight @value=0,75742144871383
      LastValue = 0,483394719419719, Top.Value = 0,236504829598826, Right.Count = 32, Left.Count = 14
      RotateLeft @value=0,431767346538495
      RotateRight @value=0,453910283024381
      LastValue = 0,453370328738061, Top.Value = 0,236504829598826, Right.Count = 33, Left.Count = 14
      LastValue = 0,762330518459124, Top.Value = 0,236504829598826, Right.Count = 34, Left.Count = 14
      LastValue = 0,699010426969738, Top.Value = 0,236504829598826, Right.Count = 35, Left.Count = 14
      

      旋转的发生并不是因为树不平衡,而是因为优先级是随机选择的。例如,我们在第 13 次插入时旋转了 4 次。我们有一棵树平衡在 5/7(这很好),但要达到 13/0! 使用随机优先级似乎值得进一步研究。无论如何,很明显随机插入比有序插入引起了更多的旋转。

      【讨论】:

      • 左右旋转对称。如果你做 5-0 或 2-3,这无关紧要。除非你真的在一种情况下比另一种情况下做更多的事情。是这样吗?
      • 你是对的。排序后的情况平均旋转次数是随机情况的一半——这有很大的不同。 +1
      • 树的高度不用于平衡,只用于优先级,每个节点都有固定的优先级。一旦树被优先旋转,我不希望(事实上我永远不会)以违反堆属性的方式旋转树。当我将一个节点插入树中时,我预计其计算的优先级平均大于树中至少 50% 的元素,以及插入路径中 50% 的元素。我仍然不清楚为什么随机数据的树的旋转次数是有序数据的两倍。
      • @Juliet:没错。但是,旋转次数受插入路径长度的限制,而不是树的高度。
      【解决方案6】:

      @Guge 是对的。 然而,它还有一点点。 我并不是说在这种情况下它是最大的因素 - 但是它就在那里并且很难对此做任何事情。

      对于已排序的输入,查找可能会触及缓存中的热点节点。 (这通常适用于平衡树,如 AVL 树、红黑树、B 树等)

      由于插入以查找开始,因此这也会影响插入/删除性能。

      再次重申,我并不是说它在所有情况下都是最重要的因素。 但是,它确实存在,并且可能会导致在这些数据结构中排序的输入总是比随机输入更快。

      【讨论】:

      • 如果将已排序的输入添加到自身不平衡的树中,则该树将变为链表。那么唯一的问题是您是否将新节点添加到树的底部,或者从新节点构建一个新的顶部并将现有树添加到它。后者当然是 O(1) 而前者是 O(n)。
      • 由于树是不可变的,每次添加新节点时,都会沿插入路径创建全新的节点。如果我一直在创建新节点,还会有缓存影响吗?
      • @Juliet:因为在下一轮你可能会访问新创建的节点,我猜是的——尽管你可能不会像可变treap那样从排序输入中受益。但是,如果您只对现有的 treap 进行查找进行基准测试,您很可能会发现随机查找和排序查找之间存在显着差异。
      【解决方案7】:

      是的,这是导致额外时间的旋转次数。这是我所做的:

      • 删除HeapifyLeftHeapifyRight 中检查优先级的行,以便始终完成轮换。
      • RotateLeftRotateRight 的if 之后添加了Console.WriteLine
      • Insert 方法的IsEmpty 部分添加了Console.WriteLine,以查看插入的内容。
      • 使用 5 个值运行一次测试。

      输出:

      TimeIt(5, RandomInsert)
      Inserting 0.593302943554382
      Inserting 0.348900582338171
      RotateRight
      Inserting 0.75496212381635
      RotateLeft
      RotateLeft
      Inserting 0.438848891499848
      RotateRight
      RotateLeft
      RotateRight
      Inserting 0.357057290783644
      RotateLeft
      RotateRight
      
      TimeIt(5, OrderedInsert)
      Inserting 0.150707998383189
      Inserting 1.58281302712057
      RotateLeft
      Inserting 2.23192588297274
      RotateLeft
      Inserting 3.30518679009061
      RotateLeft
      Inserting 4.32788012657682
      RotateLeft
      

      结果:随机数据的旋转次数是原来的 2 倍。

      【讨论】:

      • 如果你去掉支票,你测量的是什么?
      • 删除 HeapifyLeft/Right 中的检查会强制算法始终执行最坏的情况 - 执行轮换。
      • 测量 5 次插入可能会有很高的信噪比,试试我的扩展基准程序?
      • @Cameron, @Davy:我在不修改代码和 16000 次迭代的情况下进行了测量。结果是一样的:在随机情况下,旋转次数是原来的 2 倍。
      • @Davy 我并不是说它因为超过 5 次插入测量的时间而变慢,我是说超过 5 次插入 Random 的旋转次数是 Ordered 的两倍。 @Andras 这不是猜测 :)
      【解决方案8】:

      我添加了标准偏差的计算,并将您的测试更改为以最高优先级运行(以尽可能减少噪音)。结果如下:

      Random                                   Ordered
      0,2835 (stddev 0,9946)                   0,0891 (stddev 0,2372)
      0,1230 (stddev 0,0086)                   0,0780 (stddev 0,0031)
      0,2498 (stddev 0,0662)                   0,1694 (stddev 0,0145)
      0,5136 (stddev 0,0441)                   0,3550 (stddev 0,0658)
      1,1704 (stddev 0,1072)                   0,6632 (stddev 0,0856)
      1,4672 (stddev 0,1090)                   0,8343 (stddev 0,1047)
      3,3330 (stddev 0,2041)                   1,9272 (stddev 0,3456)
      7,9822 (stddev 0,3906)                   3,7871 (stddev 0,1459)
      18,4300 (stddev 0,6112)                  10,3233 (stddev 2,0247)
      44,9500 (stddev 2,2935)                  22,3870 (stddev 1,7157)
      110,5275 (stddev 3,7129)                 49,4085 (stddev 2,9595)
      275,4345 (stddev 10,7154)                107,8442 (stddev 8,6200)
      667,7310 (stddev 20,0729)                242,9779 (stddev 14,4033)
      

      我运行了一个采样分析器,结果如下(程序在此方法中的次数):

      Method           Random        Ordered
      HeapifyRight()   1.95          5.33
      get_IsEmpty()    3.16          5.49
      Make()           3.28          4.92
      Insert()         16.01         14.45
      HeapifyLeft()    2.20          0.00
      

      结论:随机的左右旋转分布比较合理,而有序的从不向左旋转。

      这是我改进的“基准”程序:

          static void Main(string[] args)
          {
              Thread.CurrentThread.Priority = ThreadPriority.Highest;
              Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
      
              List<String> rndTimes = new List<String>();
              List<String> orderedTimes = new List<String>();
      
              rndTimes.Add(TimeIt(50, RandomInsert));
              rndTimes.Add(TimeIt(100, RandomInsert));
              rndTimes.Add(TimeIt(200, RandomInsert));
              rndTimes.Add(TimeIt(400, RandomInsert));
              rndTimes.Add(TimeIt(800, RandomInsert));
              rndTimes.Add(TimeIt(1000, RandomInsert));
              rndTimes.Add(TimeIt(2000, RandomInsert));
              rndTimes.Add(TimeIt(4000, RandomInsert));
              rndTimes.Add(TimeIt(8000, RandomInsert));
              rndTimes.Add(TimeIt(16000, RandomInsert));
              rndTimes.Add(TimeIt(32000, RandomInsert));
              rndTimes.Add(TimeIt(64000, RandomInsert));
              rndTimes.Add(TimeIt(128000, RandomInsert));
              orderedTimes.Add(TimeIt(50, OrderedInsert));
              orderedTimes.Add(TimeIt(100, OrderedInsert));
              orderedTimes.Add(TimeIt(200, OrderedInsert));
              orderedTimes.Add(TimeIt(400, OrderedInsert));
              orderedTimes.Add(TimeIt(800, OrderedInsert));
              orderedTimes.Add(TimeIt(1000, OrderedInsert));
              orderedTimes.Add(TimeIt(2000, OrderedInsert));
              orderedTimes.Add(TimeIt(4000, OrderedInsert));
              orderedTimes.Add(TimeIt(8000, OrderedInsert));
              orderedTimes.Add(TimeIt(16000, OrderedInsert));
              orderedTimes.Add(TimeIt(32000, OrderedInsert));
              orderedTimes.Add(TimeIt(64000, OrderedInsert));
              orderedTimes.Add(TimeIt(128000, OrderedInsert));
              var result = string.Join("\n", (from s in rndTimes
                              join s2 in orderedTimes
                                  on rndTimes.IndexOf(s) equals orderedTimes.IndexOf(s2)
                              select String.Format("{0} \t\t {1}", s, s2)).ToArray());
              Console.WriteLine(result);
              Console.WriteLine("Done");
              Console.ReadLine();
          }
      
          static double StandardDeviation(List<double> doubleList)
          {
              double average = doubleList.Average();
              double sumOfDerivation = 0;
              foreach (double value in doubleList)
              {
                  sumOfDerivation += (value) * (value);
              }
              double sumOfDerivationAverage = sumOfDerivation / doubleList.Count;
              return Math.Sqrt(sumOfDerivationAverage - (average * average));
          }
          static String TimeIt(int insertCount, Action<int> f)
          {
              Console.WriteLine("TimeIt({0}, {1})", insertCount, f.Method.Name);
      
              List<double> times = new List<double>();
              for (int i = 0; i < ITERATION_COUNT; i++)
              {
                  Stopwatch sw = Stopwatch.StartNew();
                  f(insertCount);
                  sw.Stop();
                  times.Add(sw.Elapsed.TotalMilliseconds);
              }
      
              return String.Format("{0:f4} (stddev {1:f4})", times.Average(), StandardDeviation(times));
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-08-27
        • 1970-01-01
        • 1970-01-01
        • 2011-11-30
        • 1970-01-01
        • 2016-02-01
        • 2023-02-05
        相关资源
        最近更新 更多