% !TeX spellcheck = cs_CZ
% !TeX root = ../BP_Prokopič_Martin_2021.tex

V~této kapitole představíme problém přesného vyhledávání vzorku v~textu a~popíšeme algoritmy, které pracují s~textem v~transformované podobě.

\section{Představení problému}

Mějme řetězec $T = T[1]\dots T[n]$ délky $n$ nad abecedou $\Sigma = \set{\sigma_1, \dots, \sigma_\abs{\Sigma}}$ ($\sigma_1 < \sigma_2 < \dots < \sigma_\abs{\Sigma}$). Problém přesného vyhledávání spočívá v~nalezení všech výskytů \emph{vzorku} $P = P[1]\dots P[m]$, $P \in \Sigma^+$ v~dlouhém textu $T$. Konkrétně nás zajímá množina indexů $\set{i : T[i\dotdot(i+m-1)] = P}$ začátků $P$ v~$T$. Předpokládáme $n \geq m$, jinak by bylo řešení triviální. \cite{melichar05}

V~rámci této bakalářské práce se ale zaměřujeme na přesné vyhledávací algoritmy pracující s~výstupem Burrows-Wheelerovy transformace. Jejich vstupem tak není původní text $T$, ale jeho transformovaná verze $L$. Stále nás ale zajímají pozice výskytů $P$ jako podřetězce v~$T$.

Abychom zachovali relaci mezi \acrshort{bwt} a~sufixovým polem, předpokládáme, že poslední znak $T$ je ukončovací symbol \texttt{\$}, který se v~$T$ nikde jinde nevyskytuje a~je nejmenší v~pořadí ze všech symbolů abecedy (platí $T[n] = \sigma_1 = \texttt{\$}$). \cite{burrows94}

\section{Přístupy k~vyhledávání v~transformovaném textu}\label{sec:approaches}

Mějme uložený textový soubor, který byl transformován pomocí BWT (a~následně případně zkomprimován). Výhodou vyhledávání přímo v~transformovaném textu je, že ušetříme čas potřebný na provedení reverzní transformace, a~díky struktuře \acrshort{bwt} obsahující informace o~seřazených sufixech $T$ můžeme navíc samotné vyhledávání značně zrychlit. \cite{bell05}

\pagebreak

Vyhledávací algoritmy pracující s~transformovaným textem $L$ můžeme dle \cite{bell05} rozdělit do několika skupin v~závislosti na vstupu a~principu činnosti:
\begin{description}
	\item[Dekóduj a~hledej] Nejprve algoritmus provede zpětnou transformaci pro de\-kódování kompletního původního textu $T$, který se potom použije jako vstup tradičního vyhledávacího algoritmu.\\
	\emph{Příklady algoritmů: Boyer-Moore, Sufixové pole}.

	\item[Přímé vyhledávání bez pomocných dat] Algoritmus na vstupu vyžaduje pouze transformovaný text $L$. Z~něj si následně dle potřeby zkonstruuje potřebná pomocná pole a~provede samotné hledání.\\
	\emph{Příklady algoritmů: Boyer-Moore pro BWT, Binární vyhledávání}.

	\item[Přímé vyhledávání s~pomocnými daty] Kromě $L$ na vstupu algoritmus vyžaduje další pomocné datové struktury, které tak zvyšují nároky na úložný prostor pro vstupní text.\\
	\emph{Příklady algoritmů: FM-index}.
\end{description}

\section{Algoritmus Boyer-Moore}

Jako představitele první skupiny \emph{dekóduj a~hledej} uvedeme algoritmus Boyer-Moore poprvé představený v~\cite{boyer77}. Aktuálně se jedná o~jeden z~nejefektivnějších algoritmů pro vyhledávání v~běžném (netransformovaném) textu \cite{bell08}. Patří do rodiny algoritmů porovnávajících vzorek s~textem pozpátku pomocí klouzavého okénka a~v~sekci \ref{sec:bmmod} ukážeme, jak tento a~podobné algoritmy upravit, aby pracovaly přímo s~transformovaným textem $L$ bez jeho dekódování. Pseudokód společný pro všechny členy této rodiny uvádíme v~algoritmu \ref{algo:bwmatch}.

Volba směru porovnávání vzorku (popředu vs. pozpátku) může mít vliv na rychlost algoritmu, přičemž porovnávání pozpátku je zpravidla rychlejší. Cílem pro dosažení nejlepší časové složitosti je zbytečně neporovnávat symboly, pokud již máme dostatek informací pro odvození výsledku. \cite{melichar06}

Algoritmus Boyer-Moore využívá dvou funkcí \BCS{$c$} (\emph{bad-character shift}) a~\GSS{$i$} (\emph{good-suffix shift}) pro určení, o~kolik symbolů posunout klouzavé okénko v~případě neshody se vzorkem $P$. Konkrétně nahradíme pevný posun o~jeden symbol na řádku \ref{algo:bwmatch:shift} algoritmu \ref{algo:bwmatch} maximem z~výsledků obou funkcí: $i \leftarrow i + \max\,\{\BCS{$T[i+m]$}, \GSS{$j$}\}$. V~nejlepším případě dosahuje časové složitosti $\BigO(\frac{n}{m})$, průměrně ale $\BigO(n)$ a~v~nejhorším případě, stejně jako naivní algoritmus, $\BigO(n\cdot m)$. \cite{boyer77}

\begin{algorithm}
\SetNoFillComment
\AlgoName{Backwards-Matching}{($P, T$)}
$n = \abs{T}, m = \abs{P}$\;
$i \leftarrow 0$\;
\While{$i \le n - m$}{
	$j \leftarrow m$\;
	\While{$j \ge 1$ \KwAnd $T[i+j] = P[j]$}{\label{algo:bwmatch:inner}
		$j \leftarrow j - 1$\;
	}
	\If{$j = 0$}{
		přidej $i+1$ do výstupní množiny výskytů\;
	}
	$i \leftarrow i + 1$\tcp*{posun klouzavého okénka vpřed o~1 pozici}\label{algo:bwmatch:shift}
}
\caption{Základní vyhledávací algoritmus využívající principu porovnávání klouzavého okénka pozpátku}\label{algo:bwmatch}
\end{algorithm}

\subsection{Pravidlo \emph{bad-character shift}}

Mějme klouzavé okénko posunuté o~$i$ pozic, to znamená, že jako první symbol budeme porovnávat $P[m]$ proti $c = T[i + m]$. Pravidlo \emph{bad-character shift} se snaží o~to, aby po posunu okénka doprava na pozici $i_n$ nastala shoda mezi $c$ a~odpovídajícím symbolem posunutého okénka ($P[i-i_n+m]$). Pokud se symbol $\sigma_i$ nikde v~$P$ nevyskytuje, posouváme okénko o~celou délku $m$.
%todo: něco udělat s tím slovem posun, ale nevím úplně, co

