【问题标题】:What data structure using O(n) storage with O(log n) query time should I use for Range Minimum Queries?我应该将什么数据结构使用 O(n) 存储和 O(log n) 查询时间用于范围最小查询?
【发布时间】:2010-02-23 00:41:24
【问题描述】:

我被以下算法课的作业问题难住了:

假设给定一个序列 n 个值 x1, x2 ... xn,并寻求 快速回答客户的重复查询 形式:给定 i 和 j,找出最小的 xi中的值... xj

设计一个使用 O(n) 的数据结构 空间并回答 O(log n) 中的查询 时间。

首先,我不确定 sequence 是指已排序的集合还是未排序的集合 - 但既然它没有说明,我将假设 sequence表示未排序。

所以,我意识到这显然必须涉及二叉树,如果我们谈论的是 O(log N) 查找时间。所以基本上,我想,你有一个集合S,你将S 中的每个元素插入到二叉树中。问题是,这个问题基本上是要我想出一种方法来回答查询,在该查询中,我将一系列索引分配到 unsorted 集合中 - 然后确定 O 中该范围内的最小值(log N) 时间。这怎么可能?即使将集合的每个数字都插入到树中,我能做的最好的事情就是在 O(log N) 时间内查找任何特定数字。这不允许我在 S 的未排序数字范围内找到最小值。

有什么建议吗?

【问题讨论】:

  • +1 表示您正在做作业并在询问之前进行了尝试。欢迎来到 SO!
  • +1:优秀的作业题
  • 这来自 Skiena 的《算法设计手册》一书。

标签: algorithm data-structures binary-tree big-o rmq


【解决方案1】:

如果集合已排序,则不需要树。 [i,j] 范围内的最小元素的索引为 i。

因此,假设您的序列中的元素按照它们的索引顺序存储在树的叶子上。您能否在每个内部节点存储任何其他信息(ahem,也许是某种最小值和最大值)以方便您的查询?

如果是这样,那么如果树是平衡的,并且如果您可以通过仅查看从根到 {i,j} 处的两个元素的两条路径来回答您的查询,那么您将实现 O(log N ) 查找成本。由于具有 N 个叶子的平衡二叉树包含 (2N-1) 个总节点,因此您还将满足 O(N) 存储限制。


更多细节:考虑计算 [i,j] 范围内的最小值。

在树的每个内部节点 A 处,保持其下方所有叶子的最小值。这可以在首次构建树时自下而上计算。

现在从叶子 i 开始。沿着树向上走,将 i 处的值或任何已知位于 i 右侧和 j 左侧的值作为候选最小值。在 i 和 j 的共同祖先之下停止一个节点。

从叶 j 重新开始。走上树,再次将 j 处的值或已知为 j 左侧和 i 右侧的任何值作为您的候选者最小值。

[i,j] 的最小值是您计算的两个值中的最小值。计算最大值是类似的。总存储要求是每个内部节点 2 个值加上每个内部节点两个指针加上每个叶子一个值,对于完整的树,这是 N + 4(N-1)。

从叶子 i 开始向上树的路径与搜索叶子 i 时向下树的路径相同。


