Zoznam úloh

2. Riadne dlhé správy

Zadanie

Keď sa Lukášovi podarilo preložiť si hľadanie dievčaťa do jeho materinského jazyku - do Júlie, riešenie sa mu zdalo priamočiare. A tak si teda našiel Julku. Lukáš a Julka si radi po večeroch píšu správy a tieto správy zvyknú byť občasne aj dosť dlhé. Lukáš však býva aspoň za 7 horami a jedným morom od Julky, a v dnešnej dobe sú u mobilných operátorov poplatky za medzinárodné správy dosť veľké. Obaja sú však chudobní študenti a peniaze na slohovky im už dávno došli. Pomôžte Julke nájsť spôsob ako poslať Lukášovy dlhú slohovku tak, aby neskončila na mizine a Lukáš si vedel (ne)spokojne správu prečítať.

Úloha

a. Lukáš sa potkol a tak poslal Julke správu "AAAAAAAAAAAAUAAAAAAAAAAAAUUUAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAA". Na túto správu potreboval 67 znakov. Julka sa zujíma, či to naozaj bolo potrebné. Ako by vedel Lukáš túto správu skrátiť, tak aby nestratil žiaden znak na žiadnej pozícii? Dá sa to aj na 18 znakov?

b. Povedzme, že Lukáš sa tento krát nepotkol ale periodicky padal po schodoch. Aby Julke sprostedkoval tento galimatiáš1, poslal jej správu "AUAUAUAUAUAUAUAUAUAUAUAUAU..." (Jedno "AU" pre každý schod). Vedel by Lukáš aj túto správu skrátiť tak, ako v predošlej úlohe? Ako sa táto správa odlišuje od predošlej úlohy?

c. Julka pozná veľa romantických slov ako napríklad ŤUŤMÁK, LINUX2, HALDA, VZDUCH, MOJKO, ŠLAMASTIKA, a posiela ich vo svojích správach Lukášovi veľmi často. Napríklad Jukina správa môže vyzerať takto: AhojDvaŤUŤMÁKtrLINUXHALDAktoakMOJKOMOJKOŤUŤMÁKkdeVZDUCHHALDAvelkaŠLAMASTIKA. Každý deň používa iné slová a Lukáš, ako muž, nevie aké to sú. Správy, kde sa tieto slová vyskytujú často by Lukáš rád zapísal na čo najmenej znakov. Ako by vedel Lukáš túto správu zapísať čo najkratšie tak, aby sa nestratil ani jeden znak? Skús nájsť taký zápis, v ktorom čo najviac využije už napísaný text a čo najmenej bude písať znova.

d. Stručne popíš algoritmus, ktorým by si skracoval podobné reťazce.

e. Lukaš už odhalil Julkine ľúbostné slová, a tak si ich zapísal do slovníku3.

  • kucharkaKSP
  • lukosprava
  • myxa
  • kucapaca
  • luk
  • myxophyta
  • kucho
  • lukostrelba
  • lukas
  • kucharka
  • luka
  • myxopod
  • kuchar

Chcel by ale zmenšiť počet znakov v každom slove, ktoré si viem ľubovolne usporiadať ako sa nám zíde. Ako by sme mali zoznam usporiadať tak, aby sme vedeli na každé slovo využiť čo najmenej znakov? Čo sa oplatí robiť so slovami s rovnakým začiatkom, vieme využiť podobný prístup ako v predošlej úlohe?

f. Jednoducho všeobnecne popíš algritmus, ktorým by si vedel slová zoradiť v predošlej úlohe. Možno ti pomôže zamyslieť sa nad výberom dátovej štruktúry.

g. Lukáš a Julka si vymenili svoje čísla. V rastúcej postupnosti veľkých čísel $k \leq a_1, a_2, a_3, \dots, a_n$ platí, že rozdiel dvoch po sebe následujúcich čísel $a_{i + 1} - a_i < k$. Chceli by sme ju skrátiť do postupnosti $n$ čísel, ktoré sú zaručene menšie ako $k$. Vieme si však k nim dovoliť pridať práve jedno číslo väčšie ako $k$. Ako to vieme docieliť?