Jestliže se v~$P[1\dotdot (m-1)]$ nachází symbol $c$, potom je \BCS{$c$} rovno $m - (\text{pozice posledního výskytu}\ c\ \text{v}~P[1\dotdot (m - 1)])$. Symbol vyhledáváme pouze v~prefixu $P[1\dotdot (m-1)]$, abychom zaručili nenulovost výsledku, posunuli tak okénko vždy dopředu a~tím zabránili zacyklení. Pokud se v~$P[1\dotdot (m-1)]$ nenachází žádný výskyt $c$, vracíme délku vzorku $m$.

Funkce se dá předpočítat jednoduchým algoritmem pro všech $\abs{\Sigma}$ symbolů v~čase $\BigO(m)$. Nejprve inicializujeme hodnoty pro všechny symboly na $m$, a~poté procházíme $P[1\dotdot (m-1)]$ a~hodnoty postupně upravujeme.

\begin{example}
	Hledáme všechny výskyty vzorku $P =~$\texttt{ssi} v~textu $T=~$\texttt{mississippi} (abeceda $\Sigma = \set{i,m,p,s}$). V~obrázku \ref{tab:bcsex} uvádíme hodnoty funkce \BCS{$c$} $\forall c \in \Sigma$ a~průběh vyhledávání s~použitím tohoto pravidla. Shoda nastala na pozicích $i \in \set{2,5}$, proto je výsledek $\set{3,6}$.
\end{example}

\vspace{-10pt}
\begin{figure}[H]
	\centering
	\subfloat[]{
		\begin{tabular}{cc}
			$\sigma_i$ & \BCS{$\sigma_i$}\\
			\hline
			\texttt{i} & 3\\
			\texttt{m} & 3\\
			\texttt{p} & 3\\
			\texttt{s} & 1\\
			\\
		\end{tabular}\label{tab:bcsex:bcs}
	}
	\qquad
	\subfloat[]{
		\begin{tabular}{rll}
			$i$ & \texttt{mi\underline{s}\underline{s}\underline{i}ss\underline{i}ppi} &\\
			\cline{1-2}
			0 & \texttt{ssi} & $\BCS{\texttt{s}} = 1$\\
			1 & \texttt{~ssi} & stejně jak výše\\
			2 & \texttt{~~ssi} & $\texttt{i} \notin P[1\dotdot (m-1)]$\\
			5 & \texttt{~~~~~ssi} & stejně jak výše\\
			8 & \texttt{~~~~~~~~ssi} &\\
		\end{tabular}\label{tab:bcsex:s}
	}
	\caption{Vyhledávání vzorku $P =~$\texttt{ssi} v~textu $T=~$\texttt{mississippi} (abeceda $\Sigma = \set{i,m,p,s}$) pomocí klouzavého okénka a~funkce \protect\BCS: \protect\subref{tab:bcsex:bcs} hodnoty \protect\BCS; \protect\subref{tab:bcsex:s} průběh vyhledávání}\label{tab:bcsex}
\end{figure}
	
\subsection{Pravidlo \emph{good-suffix shift}}

Pravidlo \emph{good-suffix shift} se uplatní, pokud již máme alespoň částečnou shodu v~klouzavém okénku ($j < m$). Dokáže identifikovat další výskyty již porovnaného sufixu okénka uvnitř vzorku a~díky tomu vyhledávání urychlit. Při neshodě mezi $P[j]$ a~$T[i+j]$ ($j < m$) může nastat jeden ze dvou případů:
\begin{description}
	\nitem{případ} Shodující se sufix vzorku $\str{s} = P[j\dotdot m]$ existuje ještě někde dál v~$P$ jako podřetězec. Tuto situaci zachycuje obrázek \ref{fig:gss} a~okénko můžeme posunout tak, aby byl poslední výskyt $\str{s}$ v~$P$ zarovnaný s~textem~$T$.
	%Alternativně můžeme říci, že existuje $k \le j$ takové, že sufix $\str{s} = \Border(P[k\dotdot m])$.
	
	\nitem{případ} Shodný sufix se již nikde jinde v~$P$ nenachází, proto okénko posuneme tak, aby byla zarovnaná nejdelší hranice vzorku, konkrétně $\GSS{$j$}$ bude rovno $m - \abs{\Border(P)}$. Tuto situaci jsme znázornili na obrázku \ref{fig:gss2}. \cite{melichar06}
\end{description}

\begin{figure}[h]
	\centering
	\input{figures/gss.pdf_tex}	
	\caption{První případ \protect\GSS: ve vzorku $P$ existuje další výskyt sufixu}\label{fig:gss}
\end{figure}

\begin{figure}[h]
	\centering
	\input{figures/gss2.pdf_tex}	
	\caption{Druhý případ \protect\GSS: pokud další výskyt sufixu neexistuje, posuneme vzorek tak, aby byla zarovnána jeho hranice}\label{fig:gss2}
\end{figure}

\subsubsection*{Postup výpočtu hodnot}

Hodnoty \GSS pro první případ můžeme odvodit z~tabulky přechodů deterministického faktorového automatu pro obrácený vzorek $P^R$. Kroky výpočtu ukážeme na příkladu.

\begin{example}
	Počítáme \GSS pro vzorek $P = P^R = \texttt{ipipi}$. Za použití algoritmu \ref{algo:facaconstr} sestavíme nedeterministický faktorový automat $M_1$ pro řetězec $P^R$, který přijímá všechny jeho podřetězce. Automat poté determinizujeme pomocí podmnožinové konstrukce (algoritmus \ref{algo:powerset}), a~ekvivalentní \acrshort{dfa} označíme $M_2$. Na obrázcích \ref{fig:faca} a~\ref{fig:facadet} jsme graficky znázornili oba automaty a~v~tabulce \ref{tab:faca} uvádíme jejich přechodové funkce. \cite[str.~9--11]{melichar06}
	
	Následně odebráním přechodu $(\set{0},\texttt{p}) \rightarrow \set{2,4}$ (označen čárkovaně) vytvoříme páteř (\emph{backbone}) automatu $M_2$. Pro určení $\GSS{$j$}$ využíváme pozorování, že v~případě neshody faktorový automat stále pokračuje s~porovnáváním od dalších výskytu daného sufixu. Stačí se tedy podívat na vzdálenost nejmenších dvou prvků podmnožin stavů $M_1$ reprezentovaných jednotlivými stavy $M_2$. Pokud je podmnožina pouze jednoprvková, znamená to, že další výskyt odpovídajícího sufixu v~$P$ neexistuje a~nastává druhý případ. Hodnota \GSS potom bude rovna $m - \abs{\Border(P)})$. Pro počáteční stav (sufix $\epsilon$) je $\GSS{$m$}$ vždy rovna 1. \cite[str.~9--11]{melichar06}
	
	Pro náš vzorek \texttt{ipipi} jsme výsledek znázornili v~tabulce \ref{tab:gssex}.
\end{example}

