Skip to content

Commit

Permalink
Add binary search tree meld
Browse files Browse the repository at this point in the history
  • Loading branch information
MiSawa committed Apr 29, 2024
1 parent 0ec19b3 commit 8dc47c4
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 2 deletions.
1 change: 1 addition & 0 deletions others/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ <h2>その他</h2>
<li><a href="avoid_errors/compare_fractions.html">有理数同士の比較</a></li>
</ul>
</li>
<li><a href="meld_binary_search_trees.html">二分探索木の meld</a></li>
</ul>


Expand Down
244 changes: 244 additions & 0 deletions others/meld_binary_search_trees.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>二分探索木の meld - みさわめも</title>
<meta name="description" content="二分探索木を meld するアルゴリズムとその計算量解析" />
<meta itemprop="name" content="二分探索木の meld" />
<meta itemprop="description" content="二分探索木を meld するアルゴリズムとその計算量解析" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@Mi_Sawa" />
<meta name="twitter:title" content="二分探索木の meld" />
<meta name="twitter:description" content="二分探索木を meld するアルゴリズムとその計算量解析" />
<meta property="og:type" content="website" />
<meta property="og:title" content="二分探索木の meld" />
<meta property="og:description" content="二分探索木を meld するアルゴリズムとその計算量解析" />
<meta property="og:site_name" content="みさわめも" />
<meta property="og:locale" content="ja_JP" />
<meta name="twitter:creator" content="@Mi_Sawa" />

<link href="../stylesheets/normalize-672beeec.css" rel="stylesheet" /><link href="../stylesheets/all-69f25411.css" rel="stylesheet" /><link href="../stylesheets/rouge-1aec61e0.css" rel="stylesheet" />
<script>
window.WebFontConfig = {
custom: {
families: ['KaTeX_AMS', 'KaTeX_Caligraphic:n4,n7', 'KaTeX_Fraktur:n4,n7',
'KaTeX_Main:n4,n7,i4,i7', 'KaTeX_Math:i4,i7', 'KaTeX_Script',
'KaTeX_SansSerif:n4,n7,i4', 'KaTeX_Size1', 'KaTeX_Size2', 'KaTeX_Size3',
'KaTeX_Size4', 'KaTeX_Typewriter'],
},
};
</script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/webfontloader.js" integrity="sha256-4O4pS1SH31ZqrSO2A/2QJTVjTPqVe+jnYgOWUVr7EEc=" crossorigin="anonymous"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js" integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js" integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa" crossorigin="anonymous"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
renderMathInElement(document.body, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
{left: "\\[", right: "\\]", display: true},
],
macros: {
// '\\N': '{\\mathbb{N}}',
// '\\Z': '{\\mathbb{Z}}',
'\\Q': '{\\mathbb{Q}}',
// '\\R': '{\\mathbb{R}}',
'\\C': '{\\mathbb{C}}',
'\\divides': '{\\mid}',
'\\abs': '{\\left| #1 \\right|}',
'\\relmiddle': '\\mathrel{}\\middle#1\\mathrel{}',
'\\setmid': '\\relmiddle|',
'\\set': '\\left\\{\\,#1\\,\\right\\}',
},
});
});
</script>



<script async src="https://www.googletagmanager.com/gtag/js?id=UA-25117970-3"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-25117970-3');
gtag('config', 'G-L8X6JXB71M');
</script>
</head>

<body class='others others_meld_binary_search_trees'>
<header>
<input id='nav-hamburger-checkbox' type='checkbox' />
<label id='nav-hamburger-button' for='nav-hamburger-checkbox'>
<svg id="menu-toggle-icon" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" height="32px" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon to toggle menu</title>
<g class='hamburger' fill="none" stroke="black" stroke-width="4">
<path stroke-linecap="round" d="M4,8h24"/>
<path stroke-linecap="round" d="M4,16h24"/>
<path stroke-linecap="round" d="M4,24h24"/>
</g>
<g class='close' fill="none" stroke="black" stroke-width="4">
<path stroke-linecap="round" d="M8,8l16,16"/>
<path stroke-linecap="round" d="M8,24l16,-16"/>
</g>
</label>
<nav>
<ul>
<li> <a href="../index.html">Top</a> </li>
<li> <a href="../contests.html">Contests</a> </li>
<li> <a href="index.html">Articles</a> </li>
<li> <a href="../tags/index.html">Tags</a> </li>
<li> <a href="../about.html">About</a> </li>
</nav>
</header>

