<p>Červeno-černé stromy jsou celkem oblíbené pro implementaci ADT množiny nebo slovníku za předpokladu, že nad vkládanými klíči existuje uspořádání. Jazyky níže implementují dané datové struktury v 2 variantách a to:</p>
<ul>
<li>seřazené: používají na pozadí právě červeno-černý strom</li>
<li>neseřazené: používají na pozadí hašovací tabulku</li>
</ul>
<p>Pro srovnání, jak jsme si říkali na cvičení, červeno-černý strom má operace hledání, vkládání a mazání v časové složitosti <spanclass="katex"><spanclass="katex-mathml"><mathxmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mimathvariant="script">O</mi><mostretchy="false">(</mo><mi>log</mi><mo></mo><mi>n</mi><mostretchy="false">)</mo></mrow><annotationencoding="application/x-tex">\mathcal{O}(\log n)</annotation></semantics></math></span><spanclass="katex-html"aria-hidden="true"><spanclass="base"><spanclass="strut"style="height:1em;vertical-align:-0.25em"></span><spanclass="mord mathcal"style="margin-right:0.02778em">O</span><spanclass="mopen">(</span><spanclass="mop">lo<spanstyle="margin-right:0.01389em">g</span></span><spanclass="mspace"style="margin-right:0.1667em"></span><spanclass="mord mathnormal">n</span><spanclass="mclose">)</span></span></span></span>. Na druhou stranu hašovací tabulka má ideálně konstantní časovou složitost, ale v nejhorším případě (detaily na posledním cvičení v semestru) je to bohužel <spanclass="katex"><spanclass="katex-mathml"><mathxmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mimathvariant="script">O</mi><mostretchy="false">(</mo><mi>n</mi><mostretchy="false">)</mo></mrow><annotationencoding="application/x-tex">\mathcal{O}(n)</annotation></semantics></math></span><spanclass="katex-html"aria-hidden="true"><spanclass="base"><spanclass="strut"style="height:1em;vertical-align:-0.25em"></span><spanclass="mord mathcal"style="margin-right:0.02778em">O</span><spanclass="mopen">(</span><spanclass="mord mathnormal">n</span><spanclass="mclose">)</span></span></span></span>.</p>
<p>Výše jsme si ukázali nějaké předpoklady nutné pro hašovací tabulku i červeno-černý strom. Co je tedy lepší?</p>
<ul>
<li>červeno-černý strom nám poskytuje <em>stabilní časovou složitost</em>, ale za cenu požadavku <em>uspořádání</em> nad prvky</li>
<li>hašovací tabulka nám poskytuje <em>pomyslnou perfektní časovou složitost</em></li>
</ul>
<h2class="anchor anchorWithStickyNavbar_LWe7"id="různé-implementace">Různé implementace<ahref="#různé-implementace"class="hash-link"aria-label="Direct link to Různé implementace"title="Direct link to Různé implementace"></a></h2>
<p>Pro ukázku použití červeno-černých stromů v implementacích standardních knihoven
<p>Pokud Vás zajímají různé implementace, tak bychom doporučili „prohrabávat“ se přes ně v následujícím pořadí: <code>C# → Java → C++</code>. Důvod pro zvolené pořadí vychází z toho, že C# implementace je poměrně čitelná a obsahuje množství vysvětlujících komentářů. Implementace v Javě je stejně čitelná, ačkoli již s minimem komentářů, které se maximálně odkazují na CLRS. C++ implementace je „značně poznačená“ podtržítky ;)</p>
<h3class="anchor anchorWithStickyNavbar_LWe7"id="c">C++<ahref="#c"class="hash-link"aria-label="Direct link to C++"title="Direct link to C++"></a></h3>
<p>V C++ si můžeme vybrat mezi 2 různými implementacemi (<code>clang</code> nebo <code>gcc</code>).</p>
<h4class="anchor anchorWithStickyNavbar_LWe7"id="clang">clang<ahref="#clang"class="hash-link"aria-label="Direct link to clang"title="Direct link to clang"></a></h4>
<p>Hlavičkové soubory, které používáme při práci s množinou nebo slovníkem (zajímavé sekce jsou vytaženy):</p>
<p>U obou hlaviček si můžeme všimnout, že deklarují nějaký soukromý typ <code>__base</code>, který je aliasem pro <code>__tree</code>. Ten nás pak vede k hlavičce <ahref="https://github.com/llvm/llvm-project/blob/main/libcxx/include/__tree"target="_blank"rel="noopener noreferrer"><code>__tree</code></a>.</p>
<divclass="language-cpp codeBlockContainer_Ckt0 theme-code-block"style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><divclass="codeBlockContent_biex"><pretabindex="0"class="prism-code language-cpp codeBlock_bY9V thin-scrollbar"style="color:#393A34;background-color:#f6f8fa"><codeclass="codeBlockLines_e6Vv"><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">/*</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">_NodePtr algorithms</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">The algorithms taking _NodePtr are red black tree algorithms. Those</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">algorithms taking a parameter named __root should assume that __root</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">points to a proper red black tree (unless otherwise specified).</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">…</span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token comment"style="color:#999988;font-style:italic">*/</span><br></span></code></pre><divclass="buttonGroup__atx"><buttontype="button"aria-label="Copy code to clipboard"title="Copy"class="clean-btn"><spanclass="copyButtonIcons_eSgA"aria-hidden="true"><svgviewBox="0 0 24 24"class="copyButtonIcon_y97N"><pathfill="currentColor"d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svgviewBox="0 0 24 24"class="copyButtonSuccessIcon_LjdS"><pathfill="currentColor"d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4class="anchor anchorWithStickyNavbar_LWe7"id="gcc">gcc<ahref="#gcc"class="hash-link"aria-label="Direct link to gcc"title="Direct link to gcc"></a></h4>
<p>Pro <code>gcc</code> je postup téměř stejný. Pro změnu v hlavičkách <code>map</code> a <code>set</code> nenajdeme nic, deklarace jsou až v hlavičkových souborech:</p>
<p>V obou se zase odkazuje na nějakou hlavičku <ahref="https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/bits/stl_tree.h;h=a4de61417652a288e361a55fcc8bb7a9838c58a5;hb=HEAD"target="_blank"rel="noopener noreferrer"><code>bits/stl_tree.h</code></a>, zase výňatek:</p>
<divclass="language-cpp codeBlockContainer_Ckt0 theme-code-block"style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><divclass="codeBlockContent_biex"><pretabindex="0"class="prism-code language-cpp codeBlock_bY9V thin-scrollbar"style="color:#393A34;background-color:#f6f8fa"><codeclass="codeBlockLines_e6Vv"><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// Red-black tree class, designed for use in implementing STL</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// associative containers (set, multiset, map, and multimap). The</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// insertion and deletion algorithms are based on those in Cormen,</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// Leiserson, and Rivest, Introduction to Algorithms (MIT Press,</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 1990), except that</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">//</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// (1) the header cell is maintained with links not only to the root</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// but also to the leftmost node of the tree, to enable constant</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// time begin(), and to the rightmost node of the tree, to enable</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// linear time performance when used with the generic set algorithms</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// (set_union, etc.)</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">//</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// (2) when a node being deleted has two children its successor node</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// is relinked into its place, rather than copied, so that the only</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// iterators invalidated are those referring to the deleted node.</span><spanclass="token plain"></span
<p>Tady už taky vidíme nějaký kód pro nalezení minima/maxima ve stromě. Mimo jiné
ještě existuje <ahref="https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/src/c%2B%2B98/tree.cc;h=fd14991589a57c6aa847f57105a938cd2bf4df6f;hb=HEAD"target="_blank"rel="noopener noreferrer"><code>tree.cc</code></a>, kde je lze nalézt třeba funkci s následující hlavičkou:</p>
<h3class="anchor anchorWithStickyNavbar_LWe7"id="java">Java<ahref="#java"class="hash-link"aria-label="Direct link to Java"title="Direct link to Java"></a></h3>
<p>V Javě jsou pro nás klíčové implementace <ahref="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/TreeSet.java"target="_blank"rel="noopener noreferrer"><code>TreeSet</code></a> a <ahref="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/TreeMap.java"target="_blank"rel="noopener noreferrer"><code>TreeMap</code></a>.</p>
<p>V implementaci <code>TreeSet</code> si můžete povšimnout:</p>
<p><code>TreeSet</code> v Javě tedy používá na pozadí <code>TreeMap</code> (což je vidět ve výchozím konstruktoru, kde se volá konstruktor přebírající <code>NavigableMap<E, Object></code>, a je mu předáno <code>new TreeMap<>()</code>).</p>
<p>Co se týče <code>TreeMap</code>, tak hned ze začátku definice <code>TreeMap</code> je vidět:</p>
<h3class="anchor anchorWithStickyNavbar_LWe7"id="c-1">C#<ahref="#c-1"class="hash-link"aria-label="Direct link to C#"title="Direct link to C#"></a></h3>
<p>V C# se zaměříme na nejnovější vydání (.NET), které je open-source a podporováno i na operačních systémech založených na Linuxu.</p>
<p>Nejdříve se podíváme na implementaci slovníku (<ahref="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/src/System/Collections/Generic/SortedDictionary.cs"target="_blank"rel="noopener noreferrer"><code>SortedDictionary</code></a>).</p>
<p>Na první pohled máme problém, protože <code>TreeSet</code> není <code>SortedSet</code>, který by jsme čekali. Když se přesuneme na konec souboru, tak zjistíme, že <code>TreeSet</code> je jenom <em>backward-compatible wrapper</em> pro <code>SortedSet</code>.</p>
<p>Přesuneme se k <ahref="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs"target="_blank"rel="noopener noreferrer"><code>SortedSet</code></a>. A hned ze začátku vidíme:</p>
<divclass="language-cs codeBlockContainer_Ckt0 theme-code-block"style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><divclass="codeBlockContent_biex"><pretabindex="0"class="prism-code language-cs codeBlock_bY9V thin-scrollbar"style="color:#393A34;background-color:#f6f8fa"><codeclass="codeBlockLines_e6Vv"><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// A binary search tree is a red-black tree if it satisfies the following red-black properties:</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 1. Every node is either red or black</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 2. Every leaf (nil node) is black</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 3. If a node is red, the both its children are black</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 4. Every simple path from a node to a descendant leaf contains the same number of black nodes</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">//</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// The basic idea of a red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// per node to encode 3-nodes and 4-nodes.</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 4-nodes will be represented as: B</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// R R</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">//</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// 3 -node will be represented as: B or B</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// R B B R</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">//</span><spanclass="token plain"></span><br></span><spanclass="token-line"style="color:#393A34"><spanclass="token plain"></span><spanclass="token comment"style="color:#999988;font-style:italic">// For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewic
<p>Vysvětlení v komentáři trochu předbíhá náplň cvičení zaměřeného na B-stromy ;)</p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id="vztah-mezi-množinou-a-mapou">Vztah mezi množinou a mapou<ahref="#vztah-mezi-množinou-a-mapou"class="hash-link"aria-label="Direct link to Vztah mezi množinou a mapou"title="Direct link to Vztah mezi množinou a mapou"></a></h2>
<table><thead><tr><th>Jazyk</th><th>Způsob implementace</th></tr></thead><tbody><tr><td>C++</td><td>mapa ukládá dvojice do množiny</td></tr><tr><td>Java</td><td>množina ukládá prvky s „dummy“ hodnotou do mapy</td></tr><tr><td>C#</td><td>mapa ukládá dvojice do množiny</td></tr></tbody></table>
<p>Mapa vyžaduje, aby každý klíč měl přiřazenou právě jednu hodnotu, tedy klíče jsou navzájem mezi sebou unikátní. To nám umožňuje organizovat klíče do množiny, zde ale narazíme na nepříjemný problém spočívající v tom, že musíme do množiny vkladat dvojice prvků: <code>(key, value)</code>. Tenhle přístup má ale zásadní problém:</p>