用于搜索的 C# 代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RangeSearch
{
    public class RangeSearch
    {
        int[] tree;
        int N;

        int LeafLocation(int leafNumber) { return leafNumber + N - 1; }
        int LeafValue(int leafNumber) { return tree[ LeafLocation(leafNumber)]; }
        int LeftChild(int x) { return 2*x + 1; }
        int RightChild(int x) { return 2*x + 2; }
        int Parent(int x) { return (x-1)/2; }
        bool IsPowerOf2(int x) { while (x > 0) { if (x == 1) return true; if ((x & 1) == 1 ) return false; x = x >> 1; } return false; }
        bool IsAncestorOf( int x, int y ) { if( x>y ) return false;  return x==y || IsAncestorOf(LeftChild(x), y) || IsAncestorOf(RightChild(x),y); } // note: violating time bound for legibility, can fix by storing min/max descendant index at each node

        public RangeSearch(params int[] vals)
        {
            if (!IsPowerOf2(vals.Length))
                throw new ArgumentException("this implementation restricted to N being power of 2");
            N = vals.Length;
            tree = new int[2 * N - 1];
            // the right half of the array contains the leaves
            vals.CopyTo(tree, N - 1);
            // the left half of the array contains the interior nodes, each of which holds the minimum of all its children
            for (int i = N - 2; i >= 0; i--)
                tree[i] = Math.Min(tree[LeftChild(i)], tree[RightChild(i)]);           
        }

        public int FindMin(int a, int b)
        {
            if( a>b )
                throw new ArgumentException( "FindMin expects a range [a,b] with a<=b" );
            int x = Walk( a, true, b);
            int y = Walk( b, false, a);
            return Math.Min(x, y);
        }

        int Walk( int leafNumber, bool leftSide, int otherLeafNumber )
        {
            int minSoFar =  LeafValue(leafNumber);
            int leafLocation = LeafLocation(leafNumber);
            int otherLeafLocation = LeafLocation(otherLeafNumber);
            int parent = Parent(leafLocation);
            bool cameFromLeft = (leafLocation == LeftChild(parent));
            return Walk2(minSoFar, parent, cameFromLeft, leftSide, otherLeafLocation);
        }
        int Walk2(int minSoFar, int node, bool cameFromLeft, bool leftSide, int otherLeafLocation)
        {
            if (IsAncestorOf(node, otherLeafLocation))
                return minSoFar;
            if (leftSide)
                minSoFar = !cameFromLeft ? minSoFar : Math.Min(minSoFar, tree[RightChild(node)]);
            else
                minSoFar = cameFromLeft ? minSoFar : Math.Min(minSoFar, tree[LeftChild(node)]);
            return Walk2(minSoFar, Parent(node), node == LeftChild(Parent(node)), leftSide, otherLeafLocation);
        }
    }
}

测试它的 C# 代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RangeSearch
{
    class Program
    {
        static void Main(string[] args)
        {
            RangeSearch rngA = new RangeSearch(9, 3, 7, 1);
            System.Diagnostics.Trace.Assert(3 == rngA.FindMin(0, 2) );
            System.Diagnostics.Trace.Assert(1 == rngA.FindMin(0, 3));
            System.Diagnostics.Trace.Assert(1 == rngA.FindMin(1, 3));

            RangeSearch rngB = new RangeSearch(1, 7, 3, 9);
            System.Diagnostics.Trace.Assert(3 == rngB.FindMin(1, 3));
            System.Diagnostics.Trace.Assert(1 == rngB.FindMin(0, 3));
            System.Diagnostics.Trace.Assert(1 == rngB.FindMin(0, 2));

            RangeSearch rngC = new RangeSearch(17, 21, 77, 70, 58, 79, 79, 89);
            System.Diagnostics.Trace.Assert(21 == rngC.FindMin(1, 7));

            RangeSearch rngD = new RangeSearch(94, 78, 88, 72, 95, 97, 89, 83);
            System.Diagnostics.Trace.Assert(72 == rngD.FindMin(1, 6));

            RangeSearch rngE = new RangeSearch(0, 66, 6, 43, 34, 34, 63, 49);
            System.Diagnostics.Trace.Assert(34 == rngE.FindMin(3, 4));

            Random rnd = new Random();
            for (int i = 0; i < 1000000; i++)
            {
                int[] tmp = new int[64];
                for (int j = 0; j < tmp.Length; j++)
                    tmp[j] = rnd.Next(0, 100);
                int a = rnd.Next(0, tmp.Length);
                int b = rnd.Next(a, tmp.Length);
                RangeSearch rng = new RangeSearch(tmp);
                System.Diagnostics.Trace.Assert(Min(tmp, a, b) == rng.FindMin(a, b));
            }
        }

        static int Min(int[] ar, int a, int b)
        {
            int x = ar[a];
            for (int i = a + 1; i <= b; i++)
                x = Math.Min(x, ar[i]);
            return x;
        }

    }
}

