使用O(n)存储和O(log n)查询时间的数据结构应该用于范围最小查询?

时间:2010-02-23 00:41:25

标签: algorithm binary-tree big-o

我对算法类的以下作业问题感到困惑:

  

假设我们有一个序列   n值x 1 ,x 2 ... x n ,并寻求   快速回答重复的询问   形式:给定i和j,找到最小的   x i ... x j

中的值      

设计使用O(n)的数据结构   O(log n)中的空格和答案查询   时间。

首先,我不确定序列是指一个有序集合,还是一个未排序的集合 - 但由于它没有说明,否则我将假设 sequence 意思是未分类。

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

有什么建议吗?

6 个答案:

答案 0 :(得分:12)

如果对该集进行了排序,则不需要树。范围[i,j]中的最小元素将具有索引i。

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

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


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

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

现在从叶i开始。走上树,把你的候选人最小值保持在i的值或任何已知的j和i的左边。停止i和j的共同祖先下面的一个节点。

再次从叶j开始。走上树,再次保持你的候选人最小值为j或任何已知的左边j和i左边。

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

你从树林旅行的路径,如果你正在寻找叶子,那么就是你将向下树的路径。


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;
        }

    }
}

答案 1 :(得分:8)

好的,我认为我有一个良好的开端,我在这个过程中学到了一些新东西。

我会看一下Cartesian trees上的维基百科条目。我不会因为害怕为你做功课而告诉你更多,但你看起来像个聪明人,所以我想你可以解决它。

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

您是否考虑过间隔树?

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

编辑:

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

答案 4 :(得分:0)

一些刺激:

假设你以某种方式存储了长度为1,2,4,8 ......的所有良好对齐的*子阵列的最小值?您是否可以通过查看返回正确答案来获得这些最小值?如何存储它们以便有效地检索它们?

(*例如存储min(x 0 ... 3 )和min(x 4 ... x 7 ),但不是min(x 1 ... x 4 ))

答案 5 :(得分:0)

您可以使用“细分树” 。在段发束中,更新查询时间均为O(登录)。如果您想了解其工作原理,请点击以下链接。

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