PythonでInsert Delete GetRandom O(1)を実装する方法|平均O(1)で動作するデータ構造
本記事では、平均O(1)時間で以下の3つの操作をすべてサポートできるデータ構造を、Pythonで実装する方法を解説します。
- insert(val) — 要素
valがまだセットに存在しない場合に挿入します。 - remove(val) — 要素
valがセットに存在する場合に削除します。 - getRandom() — 現在の要素集合からランダムに1つの要素を返します。どの要素も等しい確率で選ばれる必要があります。
アルゴリズムの考え方
ポイントは、辞書(ハッシュマップ)とリスト(動的配列)を組み合わせることです。辞書によって要素の有無をO(1)で判定でき、リストの末尾操作により削除もO(1)で行えます。具体的な手順は以下の通りです。
- 初期化: 要素の有無を記録する辞書(
present)と、要素を格納する配列(elements)を用意します。 - insert(val): valが辞書に存在しない、または
present[val] == 0の場合、elementsの末尾にvalを追加し、present[val] = 1を設定してTrueを返します。それ以外の場合はFalseを返します。 - remove(val): valが辞書に存在しない、または
present[val] == 0の場合はFalseを返します。存在する場合は、まずpresent[val] = 0とし、elements内でのvalのインデックスを取得します。そのインデックスが末尾でなければ、末尾の要素と入れ替えます。最後にelementsの末尾要素を削除してTrueを返します。 - getRandom(): elements配列からランダムに1つの値を選んで返します。
削除時に「対象要素を末尾の要素と入れ替えてからpopする」のが重要なテクニックです。リストの中間要素の削除はO(n)かかりますが、末尾の削除であればO(1)で完了するためです。
Pythonでの実装例
それでは、実際のコードを見てみましょう。
import random
class RandomizedSet(object):
def __init__(self):
self.present = {}
self.elements = []
def insert(self, val):
if val not in self.present or self.present[val] == 0:
self.elements.append(val)
self.present[val] = 1
return True
return False
def remove(self, val):
if val not in self.present or self.present[val] == 0:
return False
self.present[val] = 0
index = self.elements.index(val)
if index != len(self.elements)-1:
temp = self.elements[-1]
self.elements[-1] = val
self.elements[index] = temp
self.elements.pop()
return True
def getRandom(self):
return random.choice(self.elements)
ob = RandomizedSet()
print(ob.insert(1))
print(ob.remove(2))
print(ob.insert(2))
print(ob.getRandom())
print(ob.remove(1))
print(ob.insert(2))
print(ob.getRandom())
入力
クラスを初期化した後、insert()、remove()、getRandom()関数を順に呼び出します。詳細は上記の実装例をご覧ください。
出力
True False True 2 True False 2
このように、辞書とリストを併用することで、挿入・削除・ランダム取得のすべての操作を平均O(1)時間で実現できます。要素の重複チェックには辞書、ランダムアクセスにはリスト、という役割分担がこのデータ構造の鍵となります。
-
Python Tkinterで折りたたみ可能なペイン(Collapsible Pane)を作成する方法
Tkinterは、Pythonに標準搭載されているGUI構築用ライブラリです。本記事では、このTkinterを使って「折りたたみ可能なペイン(Collapsible Pane)」を作成する方法を解説します。折りたたみ可能なペインは、GUIキャンバス上に大量のデータを表示したいものの、常に画面に出しておきたくない場合に非常に便利です。必要なときだけ展開して内容を確認でき、不要なときは折りたたんで画面スペースを節約できます。折りたたみペインの実装例以下のプログラムは、矢印(チェックボタン)をクリックして展開・折りたたんだ両方の状態を確認できる折りたたみペインを作成するサンプルです。コード内のコメン
-
Pythonの継承とは?単一継承と階層継承の基本をサンプルコードで解説
本記事では、Python 3.xにおける継承(インヘリタンス)とクラスの拡張方法について詳しく解説します。 継承とは、現実世界のモノや概念の関係性を自然に表現できる、オブジェクト指向プログラミングの中核となる仕組みです。継承を活用すると、次のようなメリットが得られます。 再利用性:すでに書いたコードを流用でき、重複を削減できる 推移性:クラス間の関係を連鎖的に引き継げる 開発速度の向上:ゼロから書かずに済むため、短期間で開発できる 保守性・拡張性:既存クラスを壊さずに機能を追加しやすい 継承の5つの種類 Pythonの継承は、その構造によって主に以下の5種類に分類されます。 単一継承(