h. Máme teraz zoznam $n \leq 10^k - 1$, kde $k \leq n$, veľkých po sebe idúcich čísel od $a, a+1, a+2, \dots , a+n-1 \leq 10^n - 1$. Nepoznáme $a,n,k$ a zoznamom vieme prejsť iba raz. Vieme ju zapísať pomocou dvoch čísel tak, aby každé z nich malo najviac $n$ číslic?



  1. https://slovnik.juls.savba.sk/?w=galimatiáš 

  2. Julka nesúhlasí s tým, že by toto slovo malo byť romantické 

  3. Toto nieje náhodný výber slova. 

a

Všimnime si, že v reťazci "AAAAAAAAAAAAUAAAAAAAAAAAAUUUAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAA" máme veľa po sebe idúcich znakov A, raz za čas preuršovaných krátkou postupnosťou U. Ku každému znaku si teda vieme pripísať číslo - počet koľko krát sa daný znak zopakuje. Tak vieme skrátiť spŕavu na: "A12U1A12U3A24U1A14" - čo má dĺžku 18 znakov. Takýto spôsob skacovania (“kompresie”) rôznych postupností s veľa dlhými sekvenciami rovnakých znakov sa nazýva run length encoding.

b

Keď máme namiesto reťazca z a reťazec AUAUAUAU... alebo ľubovolný iný reťazec, kde sa znaky neopakujú po dlhých súvislých skupinách, ale sa častejšie nachádzajú “náhodne” premiešane po jednom alebo po dvoch po sebe idúcich znakoch, použiť run length encoding nám reťazec neskráti, ale naopak predĺži, keďže popridávame 1-ky a 2-ky za viac znakov ako skrátime dlhých opakujúcich sa postupností. Preto sa run length ecnoding nepoužíva na skracovanie obyčajného textu, keďže vo väčšine jazykov sa málokedy nachádza to isté písmeno 2- alebo viac-krát po sebe.

c

Síce všeobecne nevieme, aké slová to presne sú, ale vieme, že v reťazci sa nám budú často opakovať rôzne podreťazce. Stačí nám preto, aby sme ich pri prvom výskyte zapísali celé a pri každom ďalšom výskyte na ne odkázali párom dvoch čísel — vzdialenosťou od aktuálnej pozície k začiatku predchádzajúceho výskytu a dĺžkou podreťazca. Takým párom nahradíme každý ďalší výskyt daného podreťazca. Správu zo zadania by sme teda mohli upraviť napríklad takto: AhojDvaŤUŤMÁKtrLINUXHALDAktoakMOJKO(5,5)(34,6)kdeVZDUCH(35,5)velkaŠLAMASTIKA. Pôvodnú správu potom dešifrujeme zľava, takže pri čítaní odkazu už vždy existuje celý text, na ktorý sa odkazujeme. Spôsob kompresie založený na tomto princípe sa nazýva LZ77.

d

Jednoduchá verzia algoritmu by vyzerala nasledovne: Nastavíme si dolnú hranicu $w$, ktorá určuje, aký dlhý musí byť opakujúci sa podreťazec, aby sa oplatilo ho zapísať ako odkaz. Potom prechádzame pôvodný reťazec zľava doprava. Nech $i$ je aktuálna pozícia. Pre každé $i$ skúšame všetky predošlé pozície $j$ < $i$ a porovnávame znaky $c_{j+t}$ a $c_{i+t}$ pre $t = 0, 1, 2, \dots$, kým sa zhodujú a kým sme neprekročili koniec reťazca. Potom nech $k$ je dĺžka najdlhšej zhody pre dané $j$.

  • Ak platí $k \geq w$, zapíšeme do skráteného reťazca pár $\left(i-j; k\right)$, teda vzdialenosť k začiatku zhody a jej dĺžku.
  • Ak platí $k < w$, zapíšeme doslova znak $c_i$ a pokračujeme na pozíciu $i+1$.