\begin{figure}
	\centering
	\begin{tikzpicture}[
	shorten >=1pt,node distance=1.75cm,on grid,auto,
	state/.style={circle,draw,minimum size=1.05cm,inner sep=1pt}]
	
	\node[state,accepting](5) {$5$};			
	\node[state,accepting](4) [right=of 5] {$4$};
	\node[state,accepting](3) [right=of 4] {$3$};
	\node[state,accepting](2) [right=of 3] {$2$};
	\node[state,accepting](1) [right=of 2] {$1$};
	\node[state,initial right,accepting](0) [right=of 1] {$0$};				
	\path[->] 
	(0) edge  node {\texttt{i}} (1)
	(0) edge [bend right=60] node {\texttt{i}} (3)
	(0) edge [bend right=60] node {\texttt{i}} (5)
	(0) edge [bend right=60] node {\texttt{p}} (2)
	(0) edge [bend right=60] node {\texttt{p}} (4)
	(1) edge  node {\texttt{p}} (2)
	(2) edge  node {\texttt{i}} (3)
	(3) edge  node {\texttt{p}} (4)
	(4) edge  node {\texttt{i}} (5)
	;
	\end{tikzpicture}
	\caption{Diagram stavů nedeterministického faktorového automatu $M_1$ pro řetězec $P^R = \texttt{ipipi}$}\label{fig:faca}
\end{figure}

\begin{figure}
	\centering
	\begin{tikzpicture}[
		shorten >=1pt,node distance=1.75cm,on grid,auto,
		state/.style={circle,draw,minimum size=1.05cm,inner sep=1pt}]
		
		\node[state,accepting](5) {$\set{5}$};			
		\node[state,accepting](4) [right=of 5] {$\set{4}$};
		\node[state,accepting](35) [right=of 4] {$\set{3,5}$};
		\node[state,accepting](24) [right=of 35] {$\set{2,4}$};
		\node[state,accepting](135) [right=of 24] {$\set{1,3,5}$};
		\node[state,initial right,accepting](0) [right=of 135] {$\set{0}$};		
		\path[->] 
		(0) edge  node {\texttt{i}} (135)
		(0) edge [dashed,bend right=60] node {\texttt{p}} (24)
		(135) edge node {\texttt{p}} (24)
		(24) edge node {\texttt{i}} (35)
		(35) edge node {\texttt{p}} (4)
		(4) edge node {\texttt{i}} (5)
		;
	\end{tikzpicture}\label{fig:faca:m2}
	\caption{Diagram stavů determinizovaného faktorového automatu $M_2$ (stav $\emptyset$ jsme vynechali)}\label{fig:facadet}
\end{figure}

\begin{table}
	\centering
	\subfloat[]{
		\begin{tabular}{rl|ccc}
			\multicolumn{2}{c|}{$\delta_{M_1}$} & \texttt{i} & \texttt{p}\\\hline
			$\leftrightarrow$ & 0 & 1,3,5 & 2,4 \\
			$\leftarrow$      & 1 & - & 2 \\
			$\leftarrow$      & 2 & 3 & - \\
			$\leftarrow$      & 3 & - & 4 \\
			$\leftarrow$      & 4 & 5 & - \\
			$\leftarrow$      & 5 & - & - \\
		\end{tabular}\label{tab:faca:m1}
	}
	\qquad
	\subfloat[]{
		\begin{tabular}{rl|ccc}
			\multicolumn{2}{c|}{$\delta_{M_2}$} & \texttt{i} & \texttt{p}\\\hline
			$\leftrightarrow$ & $\set{0}$ & $\set{1, 3, 5}$ & $\set{2, 4}$ \\
			$\leftarrow$      & $\set{1, 3, 5}$ & $\emptyset$ & $\set{2, 4}$ \\
			$\leftarrow$      & $\set{2, 4}$ & $\set{3, 5}$ & $\emptyset$ \\
			$\leftarrow$      & $\set{3, 5}$ & $\emptyset$ & $\set{4}$ \\
			$\leftarrow$      & $\set{4}$ & $\set{5}$ & $\emptyset$\\
			$\leftarrow$      & $\set{5}$ & $\emptyset$ & $\emptyset$ \\
		\end{tabular}\label{tab:faca:m2}
	}
	\caption{Přechodová funkce \protect\subref{tab:faca:m1} nedeterministického faktorového automatu $M_1$ a~\protect\subref{tab:faca:m2} jeho determinizované podoby $M_2$}\label{tab:faca}
\end{table}

\begin{table}
	\centering
	\begin{tabular}{ccccl}
		$j$ & sufix $P$ & stav $M_2$ & $\GSS{$j$}$ &\\
		\cline{1-4}
		5 & $\epsilon$ & $\set{0}$ & 1 & prázdný sufix\\
		4 & \texttt{i} & $\set{1,3,5}$ & 2 & $3-1$\\
		3 & \texttt{pi} & $\set{2,4}$ & 2 & $4-2$\\
		2 & \texttt{ipi} & $\set{3,5}$ & 2 & $5-3$\\
		1 & \texttt{pipi} & $\set{4}$ & 2 & $m-\abs{\Border(P)} = 5-\abs{\texttt{ipi}}$\\
		0 & \texttt{ipipi} & $\set{5}$ & 2 & stejně jak výše\\
	\end{tabular}
	\caption{Ukázka hodnot \protect\GSS pro vzorek $P = \texttt{ipipi}$}\label{tab:gssex}
\end{table}

\begin{example}
	Vyhledáváme všechny výskyty vzorku $P = \texttt{ipipi}$ v~textu $T = \texttt{missipipi}$. Využíváme hodnoty \GSS z~tabulky \ref{tab:gssex}. Průběh vyhledávání ilustrujeme v~tabulce \ref{tab:gsssearchex}. Shoda nastala při $i \in \set{4}$, proto je výsledkem $\set{5}$.
\end{example}

\begin{table}
	\centering
	\begin{tabular}{rll}
		$i$ & \texttt{missipipi} &\\
		\cline{1-2}
		0 &   \texttt{ipipi} & $\GSS{4} = 2$\\
		2 &   \texttt{~~ipipi} & $\GSS{2} = 2$\\
		4 &   \texttt{~~~~ipipi}&\\
	\end{tabular}
	\caption{Vyhledávání vzorku $P = \texttt{ipipi}$ v~textu $T = \texttt{missipipi}$ pomocí klouzavého okénka a~funkce \protect\GSS}\label{tab:gsssearchex}
\end{table}

\subsection{Modifikace Boyer-Moore pro BWT}\label{sec:bmmod}

Algoritmus Boyer-Moore potřebuje pro svoji práci mít přístup do textu a~možnost se v~něm pohybovat v~původním pořadí. Abychom nemuseli text $T$ dekódovat, můžeme přístupy do $T$ jednoduše nahradit pomocí pole $Hr$ zavedeného v~\ref{sec:auxiliary}. Jedná se o~inverzi sufixového pole řetězce $T$ a~platí vztah $T[i] = L[Hr[i]]$. \cite{bell05}

Obdobně lze modifikovat všechny algoritmy pracující na podobném principu klouzavého okénka. Pole $Hr$ zabírá až na konstantu stejně paměti jako text $T$, proto je otázka, jestli přístup bez dekódování $T$ může někdy vyplatit. V~\cite{bell05} se autoři zmiňují, že přestože přístup pomocí pole $Hr$ vyžaduje dvě dereference místo jedné, při vyhledávání malého počtu vzorků je verze Boyer-Moore pracující s~transformovaným textem rychlejší.

\section{Binární vyhledávání}\label{sec:bs}

