Python の高度なアルゴリズムとデータ構造: treap を使用してデュアル インデックスを実装する (パート 1)

Python の高度なアルゴリズムとデータ構造: treap を使用してデュアル インデックスを実装する (パート 1)

\

上記で紹介したヒープ構造では、データを部分的にしかソートできません。つまり、一部の要素のソートしか知ることができません。たとえば、ルート ノードから開始して左の子または右の子に沿って移動すると、トラバースされた要素が増加 (小さなヒープ) または減少 (大きなヒープ) 関係にあることがわかりますが、左のサブツリーと右のサブツリーのノード間のソート関係を知ることはできません。

多くのアプリケーション シナリオでは、データの最大値や最小値をすばやく知るなどのヒープ特性だけでなく、要素のソート情報も知る必要があります。したがって、このセクションでは、両方の長所を実現する方法について説明します。要素が 2 つの部分から構成される一連のデータがあるとします。1 つの部分は製品名に対応し、その型は文字列です。もう 1 つの部分は製品の在庫数に対応し、その型は整数です。製品を名前で並べ替える必要があり、同時に、現在の在庫が最も少ない製品をすばやく照会する必要があります。このような特性を満たすために、対応するアルゴリズムとデータ構造をどのように設計すればよいでしょうか。

たとえば、次のようになります。

上図から、対応する要素の文字列がソートされたバイナリツリーであることがわかります。したがって、ルートノードの左側のサブツリーの要素に対応する文字列はルート文字列よりも小さく、右側のサブツリーに対応する文字列はルートノードの文字列よりも大きくなります。同時に、各要素は対応する製品の在庫数にも対応しています。在庫が最も少ない製品を追跡して、在庫切れになる前に迅速に補充できるようにする必要があります。しかし、上図からわかるように、文字列の順序性を保証するためには、商品の数量の小ヒープ特性を犠牲にしなければなりません。例えば、上図の水に対応する在庫とワインに対応する在庫は、小ヒープ特性に違反しています。ここで問題となるのは、文字列の順序性を確保しながら、数量が小ヒープ特性を満たすようにするにはどうすればよいかということです。

まず、データ構造を定義しましょう。

  1. クラス ノード:
  2. def __init__(self,キー: str, 優先度: float ):
  3. self._key =キー 
  4. self._priority = 優先度
  5. self._left: ノード = なし
  6. self._right: ノード = なし
  7. self._parent: ノード = なし
  8.  
  9. @財産
  10. def left (自分):
  11. self._leftを返す
  12.  
  13. @財産
  14. def right (自分):
  15. self._rightを返す
  16.  
  17. @財産
  18. 親(自分)を定義します。
  19. self._parentを返す
  20.  
  21. @left .setter
  22. def left (self, ノード):
  23. self._left = ノード
  24. ノード なしではない:
  25. ノード.親 = 自分
  26.  
  27. @right .setter
  28. def right (self, ノード):
  29. self._right = ノード
  30. ノード なしではない:
  31. ノード.親 = 自分
  32.  
  33. @親セッター
  34. 親を定義します(自分自身、ノード):
  35. self._parent = ノード
  36.  
  37. def is_root(self) -> bool:
  38. self.parentNone の場合:
  39. 戻る 真実 
  40. 戻る 間違い 
  41.  
  42. __repr__(self)を定義します:
  43. 戻る  "({}, {})" .format(self._key, self._priority)
  44.  
  45. __str__(自分)を定義します:
  46. repr_str: str = ""  
  47. repr_str += repr(自分自身)
  48. self.parent なしではない:
  49. repr_str += " 親: " + repr(self.parent)
  50. それ以外
  51. repr_str += " 親: なし "  
  52.  
  53. 自分自身が左の場合  なしではない:
  54. repr_str += " 左: " + repr ( self.left )
  55. それ以外
  56. repr_str += " 左: なし "  
  57.  
  58. 自己が正しい場合  なしではない:
  59. repr_str += " 右: " + repr ( self.right )
  60. それ以外
  61. repr_str += " 右: なし"  
  62.  
  63. repr_strを返す
  64.  
  65. クラスTreap:
  66. __init__(self)を定義します。
  67. self.root: ノード = なし

現在の問題は、上図のような矛盾が発生した場合に、文字列がソート特性を維持し、インベントリ値が小さなヒープ特性を満たすようにどのように調整するかということです。いくつかの状況に応じて異なるアクションを取る必要があります。以下に示すように、最初のものを見てみましょう。

