web-dev-qa-db-ja.com

JavaでTreeSetのk番目の要素を返す方法

多分私は正しいデータ構造を使っていません。セットを使用する必要がありますが、k番目に小さい要素を効率的に返します。 JavaでTreeSetを実行できますか?これを実行するTreeSetの組み込みメソッドはないようです。

私を助けてください。

22
user1096734

TreeSetがこれを直接行うメソッドを持っているとは思いません。 O(log n)ランダムアクセスをサポートするバイナリ検索ツリーがあり(order statistic treesと呼ばれることもあります)、 このデータ構造のJava実装 利用可能。これらの構造は通常、ノードの左側または右側にある要素の数をカウントする各ノードに情報を格納するバイナリ検索ツリーとして実装されているため、ツリーを下に検索して、適切な要素を見つけることができます。各ステップ。 Cormen、Rivest、Leisserson、およびSteinによる古典的な「アルゴリズム入門、第3版」の本では、自分で実装する方法に興味がある場合は、「データ構造の拡張」の章でこのデータ構造について説明しています。

あるいは、(場合によっては)TreeSetの-​​ tailSet メソッドと変更されたバイナリ検索を使用してk番目の要素を検索できる場合があります。具体的には、TreeSetの最初と最後の要素を確認し、(可能な場合はコンテンツを考慮して)2つの要素の中間にある要素を選択し、tailSetの引数として渡して取得します。中点の後のセットの要素のビュー。 tailSetの要素の数を使用して、要素が見つかったかどうか、ツリーの左半分または右半分を探索するかどうかを決定できます。これは、ツリー上で少し変更された 補間検索 であり、高速になる可能性があります。ただし、tailSetメソッドの内部の複雑さはわかりません。そのため、これは実際には順序統計ツリーよりも悪い場合があります。たとえば、StringsをTreeSetに格納している場合など、2つの要素の「中間点」を計算できない場合にも失敗する可能性があります。

お役に立てれば!

22
templatetypedef

要素kまで反復するだけです。これを行う1つの方法は、 GuavaIterables.get メソッドの1つを使用することです。

_T element = Iterables.get(set, k);
_

SetListではなく、一般にListsのために予約されているようなインデックスベースの操作なので、これを行うための組み込みメソッドはありません。 TreeSetは、ある値以上の最も近い包含要素を検索する場合などに適しています。

あなたができることは、k番目に小さい要素への可能な限り高速なアクセスが本当に重要である場合に、ではなくArrayListを使用することです。 TreeSetおよびハンドル挿入は、挿入ポイントをバイナリ検索し、そのインデックスに要素を挿入するか、検索結果に応じて、そのインデックスにある既存の要素を置き換えます。次に、get(k)を呼び出すだけで、O(1))のk番目に小さい要素を取得できます。

すべてを処理し、本当に必要な場合はget(index)メソッドを追加するSortedSetの実装を作成することもできます。

18
ColinD

TreeSet.iterator() を使用して、イテレータを昇順で取得し、next() K回呼び出します。

// Example for Integers
Iterator<Integer> it = treeSet.iterator();
int i = 0;
Integer current = null;
while(it.hasNext() && i < k) {
   current = it.next();
   i++;
}
8
Tudor

私も同じ問題を抱えていました。そこで、Java.util.TreeMapのソースコードを使用してIndexedTreeMapと記述しました。それは私自身のIndexedNavigableMapを実装します:

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

実装は、変更されたときに赤黒ツリーのノードの重みを更新することに基づいています。重みは、特定のノードの下にある子ノードの数に1を加えたもの-自己です。たとえば、木が左に回転している場合:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeightは単純にルートまでの重みを更新します。

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

そして、インデックスによって要素を見つける必要があるとき、ここに重みを使用する実装があります:

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

また、キーのインデックスを見つけるのに非常に便利です:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    if (e.left != null) {
        index += getWeight(e.left);
    }
    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

この作業の結果は http://code.google.com/p/indexed-tree-map/ で確認できます。 https://github.com/geniot/indexed-tree-map に移動しました

7

[以下では、「k番目に小さい要素の検索操作」を​​「Kth op。」と省略します。]

詳細を入力する必要があります。データ構造はどの操作を提供しますか? is [〜#〜] k [〜#〜] in Kth操作は[〜#〜] n [〜#〜]に比べて非常に小さい=、またはそれは何ですか?ルックアップと比較して、挿入と削除はどのくらいの頻度で行われますか?ルックアップと比較してKth最小要素検索をどのくらいの頻度で行いますか? Javaライブラリ内の数行の簡単な解決策を探していますか、それともカスタムデータ構造を構築するためにいくらかの努力を惜しみませんか?

提供する操作は、次のいずれかのサブセットです。

  • LookUp(キーで要素を検索します。キーは比較可能であり、何でもかまいません)

  • 挿入

  • 削除

  • Kth

ここにいくつかの可能性があります:

  • 挿入と削除がないかごくわずかな場合は、要素を並べ替えて配列を使用できますO(Log(N))ルックアップ時間とO(1) = Kthの場合。

  • O(Log(N)) for LookUpInsertDeleteおよびO(k )Kth opの場合十分ですが、おそらく最も簡単な実装はスキップリストです。 (詳細が必要な場合は、ウィキペディアの記事が非常に役立ちます)

  • [〜#〜] k [〜#〜]が十分に小さい場合、またはKth操作が「挿入および削除フェーズ」の後にのみ行われる場合、最小の[〜#〜] k [〜#〜]ヒープ内の要素。挿入と削除の後にソートしますO(N + k Log k)時間。 (LookUpには別のハッシュも必要です)

  • [〜#〜] k [〜#〜]が任意で、O(N)Kth操作に十分である場合は、 O(1)時間ルックアップのハッシュ、およびKth操作に「片側-QuickSort」アルゴリズムを使用(基本的な考え方は、迅速な並べ替えを行いますが、本当に必要な側でのみ2項除算の再帰を実行します。これにより、(これは全体的に単純化されます)N(1/2 + 1/4 + 1/8 + ... O(N))= =予想時間)

  • LookUpInsertDelete、となるように、各ノードが子の数を保持するように拡張された「単純な」間隔ツリー構造を構築できます。 Kthすべての計算はO(Log N)ツリーのバランスが取れている限り可能ですが、初心者の場合はおそらく実装が難しくありません。

etc. etc.選択肢のセットは、質問の可能な解釈として無限です。

1
Ali Ferhat
TreeSet<Integer> a=new TreeSet<>();
a.add(1);
a.add(2);
a.add(-1);

System.out.println(a.toArray()[0]);

それは役に立ちます

1
darshan hegde

ConcurrentSkipListSetを使用してtoArray()メソッドを使用できますか? ConcurrentSkipListSetは、要素の自然な順序でソートされます。 toArray()がO(n))であるか、またはリスト(ArrayListのような配列でバックアップされている)に基づいているため、O(1)であるかどうかがわかりません。

ToArray()がO(1)の場合、k番目に小さい要素を取得するには、skipList.toArray()[k]である必要があります。

0
Justin