Zajímavou vlastností Burrows-Wheelerovy transformace je, že v~transformovaném textu $L$ je jistým způsobem zakódována matice $\mat{A}_s$, která obsahuje seřazené rotace původního textu $T$. Díky tomu je možné hledat výskyty vzorku $P$ v~textu $T$ pomocí binárního vyhledávání v~čase $\BigO(m \log n)$ \cite{bell02}. Podívejme se ještě jednou na příklad matice $\mat{A}_s$ pro řetězec \texttt{mississippi\$} (tabulka \ref{tab:as2}) poprvé zmíněné v~kapitole \ref{ch:bwt}.

\begin{table}
	\centering
	\begin{tabular}{ccc}
		$F$ & & $L$\\
		\hline
		\texttt{\$} & \texttt{mississipp} & \texttt{i}\\
		\texttt{i} & \texttt{\$mississip} & \texttt{p}\\
		\texttt{i} & \texttt{ppi\$missis} & \texttt{s}\\
		\texttt{i} & \texttt{ssippi\$mis} & \texttt{s}\\
		\texttt{i} & \texttt{ssissippi\$} & \texttt{m}\\
		\texttt{m} & \texttt{ississippi} & \texttt{\$}\\
		\texttt{p} & \texttt{i\$mississi} & \texttt{p}\\
		\texttt{p} & \texttt{pi\$mississ} & \texttt{i}\\
		\texttt{s} & \texttt{ippi\$missi} & \texttt{s}\\
		\texttt{s} & \texttt{issippi\$mi} & \texttt{s}\\
		\texttt{s} & \texttt{sippi\$miss} & \texttt{i}\\
		\texttt{s} & \texttt{sissippi\$m} & \texttt{i}
	\end{tabular}
	\caption{Matice rotací $\mat{A}_s$ pro řetězec \texttt{mississippi\$} se zvýrazněnými sloupci $F$ a~$L$}\label{tab:as2}
\end{table}

Pozorný čtenář si mohl všimnout, že pro každý podřetězec $\str{u}$ řetězce $T$ existuje v~$\mat{A}_s$ řádek mající $\str{u}$ jako prefix. Navíc, díky lexikografickému řazení této matice jsou v~případě více výskytů vzorku $P$ v~$T$ všechny odpovídající řádky v~souvislém intervalu. Pro zjištění výsledných pozic stačí najít horní a~dolní mez pomocí dvou volání binárního vyhledávání a~převést indexy řádků obsažených v~tomto intervalu na pozice v~původním textu $T$ polem $I$. \cite{bell02}

Navíc díky následujícímu pozorování můžeme časovou složitost snížit až na průměrně $\BigO(m \log \frac{n}{\abs{\Sigma}})$. Při popisu algoritmu pro zpětnou \acrshort{bwt} jsme zavedli pomocná pole $K$ a~$M$, která počítají četnosti jednotlivých symbolů v~$L$, odvozeně tedy v~$T$ a~$F$ (protože obojí jsou permutace $L$). Přestože pole $F$ nikdy nemáme celé v~paměti, víme, že všech $K[i]$ výskytů symbolu $P[1] = \sigma_i$ se nachází v~intervalu $F[M[i]\dotdot(M[i] + K[i] - 1)]$. Podle prvního symbolu vzorku $P$ tak můžeme omezit rozsah hledání průměrně $\abs{\Sigma}$ krát. V~případě vzorku délky $m = 1$ dokonce známe rovnou výsledný interval pro celý vzorek $P$. \cite{bell05}

Před popisem samotného binárního vyhledávání musíme navrhnout porovnávací funkci \mcall{Binary-Search-Strcmp}{$P, W, L, i$}. Ta přijímá vzor $P$ a~ index řádku matice $\mat{A}_s$, $i$, a~vrací kladné nebo záporné číslo podle toho, zda je vzor lexikograficky větší, respektive menší než řádek $i$ matice $\mat{A}_s$, případně nulu, pokud je prvních $m$ symbolů shodných. Pseudokód uvádíme v~algoritmu \ref{algo:bsstrcmp}.

Funkce postupně porovnává symboly vzorku $P$, dokud nenarazí na neshodu mezi $P$ a~odpovídajícím symbolem v~$L$. Aby mohla funkce dekódovat potřebné symboly z~$T$ v~původním pořadí, používá pole $W$ definované v~kapitole \ref{ch:bwt}. Pokud víme, že symbol $L[i]$ odpovídá symbolu $T[j]$, potom $T[j+1] = L[W[i]]$. Díky prvnímu skoku pomocí $W$ na řádku \ref{row:bsstrcmp:wjump} se přesuneme ze sloupce $L$ do sloupce $F$ $i$-tého řádku matice $\mat{A}_s$ a~docílíme tak správného zarovnání vzorku $P$.

\begin{algorithm}
\AlgoName{Binary-Search-Strcmp}{$(P, W, L, i)$}
$m \leftarrow \abs{P}$\;
$j \leftarrow 1$\;
$i \leftarrow W[i]$\label{row:bsstrcmp:wjump}\;
\While{$m > 0$ \KwAnd $L[i] = P[j]$}{
	$i \leftarrow W[i]$\;
	$m \leftarrow m - 1$\;
	$j \leftarrow j + 1$\;
}
\eIf{$m = 0$}{
	\KwRet{$0$}\;
}{
	\KwRet{$\symdex(P[j]) - \symdex(L[i])$}\;
}
\caption{Porovnávací funkce pro transformovaný text}\label{algo:bsstrcmp}
\end{algorithm}

Nyní přejdeme k~popisu algoritmu \ref{algo:bs}, který provádí samotné binární vyhledávání. Algoritmus je oproti verzi prezentované v~\cite{bell05} upraven pro lepší srozumitelnost a~kratší zápis. Naše verze je také konzistentní s~implementací algoritmu pro hledání v~sufixovém poli, který je již přítomen v~\acrshort{alt} \cite{alt}. Naše modifikace nemá vliv na funkci ani rychlost.

\begin{algorithm}
\SetKwFunction{BSStrcmp}{Binary-Search-Strcmp}
\AlgoName{Binary-Search}{$(P, W, L, I, M, K)$}
$c \leftarrow \symdex(P[1])$\;
$low \leftarrow M[c]$\;
$high \leftarrow M[c] + K[c]$\;
\BlankLine
$l = low$\label{row:bs:start1}\;
$h = high$\;
\While{$l < h$}{
	$m \leftarrow \lfloor (l + h) / 2 \rfloor$\;
	\eIf{\BSStrcmp{$P, W, L, m$}$\ > 0$}{
		$l \leftarrow m + 1$\;
	}{
		$h \leftarrow m$\;
	}
}
$low = l$\label{row:bs:end1}\;
\BlankLine
\If{$low = high$ \KwOr \BSStrcmp{$P, W, L, low$}$\ \neq 0$}{\label{row:bs:comp}
	\KwRet{$\set{}$}\;
}
\BlankLine
$h = high$\label{row:bs:start2}\;
\While{$l < h$}{
	$m \leftarrow \lfloor (l + h) / 2 \rfloor$\;
	\eIf{\BSStrcmp{$P, W, L, m$}$\ \geq 0$}{
		$l \leftarrow m + 1$\;
	}{
		$h \leftarrow m$\;
	}
}
$high = l$\label{row:bs:end2}\;
\BlankLine
\KwRet{$I[low\dotdot(high-1)]$ jako možina}\;
\caption{Binární vyhledávání v~transformovaném textu}\label{algo:bs}
\end{algorithm}