<main>

<article>
<div style="display:none">`$$
\gdef\new{\operatorname{new}}
\gdef\insert{\operatorname{insert}}
\gdef\split{\operatorname{split}}
\gdef\concat{\operatorname{concat}}
\gdef\meld{\operatorname{meld}}
\gdef\cnt{\operatorname{cnt}}
$$`</div>

<h1>二分探索木の meld</h1>

<h2>概要</h2>

<p><a href="https://codeforces.com/blog/entry/49446" title="using merging segment tree to solve problems about sorted list">binary trie を meld できるという話</a> を読んでいたら思いついたこと.</p>

<p>順序を持つ要素型に対し, 次の操作をサポートする集合データ構造(典型的には平衡二分探索木)を考える.</p>

<ul>
<li>$\new()$: 空の集合を作る.</li>
<li>$\insert(S, x)$: $S$ に値をひとつ挿入する.</li>
<li>$\split(S, x)$: $S$ を $x$ 未満の要素全体からなる木と $x$ 以上の要素全体からなる木に分割する.</li>
<li>$\concat(S, T)$: $S$ の任意の要素は $T$ の任意の要素より小さいという条件のもと, $S$ と $T$ を結合する. $S$ と $T$ は破壊される.</li>
<li>$\min(S)$: $S$ の最小の要素を($S$ から取り除くことなく)返す. $S$ に要素が存在しない場合はそれを報告する.</li>
</ul>

<p>これらを利用して</p>

<ul>
<li>$\meld(S, T)$: $S$ と $T$ の和集合を返す. $S$ と $T$ は破壊される.</li>
</ul>

<p>を実現できる. <code>insert</code> をちょうど $n$ 回, <code>split</code> をちょうど $m$ 回, <code>meld</code> をちょうど $l$ 回含む操作列に対し, その $l$ 回の <code>meld</code> は, $w = \lceil \log_2 n \rceil$ として, $O(w (n + m) + l)$ 回の <code>min</code>, <code>split</code>, <code>concat</code> を用いて実現できる.</p>

<p>よくある 平衡二分探索木は <code>new</code> を $O(1)$, <code>insert</code>, <code>split</code>, <code>concat</code>, <code>min</code> を $O(\log n)$ でサポートするから, 償却して <code>insert</code>, <code>split</code>, <code>concat</code>, <code>min</code> を amortized $O((\log n)^2)$, <code>meld</code> を amortized $O(1)$ でサポートできる.</p>

<h2>アルゴリズム</h2>

<p>$\insert(x)$ が呼ばれた際, $x$ の換わりに $x$ と <code>insert</code> がこれまで呼ばれた回数のペアを保存することなどにより, 与えられる全ての要素は相異なるとしてよい.
<code>meld</code> はよくある sorted merge をスキップしつつ実装する. 具体的には,</p>

<div class="highlight"><pre class="highlight rust"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">meld</span><span class="p">(</span><span class="k">mut</span> <span class="n">lhs</span><span class="p">:</span> <span class="n">Set</span><span class="p">,</span> <span class="k">mut</span> <span class="n">rhs</span><span class="p">:</span> <span class="n">Set</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Set</span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">res</span> <span class="o">=</span> <span class="nn">Set</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
<span class="k">while</span> <span class="o">!</span><span class="n">lhs</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">rhs</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">pivot</span> <span class="o">=</span> <span class="n">lhs</span><span class="nf">.min</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="p">(</span><span class="n">small</span><span class="p">,</span> <span class="n">large</span><span class="p">)</span> <span class="o">=</span> <span class="n">rhs</span><span class="nf">.split</span><span class="p">(</span><span class="n">pivot</span><span class="p">);</span>
<span class="n">res</span> <span class="o">=</span> <span class="nn">Set</span><span class="p">::</span><span class="nf">concat</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">small</span><span class="p">);</span>
<span class="p">(</span><span class="n">lhs</span><span class="p">,</span> <span class="n">rhs</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">large</span><span class="p">,</span> <span class="n">lhs</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nn">Set</span><span class="p">::</span><span class="nf">concat</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="nn">Set</span><span class="p">::</span><span class="nf">concat</span><span class="p">(</span><span class="n">lhs</span><span class="p">,</span> <span class="n">rhs</span><span class="p">));</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>とする. (注: distinct でなかった場合無限ループする. <code>split</code> を未満と以上で分割するかわりに以下とより大で分割するようにすれば無限ループは解消できるが, distinct でない場合計算量解析でちょっと面倒になると思う.)</p>