Takýto algoritmus dosahuje v najhorších prípadoch časovú náročnosť $O\left(n^3\right)$. Prakticky sa zvykne zlepšovať tým, že ak vieme napríllad odhadnúť frekvenciu opakujúcich sa slov, tak si povieme, že nebudeme hladať pre každú pozíciu zhodu až úplne od začiatku, ale iba od pozície o nejakých $s$ indexov dozadu, kde $s$ si vieme nastaviť ako konštantu. Ak $s \ll n$, časová náročnosť bude v podstate lineárna.

e

V Lukášovom slovníku vidíme, že slová v ňom sa začínajú po nejakých skupinách veľmi podobne. Môžeme si teda slová zoradiť tak, aby po sebe následovali vždy slová, s čo najväčšou zhodou v prvých niekoľko znakoch. Napríklad takto:

  • lukosprava
  • lukostrelba
  • luk
  • luka
  • lukas
  • myxa
  • myxopod
  • myxophyta
  • kucapaca
  • kucho
  • kuchar
  • kucharka
  • kucharkaKSP

Teraz využijeme podobnú myšlienku ako v d. Nebudeme však odkazovať na predošlé znaky, ale vždy iba na predošlé slovo. Potom namiesto prvých $m$ znakov každého slova napíšeme iba m, kde $m$ je počet, na koľko znakov sa začiatok (“prefix”) slova zhoduje s predošlím. Teda tento konkrétny príklad by sme zapísali:

  • 0lukosprava
  • 5trelba
  • 3
  • 3a
  • 4s
  • 0myxa
  • 3opod
  • 5hyta
  • 0kucapaca
  • 3ho
  • 4ar
  • 6ka
  • 8KSP

Myšlienka takejto kompresie sa nazýva incremental encoding. Zvykne sa používať napríklad v rôznych implementáciach dátovej štruktúry dictionary (“slovník”).

f

Zo slov v zozname si môžeme zostaviť písmenkový strom. Ten vieme následne prehľadávať do hĺbky. Teda vždy ideme po jednej vetve stromu a keď prídeme na koniec do jej listového (posledného) vrcholu, až vtedy sa presunieme na najposlednejšie odvetvenú neprejdenú vetvu. Slová do zoradeného zoznamu budeme zapisovať postupne, ako sa dostaneme do ich listových vrcholov.

g

Zišlo by sa nám nájsť nejakú inú postupnosť $n$ čísel, v ktorej členy $b_1,b_2,\dots,b_n < k$. Zadanie nám prezradilo, že pre každé $i$ v pôvodnej postupnosti platí $a_{i + 1} - a_i < k$. Tak prečo si nezapamätať iba tieto rozdiely dvoch po sebe idúcich čísel v postupnosti? Teda pre $b$-čkovú postupnosť bude platiť

[ b_1 = 0,\ b_2 = a_2-a_1,\ b_3 = a_3-a_2,\ \dots,\ b_{n-1} = a_{n}-a_{n-1}. ]

Zapamätáme si ešte $a_1$, keďže môžeme si pamätať najviac jedno číslo väčšie ako $k$. Potom vieme zistiť každé pôvodné $a_i$ spočítaním súčtu $a_i = a_1 + b_1 + b_2 + \dots + b_i$. Kompresia pomocou rozdielov sa nazúva delta encoding.

h

V tejto postupnosti máme oproti g výhodu, že rozdiel každých dvoch po sebe idúcich členov je konštantný, a to $1$. Stačí tak uložiť iba prvý člen $a$ a dĺžku $n$, pretože z týchto dvoch čísel vieme celú postupnosť jednoznačne zrekonštruovať. Obe hodnoty majú zároveň najviac $n$ číslic: číslo $a$ je najviac $10^n-+$ , takže má najviac $n$ číslic, a aj samotné $n$ má najviac $n$ číslic. Keďže zoznam vieme prechádzať len raz, počas jedného prechodu si teda stačí zapamätať prvý prvok a priebežne počítať počet členov.

Pre odovzdávanie sa musíš prihlásiť.
Trojsten

Súťaž PRASK zastrešuje občianske združenie Trojsten.

Kontakt
Ďalšie projekty