Algoritmus pracuje v~několika krocích:
\begin{description}
	\nitem{Inicializace} Proměnné $low$ a~$high$, určující zdola polouzavřený interval indexů řádků matice $\mat{A}_s$ začínajících vzorkem $P$, inicializujeme na interval možných výskytů znaku $P[1]$ v~poli $F$.

	\nitem{Dolní mez} Inicializujeme proměnné $l$ a~$h$ určující aktuálně prohledávaný interval na hodnoty $low$ a~$high$. V~každém kroku binárního vyhledávání porovnáme střed aktuálního intervalu se vzorkem pomocí algoritmu \ref{algo:bsstrcmp}. Pokud $P >$ (řádek $m$ matice $\mat{A}_s$), přesuneme se do horní, jinak do dolní poloviny intervalu. Do $low$ nakonec zapíšeme novou dolní mez.

	\nitem{Horní mez} Druhé binární vyhledávání probíhá obdobně jako v~druhém kroku, ale s~tím rozdílem, že v~případě rovnosti vybíráme horní polovinu intervalu místo dolní. Výslednou horní mez uložíme do $high$.

	\nitem{Vrácení výsledku} Interval $\langle low; high)$ ukazuje na vyhovující řádky $\mat{A}_s$, výstupem algoritmu ale mají být indexy do dekódovaného textu $T$. Pro převod můžeme použít pole $I$ (\emph{first-to-text}) definované v~kapitole \ref{ch:bwt}.
\end{description}

V~případě, že v~$T$ neexistuje žádný výskyt $P$, mohou nastat dva  případy. Buď budou všechny řádky $\mat{A}_s$ v~intervalu $\langle low; high)$ ostře menší než $P$, potom po druhém kroku bude $low = high$. Zbývající případ zachytí volání funkce \method{Binary-Search-Strcmp} na řádku \ref{row:bs:comp}.

\begin{example}
	Hledáme všechny výskyty $P = \texttt{si}$ v~$T = \texttt{mississippi\$}$. Víme, že $\texttt{s} = \sigma_5$, $K[5] = 4$, $M[5] = 8$ a~$I = (12, 11, 8, 5, 2, 1, 10, 9, 7, 4, 6, 3)$.

	V~tabulce \ref{tab:bsex} jsme znázornili jednotlivé kroky algoritmu \ref{algo:bs}. V~části \subref{tab:bsex1} a~\subref{tab:bsex2} vidíme hledání dolní meze. Poté algoritmus přejde opět do stavu \subref{tab:bsex1}, dojde k~porovnání 10. řádku a~skončíme ve stavu \subref{tab:bsex3}. Výsledný interval je  $\langle 9; 11)$, který pomocí sufixového pole $I$ převedeme na množinu výskytů $\set{4, 7}$.
\end{example}

\begin{table}
	\centering
	\subfloat[]{
		\begin{tabular}{rc}
			1 & \texttt{\$m\dots}\\
			2 & \texttt{i\$\dots}\\
			3 & \texttt{ip\dots}\\
			4 & \texttt{is\dots}\\
			5 & \texttt{is\dots}\\
			6 & \texttt{mi\dots}\\
			7 & \texttt{pi\dots}\\
			8 & \texttt{pp\dots}\\
			$low,l \rightarrow$ 9 & \texttt{si\dots}\\
			10 & \texttt{si\dots}\\
			$h \rightarrow$ 11 & \texttt{ss\dots}\\
			12 & \texttt{ss\dots}\\
			$high \rightarrow$ 13 & ---
		\end{tabular}\label{tab:bsex1}
	}
	\subfloat[]{
		\begin{tabular}{rc}
			1 & \texttt{\$m\dots}\\
			2 & \texttt{i\$\dots}\\
			3 & \texttt{ip\dots}\\
			4 & \texttt{is\dots}\\
			5 & \texttt{is\dots}\\
			6 & \texttt{mi\dots}\\
			7 & \texttt{pi\dots}\\
			8 & \texttt{pp\dots}\\
			$low,l \rightarrow$ 9 & \texttt{si\dots}\\
			$h \rightarrow$ 10 & \texttt{si\dots}\\
			11 & \texttt{ss\dots}\\
			12 & \texttt{ss\dots}\\
			$high \rightarrow$ 13 & ---
		\end{tabular}\label{tab:bsex2}
	}
	\subfloat[]{
		\begin{tabular}{rc}
			1 & \texttt{\$m\dots}\\
			2 & \texttt{i\$\dots}\\
			3 & \texttt{ip\dots}\\
			4 & \texttt{is\dots}\\
			5 & \texttt{is\dots}\\
			6 & \texttt{mi\dots}\\
			7 & \texttt{pi\dots}\\
			8 & \texttt{pp\dots}\\
			$low \rightarrow$ 9 & \texttt{si\dots}\\
			10 & \texttt{si\dots}\\
			$high,l,h \rightarrow$ 11 & \texttt{ss\dots}\\
			12 & \texttt{ss\dots}\\
			13 & ---
		\end{tabular}\label{tab:bsex3}
	}
	\caption{Kroky \method{Binary-Search} pro vzorek \texttt{si} v~$T = $ \texttt{mississippi\$}.}\label{tab:bsex}
\end{table}

\section{Sufixové pole}\label{sec:sasearch}

V~sekci \ref{sec:fwimpl} jsme ukázali, že při znalosti sufixového pole dokážeme provést dopřednou Burrows-Wheelerovu transformaci v~lineárním čase. Obdobně algoritmus \ref{algo:buildaux} ve stejné kapitole popisuje, jak pomocí pole $W$ sestrojit z~transformovaného řetězce zpět sufixové pole $I$.

Drobnou úpravou algoritmu \ref{algo:decode} pro zpětnou transformaci můžeme sufixové pole plnit souběžně s~dekódováním textu v~rámci jednoho průchodu. Tuto modifikaci popisuje algoritmus \ref{algo:sadec} přebraný z~\cite{bell05}.

\begin{algorithm}
	\setcounter{AlgoLine}{12}
	\vdots
	$i \leftarrow \text{index ukončovacího symbolu \texttt{\$} v~}L$\;
	\For{$j \leftarrow n$ \KwDownTo $1$}{
		$I[i] \leftarrow j + 1$\;
		\If{$I[i] = n + 1$}{
			$I[i] \leftarrow 1$\;
		}
		$T[j] \leftarrow L[i]$\;
		$i \leftarrow C[i] + M[\symdex(L[i])]$\;
	}
	\caption{Modifikace algoritmu \ref{algo:decode} pro vytváření sufixového pole souběžně s~dekódováním textu}\label{algo:sadec}
\end{algorithm}

\SetKwFunction{SAStrcmp}{Suffix-Array-Strcmp}