上の図から、親ノードと左の子ノードが数値的にヒープの性質に違反している状況が 1 つあることがわかります。このとき、右回転操作を実行します。手順は次のとおりです。1. Beer ノードが反時計回りに回転して、親ノードを置き換えます。2. 親ノード Cabbage が時計回りに回転して、Beer の右の子ノードになります。3. Beer の元の右の子ノードが Cabbage の左の子ノードに変換されます。完了後の結果は、次の図のようになります。

このとき文字列はソートされた二分木の性質を維持しており、数値に対応する小さなヒープの性質も満たされていることがわかります。コードの実装を見てみましょう:

  1. クラスTreap:
  2. __init__(self)を定義します。
  3. self._root: ノード = なし
  4.  
  5. def right_rotate(self, x: ノード):
  6. xNoneまたはx.is_root() 真実
  7. 戻る 
  8.  
  9. y = x.親
  10. if y.left != x: # 右に回転するには左の子である必要があります
  11. 戻る 
  12.  
  13. p = y.親
  14. p  not None: # 右回転を実行する
  15. p.left == yの場合:
  16. p.左= x
  17. それ以外
  18. p.右= x
  19. それ以外
  20. 自己._root = x
  21.  
  22. y.左= x.右 
  23. x.右= y

次に、上記の実装が正しいかどうかをテストするためにいくつかのデータを構築します。

  1. def setup_right_rotate():
  2. 小麦粉: Node = Node( "小麦粉" , 10)
  3. キャベツ: Node = Node( "キャベツ" , 77)
  4. ビール: Node = Node( "ビール" , 76)
  5. ベーコン: Node = Node( "ベーコン" , 95)
  6. バター: Node = Node( "バター" , 86)
  7.  
  8. flour.parent = なし
  9. 小麦粉。=キャベツ
  10. flour.right = なし
  11. キャベツ.左= ビール
  12.  
  13.  
  14. ビール.左=ベーコン
  15. ビール。はバター
  16.  
  17. 小麦粉、ビールを返す
  18.  
  19. def print_treap(n: ノード):
  20. nNone の場合:
  21. 戻る 
  22.  
  23. 印刷(n)
  24. print_treap( n.left )
  25. print_treap(名詞)
  26.  
  27. トレップ = トレップ()
  28. ルート、x、キャベツ = setup_right_rotate()
  29. print( "---------右回転前---------:" )
  30. print_treap(ルート)
  31. treap.right_rotate(x)
  32. print( "------右回転後-------" )
  33. print_treap(ルート)

上記のコードを実行した後の出力は次のようになります。

  1. ---------右回転前---------:  
  2. (小麦粉、10) 親: なし: (キャベツ、77): なし
  3. (キャベツ、77) 親: (小麦粉、10): (ビール、76): (卵、129)
  4. (ビール、76) 親: (キャベツ、77): (ベーコン、95): (バター、86)
  5. (ベーコン、95) 親: (ビール、76): なし: なし
  6. (バター、86) 親: (ビール、76): なし: なし
  7. (卵、129) 親: (キャベツ、77): なし: なし
  8. -------右回転後-------  
  9. (小麦粉、10) 親: なし: (ビール、76): なし
  10. (ビール、76) 親: (小麦粉、10): (ベーコン、95): (キャベツ、77)
  11. (ベーコン、95) 親: (ビール、76): なし: なし
  12. (キャベツ、77) 親: (ビール、76): (バター、86): (卵、129)
  13. (バター、86) 親: (キャベツ、77): なし: なし
  14. (卵、129) 親: (キャベツ、77): なし: なし

右回転の前後のバイナリ ツリーの出力を比較すると、回転したバイナリ ツリーによって印刷される情報は、上記の回転後の対応する画像と確かに一致しています。次に、左回転を実装します。まず、上図のキャベツノードに対応する値を 75 に変更して、そのノードと親ノードが小さいヒープ プロパティに違反するようにします。

必要な作業は次のとおりです。1. キャベツ ノードをビールの位置に「左」に回転します。2. ビールの親ノードをキャベツに設定します。3. ビールの右の子をキャベツの左の子に設定します。4. キャベツの左の子がビールになります。左回転後、バイナリ ツリーは次のようになります。

上の図から、左回転後も文字列はバイナリツリーソートを維持し、数値出力もスモールヒープ原則に準拠していることがわかります。対応するコード実装を見てみましょう。

  1. クラスTreap:
  2. ...
  3.  
  4. def left_rotate(self, x:ノード):
  5. xNoneまたはx.is_root() 真実
  6. 戻る 
  7.  
  8. y = x.親
  9. yが正しい場合   not x: # 右の子だけが左に回転できる
  10. 戻る 
  11.  
  12. p = y.親
  13. p なしではない:
  14. p.leftの場合  y:
  15. p.左= x
  16. それ以外
  17. p.右= x
  18. それ以外
  19. 自己._root = x
  20.  
  21. y.右= x.左 
  22. x.左= y