<h2>計算量解析</h2>

<p><code>insert</code> をちょうど $n$ 回, <code>split</code> をちょうど $m$ 回, <code>meld</code> をちょうど $l$ 回含む操作列を考える. $w = \lceil \log_2 n \rceil$ として, <code>meld</code> のループが合計で $O(w (n + m) + l)$ 回まわることを言えばよい.
この操作列に含まれる $x$ を, ソート順で $0$-origin で 何番目に来るかを表わす $0$ から $n-1$ までの$w$ ビットの値で置き換えて考える.<sup id="fnref:compress" role="doc-noteref"><a href="#fn:compress" class="footnote">1</a></sup> <sup id="fnref:no_need_to_implement" role="doc-noteref"><a href="#fn:no_need_to_implement" class="footnote">2</a></sup></p>

<p><code>lhs</code> に含まれる要素をソートした列を $a$, <code>rhs</code> に含まれる要素をソートした列を $b$, $\meld(\mathrm{lhs}, \mathrm{rhs})$ に含まれる要素をソートした列を $c$ とする. $c$ は, $a$ から来た要素からなる連続部分列と $b$ から来た要素からなる連続部分列が交互に表れるが, これが切り換わる回数 $\cnt(a, b)$ がほぼループ回数なので, これを上から評価したい.
<a href="https://codeforces.com/blog/entry/49446" title="using merging segment tree to solve problems about sorted list">参考文献1</a> のように, 各集合に深さ $w$ の binary trie を対応させ, その頂点数を考える.</p>

<p><code>insert</code>, <code>split</code> を実行すると, binary trie の合計頂点数は高々 $w$ 増える.
<code>new</code>, <code>concat</code>, <code>min</code> で binary trie の合計頂点数が増えることはない.</p>

<p>一方で $c = \meld(a, b)$ を実行すると, $a$ に対応する binary trie の含まれる頂点と $b$ に対応する trie に含まれる頂点のうち, 同じ prefix に対応する頂点の個数だけ合計頂点数が減る.
$c$ 内で $a$ の要素と $b$ の要素が切り換わる隣接ペアの, $c$ に対応する binary trie 上での LCA を考えると, これは当然 $a$ に対応する binary trie にも $b$ に対応する binary trie にも含まれる. 更にこの対応は単射である. 実際, $a$ に対応する binary trie と $b$ に対応する binary trie で重複していた $c$ に対応する binary trie の頂点 $t$ に対し, それを LCA として持つ $c$ 内の隣接要素は, $t$ の左部分木の最大の葉と $t$ の右部分木の最小の葉に対応するもののみである.
従って, $c = \meld(a, b)$ で binary trie の合計頂点数は少なくとも $\cnt(a, b)$ だけ減る.</p>

<p>以上を合わせると, $\cnt$ の合計は $w (n + m)$ を越えることはなく, 従って <code>meld</code> のループは合計高々 $w (n + m) + l$ 回まわる.</p>

<h2>わからんこと</h2>