Protože pole $I$ obsahuje informace o~pozicích všech sufixů $T$ v~lexikografickém pořadí, můžeme použít obdobný algoritmus využívající binární vyhledávání, jako jsme popsali v~sekci \ref{sec:bs}. Ve skutečnosti bude hlavní rozdíl pouze v~porovnávací funkci, kde volání \BSStrcmp{$P, W, L, k$} nahradíme funkcí \SAStrcmp{$P, T, I[k]$} uvedené ve výpisu \ref{algo:sastrcmp}. Modifikovaný algoritmus \method{SA-Binary-Search} bude přijímat dekódovaný $T$ jako vstup místo $W$ a~$L$.

\begin{algorithm}
\AlgoName{Suffix-Array-Strcmp}{$(P, T, i)$}
$m \leftarrow \abs{P}$\;
$j \leftarrow 1$\;
\While{$m > 0$ \KwAnd $T[i] = P[j]$}{
	$i \leftarrow i + 1$\;
	$j \leftarrow j + 1$\;
	$m \leftarrow m - 1$\;
}
\eIf{$m = 0$}{
	\KwRet{0}\;
}{
	\KwRet{$\symdex(P[j])$ - $\symdex(T[i])$}\;
}
\caption{Porovnávací funkce pro hledání v~sufixovém poli}\label{algo:sastrcmp}
\end{algorithm}

Pokud už máme sestrojené sufixové pole a~dekódovaný text $T$ (což trvá $\BigO(n)$), tak samotné volání algoritmu \method{SA-Binary-Search} potřebuje stejně jako u~\method{Binary-Search} čas $\BigO(m \log n)$. Díky tomu, že porovnávací funkce \SAStrcmp přistupuje k~paměti přímo a~sekvenčně, bude konstantní faktor menší. Paměťová složitost \method{SA-Binary-Search} bude také o~násobek menší, protože měníme pole $W$ a~$L$ za $T$, pořád ale asymptoticky $\BigO(n)$.

\vfill
\section{FM-index}\label{sec:fmindex}

\SetKwFunction{FMOcc}{Occ}

Předchozí tři představené algoritmy potřebují před provedením samotného vyhledávání zkonstruovat jedno nebo více pomocných polí. Každé z~těchto polí má $n$ prvků, takže potřebujeme mít v~paměti několikrát více místa, než zabírá samotný transformovaný text $L$.

\emph{FM-index}, příležitostná datová struktura, kterou navrhli P. Ferragina a~G. Manzini v~\cite{ferragina00}, tento problém řeší kombinací BWT a~sufixového pole. Označení \emph{příležitostná} používají autoři pro popis datových struktur, které snižují nároky na úložný prostor (v~případě, že text $T$ je komprimovatelný) bez zpomalení vyhledávání.

Aby umožnili náhodný přístup do transformovaného textu bez jeho kompletní dekomprese a~vytváření pomocných polí, rozšiřují ho o~index obsahující odlehčené informace o~četnostech symbolů v~textu. Autoři svoji strukturu porovnávají s~komprimovaným sufixovým polem od Grossi a~Vitter se závěrem, že FM-index je lepší v~rychlosti hledání, paměťové náročnosti nebo obojím.

K~FM-indexu se přistupuje přes dvě základní operace: \method{Count} vrátí počet výskytů vzorku $P$ v~textu $T$. Druhá, \method{Locate} vrátí pro jednotlivé výskyty pozice jejich začátků v~$T$. Obě operace pro svůj chod využívají funkci \FMOcc{$c, k$}, která vrací počet výskytů symbolu $c$ v~podřetězci $L[1\dotdot k]$. Jedná se tak o~zobecnění pole $C$ zavedeného v~sekci \ref{sec:auxiliary}. O~tom, jak budeme reprezentovat tuto funkci v~paměti pojednává sekce \ref{sec:fmocc}.

\subsection{\method{Count}}\label{sec:fmcount}

Operace \method{Count} zjistí (podobně jako algoritmus \method{Binary-Search}) interval řádků matice $\mat{A}_s$, které obsahují jako prefix vzorek $P$. Pro jeho činnost ale místo pomocných polí $I$ nebo $V$ využívá funkci \FMOcc.

Vyhledávání v~FM-indexu funguje na opačném principu, než jak tomu bylo u~algoritmů \method{Binary-Search} a~\method{SA-Binary-Search}. Zatímco u~nich jsme pomocí binárního vyhledávání nalezli interval řádků matice $\mat{A}_s$ s~prefixem $P$ rovnou, u~FM-indexu hledáme postupně sufixy $P[m], P[(m-1)\dotdot m], \dots, P$. Pseudokód operace jsme zachytili v~algoritmu \ref{algo:fmcount}.

V~průběhu všech $m$ kroků výpočtu ($i = m, \dots, 1$) je zachováván následující invariant: \emph{v~$i$-tém kroku proměnná $sp$ ukazuje na první řádek v~$\mat{A}_s$, který obsahuje $P[i\dotdot m]$ jako prefix a~$ep$ ukazuje na řádek $\mat{A}_s$ ihned následující za posledním řádkem, jehož prefixem je $P[i\dotdot m]$.}

Algoritmus jsme oproti verzi v~\cite{ferragina00} upravili tak, aby proměnné $sp$ a~$ep$ tvořily zdola polouzavřený interval. Také jsme (stejně jako autoři \cite{bell05}) sjednotili značení pole uchovávající prefixovou sumu četností z~$C$ na $M$.

Stejně jako u~\method{Binary-Search} inicializujeme počáteční prohledávaný interval pomocí polí $M$ a~$K$. Tato pole mají pouze $\abs{\Sigma}$ prvků, jejich velikost je tedy vůči zbytku FM-indexu zanedbatelná. V~dalších krocích probíhá na řádcích \ref{algo:fmcount:spupd} a~\ref{algo:fmcount:epupd} aktualizace proměnných $sp$ a~$ep$. Pomocí funkce \FMOcc počítáme, na jaké prvky pole $F$ se zobrazí první a~poslední výskyt symbolu $P[i-1]$ v~$L[sp\dotdot (ep-1)]$. Využíváme tak relace mezi $F$ a~$L$ uvedené v~\ref{sec:revimpl}.

\begin{algorithm}
	\AlgoName{Count}{$(P, M, K)$}
	$i \leftarrow \abs{P}$\;
	$c \leftarrow P[i]$\;
	$sp \leftarrow M[\symdex(c)]$\;
	$ep \leftarrow M[\symdex(c)] + K[\symdex(c)]$\;
	\While{$sp < ep$ \KwAnd $i \geq 2$}{
		$c \leftarrow P[i - 1]$\;
		$sp \leftarrow M[\symdex(c)] + \FMOcc{$c, sp - 1$}$\;\label{algo:fmcount:spupd}
		$ep \leftarrow M[\symdex(c)] + \FMOcc{$c, ep - 1$}$\;\label{algo:fmcount:epupd}
		$i \leftarrow i - 1$\;
	}
	\BlankLine
	\eIf{$ep > sp$}{
		\KwRet{$ep - sp$}\;
	}{
		\KwRet{$0$}\;
	}
	\caption{Výpočet počtu výskytů $P$ v~$T$ pomocí FM-indexu}\label{algo:fmcount}