【讨论】:

  • 不使用 O(N log N) 存储吗?
  • 这是否违反 O(n) 存储要求?您需要 O(3n) 存储 -> O(n) 存储对吗?
  • 这不会违反 O(N) 存储要求。具有 N 个元素的平衡二叉树总共包含 2N-1 个节点。如果每个节点的数据量是固定的,那么总存储量是O(N)。
  • 这个答案让我很反感。您能否提供一些更恰当地演示如何在 O(log N) 时间内获得范围的最小值的伪代码?我看到您的方法与笛卡尔树方法非常相似,但我认为您高估了查找的效率。
  • 现在我想起来了,你描述的是一个分段树.. 具有 O(N log N) 存储。见:topcoder.com/…
【解决方案2】:

好的,我想我有一个好的开始,并且在这个过程中我学到了一些新东西。

我会查看Cartesian trees 上的维基百科条目。我不会告诉你更多的,因为害怕你会为你做作业,但你看起来很聪明,所以我认为你可以解决这个问题。

顺便说一句,感谢您帮助我学习新的数据结构!

【讨论】:

  • 不幸的是,我不认为笛卡尔树会给你 O(log N) 的搜索时间——至少在最坏的情况下不会,因为它们不平衡。
  • @comingstorm:你是对的。但是,如果我们假设输入集是随机的,它将平均为 O(log N)。只要 OP 在他的作业中证明他对此的理解是正确的,我相信他会得到充分的信任。
  • 我作为助教评估了算法考试的答案。我会给予部分信任。它并没有解决上述问题——但它确实通过解决一个(仅)稍微简单的问题来显示理解。如果没有任何解释,我会给予较少的信任:那么它看起来就像一个疏忽。
【解决方案3】:

sequence 的定义是有序的set排序)。

知道集合是有序,您可以使用Cartesian Tree,这是Range minimum queries 的理想数据结构。

【讨论】:

  • 看到这就是我最初的想法。但是这个问题没有意义,因为如果集合是排序的,那么在序列的任何子集中找到最低元素是一个 O(1) 操作。给定索引 i 和 j,最低元素将始终位于索引 i。
  • 排序和有序不是一回事,@Channel72。
  • 同上 Welbog:排序和有序不是一回事。有序只是意味着项目以固定的顺序出现 - 而不是它们被排序。有序示例:{3, 1, 7, 2} 显然没有排序。我认为作业描述意味着你得到了一个未排序数字数组的输入。
  • 为什么我们需要那些花哨的算法比如稀疏表来解决rmq问题?我们为什么不直接对集合进行排序,然后查询将在恒定时间内提供,对吧?
  • @Alcott: 如果输入数组是 [9 1 7 3] 并且我查询 (1, 2)---代表子序列 [1 7]---那么你的答案应该是1.如果你存储数组[1 3 7 9],我查询(1, 2),你怎么到1?
【解决方案4】:

您可以使用'Segment Tree'。在段中,updatequery 时间都是 O(logn)。如果您想了解它的工作原理,请点击以下链接。

  1. https://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/
  2. https://www.youtube.com/watch?v=ZBHKZF5w4YU

【讨论】:

    【解决方案5】:

    你考虑过区间树吗?

    查看维基百科条目,它似乎与您的要求非常匹配。 http://en.wikipedia.org/wiki/Interval_tree

    编辑:

    是的,区间树似乎不适合这种情况......

    【讨论】:

    • 不,查看该结构的描述,它根本不适合这种情况。该结构以排序的方式存储间隔,这里的数据是未排序的,而是有序的。在这种情况下,间隔没有意义。除非你有一种奇特的方式来创建间隔?
    • 不过,答案不值得-3。 +1
    【解决方案6】:

    一些刺激:

    假设您以某种方式存储了长度为 1、2、4、8、...的所有良好对齐*子数组的最小值?您可以通过查看这些最小值中的多少来返回正确答案?如何存储它们以便有效地检索它们?

    (* 例如存储 min(x0...3) 和 min(x4...x7 ),但不是 min(x1...x4))

    【讨论】:

      猜你喜欢
      • 2013-01-24
      • 1970-01-01
      • 1970-01-01
      • 2015-06-04
      • 1970-01-01
      • 2012-05-09
      • 2011-12-12
      • 1970-01-01
      • 2011-08-26
      相关资源
      最近更新 更多