<ul>
<li>計算量のオーダーはタイトだろうか. ループの回数が除去される頂点数以下であることしか言っていないのとか, <code>concat</code> にかかる計算量が高さの差分くらいのオーダーなことを利用していないとか, <code>concat</code> の順序を調節してマージテクのようにしたらどうかとか.</li>
<li>Splay 木だと計算量のオーダーが落ちたりしてもおかしくない気がするが, どうだろうか.</li>
<li>binary trie の <code>meld</code> と似た, 平衡二分探索木向けの次のようなアルゴリズムが考えられるが, これの計算量はどうなるだろうか. 上の議論での切り換わる点を子孫として含む $O(\cnt(a, b) \log n)$ 頂点くらいで <code>meld</code> が呼ばれ, $O((\log n)^3)$ くらいになりそうな気がするが, よりよい評価はできるだろうか. <code>split</code> が非空な分割をする回数は $O(\cnt(a, b))$ に抑えられそうだが...</li>
</ul>

<div class="highlight"><pre class="highlight rust"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">meld</span><span class="p">(</span><span class="k">mut</span> <span class="n">lhs</span><span class="p">:</span> <span class="n">Tree</span><span class="p">,</span> <span class="k">mut</span> <span class="n">rhs</span><span class="p">:</span> <span class="n">Tree</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Tree</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">lhs</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">rhs</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span> <span class="c">// もしくは平衡条件に合うよりよい指標で</span>
<span class="p">(</span><span class="n">lhs</span><span class="p">,</span> <span class="n">rhs</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">rhs</span><span class="p">,</span> <span class="n">lhs</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">rhs</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">lhs</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">let</span> <span class="p">(</span><span class="n">rl</span><span class="p">,</span> <span class="n">rr</span><span class="p">)</span> <span class="o">=</span> <span class="n">rhs</span><span class="nf">.split</span><span class="p">(</span><span class="n">lhs</span><span class="py">.key</span><span class="p">);</span>
<span class="n">lhs</span><span class="py">.l</span> <span class="o">=</span> <span class="nf">meld</span><span class="p">(</span><span class="n">lhs</span><span class="py">.l</span><span class="p">,</span> <span class="n">rl</span><span class="p">);</span>
<span class="n">lhs</span><span class="py">.r</span> <span class="o">=</span> <span class="nf">meld</span><span class="p">(</span><span class="n">lhs</span><span class="py">.r</span><span class="p">,</span> <span class="n">rr</span><span class="p">);</span>
<span class="n">lhs</span><span class="nf">.rebalance</span><span class="p">();</span>
<span class="k">return</span> <span class="n">lhs</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<h2>参考文献</h2>

<ol>
<li>Codeforces の <a href="https://codeforces.com/profile/TLE">TLE</a> によるブログ記事 <a href="https://codeforces.com/blog/entry/49446" title="using merging segment tree to solve problems about sorted list">using merging segment tree to solve problems about sorted list</a></li>
</ol>

<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:compress" role="doc-endnote">
<p>いわゆる座標圧縮 <a href="#fnref:compress" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
</li>
<li id="fn:no_need_to_implement" role="doc-endnote">
<p>計算量解析のために仮想的に番号を振るだけで, 実装する際に気にする必要は無い. 特に, クエリ先読みするオフラインアルゴリズムにする必要は無い. <a href="#fnref:no_need_to_implement" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
</li>
</ol>
</div>


<footer>
<h3>Tags</h3>
<div class='tag'><a href="../tags/データ構造.html">データ構造</a></div>
</footer>
</article>
</main>
</body>
</html>
16 changes: 14 additions & 2 deletions sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</url>
<url>
<loc>https://misawa.github.io/others/flow/dinic_time_complexity.html</loc>
<lastmod>2024-02-29T13:17:33+00:00</lastmod>
<lastmod>2024-02-29T13:19:17+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
Expand All @@ -54,9 +54,15 @@
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://misawa.github.io/others/meld_binary_search_trees.html</loc>
<lastmod>2024-04-29T07:04:38+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://misawa.github.io/others/index.html</loc>
<lastmod>2021-02-11T06:48:43+00:00</lastmod>
<lastmod>2024-04-28T07:13:54+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
Expand Down Expand Up @@ -606,6 +612,12 @@
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://misawa.github.io/tags/%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0.html</loc>
<lastmod>2020-06-12T16:20:22+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://misawa.github.io/tags/%E8%AA%A4%E5%B7%AE.html</loc>
<lastmod>2020-06-12T16:20:22+00:00</lastmod>
Expand Down
Loading

0 comments on commit 8dc47c4

Please sign in to comment.