\end{algorithm}

Celková složitost algoritmu závisí na složitosti funkce \FMOcc. V~sekci \ref{sec:fmocc} ukážeme, jak ji implementovat v~čase $\BigO(1)$, v~tom případě celková časová složitost algoritmu \method{Count} bude $\BigO(m)$.

\begin{example}
	Hledáme všechny výskyty $P = \texttt{si}$ v~$T = \texttt{mississippi\$}$. Začínáme symbolem $P[2] = \texttt{i} = \sigma_2$. Známe hodnoty pomocných polí $M[2] = 2$, $K[2] = 4$. Kroky algoritmu ukazuje tabulka \ref{tab:fmex}. Začínáme s~intervalem $\langle2; 2+4)$ znázorněném v~\subref{tab:fmex1}, a~čteme další symbol $P[1] = \texttt{s} = \sigma_5$. Nyní musíme zjistit, kolik výskytů symbolu \texttt{s} v~$F$ přeskočit, to je stejný počet, jako se nachází v~podřetězci $L[1\dotdot 2]$ a~který vrátí funkce \FMOcc{\texttt{s}$, 2-1$}$~= 0$. Dále \FMOcc{\texttt{s}$, 6-1$}$~= 2$ nám řekne počet symbolů \texttt{s} v~$L[1\dotdot 5]$. Po přičtení ofsetu $M[5] = 9$ získáme výsledný interval $\langle 9; 11)$ v~\subref{tab:fmex2}. Protože jsme zpracovali celý $P$, víme, že se v~řetězci $T$ nacházejí právě dva výskyty vzorku.
\end{example}

\begin{table}
	\centering
	\subfloat[][]{
		\begin{tabular}{rccc}
			1 & \texttt{\$m\dots i}\\
			$sp \rightarrow$ 2 & \texttt{i\$\dots p}\\
			3 & \texttt{ip\dots s}\\
			4 & \texttt{is\dots s}\\
			5 & \texttt{is\dots m}\\
			$ep \rightarrow$ 6 & \texttt{mi\dots \$}\\
			7 & \texttt{pi\dots p}\\
			8 & \texttt{pp\dots i}\\
			9 & \texttt{si\dots s}\\
			10 & \texttt{si\dots s}\\
			11 & \texttt{ss\dots i}\\
			12 & \texttt{ss\dots i}\\
			13 & ---\\
		\end{tabular}\label{tab:fmex1}
	}
	\subfloat[][]{
		\begin{tabular}{rc}
			1 & \texttt{\$m\dots i}\\
			2 & \texttt{i\$\dots p}\\
			3 & \texttt{ip\dots s}\\
			4 & \texttt{is\dots s}\\
			5 & \texttt{is\dots m}\\
			6 & \texttt{mi\dots \$}\\
			7 & \texttt{pi\dots p}\\
			8 & \texttt{pp\dots i}\\
			$sp \rightarrow$ 9 & \texttt{si\dots s}\\
			10 & \texttt{si\dots s}\\
			$ep \rightarrow$ 11 & \texttt{ss\dots i}\\
			12 & \texttt{ss\dots i}\\
			13 & ---\\
		\end{tabular}\label{tab:fmex2}
	}
	\caption{Průběh \method{Count} pro vzorek \texttt{si} v~$T = $ \texttt{mississippi\$}: \protect\subref{tab:fmex1} inicializace pomocí $M$ a~$K$ pro \texttt{i}; \protect\subref{tab:fmex2} výsledný stav po druhém kroku}\label{tab:fmex}
\end{table}

\subsection{\method{Locate}}\label{sec:fmlocate}

Nyní už víme, na jakých řádcích $\mat{A}_s$ se nacházejí výskyty hledaného vzorku $P$. Protože ale nemáme dostupné sufixové pole $I$, nedokážeme zjistit jejich pozice v~původním textu $T$. Ukládání celého pole $I$ spolu s~FM-indexem by negovalo jeho prostorovou efektivitu, proto autoři v~\cite{ferragina00} popsali algoritmus \method{Locate}.

U~některých řádků matice $\mat{A}_s$, říkejme jim \emph{označené}, si uložíme informaci o~pozici jejich prvního symbolu v~$T$ (hodnotu pole $I[i]$). Pokud potřebujeme zjistit pozici neoznačeného řádku, pomocí funkce \FMOcc se pohybujeme v~$T$ pozpátku, dokud nenalezneme označený řádek a~jeho pozici potom patřičně korigujeme.

Normálně bychom pro dekódování pozpátku použili pole $V$, to ale nemáme k~dispozici. Místo toho můžeme využít vztahu $C[i] = \FMOcc{$L[i], i$}-1$ a~pomocí funkce \FMOcc tak na požádání dopočítat prvky $V$ následujícím způsobem:
\begin{equation*}
	V[i] = M[c] +  \FMOcc{$c, i$} - 1,
\end{equation*}
kde $c = L[i]$. \cite{bell05}

Pseudokód funkce \method{Locate} shrnuje algoritmus \ref{algo:fmlocate}. Složitost algoritmu závisí na počtu a rozmístění označených řádků. Hledáme tedy kompromis mezi spotřebou paměti a~časem běhu algoritmu. O~způsobech označování navržených v~\cite{ferragina00} se blíže zmiňujeme v~sekci \ref{sec:fmocc}.

\begin{algorithm}
\AlgoName{Locate}{$(M, i)$}
$v \leftarrow 0$\;
\While{řádek $i$ není označený}{
	$c \leftarrow L[i]$\;
	$i \leftarrow M[c] + \FMOcc{$c, i$} - 1$\;
	$v \leftarrow v + 1$\;
}
\KwRet{$I[i] + v$}\;
\caption{Nalezení pozice symbolu $F[i]$ v~textu $T$ pomocí řídkého sufixového pole}\label{algo:fmlocate}
\end{algorithm}

\subsection{Reprezentace \textsc{Occ} a~řídkého sufixového pole}\label{sec:fmocc}

\hyphenation{pod-při-hrád-ka pod-při-hrá-dek pod-při-hrád-ce pod-při-hrád-ky}

Ferragina a~Manzini navrhují v~\cite{ferragina00} rozdělit výstup transformace do přihrádek, takzvaných \emph{superbuckets}, o~délce $l_{sb}$. Každou z~těchto přihrádek dále dělí na menší podpřihrádky délky $l_b$ (\emph{buckets}). Je zřejmé, že $l_b$ dělí $l_{sb}$. Součástí přihrádek i~podpřihrádek je hlavička obsahující informace o~četnostech symbolů abecedy v~předchozí části řetězce $L$.

Hlavička $i$-té přihrádky zahrnující podřetězec $L[((i-1)\cdot l_{sb} + 1)\dotdot (i\cdot l_{sb})]$ obsahuje pole $NO_i[1 \dotdot |\Sigma|]$, kde hodnota $NO_i[j]$ udává počet výskytů symbolu $\sigma_j$ ve všech předchozích přihrádkách (prefix $L[1\dotdot((i-1)\cdot l_{sb})]$). Hlavička $i$-té podpřihrádky (indexujeme od začátku $L$) potom drží pole $NO'_i[j]$ obsahující četnost symbolu $\sigma_j$ ve všech předchozích podpřihrádkách v~aktuální přihrádce.