上記のコード実装をテストするには、まず cabbage の値を変更してから、上記のコードを呼び出します。

  1. キャベツ._優先度 = 75
  2. print( "-------左回転前--------" )
  3. print_treap(ルート)
  4. treap.left_rotate(キャベツ)
  5. print( "-------左回転後----------" )
  6. print_treap(ルート)

コードを実行した後の出力は次のようになります。

  1. -------左回転前--------  
  2. (小麦粉、10) 親: なし: (ビール、76): なし
  3. (ビール、76) 親: (小麦粉、10): (ベーコン、95): (キャベツ、75)
  4. (ベーコン、95) 親: (ビール、76): なし: なし
  5. (キャベツ、75) 親: (ビール、76): (バター、86): (卵、129)
  6. (バター、86) 親: (キャベツ、75): なし: なし
  7. (卵、129) 親: (キャベツ、75): なし: なし
  8. -------左回転後---------  
  9. (小麦粉、10) 親: なし: (キャベツ、75): なし
  10. (キャベツ、75) 親: (小麦粉、10): (ビール、76): (卵、129)
  11. (ビール、76) 親: (キャベツ、75): (ベーコン、95): (バター、86)
  12. (ベーコン、95) 親: (ビール、76): なし: なし
  13. (バター、86) 親: (ビール、76): なし: なし
  14. (卵、129) 親: (キャベツ、75): なし: なし

出力結果の説明は、上図の左回転後の結果と一致しています。 Treap は要素のキーを基準にソートされたバイナリ ツリーであるため、文字列を指定すると、その文字列が Treap 内にあるかどうかを簡単に照会できます。その本質はソートされたバイナリ ツリーの検索であり、ここではその実装については無視します。

クエリはシンプルですが、挿入後に新しいノードとその親ノードが小さいヒープのプロパティに違反する可能性があるため、ノードの挿入は少し面倒です。したがって、挿入が完了した後も、上記で実装した左回転または右回転を使用して調整する必要があります。

<<:  人工知能時代の機械の未来

>>:  Microsoft TensorFlow-DirectML 正式版リリース: WSL での GPU による機械学習の高速化

ブログ    
ブログ    

推薦する

2020年の人工知能レビュー:AIが時代に知性をもたらす

2020年は人工知能(AI)にとって節目の年です。今年、新型コロナウイルス感染症のパンデミックが世界...

AIチップアーキテクチャは最先端へ向かう

企業は、AI をエッジに押し上げるための最適な武器として、さまざまなチップ アーキテクチャを採用しよ...

ついに、トップNLPカンファレンスACLへの投稿は匿名である必要がなくなりました

自然言語処理分野の研究者にとって朗報があります。最近、計算言語学会(ACL)の年次総会は、この一連の...

AIは自己反復と最適化が可能で、わずか26秒で歩行ロボットを設計できる

10月10日のニュース、AIに陸上を歩けるロボットを設計するように頼んだら何秒かかるでしょうか?答え...

Googleの上級研究員ネイチャーが記事を公開: 機械学習の3つの大きな「落とし穴」を避ける

アルゴリズム分析は科学研究の重要な方法となっている。生物学者、高エネルギー物理学者、病理学者など、多...

...

Alibaba Cloudは、Llama2トレーニングの展開を全面的にサポートする最初の企業であり、企業が独自の大規模モデルを迅速に構築できるように支援します。

Llama2 はオープンソースであり、無料の商用利用をサポートしているため、オープンソースの大規模...

中国ダイビングチームの勝利には人工知能が貢献した

ネットユーザーたちはこのオリンピックについて不満を述べている。たとえ境界線を越えたとしても、高得点を...

AIは古い建物のエネルギー効率を変えるでしょうか?

スマート ビルディングの観点から見ると、AI は多くの居住者向けテクノロジーに統合され、建物やキャン...

TensorFlow を使用して Android デバイスでディープラーニング推論を実装する方法

[[211369]]個人や企業にとって、ローカルデバイスでディープラーニング推論を実行することが望ま...

AI 実践者が習得する必要がある 10 種類のディープラーニング手法: バックプロパゲーション、転移学習、勾配降下法...

機械学習への関心は過去 10 年間で爆発的に高まりました。ほぼ毎日、さまざまなコンピューターサイエン...

...

Transformer モデルを使用した時系列予測の Pytorch コード例

時系列予測は永続的なトピックです。自然言語処理の分野での成功に触発されて、トランスフォーマー モデル...

Nature: 科学者がディープラーニングを使って初めて人間の意識を定量化

今、科学者たちは人間の意識について新たな理解を得ています!この研究では、ディープラーニングアルゴリズ...

...