22

アルゴリズムクラスの次の宿題の質問に困惑しています。

n 個の値 x 1、 x 2 ... x nのシーケンスが与えられ、次の形式の繰り返しクエリにすばやく答えようとしているとします。

O(n) 空間を使用し、O(log n) 時間でクエリに応答するデータ構造を設計します。

まず、シーケンスがソートされたセットを参照するのか、ソートされていないセットを参照するのかはわかりませ

したがって、O(log N) ルックアップ時間について話している場合、これには明らかに二分木が必要であることがわかります。基本的に、 set がSあり、各要素をS二分木に挿入すると思います。問題は、質問は基本的に、ソートされていないセットへのインデックスの範囲が与えられたクエリに答える方法を考え出し、その範囲の最小値を O(log N) 時間で決定することです。そんなことがあるものか?セットの各数値がツリーに挿入されたとしても、私ができる最善の方法は、O(log N) 時間で特定の数値を検索することです。これにより、ソートされていない数値範囲の最小値を見つけることができませんS

助言がありますか?

4

6 に答える 6

13

セットがソートされていれば、ツリーは必要ありません。範囲 [i,j] の最小要素のインデックスは i です。

したがって、シーケンスの要素がツリーの葉のインデックスの順に格納されているとします。クエリを容易にするために、各内部ノードに追加情報 ( ahem、おそらく何らかの最小値と最大値)を保存できますか?

その場合、ツリーのバランスが取れていて、ルートから {i,j} の 2 つの要素への 2 つのパスのみを見てクエリに答えることができれば、O(log N) ルックアップ コストを達成できます。 . N 個の葉を持つバランスの取れた二分木には合計 (2N-1) 個のノードが含まれるため、O(N) 個のストレージ制限も満たします。


詳細: 範囲 [i,j] の最小値を計算することを検討してください。

ツリーの各内部ノード A で、その下にあるすべての葉の最小値を維持します。これは、ツリーが最初に構築されるときにボトムアップで計算できます。

リーフ i から開始します。i の値、または i の右側と j の左側の両方にあることがわかっている値を最小値の候補として保持しながら、ツリーを上っていきます。i と j の相互の祖先の 1 つ下のノードを停止します。

リーフ j で再び開始します。j の値、または j の左側と i の右側の両方にあることがわかっている値を最小値としてもう一度保持しながら、ツリーを上っていきます。

[i,j] の最小値は、計算した 2 つの値の最小値です。最大値の計算は類似しています。合計ストレージ要件は、内部ノードごとに 2 つの値、内部ノードごとに 2 つのポインター、およびリーフごとに 1 つの値であり、完全なツリーの場合は 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;
        }

    }
}
于 2010-02-23T00:58:17.430 に答える
8

いいスタートが切れたと思います。その過程で新しいことを学びました。

ウィキペディアのCartesian treesのエントリを見てみましょう。宿題をやってしまうのが怖いので、これ以上は言いませんが、あなたは頭がいいように見えるので、うまくやっていけると思います。

ところで、新しいデータ構造を学ぶのを手伝ってくれてありがとう!

于 2010-02-23T01:03:50.323 に答える
1

sequenceの定義は順序付きセットです(ソートされていません)。

セットが順序付けられていることがわかっていると、範囲最小クエリの理想的なデータ構造であるデカルト ツリーを使用できます。

于 2010-02-23T00:49:47.300 に答える
1

「セグメント ツリー」を使用できます。セグメント トレスでは、更新時間とクエリ時間の両方が O(logn) です。それがどのように機能するかを理解したい場合は、ここにリンクがあります。

  1. https://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/
  2. https://www.youtube.com/watch?v=ZBHKZF5w4YU
于 2018-07-28T03:08:41.100 に答える
0

間隔ツリーを検討しましたか?

ウィキペディアのエントリを見ると、あなたが求めているものとよく一致しているようです。 http://en.wikipedia.org/wiki/Interval_tree

編集:

はい、間隔ツリーはこのシナリオには適していないようです...

于 2010-02-23T00:51:52.853 に答える
0

いくつかのプロディング:

長さ 1、2、4、8、... の適切に整列されたすべてのサブ配列の最小値を何らかの形で保存したとしますか? 正しい答えを返すために、これらの最小値のどれだけを見ることができますか? それらを効率的に検索できるようにするには、どうすればそれらを保管できますか?

(* たとえば、min(x 0 ... 3 ) と min(x 4 ...x 7 ) を保存しますが、min(x 1 ...x 4 ) は保存しません)

于 2013-07-05T19:26:14.850 に答える