Víme, že symbol $i$ se nachází v~přihrádce $p_s = \mfloor{\frac{i-1}{l_{sb}}}+1$ a~podpřihrádce s~indexem $p = \mfloor{\frac{i-1}{l_{b}}}+1$. Pro zjištění četnosti \FMOcc{$c, i$} v~optimálním čase musíme prefix $L[1\dotdot i]$ rozdělit na tři disjunktní části a~výsledky sečíst:
\begin{enumerate}		
	\item Prefix $L[1\dotdot((p_s-1)\cdot l_{sb})]$ obsažený v~předchozích přihrádkách. Četnost symbolu $c$ přečteme z~$NO_{p_s}[\symdex(c)]$.
	
	\item Podřetězec od začátku přihrádky $p_s$ do začátku podpřihrádky $p$: $L[((p_s-1)\cdot l_{sb}+1)\dotdot((p-1)\cdot l_b)]$. Četnost přečteme z~$NO'_p[\symdex(c)]$.
	
	\item Podřetězec od začátku podpřihrádky do pozice $i$: $L[((p-1)\cdot l_b+1)\dotdot i]$. Ten jednoduše projdeme a~napočítáme četnost symbolu $c$. Počet navštívených symbolů je omezen $l_b$, proto je složitost kroku vzhledem k~délce textu $\BigO(1)$.
\end{enumerate}

Díky rozdělení transformovaného textu do přihrádek tak dostáváme algoritmus s~celkovou časovou složitostí $\BigO(1)$.

\begin{example}
	Máme uložený řetězec $L =~$\texttt{ipssm\$pissii} v~přihrádkách velikosti $l_{sb} = 4$ a~$l_b = 2$. Chceme spočítat \FMOcc{\texttt{s}$, 5$} z~předchozího příkladu. Dělení $L$ do (pod)přihrádek znázorňuje tabulka \ref{tab:fmsplitex}. Spočítáme, že 5. symbol se nachází v~přihrádce $p_s = \mfloor{\frac{5-1}{4}}+1 = 2$ a~podpřihrádce $p = \mfloor{\frac{5-1}{2}}+1 = 3$. Sečteme tedy četnosti $\texttt{s} = \sigma_5$ v~předchozích přihrádkách: $NO_2[5] + NO'_3[5] = 2 + 0 = 2$. Nakonec přičteme počet symbolů v~podřetězci $L[(2\cdot2+1)\dotdot5] =~$\texttt{m}, což je 0 a~dostaneme výsledek $2+0+0=2$, který můžeme ověřit pohledem na $L$.
\end{example}

\begin{table}
	\centering
	\def\arraystretch{1.2}
	\begin{tabular}{ccl}
		\multirow{2}{*}{$NO_1[5] = 0$} & $NO'_1[5] = 0$ & \texttt{ip}\\
		 & $NO'_2[5] = 0$ & \texttt{ss}\\
		\hline
		\multirow{2}{*}{$NO_2[5] = 2$} & $NO'_3[5] = 0$ & \texttt{m\$}\\
		 & $NO'_4[5] = 0$ & \texttt{pi}\\
		\hline
		\multirow{2}{*}{$NO_3[5] = 2$} & $NO'_5[5] = 0$ & \texttt{ss}\\
		 & $NO'_6[4] = 2$ & \texttt{ii}\\
	\end{tabular}
	\caption{Dělení slova \texttt{ipssm\$pissii} do přihrádek velikosti $l_{sb} = 4$ a~$l_b = 2$}\label{tab:fmsplitex}
\end{table}

%\emph{move-to-front}, \emph{run-length} a \emph{variable-length prefix}
Obsah přihrádek je před uložením zkomprimován. V~\cite{ferragina00} používají autoři třístupňový kodér pro dosažení určité horní meze paměťové složitosti. V~reálné implementaci prezentované v~\cite{ferragina01} vyšlo nejlépe použití \emph{hierarchického kódování} a~\emph{Huffmanova kódu}. Po experimentech s~různou velikostí přihrádek a~podpřihrádek zjistili, že nejlepšího kompromisu mezi pamětí a~rychlostí vyhledávání dosahují $l_{sb}$ a~$l_b$ okolo 16~KB, respektive 1~KB.

Kromě funkce \FMOcc se musíme ještě zabývat výběrem a~uložením pozic označených řádků. Pokud bychom neoznačili žádné, získáme takzvaný \emph{lehký FM-index}, který podporuje pouze operaci \method{Count}. Pro \emph{úplný FM-index} došli autoři v~\cite{ferragina01} experimentálních závěrů, že při velikosti přihrádek 16~KB a~1~KB se nejlepšího kompromisu mezi velikostí indexu a~rychlostí \method{Locate} dosáhne při označení zhruba 2 \% řádků.

V~\cite{ferragina00} autoři navrhli dva způsoby označování řádků:
\begin{description}
	\item[Označ řádky $\mat{A}_s$ odpovídající každému $k$-tému symbolu $T$] Tento přístup má nevýhodu, že je potřeba ukládat páry klíč--hodnota do speciální datové struktury.
	
	\item[{Označ řádky, kde $L[i] = \sigma_j$}] Symbol $\sigma_j$ se stanoví tak, aby jeho relativní četnost $K[j]/n$ byla co nejbližší požadované hodnotě. To vyžaduje, aby symboly v~$T$ byly zhruba rovnoměrně rozmístěny, což ale v~případě běžného textu platí. Výhodou je, že pozice $I[i]$ označených řádků můžeme uložit do obyčejného pole na index \FMOcc{$\sigma_j, i$}. Přestože v~nejhorším případě tato strategie nemusí být úplně výhodná, v~praxi díky své jednoduchosti a~struktuře obvyklého textu podává dobré výsledky.
\end{description}

\subsection{Zjednodušený FM-index}\label{sec:fmsimple}

Pro účely této bakalářské práce jsme navrhli zjednodušenou verzi datové struktury FM-index. Hlavní rozdíl oproti původní verzi v~\cite{ferragina00} je ve vynechání komprese přihrádek, která je již nad rámec této práce. Algoritmy \method{Count} a~\method{Locate} zůstávají beze změny, ale zjednodušili jsme implementaci funkce \FMOcc, protože pro přístup k~hlavičce přihrádky již není třeba provádět dekompresi.

Díky dobrým výsledkům v~\cite{ferragina01} a~jednoduchosti implementace jsme se rozhodli použít označování řádků matice $\mat{A}_s$ podle symbolu v~prvním sloupci. V~experimentální sekci \ref{sec:compare} porovnáváme spotřebu paměti a~rychlost operací v~závislosti na zvolené velikosti přihrádek a~hustotě označených řádků.

%V~experimentální sekci \ref{sec:compare} porovnáme spotřebu paměti a~rychlost \method{Locate} v~závislosti na četnosti vybraného symbolu.