├── .gitignore ├── Makefile ├── README.md ├── app-a.tex ├── bibliography.tex ├── chapter01.tex ├── chapter02.tex ├── chapter03.tex ├── chapter04.tex ├── chapter05.tex ├── chapter06.tex ├── chapter07.tex ├── chapter08.tex ├── chapter09.tex ├── chapter10.tex ├── chapter11.tex ├── figures ├── fig.10.4.tex ├── fig.10.5.tex ├── fig.10.trie.tex ├── fig.2.4.after.tex ├── fig.2.4.before.tex ├── fig.2.5.after.tex ├── fig.2.5.before.tex ├── fig.2.6.after.tex ├── fig.2.6.before.tex ├── fig.2.8.after.tex ├── fig.2.8.before.tex ├── fig.3.3.tex ├── fig.3.5.tex ├── fig.5.4.tex ├── fig.9.2a.tex ├── fig.9.2b.tex ├── fig.9.2c.tex ├── fig.9.3a.tex ├── fig.9.3b.tex ├── fig.9.3c.tex ├── fig.9.5.tex └── formula.theorem.5.2.tex ├── haskell ├── AltBinaryRandomAccessList.lhs ├── BankersDeque.lhs ├── BankersQueue.lhs ├── BatchedQueue.lhs ├── BinaryRandomAccessList.lhs ├── BinomialHeap.lhs ├── BootstrapHeap.lhs ├── BootstrappedQueue.lhs ├── BottomUpMergeSort.lhs ├── CatList.lhs ├── CatenableDeque.lhs ├── CatenableList.lhs ├── Deque.lhs ├── FiniteMap.lhs ├── Heap.lhs ├── HoodMelvilleQueue.lhs ├── ImplicitCatenableDeque.lhs ├── ImplicitQueue.lhs ├── LazyPairingHeap.lhs ├── LeftistHeap.lhs ├── PairingHeap.lhs ├── PhysicistsQueue.lhs ├── Queue.lhs ├── RandomAccessList.lhs ├── RedBlackSet.lhs ├── Set.lhs ├── SimpleCatenableDeque.lhs ├── SkewBinaryRandomAccessList.lhs ├── SkewBinomialHeap.lhs ├── Sortable.lhs ├── SplayHeap.lhs ├── Trie.lhs ├── TrieOfTrees.lhs └── UnbalancedSet.lhs ├── pfds.tex ├── preface.tex └── terminology.tex /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.aux 3 | *.log 4 | *.dvi 5 | *.pdf 6 | *.hi 7 | *.o 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LATEX ?= latex 2 | PDFLATEX ?= pdflatex 3 | HC ?= ghc 4 | 5 | RM := rm -rf 6 | MKDIR := mkdir -p 7 | 8 | LATEXFLAGS := -interaction=batchmode 9 | HOUT := out 10 | 11 | latex-files := $(wildcard *.tex) 12 | haskell-files := $(wildcard haskell/*.lhs) 13 | main-file := pfds.tex 14 | 15 | define run-twice 16 | $1 && $1 17 | endef 18 | 19 | pfds.dvi: $(latex-files) $(haskell-files) 20 | $(call run-twice,$(LATEX) $(LATEXFLAGS) $(main-file)) 21 | 22 | pfds.pdf: $(latex-files) $(haskell-files) 23 | $(call run-twice,$(PDFLATEX) $(LATEXFLAGS) $(main-file)) 24 | 25 | build-haskell: $(haskell-files) 26 | $(MKDIR) $(HOUT) && $(HC) -outputdir $(HOUT) $^ 27 | 28 | clean: 29 | $(RM) *.log *.aux *.pdf *.dvi haskell/*.hi haskell/*.o $(HOUT) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pfds 2 | ==== 3 | 4 | Russian translation of Okasaki's Purely Functional Data Structures 5 | 6 | Русский перевод книги Криса Окасаки "Чисто функциональные структуры данных". 7 | Перевод издан в 2016 году издательством ДМК Пресс (редактор В. Брагилевский): https://dmkpress.com/catalog/computer/programming/functional/978-5-97060-233-1/ 8 | 9 | В этом репозитории хранится предварительная версия перевода, до редактирования. Отсутствует большая часть листингов и рисунков. 10 | -------------------------------------------------------------------------------- /app-a.tex: -------------------------------------------------------------------------------- 1 | \appendix 2 | \chapter{Код на языке Haskell} 3 | \label{app:A} 4 | \lstset{language=Haskell} 5 | \section{Очереди} 6 | \input{haskell/Queue.lhs} 7 | \codesep 8 | \input{haskell/BatchedQueue.lhs} 9 | \codesep 10 | \input{haskell/BankersQueue.lhs} 11 | \codesep 12 | \input{haskell/PhysicistsQueue.lhs} 13 | \codesep 14 | \input{haskell/HoodMelvilleQueue.lhs} 15 | \codesep 16 | \input{haskell/BootstrappedQueue.lhs} 17 | \codesep 18 | \input{haskell/ImplicitQueue.lhs} 19 | 20 | \section{Двусторонние очереди} 21 | \input{haskell/Deque.lhs} 22 | \codesep 23 | \input{haskell/BankersDeque.lhs} 24 | 25 | \section{Списки с конкатенацией} 26 | \input{haskell/CatenableList.lhs} 27 | \codesep 28 | \input{haskell/CatList.lhs} 29 | 30 | \section{Двусторонние очереди с конкатенацией} 31 | \input{haskell/CatenableDeque.lhs} 32 | \codesep 33 | \input{haskell/SimpleCatenableDeque.lhs} 34 | \codesep 35 | \input{haskell/ImplicitCatenableDeque.lhs} 36 | 37 | \section{Списки с произвольным доступом} 38 | \input{haskell/RandomAccessList.lhs} 39 | \codesep 40 | \input{haskell/BinaryRandomAccessList.lhs} 41 | \codesep 42 | \input{haskell/SkewBinaryRandomAccessList.lhs} 43 | \codesep 44 | \input{haskell/AltBinaryRandomAccessList.lhs} 45 | 46 | \section{Кучи} 47 | \input{haskell/Heap.lhs} 48 | \codesep 49 | \input{haskell/LeftistHeap.lhs} 50 | \codesep 51 | \input{haskell/BinomialHeap.lhs} 52 | \codesep 53 | \input{haskell/SplayHeap.lhs} 54 | \codesep 55 | \input{haskell/PairingHeap.lhs} 56 | \codesep 57 | \input{haskell/LazyPairingHeap.lhs} 58 | \codesep 59 | \input{haskell/SkewBinomialHeap.lhs} 60 | \codesep 61 | \input{haskell/BootstrapHeap.lhs} 62 | 63 | \section{Сортируемые коллекции} 64 | \input{haskell/Sortable.lhs} 65 | \codesep 66 | \input{haskell/BottomUpMergeSort.lhs} 67 | 68 | \section{Множества} 69 | \input{haskell/Set.lhs} 70 | \codesep 71 | \input{haskell/UnbalancedSet.lhs} 72 | \codesep 73 | \input{haskell/RedBlackSet.lhs} 74 | 75 | \section{Конечные отображения} 76 | \input{haskell/FiniteMap.lhs} 77 | \codesep 78 | \input{haskell/Trie.lhs} 79 | \codesep 80 | \input{haskell/TrieOfTrees.lhs} 81 | 82 | %%% Local Variables: 83 | %%% mode: latex 84 | %%% TeX-master: "pfds" 85 | %%% End: 86 | -------------------------------------------------------------------------------- /bibliography.tex: -------------------------------------------------------------------------------- 1 | \begin{thebibliography}{XXXXXXX} 2 | \bibitem[Ada93]{Adams1993} Stephen Adams. Efficient sets~--- a 3 | balancing act. \textit{Journal of Functional Programming}, 4 | 3(4):553-561, October 1993. 5 | \bibitem[AFM$^+$95]{Ariola-etal1995} Zena M.~Ariola, Matthias 6 | Felleisen, John Maraist, Martin Odersky, and Philip Wadler. A 7 | call-by-need lambda calculus. In \textit{ACM Symposium on Principles 8 | of Programming Languages}, pages 233--246, January 1995. 9 | \bibitem[And95]{Andersson1991} Arne Andersson. A note on searching in 10 | a binary search tree. \textit{Software---Practice and Experience}, 11 | 21(10):1125-1128, October 1991. 12 | \bibitem[AVL62]{AdelsonVelskiiLandis1962} Г.~М.~Адельсон-Вельский 13 | и Е.~М.~Ландис. Один алгоритм организации 14 | информации. \textit{Доклады Академии Наук СССР}, 146:263--266, 1962. 15 | \bibitem[Bac78]{Backus1978} John Backus. Can programming be liberated 16 | from the von Neumann style? A functional style and its algebra of 17 | programs. \textit{Communications of the ACM}, 21(8):613--641, August 1978. 18 | \bibitem[BAG92]{BenAmramGalil1992} Amir Ben-Amram and Zvi Galil. On 19 | pointers versus addresses. \textit{Journal of the ACM}, 20 | 39(3):617-648, July 1992. 21 | \bibitem[BC93]{BurtonCameron1993} F.~Warren Burton and 22 | Robert D.~Cameron. Pattern matching with abstract data 23 | types. \textit{Journal of Functional Programming}, 3(2):171-190, 24 | April 1993. 25 | \bibitem[Bel57]{Bellman1957} Richard Bellman. \textit{Dynamic 26 | Programming.}\/ Princeton University Press, 1957. 27 | \bibitem[BH89]{BjernerHolmstrom1989} Bjor Bjerner and S\"oren 28 | Holmstr\"om. A compositional approach to time analysis of first 29 | order lazy functional programs. In \textit{Conference on Functional 30 | Programming Languages and Computer Architecture}, pages 157--165, 31 | September 1989. 32 | \bibitem[BO96]{BrodalOkasaki1996} Gerth St\o{}lting Brodal and Chris 33 | Okasaki. Optimal purely functional priority queues. \textit{Journal 34 | of Functional Programming}, 6(6):839--857, November 1996. 35 | \bibitem[Bro78]{Brown1978} Mark R.~Brown. Implementation and analysis 36 | of binomial queue algorithms. \textit{SIAM Journal of Computing}, 37 | 7(3):298--319, August 1978. 38 | \bibitem[Bro95]{Brodal1995} Gerth St\o{}lting Brodal. Fast meldable 39 | priority queues. In \textit{Workshop on Algorithms and Data 40 | Structures}, volume 995 of \textit{LNCS}, pages 41 | 282--290. Springer-Verlag, August 1995. 42 | \bibitem[Bro96]{Brodal1996} Gerth St\o{}lting Brodal. Worst-case 43 | priority queues. In \textit{ACM-SIAM Symposium on Discrete 44 | Algorithms}, pages 52--58, January 1996. 45 | \bibitem[BST95]{BuchsbaumSundarTarjan1995} Adam L.~Buchsbaum, Rajamani 46 | Sundar, and Robert E.~Tarjan. Data-structural bootstrapping, linear 47 | path compression, and catenable heap-ordered double-ended 48 | queues. \textit{SIAM Journal on Computing}, 24(6):1190--1206, 49 | December 1995. 50 | \bibitem[BT95]{BuchsbaumTarjan1995} Adam L.~Buchsbaum and 51 | Robert E.~Tarjan. Confluently persistent deques via data structural 52 | bootstrapping. \textit{Journal of Algorithms}, 18(3):513--547, May 1995. 53 | \bibitem[Buc93]{Buchsbaum1993} 54 | Adam L.~Buchsbaum. \textit{Data-structural bootstrapping and 55 | catenable deques}. PhD thesis, Department of Computer Science, 56 | Princeton University, June 1993. 57 | \bibitem[Bur82]{Burton1982} F.~Warren Burton. An efficient functional 58 | implementation of FIFO queues. \textit{Information Processing 59 | Letters}, 14(5):205--206, July 1982. 60 | \bibitem[But83]{Butler1983} T.~W.~Butler. Computer response time and 61 | user performance. In \textit{Conference on Human Factors in 62 | Computing Systems}, pages 58--62, December 1983. 63 | \bibitem[BW88]{BirdWadler1988} Richard S.~Bird and Philip 64 | Wadler. \textit{Introduction to Functional Programming}. Prentice 65 | Hall International, 1988. 66 | \bibitem[CG93]{ChuangGoldberg1993} Tung-Ruey Chuang and Benjamin 67 | Goldberg. Real-time deques, multihead Turing machines, and purely 68 | functional programming. In \textit{Conference on Functional 69 | Programming Languages and Computer Architecture}, pages 289-298, 70 | June 1993. 71 | \bibitem[CLR90]{CormenLeisersonRivest1990} Thomas H.~Cormen, Charles 72 | E.~Leiserson, and Ronald L.~Rivest. \textit{Introduction to 73 | Algorithms}. MIT Press, 1990. Русский перевод: Т.~Кормен, 74 | Ч.~Лейзерсон, Р.~Ривест. \textit{Алгоритмы: построение и 75 | анализ}. Москва, МЦНМО, 2001. 76 | \bibitem[CM95]{ConnellyMorris1995} Richard H.~Connelly and F.~Lockwood 77 | Morris. A generalization of the trie data 78 | structure. \textit{Mathematical Structures in Computer Science}, 79 | 5(3):381--418, September 1995. 80 | \bibitem[CMP88]{CarlssonMunroPoblete1988} Svante Carlsson, Ian Munro, 81 | and Patricio V.~Poblete. An implicit binomial queue with constant 82 | insertion time. In \textit{Scandinavian Workshop on Algorithm 83 | Theory}, volume 318 of \textit{LNCS}, pages 84 | 1--13. Springer-Verlag, July 1988. 85 | \bibitem[Cra72]{Crane1972} Clark Allan Crane. \textit{Linear lists and 86 | priority queues as balanced binary trees}. PhD thesis, Computer 87 | Science Department, Stanford University, February 1972. Available as STAN-CS-72-259. 88 | \bibitem[CS96]{ChoSahni1996} Seonghun Cho and Sartaj Sahni. Weight 89 | biased leftist trees and modified skip lists. In 90 | \textit{International Computing and Combinatorics Conference}, pages 91 | 361--370, June 1996. 92 | \bibitem[DGST88]{Driscoll-etal1988} James R.~Driscoll, Harold 93 | N.~Gabow, Ruth Shrairman, and Robert E.~Tarjan. Relaxed heaps: An 94 | alternative to Fibonacci heaps with applications to parallel 95 | computation. \textit{Communications of the ACM}, 31(11):1343-1354, 96 | November 1988. 97 | \bibitem[Die82]{Dietz1982} Paul F.~Dietz. Maintaining order in a 98 | linked list. In \textit{ACM Symposium on Theory of Computing}, pages 99 | 122--127, May 1982. 100 | \bibitem[Die89]{Dietz1989} Paul F.~Dietz. Fully persistent arrays. In 101 | \textit{Workshop on Algorithms and Data Structures}, volume 382 of 102 | \textit{LNCS}, pages 67--74. Springer-Verlag, August 1989. 103 | \bibitem[DR91]{DietzRaman1991} Paul F.~Dietz and Rajeev 104 | Raman. Persistence, amortization and randomization. In 105 | \textit{ACM-SIAM Symposium on Discrete Algorithms}, pages 78--88, 106 | January 1991. 107 | \bibitem[DR93]{DietzRaman1993} Paul F.~Dietz and Rajeev Raman. 108 | Persistence, randomization and parallelization: On some 109 | combinatorial games and their applications. in \textit{Workshop on 110 | Algorithms and Data Structures}, volume 709 of \textit{LNCS}, 111 | pages 289--301. Springer-Verlag, August 1993. 112 | \bibitem[DS87]{DietzSleator1987} Paul F.~Dietz and Danial 113 | D.~Sleator. Two algorithms for maintaining order in a list. In 114 | \textit{ACM Symposium on Theory of Computing}, pages 365--372, May 1987. 115 | \bibitem[DSST89]{Driscoll-etal1989} James R.~Driscoll, Neil Sarnak, 116 | Daniel D.~K.~Sleator, and Robert E.~Tarjan. Making data structures 117 | persistent. \textit{Journal of Computing and System Sciences}, 118 | 38(1):86--124, February 1989. 119 | \bibitem[DST94]{DriscollSleatorTarjan1994} James R.~Driscoll, Daniel 120 | D.~K.~Sleator, and Robert E.~Tarjan. Fully persistent lists with 121 | catenation. \textit{Journal of the ACM}, 41(5):943--959, September 1994. 122 | \bibitem[FB97]{FahndrichBoyland1997} Manuel F\"ahndrich and John 123 | Boyland. Statically checkable pattern abstractions. In \textit{ACM 124 | SIGPLAN International Conference on Functional Programming}, pages 125 | 75--84, June 1997. 126 | \bibitem[FMR72]{FischerMeyerRosenberg1972} Patrick C.~Fischer, Albert 127 | R.~Meyer and Arnold L.~Rosenberg. Real-time simulation of multihead 128 | tape units. \textit{Journal of the ACM}, 19(4):590--607, October 1972. 129 | \bibitem[FSST86]{Fredman-etal1986} Michael L.~Fredman, Robert 130 | Sedgewick, Daniel D.~K.~Sleator, and Robert E.~Tarjan. The pairing 131 | heap: A new form of self-adjusting heap. \textit{Algorithmica}, 132 | 1(1):111--129, 1986. 133 | \bibitem[FT87]{FredmanTarjan1987} Michael L.~Fredman and Robert 134 | E.~Tarjan. Fibonacci heaps and their uses in improved network 135 | optimization algorithms. \textit{Journal of the ACM}, 136 | 34(3):596--615, July 1987. 137 | \bibitem[FW76]{FriedmanWise1976} Daniel P.~Friedman and David 138 | S.~Wise. CONS should not evaluate its arguments. In 139 | \textit{Automata, Languages and Programming}, pages 257--281, July 1976. 140 | \bibitem[GMPR77]{Guibas-etal1977} Leo J.~Guibas, Edward M.~McCreight, 141 | Michael F.~Plass, and Janet R.~Roberts. A new representation for 142 | linear lists. In \textit{ACM Symposium on Theory of Computing}, 143 | pages 49--60, May 1977. 144 | \bibitem[Gri81]{Gries1981} David Gries. \textit{The Science of 145 | Programming}. Texts and Monographs in Computer 146 | Science. Springer-Verlag, New York, 1981. 147 | \bibitem[GS78]{GuibasSedgewick1978} Leo J.~Guibas and Robert 148 | Sedgewick. A dichromatic framework for balanced trees. In 149 | \textit{IEEE Symposium on Foundations of Computer Science}, pages 150 | 8--21, October 1978. 151 | \bibitem[GT86]{GajewskaTarjan1986} Hania Gajewska and Robert 152 | E.~Tarjan. Deques with heap order. \textit{Information Processing 153 | Letters}, 22(4):197--200, April 1986. 154 | \bibitem[Hen93]{Henglein1993} Fritz Henglein. Type inference with 155 | polymorphic recursion. \textit{ACM Transactions on Programming 156 | Languages and Systems}, 15(2):253--289, April 1993. 157 | \bibitem[HJ94]{HudakJones1994} Paul Hudak and Mark P.~Jones. Haskell 158 | vs. Ada vs. C$++$ vs. \ldots An experiment in software prototyping 159 | productivity, 1994. 160 | \bibitem[HM76]{HendersonMorris1976} Peter Henderson and James 161 | H.~Morris, Jr. A lazy evaluator. In \textit{ACM Symposium on 162 | Principles of Programming Languages}, pages 95--103, January 1976. 163 | \bibitem[HM81]{HoodMelville1981} Robert Hood and Robert 164 | Melville. Real-time queue operations in pure 165 | Lisp. \textit{Information Processing Letters}, 13(2): 50--53, 166 | November 1981. 167 | \bibitem[Hoo82]{Hood1982} Robert Hood. \textit{The Efficient 168 | Implementation of Very-High-Level Programming Language 169 | Constructs.}\/ PhD thesis, Department of Computer Science, Cornell 170 | University, August 1982. (Cornell TR 82-503). 171 | \bibitem[Hoo92]{Hoogerwoord1992} Rob R.~Hoogerwoord. A symmetric set 172 | of efficient list operations. \textit{Journal of Functional 173 | Programming}, 2(4):505--513, October 1992. 174 | \bibitem[HU73]{HopcroftUllman1973} John E.~Hopcroft and Jeffrey 175 | D.~Ullman. Set merging algorithms. \textit{SIAM Journal on 176 | Computing}, 2(4):294--303, December 1973. 177 | \bibitem[Hug85]{Hughes1985} John Hughes. Lazy memo functions. In 178 | \textit{Conference on Functional Programming Languages and Computer 179 | Architecture}, volume 201 of \textit{LNCS}, pages 180 | 129--146. Springer-Verlag, September 1985. 181 | \bibitem[Hug86]{Hughes1986} John Hughes. A novel representation of 182 | lists and its application to the function 183 | ``reverse''. \textit{Information Processing Letters}, 184 | 22(3):141--144, March 1986. 185 | \bibitem[Hug89]{Hughes1989} John Hughes. Why functional programming 186 | matters. \textit{The Computer Journal}, 32(2):98--107, April 1989. 187 | \bibitem[Joh86]{Jones1986} Douglas W.~Jones. An empirical comparison 188 | of priority-queue and event-set 189 | implementations. \textit{Communications of the ACM}, 29(4):300--311, 190 | April 1986. 191 | \bibitem[Jos89]{Josephs1989} Mark B.~Josephs. The semantics of lazy 192 | functional languages. \textit{Theoretical Computer Science}, 193 | 68(1):105--111, October 1989. 194 | \bibitem[KD96]{KaldewaijDielissen1996} Anne Kaldewaij and Victor 195 | J.~Dielissen. Leaf trees. \textit{Science of Computer Programming}, 196 | 26(1--3):149--165, May 1996. 197 | \bibitem[Kin94]{King1994} David J.~King. Functional binomial 198 | queues. In \textit{Glasgow Workshop on Functional Programming}, 199 | pages 141--150, September 1994. 200 | \bibitem[KL93]{KhoongLeong1993} Chan Meng Khoong and Hon Wai 201 | Leong. Double-ended binomial queues. In \textit{International 202 | Symposium on Algorithms and Computation}, volume 762 of 203 | \textit{LNCS}, pages 128--137. Springer-Verlag, December 1993. 204 | \bibitem[Knu73a]{Knuth1973a} Donald E.~Knuth. \textit{Searching and 205 | Sorting}, volume 3 of \textit{The Art of Computer 206 | Programming}. Addison-Wesley, 1973. Русский перевод: Дональд 207 | Э.~Кнут. \textit{Искусство программирования. Том 3: Сортировка и 208 | поиск.}\/ Вильямс, 2012. 209 | \bibitem[Knu73b]{Knuth1973b} Donald E.~Knuth. \textit{Seminumerical 210 | Algorithms}, volume 2 of \textit{The Art of Computer 211 | Programming}. Addison-Wesley, 1973. Русский перевод: Дональд 212 | Э.~Кнут. \textit{Искусство программирования. Том 2: Получисленные 213 | алгоритмы.}\/ Вильямс, 2011. 214 | \bibitem[KT95]{KaplanTarjan1995} Haim Kaplan and Robert 215 | E.~Tarjan. Persistent lists with catenation via recursive 216 | slow-down. In \textit{ACM Symposium on Theory of Computing}, pages 217 | 93--102, May 1995. 218 | \bibitem[KT96a]{KaplanTarjan1996a} Haim Kaplan and Robert 219 | E.~Tarjan. Purely functional lists with catenation via recursive 220 | slow-down. Draft revision of \cite{KaplanTarjan1995}, August 1996. 221 | \bibitem[KT96b]{KaplanTarjan1996b} Haim Kaplan and Robert 222 | E.~Tarjan. Purely functional representation of catenable sorted 223 | lists. In \textit{ACM Symposium on Theory of Computing}, pages 224 | 202--211, May 1996. 225 | \bibitem[KTU93]{KfouryTiurynUrzyczyn1993} Assaf J.~Kfoury, Jerzy 226 | Tiuryn, and Pawel Urzyczyn. Type reconstruction in the presence of 227 | polymorphic recursion. \textit{ACM Transactions on Programming 228 | Languages and Systems}, 15(2):290--311, April 1993. 229 | \bibitem[Lan65]{Landin1965} P.~J.~Landin. A correspondence between 230 | ALGOL 60 and Church's lambda-notation: Part 231 | I. \textit{Communications of the ACM}, 8(2):89--101, February 1965. 232 | \bibitem[Lau93]{Launchbury1993} John Launchbury. A natural semantics 233 | for lazy evaluation. In \textit{ACM Symposium on Principles of 234 | Programming Languages}, pages 144--154, January 1993. 235 | \bibitem[Lia92]{Liao1992} Andrew M.~Liao. Three priority queue 236 | applications revisited. \textit{Algorithmica}, 7(4):415--427, 1992. 237 | \bibitem[LS81]{LeongSeiferas1981} Benton L.~Leong and Joel 238 | I.~Seiferas. New real-time simulations of multihead tape 239 | units. \textit{Journal of the ACM}, 28(1):166--180, January 1981. 240 | \bibitem[MEP96]{MoffatEddyPetersson1996} Alistair Moffat, Gary Eddy, 241 | and Ola Petersson. Splaysort: Fast, versatile, 242 | practical. \textit{Software---Practice and Experience}, 243 | 26(7):781--797, July 1996. 244 | \bibitem[Mic68]{Michie1968} Donald Michie. ``Memo'' functions and 245 | machine learning. \textit{Nature}, 218:19--22, April 1968. 246 | \bibitem[MS91]{MoretShapiro1991} Bernard M.~E.~Moret and Henry 247 | D.~Shapiro. An empirical analysis of algorithms for constructing a 248 | minimum spanning tree. In \textit{Workshop on Algorithms and Data 249 | Structures}, volume 519 of \textit{LNCS}, pages 250 | 400--411. Springer-Verlag, August 1991. 251 | \bibitem[MT94]{MacQueenTofte1994} David B.~MacQueen and Mads Tofte. A 252 | semantics for higher-order functors. In \textit{European Symposium 253 | on Programming}, pages 409--423, April 1994. 254 | \bibitem[MTHM97]{Milner-etal1997} Robin Milner, Mads Tofte, Robert 255 | Harper, and David MacQueen. \textit{The Definition of Standard ML 256 | (Revised)}. The MIT Press, Cambridge, Massachusetts, 1997. 257 | \bibitem[Myc84]{Mycroft1984} Alan Mycroft. Polymorphic type schemes 258 | and recursive definitions. In \textit{International Symposium on 259 | Programming}, volume 167 of \textit{LNCS}, pages 260 | 217--228. Springer-Verlag, April 1984. 261 | \bibitem[Mye82]{Myers1982} Eugene W.~Myers. AVL dags. Technical Report 262 | TR82-9, Department of Computer Science, University of Arizona, 1982. 263 | \bibitem[Mye83]{Myers1983} Eugene W.~Myers. An applicative 264 | random-access stack. \textit{Information Processing Letters}, 265 | 17(5):241--248, December 1983. 266 | \bibitem[Mye84]{Myers1984} Eugene W.~Myers. Efficient applicative data 267 | types. In \textit{ACM Symposium on Principles of Programming 268 | Languages}, pages 66--75, January 1984. 269 | \bibitem[NPP95]{NunezPalaoPena1995} Manuel N\'u\~nez, Pedro Palao, and 270 | Ricardo Pe\~na. A second year course on data structures based on 271 | functional programming. In \textit{Functional Programming Languages 272 | in Education}, volume 1022 of \textit{LNCS}, pages 273 | 65--84. Springer-Verlag, December 1995. 274 | \bibitem[Oka95a]{Okasaki1995a} Chris Okasaki. Amortization, lazy 275 | evaluation, and persistence: Lists with catenation via lazy 276 | linking. In \textit{IEEE Symposium on Foundations of Computer 277 | Science}, pages 646--654, October 1995. 278 | \bibitem[Oka95b]{Okasaki1995b} Chris Okasaki. Purely functional 279 | random-access lists. In \textit{Conference on Functional Programming 280 | Languages and Computer Architecture}, pages 86--95, June 1995. 281 | \bibitem[Oka95c]{Okasaki1995c} Chris Okasaki. Simple and efficient 282 | purely functional queues and deques. \textit{Journal of Functional 283 | Programming}, 5(4):583--592, October 1995. 284 | \bibitem[Oka96a]{Okasaki1996a} Chris Okasaki. \textit{Purely 285 | Functional Data Structures.}\/ PhD thesis, School of Computer 286 | Science, Carnegie Mellon University, September 1996. 287 | \bibitem[Oka96b]{Okasaki1996b} Chris Okasaki. The role of lazy 288 | evaluation in amortized data structures. In \textit{ACM SIGPLAN 289 | International Conference on Functional Programming}, pages 62--72, 290 | May 1996. 291 | \bibitem[Oka97]{Okasaki1997} Chris Okasaki. Catenable double-ended 292 | queues. In \textit{ACM SIGPLAN International Conference on 293 | Functional Programming}, pages 64--74, June 1997. 294 | \bibitem[OLT94]{OkasakiLeeTarditi1994} Chris Okasaki, Peter Lee, and 295 | David Tarditi. Call-by-need and continuation-passing 296 | style. \textit{Lisp and Symbolic Computation}, 7(1):57--81, January 1994. 297 | \bibitem[Ove83]{Overmars1983} Mark H.~Overmars. \textit{The Design of 298 | Dynamic Data Structures}, volume 156 of 299 | \textit{LNCS}. Springer-Verlag, 1983. 300 | \bibitem[Pau96]{Paulson1996} Laurence C.~Paulson. \textit{ML for the 301 | Working Programmer.}\/ Cambridge University Press, 2nd edition, 1996. 302 | \bibitem[Pet87]{Peterson1987} Gery L.~Peterson. A balanced tree scheme 303 | for meldable heaps with updates. Technical Report GIT-ICS-87-23, 304 | School of Information and Computer Science, Georgia Institute of 305 | Technology, 1987. 306 | \bibitem[Pip96]{Pippenger1996} Nicholas Pippinger. Pure versus impure 307 | Lisp. In \textit{ACM Symposium on Principles of Programming 308 | Languages}, pages 104--109, January 1996. 309 | \bibitem[PPN96]{PalaoGostanzaPenaNunez1996} Pedro Palao Gostanza, Ricardo 310 | Pe\~na, and Manuel Nu\~nez. A new look at pattern matching in 311 | abstract data types. In \textit{ACM SIGPLAN International Conference 312 | on Functional Programming}, pages 110--121, May 1996. 313 | \bibitem[Ram92]{Raman1992} Rajeev Raman. \textit{Eliminating 314 | Amortization: On Data Structures with Guaranteed Response 315 | Times.}\/ PhD thesis, Department of Computer Sciences, University 316 | of Rochester, October 1992. 317 | \bibitem[Rea92]{Reade1992} Chris M.~P.~Reade. Balanced trees with 318 | removals: an exercise in rewriting and proof. \textit{Science of 319 | Computer Programming}, 18(2):181--204, April 1992. 320 | \bibitem[San90]{Sands1990} David Sands. Complexity analysis for a lazy 321 | higher-order language. In \textit{European Symposium on 322 | Programming}, volume 432 of \textit{LNCS}, pages 323 | 361--376. Springer-Verlag, May 1990. 324 | \bibitem[San95]{Sands1995} David Sands. A na\"\i{}ve time analysis and 325 | its theory of cost equivalence. \textit{Journal of Logic and 326 | Computation}, 5(4):495--541, August 1995. 327 | \bibitem[Sar86]{Sarnak1986} Neil Sarnak. \textit{Persistent Data 328 | Structures.}\/ PhD thesis, Department of Computer Sciences, New 329 | York university, 1986. 330 | \bibitem[Sch92]{Schoenmakers1992} Berry Schoenmakers. \textit{Data 331 | Structures and Amortized Complexity in a Functional Setting.}\/ 332 | PhD thesis, Eindhoven University of Technology, September 1992. 333 | \bibitem[Sch93]{Schoenmakers1993} Berry Schoenmakers. A systematic 334 | analysis of splaying. \textit{Information Processing Letters}, 335 | 45(1):41--50, January 1993. 336 | \bibitem[Sch97]{Schwenke1997} Martin Schwenke. High-level refinement 337 | of random access data structures. In \textit{Formal Methods 338 | Pacific}, pages 317--318, July 1997. 339 | \bibitem[SS90]{SackStrothotte1990} J\"org-R\"udiger Sack and Thomas 340 | Strothotte. A characterization of heaps and its 341 | applications. \textit{Information and Computation}, 86(1):69--86, 342 | May 1990. 343 | \bibitem[ST85]{SleatorTarjan1985} Daniel D.~K.~Sleator and Robert 344 | E.~Tarjan. Self-adjusting binary search trees. \textit{Journal of 345 | the ACM}, 32(3):652--686, July 1985. 346 | \bibitem[ST86a]{SarnakTarjan1986a} Neil Sarnak and Robert 347 | E.~Tarjan. Planar point location using persistent search 348 | trees. \textit{Communications of the ACM}, 29(7):669--679, July 1986. 349 | \bibitem[ST86b]{SleatorTarjan1986b} Daniel D.~K.~Sleator and Robert E.~Tarjan. 350 | Self-adjusting heaps. \textit{SIAM Journal on Computing}, 351 | 15(1):52--69, February 1986. 352 | \bibitem[Sta88]{Stankovic1988} John A.~Stankovic. Misconceptions about 353 | real-time computing: A serious problem for next-generation 354 | systems. \textit{Computer}, 21(10):10--19, October 1988. 355 | \bibitem[Sto70]{Stoss1970} Hans-J\"org Sto\ss{}. K-band simulation von 356 | k-Kopf-Turing\-machinen. \textit{Computing}, 6(3):309--317, 1970. 357 | \bibitem[SV87]{StaskoVitter1987} John T.~Stasko and Jeffrey 358 | S.~Vitter. Pairing heaps: experiments and 359 | analysis. \textit{Communications of the ACM}, 30(3):234--249, March 1987. 360 | \bibitem[Tar83]{Tarjan1983} Robert E.~Tarjan. \textit{Data Structures 361 | and Network Algorithms}, volume 44 of \textit{CBMS Regional 362 | Conference Series in Applied Mathematics.}\/ Society for 363 | Industrial and Applied Mathematics, Philadelphia, 1983. 364 | \bibitem[Tar85]{Tarjan1985} Robert E.~Tarjan. Amortized computational 365 | complexity. \textit{SIAM Journal on Algebraic and Discrete Methods}, 366 | 6(2):306--318, April 1985. 367 | \bibitem[TvL84]{TarjanvanLeeuwen1984} Robert E.~Tarjan and Jan van 368 | Leeuwen. Worst-case analysis of set union 369 | algorithms. \textit{Journal of the ACM}, 31(2):245--281, April 1984. 370 | \bibitem[Ull94]{Ullman1994} Jeffrey D.~Ullman. \textit{Elements of ML 371 | Programming.}\/ Prentice Hall, Englewood Cliffs, New Jersey, 1994. 372 | \bibitem[Vui74]{Vuillemin1974} Jean Vuillemin. Correct and optimal 373 | implementations of recursion in a simple programming 374 | language. \textit{Journal of Computer and System Sciences}, 375 | 9(3):332--354, December 1974. 376 | \bibitem[Vui78]{Vuillemin1978} Jean Vuillemin. A data structure for 377 | manipulating priority queues. \textit{Communications of the ACM}, 378 | 21(4):309--315, April 1978. 379 | \bibitem[Wad71]{Wadsworth1971} Christopher 380 | P.~Wadsworth. \textit{Semantics and Pragmatics of the Lambda 381 | Calculus.}\/ PhD thesis, University of Oxford, September 1971. 382 | \bibitem[Wad87]{Wadler1987}Philip Wadler. Views: A way for 383 | pattern-matching to cohabit with data abstraction. In \textit{ACM 384 | Symposium on Principles of Programming Languages}, pages 307--313, 385 | January 1987. 386 | \bibitem[Wad88]{Wadler1988} Philip Wadler. Strictness analysis aids 387 | time analysis. In \textit{ACM Symposium on Principles of Programming 388 | Languages}, pages 119--132, January 1988. 389 | \bibitem[WV86]{vanWykVitter1986} Christopher van Wyk and Jeffrey Scott 390 | Vitter. The complexity of hashing with lazy 391 | deletion. \textit{Algorithmica}, 1(1):17--29, 1986. 392 | \end{thebibliography} 393 | 394 | %%% Local Variables: 395 | %%% mode: latex 396 | %%% TeX-master: "pfds" 397 | %%% End: 398 | -------------------------------------------------------------------------------- /chapter01.tex: -------------------------------------------------------------------------------- 1 | \chapter{Введение} 2 | \label{ch:1} 3 | 4 | Когда программисту на C для решения определенной задачи требуется 5 | эффективная структура данных, часто он или она могут просто найти 6 | подходящее решение в одном из многих учебников или справочников. К 7 | сожалению, для программистов на функциональных языках вроде 8 | Стандартного ML или Haskell такая роскошь недоступна. Хотя большинство 9 | справочников стараются быть независимы от языка, независимость эта 10 | получается только в смысле Генри Форда: программисты свободны выбрать 11 | любой язык, если язык этот императивный.\footnote{% 12 | Генри Форд однажды сказал о цветах автомобилей Модели T: 13 | <<[Покупатели] могут выбрать любой цвет, при условии, что он черный.>> 14 | } 15 | Чтобы несколько исправить этот дисбаланс, в этой книге я рассматриваю 16 | структуры данных с функциональной точки зрения. В примерах программ я 17 | использую Стандартный ML, однако эти программы нетрудно перевести на 18 | другие функциональные языки, например, Haskell или Lisp. Версии наших 19 | программ на Haskell можно найти в Приложении~\ref{app:A}. 20 | 21 | \section{Функциональные и императивные структуры данных} 22 | 23 | Методологические преимущества функциональных языков хорошо известны 24 | \cite{Backus1978,Hughes1989,HudakJones1994}, но тем не менее 25 | большинство программ по-прежнему пишутся на императивных языках вроде 26 | C. Кажущееся противоречие легко объяснить тем, что исторически 27 | функциональные языки проигрывали в скорости своим более традиционным 28 | аналогам, однако этот разрыв сейчас сужается. По широкому фронту 29 | задач был достигнут впечатляющий прогресс, начиная от базовой техники 30 | построения компиляторов и заканчивая глубоким анализом и оптимизацией 31 | программ. Однако одну особенность функционального программирования не 32 | исправить никакими ухищрениями со стороны авторов компиляторов~--- 33 | использование слабых или несоответствующих задаче структур данных. К 34 | сожалению, имеющаяся литература содержит относительно мало рецептов 35 | помощи в этой области. 36 | 37 | Почему оказывается, что функциональные структуры данных труднее 38 | спроектировать и реализовать, чем императивные? Здесь две основные 39 | проблемы. Во-первых, с точки зрения проектирования и реализации 40 | эффективных структур данных, запрет функционального программирования 41 | на деструктивное обновление (т.,~е., присваивание) является 42 | существенным препятствием, подобно запрету для повара использовать 43 | ножи. Как и ножи, деструктивные обновления при неправильном 44 | употреблении опасны, но, будучи пущены в дело должным образом, 45 | чрезвычайно эффективны. Императивные структуры данных часто 46 | существенным образом полагаются на присваивание, так что в 47 | функциональных программах приходится искать другие подходы. 48 | 49 | Второе затруднение состоит в том, что от функциональных структур 50 | ожидается большая гибкость, чем от их императивных аналогов. В 51 | частности, когда мы производим обновление императивной структуры 52 | данных, мы, как правило, принимаем как данность, что старая версия 53 | данных более недоступна, в то время как при обновлении функциональной 54 | структуры мы ожидаем, что как старая, так и новая версия доступны для 55 | дальнейшей обработки. Структура данных, поддерживающая несколько 56 | версий, называется \term{устойчивой}{persistent}, в то время как 57 | структура данных, позволяющая иметь лишь одну версию в каждый момент 58 | времени, называется \term{эфемерной}{ephemeral} 59 | \cite{Driscoll-etal1989}. Функциональные языки программирования обладают тем 60 | интересным свойством, что \emph{все} структуры данных в них 61 | автоматически устойчивы. Императивные структуры данных, как правило, 62 | эфемерны. В тех случаях, когда требуется устойчивая структура, 63 | императивные программисты не удивляются, что она получается более 64 | сложной и, возможно, даже асимптотически менее эффективной, чем 65 | эквивалентная эфемерная структура. 66 | 67 | Более того, теоретики установили нижние границы, которые показывают, 68 | что в некоторых ситуациях функциональные языки по своей природе менее 69 | эффективны, чем императивные \cite{BenAmramGalil1992, Pippenger1996}. В свете 70 | перечисленного, функциональные структуры данных иногда кажутся 71 | похожими на танцующего медведя, о котором говорится: <<поразительна не 72 | красота его танца, а то, что он вообще 73 | танцует!>> Однако на практике ситуация совсем не так безнадежна. Как 74 | мы увидим, часто оказывается возможным построить функциональные 75 | структуры данных, асимптотически столь же эффективные, как лучшие 76 | императивные решения. 77 | 78 | \section{Энергичное и ленивое вычисление} 79 | 80 | Большинство (последовательных) функциональных языков программирования 81 | можно отнести либо к \term{энергичным}{strict}, либо к 82 | \term{ленивым}{lazy}, в зависимости от порядка вычислений. Какой из 83 | этих порядков предпочтительнее~--- тема, обсуждаемая функциональными 84 | программистами подчас с религиозным жаром. Различие между двумя 85 | порядками вычисления наиболее ярко проявляется в подходах к вычислению 86 | аргументов функции. В энергичных языках аргументы вычисляются 87 | прежде тела функции. В ленивых языках вычисление аргументов управляется 88 | потребностью; исходно они передаются в функцию в невычисленном виде, и 89 | вычисляются только тогда, когда (и если!) их значение нужно 90 | для продолжения работы. Кроме того, после однократного вычисления 91 | значение аргумента кэшируется, так что если оно потребуется снова, его 92 | можно получить из памяти, а не перевычислять заново. Такое 93 | кэширование называется \term{мемоизация}{memoization} 94 | \cite{Michie1968}. 95 | 96 | Каждый из этих порядков имеет свои достоинства и недостатки, но 97 | энергичное вычисление явно удобнее по крайней мере в одном 98 | отношении: с ним проще рассуждать об асимптотичееской сложности 99 | вычислений. В энергичных языках то, какие именно подвыражения 100 | будут вычислены и когда, ясно по большей части уже из синтаксиса. 101 | Таким образом рассуждения о времени выполнения каждой данной программы 102 | относительно просты. В то же время в ленивых языках даже эксперты 103 | часто испытывают сложности при ответе на вопрос, когда будет вычислено 104 | данное подвыражение и будет ли вычислено вообще. Программисты на 105 | таких языках часто вынуждены притворяться, что язык на самом деле 106 | энергичен, чтобы получить хотя бы грубые оценки времени работы. 107 | 108 | Оба порядка вычисления влияют на проектирование и анализ структур 109 | данных. Как мы увидим, энергичные языки могут описать структуры с 110 | жёсткой оценкой времени выполнения в худшем случае, но не с амортизированной 111 | оценкой, а в ленивых языках описываются амортизированные структуры 112 | данных, но не рассчитанные на худший случай. Чтобы описывать обе 113 | разновидности структур, требуется язык, поддерживающий оба 114 | порядка вычислений. Мы получаем такой язык, расширяя Стандартный ML 115 | примитивами для ленивого вычисления, как описано в Главе~\ref{ch:4}. 116 | 117 | \section{Терминология} 118 | 119 | Любой разговор о структурах данных содержит опасность возникновения путаницы, 120 | поскольку у термина \term{структура данных}{data structure} есть по 121 | крайней мере четыре различных связанных между собой значения. 122 | 123 | \begin{itemize} 124 | \item \emph{Абстрактный тип данных} (то есть, 125 | \emph{тип и набор функций над этим типом}). Для этого значения мы 126 | будем пользоваться словом \term{абстракция}{abstraction}. 127 | \item \emph{Конкретная реализация абстрактного типа данных}. Для этого 128 | значения мы используем слово 129 | \term{реализация}{implementation}. Однако от реализации мы не требуем 130 | воплощения в коде~--- достаточно детального проекта. 131 | \item \emph{Экземпляр типа данных, например, конкретный список или 132 | дерево}. Для такого экземпляра мы будем использовать слово 133 | \term{объект}{object} или \term{версия}{version}. Впрочем, 134 | для конкретных типов часто бывает свой термин. Например, стеки и 135 | очереди мы будем называть просто стеками и очередями. 136 | \item \emph{Сущность, сохраняющая свою идентичность при 137 | изменениях}. Например, в интерпретаторе, построенном на основе 138 | стека, мы часто говорим о <<стеке>>, как если бы это был один 139 | объект, а не различные версии в различные моменты времени. Для этого 140 | значения мы будем использовать выражение \term{устойчивая 141 | сущность}{persistent identity}. Нужда в этом возникает прежде 142 | всего при разговоре об устойчивых структурах данных; когда мы 143 | говорим о различных версиях одной и той же структуры, мы имеем в 144 | виду, что они все имеют одну и ту же устойчивую сущность. 145 | \end{itemize} 146 | Грубо говоря, абстракциям в Стандартном ML соответствуют сигнатуры, 147 | реализациям~--- структуры или функторы, а объектам или версиям~--- 148 | значения. Хорошего аналога понятию устойчивой сущности в Стандартном 149 | ML нет.\footnote{% 150 | Устойчивая сущность эфемерной структуры данных может быть 151 | реализована как ссылочная ячейка, но для моделирования устойчивой 152 | сущности устойчивой структуры данных такого подхода недостаточно. 153 | } 154 | 155 | Термин \term{операция}{operation} перегружен подобным же образом; он 156 | обозначает и функции, предоставляемые абстрактным типом данных, и 157 | конкретные применения этих функций. Мы пользуемся словом 158 | \emph{операция} только во втором значении, а для первого употребляем 159 | слова \term{функция}{function} или \term{оператор}{operator}. 160 | 161 | \section{Наш подход} 162 | 163 | Вместо того, чтобы каталогизировать структуры данных, подходящие для каждой 164 | возможной задачи (безнадежное предприятие!), мы сосредоточим внимание на нескольких 165 | общих методиках проектирования эффективных функциональных структур 166 | данных, и каждую такую методику будем иллюстрировать одной или 167 | несколькими реализациями базовых абстракций, таких, как 168 | последовательность, куча (очередь с приоритетами) или структуры для 169 | поиска. Когда читатель овладел этими методиками, он может с 170 | легкостью их приспособить к собственным нуждам, или даже 171 | спроектировать новые структуры с нуля. 172 | 173 | \section{Обзор книги} 174 | 175 | Книга состоит из трех частей. Первая (Главы~\ref{ch:2} и \ref{ch:3}) 176 | служит введением в функциональные структуры данных. 177 | \begin{itemize} 178 | \item В Главе~\ref{ch:2} обсуждается, как функциональные структуры 179 | данных добиваются устойчивости. 180 | \item Глава~\ref{ch:3} описывает три хорошо известных структуры 181 | данных~--- левоориентированные кучи, 182 | биномиальные кучи и красно-черные деревья,~--- и показывает, как их 183 | можно реализовать на Стандартном ML. 184 | \end{itemize} 185 | Вторая часть (Главы~\ref{ch:4}--\ref{ch:7}) посвящена соотношению 186 | между ленивым вычислением и амортизацией. 187 | \begin{itemize} 188 | \item В Главе~\ref{ch:4} кратко рассматриваются основные понятия 189 | ленивого вычисления и вводится синтаксис, которым мы пользуемся для 190 | описания ленивых вычислений в Стандартном ML. 191 | \item Глава~\ref{ch:5} служит введением в основные методы 192 | амортизации. Объясняется, почему эти методы не работают при 193 | анализе устойчивых структур данных. 194 | \item Глава~\ref{ch:6} описывает связующую роль, которую ленивое 195 | вычисление играет при сочетании амортизации и устойчивости, и дает 196 | два метода анализа амортизированной стоимости структур данных, 197 | реализованных через ленивое вычисление. 198 | \item В Главе~\ref{ch:7} демонстрируется, какую выразительную мощь дает 199 | сочетание энергичного и ленивого вычисления в одном языке. 200 | Мы показываем, как во многих случаях можно получить структуру данных 201 | с жёсткими характеристиками производительности из структуры с 202 | амортизированными характеристиками, если систематически запускать 203 | преждевременное вычисление ленивых компонент структуры. 204 | \end{itemize} 205 | В третьей части книги (Главы~\ref{ch:8}--\ref{ch:11}) исследуется 206 | несколько общих методик построения функциональных структур данных. 207 | \begin{itemize} 208 | \item В Главе~\ref{ch:8} описывается \term{ленивая перестройка}{lazy 209 | rebuilding}, вариант идеи \term{глобальной перестройки}{global 210 | rebuilding} \cite{Overmars1983}. Ленивая перестройка значительно 211 | проще глобальной, но в результате получаются структуры с 212 | амортизированными, а не с жёсткими характеристиками. Сочетание 213 | ленивой перестройки с методиками планирования из Главы~\ref{ch:7} 214 | часто позволяет восстановить жёсткие характеристики. 215 | \item В Главе~\ref{ch:9} исследуются \term{числовые 216 | представления}{numerical representations}~--- представления 217 | данных, построенные по аналогии с представлениями чисел (как 218 | правило, двоичных чисел). В этой модели нахождение эффективных 219 | процедур вставки и изъятия соответствует выбору таких вариантов 220 | двоичных чисел, где добавление или вычитание единицы занимает 221 | константное время. 222 | \item Глава~\ref{ch:10} рассматривает \term{развёртку структур 223 | данных}{data-structural bootstrapping} \cite{Buchsbaum1993}. Эта 224 | методика существует в трех вариантах: \term{структурная 225 | декомпозиция}{structural decomposition}, когда решения без 226 | ограничений строятся на основе ограниченных решений, 227 | \term{структурная абстракция}{structural abstraction}, когда 228 | эффективные решения строятся на основе неэффективных, и 229 | \term{развёртка до составных типов}{bootstrapping to aggregate 230 | types}, когда реализации с атомарными элементами развёртываются до 231 | реализаций с составными элементами. 232 | \item В Главе~\ref{ch:11} описывается \term{неявное рекурсивное 233 | замедление}{implicit recursive slowdown}, ленивый вариант метода 234 | \term{рекурсивного замедления}{recursive slowdown} Каплана и 235 | Тарьяна \cite{KaplanTarjan1995}. Подобно ленивой перестройке, 236 | неявное рекурсивное замедление значительно проще обычного 237 | рекурсивного замедления, но вместо жёстких характеристик дает лишь 238 | амортизированные. Как и в случае ленивой перестройки, часто жёсткие 239 | характеристики можно восстановить с помощью расписаний. 240 | \end{itemize} 241 | 242 | Наконец, Приложение~\ref{app:A} включает в себя перевод большинства 243 | программных реализаций этой книги на Haskell. 244 | 245 | %%% Local Variables: 246 | %%% mode: latex 247 | %%% TeX-master: "pfds" 248 | %%% End: 249 | -------------------------------------------------------------------------------- /chapter02.tex: -------------------------------------------------------------------------------- 1 | \chapter{Устойчивость} 2 | \label{ch:2} 3 | 4 | Отличительной особенностью функциональных структур данных является то, 5 | что они всегда \term{устойчивы}{persistent}~--- обновление 6 | функциональной структуры не уничтожает старую версию, а создает 7 | новую, которая с ней сосуществует. Устойчивость достигается путем 8 | \emph{копирования} затронутых узлов структуры данных, и все изменения 9 | проводятся на копии, а не на оригинале. Поскольку узлы никогда 10 | напрямую не модифицируются, все незатронутые узлы могут 11 | \term{совместно использоваться}{be shared} между старой и новой версией структуры 12 | данных без опасения, что изменения одной версии непроизвольно окажутся 13 | видны другой. 14 | 15 | В этой главе мы исследуем подробности копирования и совместного использования для 16 | двух простых структур данных: списков и двоичных деревьев. 17 | 18 | \section{Списки} 19 | \label{sc:2.1} 20 | 21 | Мы начинаем с простых связанных списков, часто встречающихся в 22 | императивном программировании и вездесущих в функциональном. Основные 23 | функции, поддерживаемые списками, в сущности те же, что и для 24 | абстракции стека, описанной в виде сигнатуры на Стандартном ML на 25 | Рис.~\ref{fig:2.1}. Списки и стеки можно тривиально реализовать либо 26 | с помощью встроенного типа <<список>> (Рис.~\ref{fig:2.2}), либо как 27 | отдельный тип (Рис.~\ref{fig:2.3}). 28 | 29 | \begin{remark} 30 | Сигнатура на Рис.~\ref{fig:2.1} использует терминологию списков 31 | (\texttt{cons}, \texttt{head}, \texttt{tail}), а не стеков 32 | (\texttt{push}, \texttt{top}, \texttt{pop}), потому что мы 33 | рассматриваем списки как частный случай общего класса 34 | последовательностей. Другими примерами этого класса являются 35 | \emph{очереди}, \emph{двусторонние очереди} и \emph{списки с 36 | конкатенацией}. Для функций во всех этих абстракциях мы используем 37 | одинаковые соглашения по именованию, чтобы можно было заменять одну 38 | реализацию другой с минимальными трудностями. 39 | \end{remark} 40 | 41 | \begin{figure} 42 | \begin{lstlisting} 43 | signature Stack = sig 44 | type $\alpha$ Stack 45 | val empty : $\alpha$ Stack 46 | val isEmpty : $\alpha$ Stack $\rightarrow$ bool 47 | val cons : $\alpha \times \alpha$ Stack $\rightarrow$ $\alpha$ Stack 48 | val head : $\alpha$ Stack $\rightarrow \alpha$ /*$\mbox{ возбуждает EMPTY для пустого стека }$*/ 49 | val tail : $\alpha$ Stack $\rightarrow \alpha$ Stack /*$\mbox{ возбуждает EMPTY для пустого стека }$*/ 50 | end 51 | \end{lstlisting} 52 | % TODO: Эта херня пихает курсив, когда у меня камлёвые комментарии. Разобраться. 53 | % \centering 54 | 55 | \caption{Сигнатура для стеков.} \label{fig:2.1} 56 | \end{figure} 57 | 58 | 59 | \begin{figure} 60 | \begin{lstlisting} 61 | structure List : Stack = structure 62 | type $\alpha$ Stack = $\alpha$ list 63 | val empty = [] 64 | fun isEmpty s = null s 65 | fun cons (x,s) = x::s 66 | fun head s = hd s 67 | fun tail s = tl s 68 | end 69 | \end{lstlisting} 70 | \caption{Реализация стека с помощью встроенного типа списков.}\label{fig:2.2} 71 | \end{figure} 72 | 73 | \begin{figure} 74 | \begin{lstlisting} 75 | structure CustomStack: Stack = structure 76 | datatype $\alpha$ Stack = Nil | Cons of $\alpha \times \alpha$ Stack 77 | val empty = Nil 78 | fun isEmpty Nil = true | isEmpty _ = false 79 | fun cons (x,s) = Cons (x, s) 80 | fun head Nil = raise EMPTY 81 | | head (Cons(x,s)) = x 82 | 83 | fun tail Nil = raise EMPTY 84 | | tail (Cons(x,s)) = s 85 | end 86 | \end{lstlisting} 87 | \caption{Реализация стека в виде отдельного типа.} 88 | \label{fig:2.3} 89 | \end{figure} 90 | 91 | К этой сигнатуре мы могли бы добавить ещё одну часто встречающуюся 92 | операцию на списках: $\concat$, которая конкатенирует (т. е., 93 | соединяет) два списка. В императивной среде эту функцию нетрудно 94 | поддержать за время $O(1)$, если сохранять указатели и на первый, и на 95 | последний элемент списка. Тогда $\concat$ просто изменяет последнюю 96 | ячейку первого списка так, чтобы она указывала на первую ячейку 97 | второго списка. Результат этой операции графически показан на 98 | Рис.~\ref{fig:2.4}. Обратите внимание, что эта операция 99 | \emph{уничтожает} оба своих аргумента~--- после выполнения 100 | \lstinline!xs $\concat$ ys! ни \lstinline!xs!, ни \lstinline!ys! использовать 101 | больше нельзя. 102 | 103 | \begin{figure}[h] 104 | \centering 105 | \input{figures/fig.2.4.before.tex}\par 106 | (до)\par 107 | \vspace{0.5cm} 108 | \input{figures/fig.2.4.after.tex}\par 109 | (после)\par 110 | \vspace{0.5cm} 111 | \caption{Выполнение \lstinline!xs $\concat$ ys! в императивной среде. Эта операция уничтожает списки-аргументы \lstinline!xs! и \lstinline!ys!.} 112 | \label{fig:2.4} 113 | \end{figure} 114 | 115 | В функциональном окружении мы не можем деструктивно модифицировать 116 | последнюю ячейку первого списка. Вместо этого мы копируем эту ячейку и 117 | модифицируем хвостовой указатель в ячейке-копии. Затем мы копируем 118 | предпоследнюю ячейку и модифицируем ее хвостовой указатель, указывая 119 | на копию последней ячейки. Такое копирование продолжается, пока 120 | не окажется скопирован весь список. Процесс в общем виде можно 121 | реализовать как 122 | \begin{lstlisting} 123 | fun xs$\concat$ys = if isEmpty xs then ys else cons (head xs, tail xs$\concat$ys) 124 | \end{lstlisting} 125 | Если у нас есть доступ к реализации нашей структуры (например, в виде 126 | встроенных списков Стандартного ML), мы можем переписать эту функцию 127 | через сопоставление с образцом: 128 | \begin{lstlisting} 129 | fun []$\concat$ys = ys 130 | | (x :: xs)$\concat$ys = x :: (xs$\concat$ys) 131 | \end{lstlisting} 132 | На Рис.~\ref{fig:2.5} изображен результат конкатенации двух 133 | списков. Обратите внимание, что после выполнения операции мы можем 134 | продолжать использовать два исходных списка, \lstinline!xs! и 135 | \lstinline!ys!. Таким образом, мы добиваемся устойчивости, но за счет 136 | копирования ценой $O(n)$.\footnote{% 137 | В Главах~\ref{ch:10} и \ref{ch:11} мы увидим, как можно поддержать 138 | $\concat$ за время $O(1)$ без потери устойчивости. 139 | } 140 | 141 | \begin{figure}[h] 142 | \centering 143 | \input{figures/fig.2.5.before.tex}\par 144 | (до)\par 145 | \vspace{0.5cm} 146 | \input{figures/fig.2.5.after.tex}\par 147 | (после)\par 148 | \vspace{0.5cm} 149 | \caption{Выполнение \lstinline!zs = xs$\concat$ys! в функциональной среде. Заметим, что списки-аргументы \lstinline!xs! и \lstinline!ys! не затронуты операцией.} 150 | \label{fig:2.5} 151 | \end{figure} 152 | 153 | Несмотря на большой объем копирования, заметим, что второй список, \lstinline!ys!, нам 154 | копировать не пришлось. Эти узлы теперь общие между 155 | \lstinline!ys! и \lstinline!zs!. Ещё одна функция, иллюстрирующая 156 | парные понятия копирования и общности подструктур~--- 157 | \lstinline!update!, изменяющая значение узла списка по данному 158 | индексу. Эту функцию можно реализовать как 159 | \begin{lstlisting} 160 | fun update ([], i, y) = raise Subscript 161 | | update (x::xs, 0, y) = y::xs 162 | | update (x::xs, i, y) = x::update(xs, i-1, y) 163 | \end{lstlisting} 164 | Здесь мы не копируем весь список-аргумент. Копировать приходится 165 | только сам узел, подлежащий модификации (узел $i$) и узлы, 166 | содержащие прямые или косвенные указатели на $i$. Другими словами, 167 | чтобы изменить один узел, мы копируем все узлы на пути от корня 168 | к изменяемому. Все узлы, не находящиеся на этом пути, используются как 169 | исходной, так и обновленной версиями. На Рис.~\ref{fig:2.6} показан 170 | результат изменения третьего узла в пятиэлементном списке: первые 171 | три узла копируются, а последние два используются совместно. 172 | 173 | \begin{figure}[h] 174 | \centering 175 | \input{figures/fig.2.6.before.tex}\par 176 | (до)\par 177 | \vspace{0.5cm} 178 | \input{figures/fig.2.6.after.tex}\par 179 | (после)\par 180 | \vspace{0.5cm} 181 | 182 | \caption{Выполнение \lstinline!ys = update(xs, 2, 7)!. Обратите 183 | внимание на совместное использование структуры списками \lstinline!xs! и \lstinline!ys!.} 184 | \label{fig:2.6} 185 | \end{figure} 186 | 187 | \begin{remark} 188 | Такой стиль программирования очень сильно упрощается при наличии 189 | автоматической сборки мусора. Очень важно освободить память от тех 190 | копий, которые больше не нужны, но многочисленные совместно используемые 191 | узлы делают ручную сборку мусора нетривиальной задачей. 192 | \end{remark} 193 | 194 | \begin{exercise}\label{ex:2.1} 195 | Напишите функцию \lstinline!suffixes! типа 196 | \lstinline!$\alpha$ list $\to$ $\alpha$ list list!, которая принимает как 197 | аргумент список \lstinline!xs! и возвращает список всех его 198 | суффиксов в убывающем порядке длины. Например, 199 | \begin{lstlisting} 200 | suffixes [1,2,3,4] = [[1,2,3,4],[2,3,4],[3,4],[4],[]] 201 | \end{lstlisting} 202 | Покажите, что список суффиксов можно породить за время $O(n)$ и 203 | занять при этом $O(n)$ памяти. 204 | \end{exercise} 205 | 206 | \section{Двоичные деревья поиска} 207 | \label{sc:2.2} 208 | 209 | Если узел структуры содержит более одного указателя, оказываются 210 | возможны более сложные сценарии совместного использования памяти. Хорошим примером 211 | совместного использования такого вида служат двоичные деревья поиска. 212 | 213 | Двоичные деревья поиска~--- это двоичные деревья, в которых элементы 214 | хранятся во внутренних узлах в \term{симметричном}{symmetric} 215 | порядке, то есть, элемент в каждом узле больше любого элемента в 216 | левом поддереве этого узла и меньше любого элемента в правом 217 | поддереве. В Стандартном ML мы представляем двоичные деревья поиска 218 | при помощи следующего типа: 219 | \begin{lstlisting} 220 | datatype Tree = E | T of Tree $\times$ Elem $\times$ Tree 221 | \end{lstlisting} 222 | где \lstinline!Elem!~--- какой-либо фиксированный полностью упорядоченный 223 | тип элементов. 224 | 225 | \begin{remark} 226 | Двоичные деревья поиска не являются полиморфными относительно типа 227 | элементов, поскольку в качестве элементов не может выступать любой 228 | тип~--- подходят только типы, снабженные отношением полного 229 | порядка. Однако это не значит, что для каждого типа элементов мы 230 | должны заново реализовывать деревья двоичного поиска. Вместо этого 231 | мы делаем тип элементов и прилагающиеся к нему функции сравнения 232 | параметрами \term{функтора}{functor}, реализующего двоичные деревья 233 | поиска (см. Рис.~\ref{fig:2.9}). 234 | \end{remark} 235 | 236 | Мы используем это представление для реализации множеств. Однако оно 237 | легко адаптируется и для других абстракций (например, конечных 238 | отображений) или поддержки более изысканных функций (скажем, найти 239 | $i$-й по порядку элемент), если добавить в конструктор \lstinline!T! 240 | дополнительные поля. 241 | 242 | На Рис.~\ref{fig:2.7} показана минимальная сигнатура для множеств. Она 243 | содержит значение <<пустое множество>>, а также функции добавления 244 | нового элемента и проверки на членство. В более практической 245 | реализации, вероятно, будут присутствовать и многие другие функции, 246 | например, для удаления элемента или перечисления всех элементов. 247 | 248 | \begin{figure} 249 | \begin{lstlisting} 250 | signature SET = sig 251 | type Elem 252 | type Set 253 | val empty : Set 254 | val insert : Elem $\times$ Set -> Set 255 | val member : Elem $\times$ Set -> bool 256 | end 257 | \end{lstlisting} 258 | \caption{Сигнатура для множеств.} 259 | \label{fig:2.7} 260 | \end{figure} 261 | 262 | Функция \lstinline!member! ищет в дереве, сравнивая запрошенный 263 | элемент с находящимся в корне дерева. Если запрошенный элемент меньше 264 | корневого, мы рекурсивно ищем в левом поддереве. Если он больше, 265 | рекурсивно ищем в правом поддереве. Наконец, в оставшемся случае 266 | запрошенный элемент равен корневому, и мы возвращаем значение 267 | <<истина>>. Если мы когда-либо натыкаемся на пустое дерево, значит, 268 | запрашиваемый элемент не является членом множества, и мы возвращаем 269 | значение <<ложь>>. Эта стратегия реализуется так: 270 | \begin{lstlisting} 271 | fun member(x,E) = false 272 | | member(x,T(a,y,b)) = 273 | if x < y then member(x,a) 274 | else if x > y then member(x,b) 275 | else true 276 | \end{lstlisting} 277 | \begin{remark} 278 | Простоты ради, мы предполагаем, что функции сравнения называются $<$ 279 | и $>$. Однако если эти функции передаются в качестве параметров 280 | функтора, как на Рис.~\ref{fig:2.9}, часто оказывается удобнее 281 | называть их именами вроде \lstinline!lt! или \lstinline!leq!, а 282 | символы $<$ и $>$ оставить для сравнения целых и других элементарных 283 | типов. 284 | \end{remark} 285 | 286 | Функция \lstinline!insert! проводит поиск в дереве по той же стратегии, 287 | что и \lstinline!member!, но только по пути она копирует каждый 288 | элемент. Когда наконец оказывается достигнут пустой узел, он 289 | заменяется на узел, содержащий новый элемент. 290 | \begin{lstlisting} 291 | fun insert(x,E) = T(E,x,E) 292 | | insert(x, s as T(a,y,b)) = 293 | if x < y then T(insert(x,a),y,b) 294 | else if x > y then T(a,y,insert(x,b)) 295 | else s 296 | \end{lstlisting} 297 | На Рис.~\ref{fig:2.8} показана типичная вставка. Каждый скопированный 298 | узел использует одно из поддеревьев совместно с исходным деревом; речь о том поддереве, 299 | которое не оказалось на пути поиска. Для большинства деревьев путь 300 | поиска содержит лишь небольшую долю узлов в дереве. Громадное 301 | большинство узлов находятся в совместно используемых поддеревьях. 302 | 303 | \begin{figure}[h] 304 | \centering 305 | \input{figures/fig.2.8.before.tex}\par 306 | (до)\par 307 | \vspace{0.5cm} 308 | \input{figures/fig.2.8.after.tex}\par 309 | (после)\par 310 | \vspace{0.5cm} 311 | \caption{Выполнение \lstinline!ys = insert("e", xs)!. Как и прежде, 312 | обратите внимание на совместное использвание структуры деревьями \lstinline!xs! и \lstinline!ys!.} 313 | \label{fig:2.8} 314 | \end{figure} 315 | 316 | На Рис.~\ref{fig:2.9} показано, как двоичные деревья поиска можно 317 | реализовать в виде функтора на Стандартном ML. Функтор принимает тип 318 | элементов и связанные с ним функции сравнения как параметры. Поскольку 319 | часто те же самые параметры будут использоваться и другими функторами 320 | (см., например, Упражнение~\ref{ex:2.6}), мы упаковываем их в 321 | структуру с сигнатурой \lstinline!Ordered!. 322 | 323 | \begin{figure} 324 | \begin{lstlisting} 325 | signature ORDERED = sig 326 | /*$\mbox{ Полностью упорядоченный тип и его функции сравнения }$*/ 327 | type T 328 | val eq : T $\times$ T -> bool 329 | val lt : T $\times$ T -> bool 330 | val leq : T $\times$ T -> bool 331 | end 332 | 333 | functor UnbalancedSet(Element: ORDERED): SET = struct 334 | type Elem = Element.T 335 | datatype Tree = E | T of Tree $\times$ Elem $\times$ Tree 336 | type Set = Tree 337 | 338 | val empty = E 339 | fun member (x,E) = false 340 | | member (x,T(a,y,b)) = 341 | if Element.lt (x,y) then member (x,a) 342 | else Element.lt (y,x) then member (x,b) 343 | else true 344 | 345 | fun insert (x,E) = T(E,x,E) 346 | | insert (x,s as T(a,y,b)) = 347 | if Element.lt (x,y) then T(insert (x,a),y,b) 348 | else Element.lt (y,x) then T(a,y,insert (x,b)) 349 | else s 350 | end 351 | \end{lstlisting} 352 | \caption{Реализация двоичных деревьев поиска в виде функтора на Стандартном ML.} 353 | \label{fig:2.9} 354 | \end{figure} 355 | 356 | \begin{exercise}\textbf{Андерсон \cite{Andersson1991}}\label{ex:2.2} 357 | В худшем случае \lstinline!member! производит $2d$ сравнений, где 358 | $d$~--- глубина дерева. Перепишите ее так, чтобы она делала не более 359 | $d+1$ сравнений, сохраняя элемент, который \emph{может} оказаться 360 | равным запрашиваемому (например, последний элемент, для которого 361 | операция $<$ вернула значение <<истина>> или $\le$~--- <<ложь>>, и 362 | производя проверку на равенство только по достижении дна дерева. 363 | \end{exercise} 364 | 365 | \begin{exercise}\label{ex:2.3} 366 | Вставка уже существующего элемента в двоичное дерево поиска копирует 367 | весь путь поиска, хотя скопированные узлы неотличимы от 368 | исходных. Перепишите \lstinline!insert! так, чтобы она избегала 369 | копирования с помощью исключений. Установите только один обработчик 370 | исключений для всей операции поиска, а не по обработчику на итерацию. 371 | \end{exercise} 372 | 373 | \begin{exercise}\label{ex:2.4} 374 | Совместите улучшения из предыдущих двух упражнений, и получите 375 | версию \lstinline!insert!, которая не делает ненужного копирования и 376 | использует не более $d+1$ сравнений. 377 | \end{exercise} 378 | 379 | \begin{exercise}\label{ex:2.5} 380 | Совместное использование может быть полезно и внутри одного объекта, не 381 | обязательно между двумя различными. Например, если два поддерева 382 | одного дерева идентичны, их можно представить одним и тем же 383 | деревом. 384 | \begin{enumerate} 385 | \item Используя эту идею, напишите функцию \lstinline!complete! типа 386 | \lstinline!Elem $\times$ Int $\to$ Tree!, такую, что 387 | \lstinline!complete(x,d)! создает полное двоичное дерево глубины 388 | \lstinline!d!, где в каждом узле содержится \lstinline!x!. 389 | (Разумеется, такая функция бессмысленна для абстракции множества, 390 | но она может оказаться полезной для какой-либо другой абстракции, 391 | например, мультимножества.) Функция должна работать за время $O(d)$. 392 | \item Расширьте свою функцию, чтобы она строила сбалансированные 393 | деревья произвольного размера. Эти деревья не всегда будут полны, 394 | но они должны быть как можно более сбалансированными: для любого 395 | узла размеры поддеревьев должны различаться не более чем на 396 | единицу. Функция должна работать за время $O(\log n)$. (Подсказка: 397 | воспользуйтесь вспомогательной функцией \lstinline!create2!, 398 | которая, получая размер $m$, создает пару деревьев~--- одно размера 399 | $m$, а другое размера $m+1$) 400 | \end{enumerate} 401 | \end{exercise} 402 | 403 | \begin{exercise}\label{ex:2.6} 404 | Измените функтор \lstinline!UnbalancedSet! так, чтобы он служил 405 | реализацией не множеств, а \term{конечных отображений}{finite maps}. На 406 | Рис.~\ref{fig:2.10} приведена минимальная сигнатура для конечных 407 | отображений. (Заметим, что исключение \lstinline!NotFound! не 408 | является встроенным в Стандартный ML~--- Вам придется его определить 409 | самостоятельно. Это исключение можно было бы сделать частью 410 | сигнатуры \lstinline!FiniteMap!, чтобы каждая реализация 411 | определяла собственное исключение \lstinline!NotFound!, но удобнее, 412 | если все конечные отображения будут использовать одно и то же 413 | исключение.) 414 | \end{exercise} 415 | 416 | \begin{figure} 417 | \begin{lstlisting} 418 | signature FINITEMAP = sig 419 | type Key 420 | type $\alpha$ Map 421 | val empty : $\alpha$ Map 422 | val bind : Key $\times$ $\alpha$ $\times$ $\alpha$ Map $\rightarrow \alpha$ Map 423 | val lookup: Key $\times$ $\alpha$ Map $\rightarrow \alpha$ /*$\mbox{ возбуждает NOTFOUND, если ключ не найден }$*/ 424 | end 425 | \end{lstlisting} 426 | \caption{Сигнатура для конечных отображений.} 427 | \label{fig:2.10} 428 | \end{figure} 429 | 430 | \section{Примечания} 431 | \label{sc:2.3} 432 | 433 | Майерс \cite{Myers1982,Myers1984} использовал копирование и совместное использование 434 | при реализации двоичных деревьев поиска (в его случае это были 435 | AVL-деревья). Для общего метода реализации устойчивых структур данных 436 | путем копирования затронутых узлов 437 | Сарнак и Тарьян \cite{SarnakTarjan1986a} выбрали термин 438 | \term{копирование путей}{path copying}. Существуют также другие методы 439 | реализации устойчивых структур данных, предложенные Дрисколлом, 440 | Сарнаком, Слейтором и Тарьяном \cite{Driscoll-etal1989} и Дитцем 441 | \cite{Dietz1989}, но эти методы не являются чисто функциональными. 442 | 443 | %%% Local Variables: 444 | %%% mode: latex 445 | %%% TeX-master: "pfds" 446 | %%% End: 447 | -------------------------------------------------------------------------------- /chapter03.tex: -------------------------------------------------------------------------------- 1 | \chapter{Некоторые известные структуры данных в функциональном 2 | окружении} 3 | \label{ch:3} 4 | 5 | Хотя реализовать в функциональной среде многие императивные структуры 6 | данных трудно или невозможно, есть и такие, которые реализуются без 7 | особых усилий. В этой главе мы рассматриваем три структуры данных, 8 | которым обычно учат в императивном контексте. Первая из них, 9 | левоориентированные кучи, просто устроена и в том, и в другом 10 | окружении. Однако две других, биномиальные очереди и красно-чёрные 11 | деревья, часто считаются сложными для понимания, поскольку 12 | их императивные реализации быстро превращаются в мешанину манипуляций 13 | с указателями. Напротив, функциональные реализации этих структур 14 | данных абстрагируются от действий с указателями и прямо отражают 15 | высокоуровневые представления. Дополнительное преимущество 16 | функциональной реализации этих структур состоит в том, что мы 17 | бесплатно получаем устойчивость. 18 | 19 | \section{Левоориентированные кучи} 20 | \label{sc:3.1} 21 | 22 | Как правило, множества и конечные отображения поддерживают эффективный 23 | доступ к произвольным элементам. Однако иногда требуется эффективный 24 | доступ только к \emph{минимальному} элементу. Структура данных, 25 | поддерживающая такой режим доступа, называется \term{очередь с 26 | приоритетами}{priority queue} или \term{куча}{heap}. Чтобы избежать 27 | путаницы с очередями FIFO, мы будем использовать второй из этих 28 | терминов. На Рис.~\ref{fig:3.1} приведена простая сигнатура для кучи. 29 | 30 | \begin{figure} 31 | \begin{lstlisting} 32 | signature HEAD = sig 33 | structure Elem: ORDERED 34 | type Heap 35 | 36 | val empty : Heap 37 | val isEmpty : Heap $\rightarrow$ bool 38 | val insert : Elem.T $\times$ Heap $\rightarrow$ Heap 39 | val merge : Heap $\times$ Heap $\rightarrow$ Heap 40 | 41 | val findMin : Heap $\rightarrow$ Elem.T /*$\mbox{ возбуждает EMPTY при пустой куче }$*/ 42 | val deleteMin : Heap $\rightarrow$ Elem.T /*$\mbox{ возбуждает EMPTY при пустой куче }$*/ 43 | end 44 | \end{lstlisting} 45 | % TODO: understand what is wrong with comments 46 | \caption{Сигнатура для кучи (очереди с приоритетами).} \label{fig:3.1} 47 | \end{figure} 48 | 49 | \begin{remark} 50 | Сравнивая сигнатуру кучи с сигнатурой множества 51 | (Рис.~\ref{fig:2.7}), мы видим, что для кучи отношение порядка на 52 | элементах включено в сигнатуру, а для множества нет. Это различие 53 | вытекает из того, что отношение порядка играет важную роль в 54 | семантике кучи, а в семантике множества не играет. С другой 55 | стороны, можно утверждать, что в семантике множества большую роль 56 | играет отношение \emph{равенства}, и оно должно быть включено в 57 | сигнатуру. 58 | \end{remark} 59 | 60 | Часто кучи реализуются через деревья \term{с порядком 61 | кучи}{heap-ordered}, т.~е., в которых элемент при каждой вершине не 62 | больше элементов в поддеревьях. При таком упорядочении минимальный 63 | элемент дерева всегда находится в корне. 64 | 65 | Левоориентированные кучи \cite{Crane1972, Knuth1973a} представляют 66 | собой двоичные деревья с порядком кучи, обладающие свойством 67 | \term{левоориентированности}{leftist property}: ранг любого левого поддерева 68 | не меньше ранга его сестринской правой вершины. Ранг узла 69 | определяется как длина его \term{правой периферии}{right spine} 70 | (т.~е., самого правого пути от данного узла до пустого). Простым 71 | следствием свойства левоориентированности является то, что правая 72 | периферия любого узла~--- кратчайший путь от него к пустому узлу. 73 | 74 | \begin{exercise}\label{ex:3.1} 75 | Докажите, что правая периферия левоориентированной кучи размера $n$ 76 | всегда содержит не более $\lfloor \log(n+1) \rfloor$ элементов. (В 77 | этой книге все логарифмы, если не указано обратного, берутся по 78 | основанию 2.) 79 | \end{exercise} 80 | 81 | Если у нас есть некоторая структура упорядоченных элементов 82 | \lstinline!Elem!, мы можем представить левоориентированные кучи как 83 | двоичные деревья, снабженные информацией о ранге. 84 | \begin{lstlisting} 85 | datatype Heap = E | T of int $\times$ Elem.T $\times$ Heap $\times$ Heap 86 | \end{lstlisting} 87 | Заметим, что элементы правой периферии левоориентированной кучи (да и 88 | любого дерева с порядком кучи) расположены в порядке возрастания. 89 | Главная идея левоориентированной кучи заключается в том, что для 90 | слияния двух куч достаточно слить их правые периферии как 91 | упорядоченные списки, а затем вдоль полученного пути обменивать 92 | местами поддеревья при вершинах, чтобы восстановить свойство 93 | левоориентированности. Это можно реализовать следующим образом: 94 | \begin{lstlisting} 95 | fun merge (h, E) = h 96 | | merge (E, h) = h 97 | | merge (h$_1$ as T(_, x, a$_1$, b$_1$), h$_2$ as T(_, y, a$_2$, b$_2$)) = 98 | if Elem.leq (x, y) then makeT (x, a$_1$, merge (b$_1$, h$_2$)) 99 | else makeT (y, a$_2$, merge (h$_1$, b$_2$)) 100 | \end{lstlisting} 101 | где \lstinline!makeT!~--- вспомогательная функция, вычисляющая ранг 102 | вершины \lstinline!T! и, если необходимо, меняющая местами ее 103 | поддеревья. 104 | \begin{lstlisting} 105 | fun rank (E) = 0 106 | | rank (T (r, _, _, _)) = r 107 | fun makeT (x, a, b) = if rank a $\ge$ rank b then T (rank b + 1, x, a, b) 108 | else T (rank a + 1, x, b, a) 109 | \end{lstlisting} 110 | Поскольку длина правой периферии любой вершины в худшем случае 111 | логарифмическая, \lstinline!merge! выполняется за время $O(\log n)$. 112 | 113 | Теперь, когда у нас есть эффективная функция \lstinline!merge!, 114 | оставшиеся функции не представляют труда: \lstinline!insert! создает 115 | одноэлементную кучу и сливает ее с существующей, \lstinline!findMin! 116 | возвращает корневой элемент, а \lstinline!deleteMin! отбрасывает 117 | корневой элемент и сливает его поддеревья. 118 | \begin{lstlisting} 119 | fun insert (x, h) = merge (T (1, x, E, E), h) 120 | fun findMin (T (_, x, a, b)) = x 121 | fun deleteMin (T (_, x, a, b)) = merge (a, b) 122 | \end{lstlisting} 123 | Поскольку \lstinline!merge! выполняется за время $O(\log n)$, столько 124 | же занимают и \lstinline!insert! с \lstinline!deleteMin!. Очевидно, 125 | что \lstinline!findMin! выполняется за $O(1)$. Полная реализация 126 | левоориентированных куч приведена на Рис.~\ref{fig:3.2} в виде 127 | функтора, принимающего в качестве параметра структуру упорядоченных 128 | элементов. 129 | 130 | \begin{remark} 131 | Чтобы не перегружать примеры мелкими деталями, мы обычно в 132 | фрагментах кода пропускаем варианты, ведущие к ошибкам. Например, 133 | приведенные выше фрагменты не показывают поведение 134 | \lstinline!findMin! и \lstinline!deleteMin! на пустых кучах. Когда 135 | дело доходит до полной реализации, как на Рис.~\ref{fig:3.2}, мы 136 | всегда включаем в нее разбор ошибок. 137 | \end{remark} 138 | 139 | \begin{figure} 140 | \begin{lstlisting} 141 | functor LeftistHeap(Element: ORDERED) : Heap = struct 142 | structure Elem = Element 143 | 144 | datatype Heap = E | T of int $\times$ Elem.T $\times$ Heap $\times$ Heap 145 | 146 | fun rank E = 0 147 | | rank (T(r,_,_,_)) = r 148 | fun makeT (x,a,b) = if rank a $\geq$ rank b then T(rank b+1, x,a,b) 149 | else T(rank a + 1, x,b,a) 150 | 151 | val empty = E 152 | fun isEmpty E = true 153 | | isEmpty _ = false 154 | 155 | fun merge (h,E) = h 156 | | merge (E,h) = h 157 | | merge ($h_1$ as T(_,x,$a_1$,$b_1$), h_2 as T(_,y,$a_2$,$b_2$)) = 158 | if Elem.leq (x,y) then makeT(x,$a_1$, merge($b_1$,$h_2$) 159 | else makeT(y,$a_2$,merge($h_1$,$b_2$)) 160 | 161 | fun insert (x,h) = merge (T(1,x,E,E),h) 162 | fun findMin E = raise EMPTY 163 | | findMin (T(_,x,a,b)) = x 164 | fun deleteMin E = raise EMPTY 165 | | deleteMin (T(_,x,a,b)) = merge(a,b) 166 | end 167 | \end{lstlisting} 168 | 169 | \caption{Левоориентированные кучи.} 170 | \label{fig:3.2} 171 | \end{figure} 172 | 173 | \begin{exercise}\label{ex:3.2} 174 | Определите \lstinline!insert! напрямую, а не через обращение к \lstinline!merge!. 175 | \end{exercise} 176 | 177 | \begin{exercise}\label{ex:3.3} 178 | Реализуйте функцию \lstinline!fromList! типа \lstinline!Elem.T list $\to$ Heap!, 179 | порождающую левоориентированную кучу из неупорядоченного списка 180 | элементов путем преобразования каждого элемента в одноэлементную 181 | кучу, а затем слияния получившихся куч, пока не останется 182 | одна. Вместо того, чтобы сливать кучи проходом слева направо или 183 | справа налево при помощи \lstinline!foldr! или \lstinline!foldl!, 184 | слейте кучи за $\lceil \log n \rceil$ проходов, где на каждом 185 | проходе сливаются пары соседних куч. Покажите, что 186 | \lstinline!fromList! требует всего $O(n)$ времени. 187 | \end{exercise} 188 | 189 | \begin{exercise}\label{ex:3.4} 190 | \textbf{(Чо и Сахни \cite{ChoSahni1996})} Левоориентированные кучи 191 | со сдвинутым весом~--- альтернатива левоориентированным кучам, где 192 | вместо свойства левоориентированности используется свойство 193 | \term{левоориентированности, сдвинутой по весу}{weight-biased leftist 194 | property}: размер любого левого поддерева всегда не меньше размера 195 | соответствующего правого поддерева. 196 | \begin{enumerate} 197 | \item Докажите, что правая периферия левоориентированной кучи со 198 | сдвинутым весом содержит не более $\lfloor \log(n+1) \rfloor$ элементов. 199 | \item Измените реализацию на Рис.~\ref{fig:3.2}, чтобы получились 200 | левоориентированные кучи со сдвинутым весом. 201 | \item Функция \lstinline!merge! сейчас выполняется в два прохода: 202 | сверху вниз, с вызовами \lstinline!merge!, и снизу вверх, с 203 | вызовами вспомогательной функции \lstinline!makeT!. Измените 204 | \lstinline!merge! для левоориентированных куч со сдвинутым весом 205 | так, чтобы она работала за один проход сверху вниз. 206 | \item Каковы преимущества однопроходной версии \lstinline!merge! в 207 | условиях ленивого вычисления? В условиях параллельного вычисления? 208 | \end{enumerate} 209 | \end{exercise} 210 | 211 | \section{Биномиальные кучи} 212 | \label{sc:3.2} 213 | 214 | Биномиальные очереди \cite{Vuillemin1978, Brown1978}, которые мы, 215 | чтобы избежать путаницы с очередями FIFO, будем называть \term{ биномиальными 216 | кучами}{binomial heaps}~--- ещё одна распространенная реализация 217 | куч. Биномиальные кучи устроены сложнее, чем левоориентированные, и, на 218 | первый взгляд, не возмещают эту сложность никакими 219 | преимуществами. Однако в последующих главах мы увидим, как в различных 220 | вариантах биномиальных куч можно заставить \lstinline!insert! и 221 | \lstinline!merge! выполняться за время $O(1)$. 222 | 223 | Биномиальные кучи строятся из более простых объектов, называемых 224 | биномиальными деревьями. Биномиальные деревья индуктивно определяются 225 | так: 226 | \begin{itemize} 227 | \item Биномиальное дерево ранга 0 представляет собой одиночный узел. 228 | \item Биномиальное дерево ранга $r+1$ получается путем 229 | \term{связывания}{linking} двух биномиальных деревьев ранга $r$, так 230 | что одно из них становится самым левым потомком второго. 231 | \end{itemize} 232 | Из этого определения видно, что биномиальное дерево ранга $r$ содержит 233 | ровно $2^r$ элементов. Существует второе, эквивалентное первому, 234 | определение биномиальных деревьев, которым иногда удобнее 235 | пользоваться: биномиальное дерево ранга $r$ представляет собой узел 236 | с $r$ потомками $t_1\ldots t_r$, где каждое $t_i$ является 237 | биномиальным деревом ранга $r-i$. На Рис.~\ref{fig:3.3} показаны 238 | биномиальные деревья рангов от 0 до 3. 239 | 240 | \begin{figure}[h] 241 | \centering 242 | \input{figures/fig.3.3.tex} 243 | \caption{Биномиальные деревья рангов 0--3.} 244 | \label{fig:3.3} 245 | \end{figure} 246 | 247 | Мы представляем вершину биномиального дерева в виде элемента и списка 248 | его потомков. Для удобства мы также помечаем каждый узел его рангом. 249 | \begin{lstlisting} 250 | datatype Tree = Node of int $\times$ Elem.T $\times$ Tree list 251 | \end{lstlisting} 252 | Каждый список потомков хранится в убывающем порядке рангов, а элементы 253 | хранятся с порядком кучи. Чтобы сохранять этот порядок, мы всегда 254 | привязываем дерево с большим корнем к дереву с меньшим корнем. 255 | \begin{lstlisting} 256 | fun link (t$_1$ as Node (r, x$_1$, c$_1$), t$_2$ as Node (_, x$_2$, c$_2$)) = 257 | if Elem.leq (x$_1$, x$_2$) then Node (r+1, x$_1$, t$_2$ :: c$_1$) 258 | else Node (r+1, x$_2$, t$_1$ :: c$_2$ 259 | \end{lstlisting} 260 | Связываем мы всегда деревья одного ранга. 261 | 262 | Теперь определяем биномиальную кучу как коллекцию биномиальных 263 | деревьев, каждое из которых имеет порядок кучи, и никакие два дерева 264 | не совпадают по рангу. Мы представляем эту коллекцию в виде списка 265 | деревьев в возрастающем порядке ранга. 266 | \begin{lstlisting} 267 | Type Heap = Tree list 268 | \end{lstlisting} 269 | Поскольку каждое биномиальное дерево содержит $2^r$ элементов, и 270 | никакие два дерева по рангу не совпадают, деревья размера $n$ в 271 | точности соответствуют единицам в двоичном представлении 272 | $n$. Например, число 21 в двоичном виде выглядит как 10101, и поэтому 273 | биномиальная куча размера 21 содержит одно дерево ранга 0, одно ранга 274 | 2, и одно ранга 4 (размерами, соответственно, 1, 4 и 16). Заметим, что 275 | так же, как двоичное представление $n$ содержит не более $\lfloor log 276 | (n+1)\rfloor$ единиц, биномиальная куча размера $n$ содержит не более 277 | $\lfloor log(n+1) \rfloor$ деревьев. 278 | 279 | Теперь мы готовы описать функции, действующие на биномиальных 280 | деревьях. Начинаем мы с \lstinline!insert! и \lstinline!merge!, 281 | которые определяются примерно аналогично сложению двоичных чисел. (Мы 282 | укрепим эту аналогию в Главе~\ref{ch:9}.) Чтобы внести элемент в кучу, 283 | мы сначала создаем одноэлементное дерево (т.~е., биномиальное дерево 284 | ранга 0), затем поднимаемся по списку существующих деревьев в порядке 285 | возрастания рангов, связывая при этом одноранговые деревья. Каждое 286 | связывание соответствует переносу в двоичной арифметике. 287 | \begin{lstlisting} 288 | fun rank (Node (r, x, c)) = r 289 | fun insTree (t,[]) = [t] 290 | | insTree (t, ts as t' :: ts') = 291 | if rank t < rank t' then t :: ts else insTree (link (t, t'), ts') 292 | fun insert (x, ts) = insTree (Node (0, x, []), ts) 293 | \end{lstlisting} 294 | В худшем случае, при вставке в кучу размера $n = 2^k -1$, требуется 295 | $k$ связываний и $O(k) = O(\log n)$ времени. 296 | 297 | При слиянии двух куч мы проходим через оба списка деревьев в порядке 298 | возрастания ранга и связываем по пути деревья равного ранга. Как и 299 | прежде, каждое связывание соответствует переносу в двоичной 300 | арифметике. 301 | \begin{lstlisting} 302 | fun merge (ts$_1$, []) = ts$_1$ 303 | | merge ([], ts$_2$) = ts$_2$ 304 | | merge (ts$_1$ as t$_1$ :: ts'$_1$, ts$_2$ as t$_2$ :: ts'$_2$) = 305 | if rank t$_1$ < rank t$_2$ then t$_1$ :: merge (ts'$_1$, ts$_2$) 306 | else if rank t$_2$ < rank t$_1$ then merge (ts$_1$, ts'$_2$) 307 | else insTree (link (t$_1$, t$_2$), merge (ts'$_1$, ts'$_2$)) 308 | \end{lstlisting} 309 | 310 | Функции \lstinline!findMin! и \lstinline!deleteMin! вызывают 311 | вспомогательную функцию \lstinline!removeMinTree!, которая находит 312 | дерево с минимальным корнем, исключает его из списка и возвращает как 313 | это дерево, так и список оставшихся деревьев. 314 | \begin{lstlisting} 315 | fun removeMinTree [t] = (t, []) 316 | | removeMinTree (t :: ts) = 317 | let val (t', ts') = removeMinTree ts 318 | in if Elem.leq (root t, root t') then (t, ts) else (t', t :: ts') end 319 | \end{lstlisting} 320 | Функция \lstinline!findMin! просто возвращает корень найденного дерева 321 | \begin{lstlisting} 322 | fun findMin ts = let val (t, _) = removeMinTree ts in root t end 323 | \end{lstlisting} 324 | Функция \lstinline!deleteMin! устроена немного похитрее. Отбросив 325 | корень найденного дерева, мы ещё должны вернуть его потомков в список 326 | остальных деревьев. Заметим, что список потомков \emph{почти} уже 327 | соответствует определению биномиальной кучи. Это коллекция 328 | биномиальных деревьев с неповторяющимися рангами, но только 329 | отсортирована она не по возрастанию, а по убыванию ранга. Таким 330 | образом, обратив список потомков, мы преобразуем его в биномиальную 331 | кучу, а затем сливаем с оставшимися деревьями. 332 | \begin{lstlisting} 333 | fun deleteMin ts = let val (Node (_, x, ts$_1$), ts$_2$) = removeMinTree ts 334 | in merge (rev ts$_1$, ts$_2$) end 335 | \end{lstlisting} 336 | Полная реализация биномиальных куч приведена на 337 | Рис.~\ref{fig:3.4}. Все четыре основные операции в худшем случае 338 | требуют $O(\log n)$ времени. 339 | 340 | \begin{figure} 341 | \begin{lstlisting} 342 | functor BinomialHeap(Element: ORDERED) : Heap = struct 343 | structure Elem = Element 344 | datatype Tree = Node of int $\times$ Elem.T $\times$ Tree list 345 | datatype Heap = Tree list 346 | 347 | val empty = [] 348 | val isEmpty ts = null ts 349 | 350 | fun rank (Node(r,x,c)) = r 351 | fun root (Node(r,x,c)) = x 352 | 353 | fun link ($t_1$ as Node ($r_1$,$x_1$,$c_1$), $t_2$ as Node ($r_2$,$x_2$,$c_2$)) = 354 | if Elem.leq ($x_1$,$x_2$) then Node(r+1, $x_1$, $t_2$::$c_1$) 355 | else Node(r+1, $x_2$, $t_1$::$c_2$) 356 | 357 | fun insTree (t,[]) = [t] 358 | | insTree (t, ts as t'::ts') = 359 | if rank t < rank t' then t::ts else insTree(link(t,t'), ts') 360 | 361 | fun insert (x,ts) = insTree(Node(0,x,[]), ts) 362 | 363 | fun merge ($ts_1$, []) $=$ $ts_1$ 364 | | merge ([], $ts_2$) $=$ $ts_2$ 365 | | merge ($ts_1$ as $t_1$::$ts_1'$, $ts_2$ as $t_2$::$ts_2'$) = 366 | if rank $t_1$ < rank $t_2$ then $t_1$ :: merge($ts_1$,$ts_2'$) 367 | else if rank $t_2$ < rank $t_1$ then $t_2$ :: merge($ts_2$, $ts_1'$) 368 | else insTree(link($t_1$,$t_2$), merge($ts_1'$,$ts_2'$) 369 | 370 | fun removeMinTree [] = raise EMPTY 371 | | removeMinTree [t] = (t,[]) 372 | | removeMinTree (t::ts) = 373 | let val ($t'$, $ts'$) $=$ removeMinTree ts 374 | in if Elem.leq (root t, root t') then (t,ts) else (t', t::ts') end 375 | 376 | fun findMin ts = let val (t,_) = removeMinTree ts in root t end 377 | 378 | fun deleteMin ts = 379 | let val (Node(_, x,$ts_1$), $ts_2$) $=$ removeMinTree ts 380 | in merge (rev $ts_1$,$ts_2$) end 381 | end 382 | \end{lstlisting} 383 | \centering 384 | 385 | \caption{Биномиальные кучи.} 386 | \label{fig:3.4} 387 | \end{figure} 388 | 389 | \begin{exercise}\label{ex:3.5} 390 | Определите \lstinline!findMin! напрямую, без обращения к \lstinline!removeMinTree!. 391 | \end{exercise} 392 | 393 | \begin{exercise}\label{ex:3.6} 394 | Большая часть аннотаций ранга в нашем представлении биномиальных куч 395 | излишня, потому что мы и так знаем, что дети узла ранга $r$ имеют 396 | ранги $r-1, \ldots, 0$. Таким образом, можно исключить 397 | поле-аннотацию ранга из узлов, а вместо этого помечать ранг корня 398 | каждого дерева, т.~е., 399 | \begin{lstlisting} 400 | datatype Tree = Node of Elem $\times$ Tree list 401 | type Heap = (int $\times$ Tree) list 402 | \end{lstlisting} 403 | Реализуйте биномиальные кучи в таком представлении. 404 | \end{exercise} 405 | 406 | \begin{exercise}\label{ex:3.7} 407 | Одно из основных преимуществ левоориентированных куч над 408 | биномиальными заключается в том, что \lstinline!findMin! занимает в 409 | них $O(1)$ времени, а не $O(\log n)$. Следующая заготовка функтора 410 | улучшает время \lstinline!findMin! до $O(1)$, сохраняя минимальный 411 | элемент отдельно от остальной кучи. 412 | \begin{lstlisting} 413 | functor ExplicitMin (H : Heap) : Heap = 414 | struct 415 | structure Elem = H.Elem 416 | datatype Heap = E | NE of Elem.t $\times$ H.Heap 417 | ... 418 | end 419 | \end{lstlisting} 420 | Заметим, что этот функтор не ограничен биномиальными кучами, а 421 | принимает любую реализацию куч в качестве параметра. Закончите этот 422 | функтор так, чтобы \lstinline!findMin! требовал время $O(1)$, а 423 | функции \lstinline!insert!, \lstinline!merge! и 424 | \lstinline!deleteMin! каждая по $O(\log n)$. Предполагается, что 425 | нижележащая реализация \lstinline!H! для всех операций занимает 426 | $O(\log n)$. 427 | \end{exercise} 428 | 429 | \section{Красно-чёрные деревья} 430 | \label{sc:3.3} 431 | 432 | В разделе~\ref{sc:2.2} мы описали двоичные деревья поиска. Такие 433 | деревья хорошо ведут себя на случайных или неупорядоченных данных, 434 | однако на упорядоченных данных их производительность резко падает, и 435 | каждая операция может занимать до $O(n)$ времени. Решение этой 436 | проблемы состоит в том, чтобы каждое дерево поддерживать в 437 | приблизительно сбалансированном состоянии. Тогда каждая операция 438 | выполняется не хуже, чем за время $O(\log n)$. Одним из наиболее 439 | популярных семейств сбалансированных двоичных деревьев поиска являются 440 | красно-чёрные \cite{GuibasSedgewick1978}. 441 | 442 | Красно-чёрное дерево представляет собой двоичное дерево поиска, в 443 | котором каждый узел окрашен либо красным, либо чёрным. Мы добавляем 444 | поле цвета в тип двоичных деревьев поиска из раздела~\ref{sc:2.2}. 445 | \begin{lstlisting} 446 | datatype Color = R | B 447 | datatype Tree = E | T of Color $\times$ Tree $\times$ Elem $\times$ Tree 448 | \end{lstlisting} 449 | Все пустые узлы считаются чёрными, поэтому пустой конструктор 450 | \lstinline!E! в поле цвета не нуждается. 451 | 452 | Мы требуем, чтобы всякое красно-чёрное дерево соблюдало два 453 | инварианта: 454 | \begin{itemize} 455 | \item \textbf{Инвариант 1.} У красного узла не может быть красного ребёнка. 456 | \item \textbf{Инвариант 2.} Каждый путь от корня дерева до пустого 457 | узла содержит одинаковое количество чёрных узлов. 458 | \end{itemize} 459 | Вместе эти два инварианта гарантируют, что самый длинный возможный 460 | путь по красно-чёрному дереву, где красные и чёрные узлы чередуются, 461 | не более чем вдвое длиннее самого короткого, состоящего только из 462 | чёрных узлов. 463 | 464 | \begin{exercise}\label{ex:3.8} 465 | Докажите, что максимальная глубина узла в красно-чёрном дереве 466 | размера $n$ не превышает $2 \lfloor \log (n+1) \rfloor$. 467 | \end{exercise} 468 | 469 | Функция \lstinline!member! для красно-чёрных деревьев не обращает 470 | внимания на цвета. За исключением заглушки в варианте для конструктора 471 | \lstinline!T!, она не отличается от функции \lstinline!member! для 472 | несбалансированных деревьев. 473 | \begin{lstlisting} 474 | fun member (x, E) = false 475 | | member (x, T (_, a, y, b) = 476 | if x < y then member (x, a) 477 | else if x > y then member (x, b) 478 | else true 479 | \end{lstlisting} 480 | Функция \lstinline!insert! более интересна, поскольку она должна 481 | поддерживать два инварианта баланса. 482 | \begin{lstlisting} 483 | fun insert (x, s) = 484 | let fun ins E = T (R, E, x, E) 485 | | ins (s as T (color, a, y, b)) = 486 | if x < y then balance (color, ins a, y, b) 487 | else if x > y then balance (color, a, y, ins b) 488 | else s 489 | val T (_, a, y, b) = ins s (* $\mbox{гарантированно непустое}$ *) 490 | in T (B, a, y, b) 491 | \end{lstlisting} 492 | Эта функция содержит три существенных изменения по сравнению с \lstinline!insert! для 493 | несбалансированных деревьев поиска. Во-первых, когда мы создаем новый 494 | узел в ветке \lstinline!ins E!, мы сначала окрашиваем его в красный 495 | цвет. Во-вторых, независимо от цвета, возвращаемого \lstinline!ins!, 496 | в окончательном результате мы корень окрашиваем чёрным. Наконец, в 497 | ветках \lstinline!x < y! и \lstinline!x > y! мы вызовы конструктора 498 | \lstinline!T! заменяем на обращения к функции 499 | \lstinline!balance!. Функция \lstinline!balance! действует подобно 500 | конструктору \lstinline!T!, но только она переупорядочивает свои 501 | аргументы, чтобы обеспечить выполнение инвариантов баланса. 502 | 503 | Если новый узел окрашен красным, мы сохраняем Инвариант 2, но в 504 | случае, если отец нового узла тоже красный, нарушается Инвариант 1. Мы 505 | временно позволяем существовать одному такому нарушению, и переносим 506 | его снизу вверх по мере перебалансирования. Функция 507 | \lstinline!balance! обнаруживает и исправляет красно-красные нарушения, 508 | когда обрабатывает чёрного родителя красного узла с красным 509 | ребёнком. Такая чёрно-красно-красная цепочка может возникнуть в 510 | четырёх различных конфигурациях, в зависимости от того, левым или 511 | правым ребёнком является каждая из красных вершин. Однако в каждом из 512 | этих случаев решение одно и то же: нужно преобразовать 513 | чёрно-красно-красный путь в красную вершину с двумя чёрными детьми, 514 | как показано на Рис.~\ref{fig:3.5}. Это преобразование можно записать 515 | так: 516 | \begin{lstlisting} 517 | fun balance (B,T (R,T (R,a,x,b),y,c),z,d) = T (R, T (B,a,x,b),T (B,c,z,d)) 518 | | balance (B,T (R,a,x,T (R,b,y,c)),z,d) = T (R, T (B,a,x,b),T (B,c,z,d)) 519 | | balance (B,a,x,T (R,T (R,b,y,c),z,d)) = T (R, T (B,a,x,b),T (B,c,z,d)) 520 | | balance (B,a,x,T (R,b,y,T (R,c,z,d))) = T (R, T (B,a,x,b),T (B,c,z,d)) 521 | | balance body = T body 522 | \end{lstlisting} 523 | Нетрудно проверить, что в получающемся поддереве будут соблюдены оба 524 | инварианта красно-чёрного баланса. 525 | 526 | \begin{figure}[h] 527 | \centering 528 | \input{figures/fig.3.5.tex} 529 | \caption{Избавление от красных узлов с красными родителями.} 530 | \label{fig:3.5} 531 | \end{figure} 532 | 533 | \begin{remark} 534 | Заметим, что в первых четырех строках \lstinline!balance! правые 535 | части одинаковы. В некоторых реализациях Стандартного ML, в 536 | частности, в Нью-Джерсийском Стандартном ML (Standard ML of New 537 | Jersey), поддерживается расширение, называемое 538 | \term{или-образцы}{or-patterns}, позволяющее слить несколько 539 | вариантов с одинаковыми правыми сторонами в один 540 | \cite{FahndrichBoyland1997}. С использованием или-образцов можно 541 | переписать функцию \lstinline!balance! так: 542 | \begin{lstlisting} 543 | fun balance ( (B,T (R,T (R,a,x,b),y,c),z,d) 544 | | (B,T (R,a,x,T (R,b,y,c)),z,d) 545 | | (B,a,x,T (R,T (R,b,y,c),z,d)) 546 | | (B,a,x,T (R,b,y,T (R,c,z,d))) ) = T (R, T (B,a,x,b),T (B,c,z,d)) 547 | | balance body = T body 548 | \end{lstlisting} 549 | \end{remark} 550 | 551 | После балансировки некоторого поддерева красный корень этого поддерева 552 | может оказаться ребёнком ещё одного красного узла. Таким образом, 553 | балансировка продолжается до самого корня дерева. На самом верху 554 | дерева мы можем получить красную вершину с красным ребёнком, но без 555 | чёрного родителя. С этим вариантом мы справляемся, всегда перекрашивая корень 556 | в чёрное. 557 | 558 | Реализация красно-чёрных деревьев полностью приведена на Рис.~\ref{fig:3.6}. 559 | 560 | \begin{figure} 561 | \begin{lstlisting} 562 | functor RedBlackSet(Element: ORDERED) : SET = 563 | type Elem = Element.T 564 | datatype Color = R | B 565 | datatype Tree = E | T of Color $\times$ Tree $\times$ Elem $\times$ Tree 566 | type Set = Tree 567 | 568 | val empty = E 569 | fun member (x, E) = false 570 | | member (x, T (_, a, y, b) = 571 | if Element.lt (x,y) then member (x, a) 572 | else if Element.lt (y,x) then member (x, b) 573 | else true 574 | 575 | fun balance (B,T (R,T (R,a,x,b),y,c),z,d) = T (R, T (B,a,x,b),y,T (B,c,z,d)) 576 | | balance (B,T (R,a,x,T (R,b,y,c)),z,d) = T (R, T (B,a,x,b),y,T (B,c,z,d)) 577 | | balance (B,a,x,T (R,T (R,b,y,c),z,d)) = T (R, T (B,a,x,b),y,T (B,c,z,d)) 578 | | balance (B,a,x,T (R,b,y,T (R,c,z,d))) = T (R, T (B,a,x,b),y,T (B,c,z,d)) 579 | | balance body = T body 580 | 581 | fun insert (x, s) = 582 | let fun ins E = T (R, E, x, E) 583 | | ins (s as T (color, a, y, b)) = 584 | if Element.lt (x,y) then balance (color, ins a, y, b) 585 | else if Element.lt (y,x) then balance (color, a, y, ins b) 586 | else s 587 | val T (_, a, y, b) = ins s /* $\mbox{гарантированно непустое}$ */ 588 | in T (B, a, y, b) 589 | end 590 | \end{lstlisting} 591 | % TODO: опять курсив там где комменты 592 | 593 | \caption{Красно-чёрные деревья.} 594 | \label{fig:3.6} 595 | \end{figure} 596 | 597 | \begin{hint} 598 | Даже без дополнительных оптимизаций наша реализация сбалансированных 599 | двоичных деревьев поиска~--- одна из самых быстрых среди 600 | имеющихся. С оптимизациями вроде описанных в 601 | Упражнениях~\ref{ex:2.2} и \ref{ex:3.10} она просто летает! 602 | \end{hint} 603 | 604 | \begin{remark} 605 | Одна из причин, почему наша реализация выглядит настолько проще, чем 606 | типичное описание красно-чёрных деревьев (напр., Глава~14 в 607 | книге~\cite{CormenLeisersonRivest1990}), состоит в том, что мы 608 | используем несколько другие преобразования перебалансировки. В 609 | императивных реализациях обычно наши четыре проблематичных случая 610 | разбиваются на восемь, в зависимости от цвета узла, соседствующего с 611 | красной вершиной с красным ребёнком. Знание цвета этого узла в 612 | некоторых случаях позволяет совершить меньше присваиваний, а в 613 | некоторых других завершить балансировку раньше. Однако в 614 | функциональной среде мы в любом случае копируем все эти вершины, и 615 | таким образом, не можем ни сократить число присваиваний, ни 616 | прекратить копирование раньше времени, так что для использования 617 | более сложных преобразований нет причины. 618 | \end{remark} 619 | 620 | \begin{exercise}\label{ex:3.9} 621 | Напишите функцию \lstinline!fromOrdList! типа \lstinline!Elem list $\to$ Tree!, 622 | преобразующую отсортированный список без повторений в красно-чёрное 623 | дерево. Функция должна выполняться за время $O(n)$. 624 | \end{exercise} 625 | 626 | \begin{exercise}\label{ex:3.10} 627 | Приведенная нами функция \lstinline!balance! производит несколько 628 | ненужных проверок. Например, когда функция \lstinline!ins! 629 | рекурсивно вызывается для левого ребёнка, не требуется проверять 630 | красно-красные нарушения на правом ребёнке. 631 | \begin{enumerate} 632 | \item Разбейте \lstinline!balance! на две функции 633 | \lstinline!lbalance! и \lstinline!rbalance!, которые проверяют, 634 | соответственно, нарушения инварианта в левом и правом 635 | ребёнке. Замените обращения к \lstinline!balance! внутри 636 | \lstinline!ins! на вызовы \lstinline!lbalance! либо \lstinline!rbalance!. 637 | \item Ту же самую логику можно распространить ещё на шаг и убрать 638 | одну из проверок для внуков. Перепишите \lstinline!ins! так, чтобы 639 | она никогда не проверяла цвет узлов, не находящихся на пути поиска. 640 | \end{enumerate} 641 | \end{exercise} 642 | 643 | \section{Примечания} 644 | \label{sc:3.4} 645 | 646 | Нуньес, Палао и Пенья \cite{NunezPalaoPena1995} и Кинг \cite{King1994} 647 | описывают подобные нашим реализации, соответственно, 648 | левоориентированных куч и биномиальных куч на Haskell. Красно-чёрные 649 | деревья до сих пор не были описаны в литературе по функциональному 650 | программированию, в отличие от некоторых других вариантов 651 | сбалансированных деревьев поиска, таких как AVL-деревья 652 | \cite{Myers1982, Myers1984, BirdWadler1988, NunezPalaoPena1995}, 653 | 2-3-деревья \cite{Reade1992} и деревья, сбалансированные по весу 654 | \cite{Adams1993}. 655 | 656 | Левоориентированные кучи были изобретены Кнутом \cite{Knuth1973a} как 657 | упрощение структуры данных, введенной Крейном 658 | \cite{Crane1972}. Виллемин \cite{Vuillemin1978} изобрел биномиальные 659 | кучи; Браун \cite{Brown1978} исследовал многие свойства этой изящной 660 | структуры данных. Гибас и Седжвик \cite{GuibasSedgewick1978} 661 | предложили красно-чёрные деревья в качестве обобщающего описания для 662 | многих других разновидностей сбалансированных деревьев. 663 | 664 | %%% Local Variables: 665 | %%% mode: latex 666 | %%% TeX-master: "pfds" 667 | %%% End: 668 | -------------------------------------------------------------------------------- /chapter04.tex: -------------------------------------------------------------------------------- 1 | \chapter{Ленивое вычисление} 2 | \label{ch:4} 3 | 4 | Ленивое вычисление является основной стратегий вычисления во многих 5 | функциональных языках программирования (но не в Стандартном ML). У 6 | этой стратегии есть два существенных свойства. Во-первых, вычисление 7 | всякого выражения задерживается, или \term{подвешивается}{suspend}, 8 | пока не потребуется его результат. Во-вторых, когда задержанное 9 | выражение вычисляется в первый раз, результат вычисления запоминается 10 | (\term{мемоизируется}{memoize}), так что, если он потребуется снова, 11 | можно его просто извлечь из памяти, а не вычислять заново. Оба этих свойства 12 | ленивого вычисления оказываются алгоритмически полезными. 13 | 14 | В этой главе мы вводим удобные обозначения для ленивых вычислений и, в 15 | качестве иллюстрации, строим при помощи этой нотации простую 16 | библиотеку потоков. В последующих главах мы будем активно пользоваться 17 | как ленивыми вычислениями вообще, так и потоками в частности. 18 | 19 | \section{$\$$-запись} 20 | \label{sc:4.1} 21 | 22 | К сожалению, определение Стандартного ML \cite{Milner-etal1997} не 23 | включает поддержки ленивого вычисления, так что каждая реализация 24 | может предоставлять свой собственный набор элементарных операций. 25 | Мы представляем здесь один такой набор, 26 | называемый $\$$-записью. Перевод программ, использующих 27 | $\$$-запись, в другие варианты примитивов ленивого вычисления не 28 | должен представлять трудности. 29 | 30 | В $\$$-записи мы вводим новый тип \lstinline!$\alpha$ susp!, 31 | представляющий задержки (задержанные вычисления). У этого типа имеется один 32 | одноместный конструктор $\$$. В первом приближении 33 | \lstinline!$\alpha$ susp! и $\$$ ведут себя так, как будто они введены при помощи 34 | обыкновенного объявления типа 35 | \begin{lstlisting} 36 | datatype $\alpha$ susp = $\$$ of $\alpha$ 37 | \end{lstlisting} 38 | Новая задержка типа \lstinline!$\tau$ susp! создается 39 | при помощи конструкции \lstinline!$\$e$!, где $e$~--- 40 | выражение типа $\tau$. Подобным же образом, содержимое задержки можно 41 | извлечь через сопоставление с образцом 42 | $\$p$. Если образец $p$ сопоставляется со значениями типа $\tau$, то 43 | $\$p$ сопоставляется с задержками типа 44 | \lstinline!$\tau$ susp!. 45 | 46 | Основное различие между $\$$ и обыкновенными конструкторами состоит в 47 | том, что $\$$ не вычисляет свой аргумент немедленно. Вместо этого он 48 | запоминает информацию, необходимую для того, чтобы вычислить 49 | выражение-аргумент позже. (Как правило, эта информация состоит из 50 | указателя на код, а также значений свободных переменных выражения.) 51 | Выражение-аргумент не вычисляется до тех пор, когда (и если) оно не 52 | сопоставится с образцом вида $\$p$. В этот момент выражение 53 | вычисляется, а его результат запоминается. Затем результат 54 | сопоставляется с образцом $p$. Если задержанное выражение потом 55 | сопоставляется с другим образцом вида $\$p'$, запомненное значение 56 | извлекается и сопоставляется с образцом $p'$. 57 | 58 | Кроме того, конструктор $\$$ отличается от прочих конструкторов 59 | синтаксически. Во-первых, его область действия распространяется 60 | направо как можно дальше. Таким образом, например, выражение 61 | \lstinline!$\$$f x! равнозначно \lstinline!$\$$(f x)!, а не 62 | \lstinline!($\$$f) x!; образец \lstinline!$\$$Cons (x, xs)! обозначает 63 | то же, что \lstinline!$\$$(Cons (x, xs))!, а не 64 | \lstinline!($\$$ Cons) (x, xs)!. Во-вторых, $\$$ не является правильно 65 | построенным выражением сам по себе~--- он всегда должен сочетаться с 66 | аргументом. 67 | 68 | В качестве примера $\$$--записи рассмотрим следующий фрагмент 69 | программы: 70 | \begin{lstlisting} 71 | val s = $\$$primes 1000000 (* $\mbox{быстро}$ *) 72 | ... 73 | val $\$$x = s (* $\mbox{медленно}$ *) 74 | ... 75 | val $\$$y = s (* $\mbox{быстро}$ *) 76 | ... 77 | \end{lstlisting} 78 | Программа вычисляет миллионное простое число. Первая строка, которая 79 | просто создает новую задержку, выполняется очень 80 | быстро. Вторая строка выполняет задержанное вычисление и 81 | находит простое число. В зависимости от алгоритма 82 | поиска простых чисел, она может потребовать значительного 83 | времени. Третья строка обращается к мемоизированному значению и также 84 | выполняется очень быстро. 85 | 86 | В качестве второго примера рассмотрим фрагмент 87 | \begin{lstlisting} 88 | let val s = $\$$primes 1000000 89 | in 15 end 90 | \end{lstlisting} 91 | В этой программе содержимое задержки никогда не 92 | требуется, и, значит, выражение \lstinline!primes 1000000! не 93 | выполняется. 94 | 95 | Хотя все примеры ленивого вычисления в этой книге можно было бы 96 | выразить только через выражения и образцы со знаком $\$$, удобно 97 | оказывается ввести два элемента синтаксического сахара. Первый из них~--- 98 | оператор \lstinline!force! (<<вынудить>>), определяемый как 99 | \begin{lstlisting} 100 | fun force ($\$$x) = x 101 | \end{lstlisting} 102 | Он полезен, чтобы извлечь содержимое задержки посредине 103 | выражения, где было бы неудобно вставлять конструкцию сопоставления с 104 | образцом. 105 | 106 | Второй элемент синтаксического сахара полезен при написании некоторых 107 | разновидностей ленивых функций. Рассмотрим, например, следующую 108 | функцию для сложения задержанных целых: 109 | \begin{lstlisting} 110 | fun plus ($\$$m, $\$$n) = $\$$m+n 111 | \end{lstlisting} 112 | Несмотря на то, что определение функции выглядит совершенно разумно, 113 | скорее всего, это не та функция, которую мы хотели написать. Проблема 114 | состоит в том, что оба ее задержанных аргумента выполняются слишком 115 | рано. Они вынуждаются в момент применения функции 116 | \lstinline!plus!, а не тогда, когда требуется выполнить задержку, 117 | создаваемую ей. Один из способов получить нужное 118 | поведение~--- явным образом задержать сопоставление с образцом 119 | \begin{lstlisting} 120 | fun plus (x, y) = $\$$case (x, y) of ($\$$m, $\$$n) $\Rightarrow$ m+n 121 | \end{lstlisting} 122 | Подобные конструкции встречаются достаточно часто, поэтому мы введём 123 | для них синтаксический сахар 124 | \begin{lstlisting} 125 | fun lazy f p = e 126 | \end{lstlisting} 127 | что равносильно 128 | \begin{lstlisting} 129 | fun f x = $\$$case x of p $\Rightarrow$ force e 130 | \end{lstlisting} 131 | При помощи дополнительного \lstinline!force! мы добиваемся того, что 132 | ключевое слово \lstinline!lazy! никак не влияет на тип функции (если 133 | предположить, что он уже был \lstinline!$\alpha$ susp!), так что эту 134 | аннотацию можно добавлять и убирать, никак не меняя остальной 135 | текст. Теперь требуемую нам функцию для сложения задержанных целых 136 | можно написать просто как 137 | \begin{lstlisting} 138 | fun lazy plus ($\$$m, $\$$n) = $\$$m+n 139 | \end{lstlisting} 140 | Раскрытие синтаксического сахара дает 141 | \begin{lstlisting} 142 | fun plus (x, y) = $\$$case (x, y) of ($\$$m, $\$$n) $\Rightarrow$ force ($\$$m+n) 143 | \end{lstlisting} 144 | что совпадает с ранее вручную написанной версией с 145 | точностью до дополнительных \lstinline!force! и $\$$ вокруг 146 | \lstinline!m+n!. Хороший компилятор уберет эти \lstinline!force! и 147 | $\$$ при оптимизации, поскольку для любого $e$ выражения $e$ и 148 | \lstinline!force ($\$e$)! эквивалентны. 149 | 150 | В функции \lstinline!plus! аннотация \lstinline!lazy! используется для 151 | задержки сопоставления с образцом, чтобы $\$$-образцы не были 152 | сопоставлены раньше времени. Однако аннотация \lstinline!lazy! полезна 153 | также, когда правая сторона определения функции возвращает задержку 154 | в результате вычисления, которое может оказаться долгим и 155 | сложным. В такой ситуации использование \lstinline!lazy! сдвигает 156 | выполнение дорогого вычисления от того момента, когда функция 157 | применяется к аргументу, на тот, когда вынуждается возвращаемая ею 158 | задержка. В следующем разделе мы увидим несколько 159 | примеров такого использования \lstinline!lazy!. 160 | 161 | Синтаксис и семантика $\$$-записи формально определены в 162 | \cite{Okasaki1996a}. 163 | 164 | \section{Потоки} 165 | \label{sc:4.2} 166 | 167 | В качестве расширенного примера ленивых вычислений и $\$$-записи в 168 | Стандартном ML мы представляем простой пакет для работы с 169 | потоками. Потоки будут использоваться в нескольких структурах данных 170 | из последующих глав. 171 | Потоки (известные также как ленивые списки) подобны обыкновенным 172 | спискам, за исключением того, что вычисление каждой их ячейки задерживается. Тип 173 | потоков выглядит так: 174 | \begin{lstlisting} 175 | datatype $\alpha$ StreamCell = Nil | Cons of $\alpha$ $\times$ $\alpha$ Stream 176 | withtype $\alpha$ Stream = $\alpha$ StreamCell susp 177 | \end{lstlisting} 178 | Простой поток, содержащий элементы 1, 2 и 3, можно записать как 179 | \begin{lstlisting} 180 | $\$$Cons (1, $\$$Cons (2, $\$$Cons (3, $\$$Nil))) 181 | \end{lstlisting} 182 | 183 | Полезно сравнить потоки с задержанными списками типа 184 | \lstinline!$\alpha$ list susp!. Вычисления, представленные последними, 185 | по существу {\em монолитны}~--- единожды начав вычислять задержанный 186 | список, мы вычисляем его до конца. Напротив, вычисления, 187 | представленные потоками, часто {\em пошаговы}~--- при обращении к 188 | потоку проводится только та часть вычисления, которая порождает его 189 | первый элемент, а остальное задерживается. Такое поведение часто 190 | встречается в типах, которые, подобно потокам, содержат вложенные 191 | задержки. 192 | 193 | Чтобы яснее прочувствовать эту разницу в поведении, рассмотрим функцию 194 | конкатенации, записываемую \lstinline!s $\concat$ t!. Для задержанных 195 | списков ее можно записать как 196 | \begin{lstlisting} 197 | fun s $\concat$ t = $\$$(force s @ force t) 198 | \end{lstlisting} 199 | что равносильно 200 | \begin{lstlisting} 201 | fun lazy ($\$$xs) $\concat$ ($\$$ys) = $\$$(xs @ ys) 202 | \end{lstlisting} 203 | Задержка, порождаемая этой функцией, вынуждает оба аргумента, а затем 204 | конкатенирует полученные списки и возвращает результат целиком. Таким 205 | образом, задержка монолитна. Можно также сказать, что монолитна вся 206 | функция. Для потоков функция записывается как 207 | \begin{lstlisting} 208 | fun lazy ($\$$Nil) $\concat$ t = t 209 | | ($\$$Cons (x, s)) $\concat$ t = $\$$Cons (x, s $\concat$ t) 210 | \end{lstlisting} 211 | Эта функция немедленно возвращает задержку, которая, будучи запущена, 212 | требует первую ячейку первого потока, сопоставляя ее с 213 | $\$$-образцом. Если эта ячейка представляет собой \lstinline!Cons!, мы 214 | строим результат из \lstinline!x! и \lstinline!s $\concat$ t!. 215 | Вследствие аннотации \lstinline!lazy! рекурсивный вызов просто 216 | порождает ещё одну задержку, не производя никакой дополнительной 217 | работы. Следовательно, эта функция описывает пошаговое вычисление: 218 | порождается первая ячейка результата, а остальное задерживается. Мы 219 | также говорим, что пошаговой является сама функция. 220 | 221 | Ещё одна пошаговая функция~--- \lstinline!take!, извлекающая первые 222 | $n$ элементов потока. 223 | \begin{lstlisting} 224 | fun lazy take (0, s) = $\$$Nil 225 | | take (n, $\$$Nil) = $\$$Nil 226 | | take (n, $\$$Cons (x, s)) = $\$$Cons (x, take (n-1, s)) 227 | \end{lstlisting} 228 | Как и в случае с $\concat$, рекурсивный вызов \lstinline!take! 229 | немедленно возвращает задержку, а не выполняет оставшуюся часть кода 230 | функции. 231 | 232 | Рассмотрим, однако, функцию, уничтожающую первые $n$ элементов потока, 233 | которую можно записать как 234 | \begin{lstlisting} 235 | fun lazy drop (0, s) = s 236 | | drop (n, $\$$Nil) = $\$$Nil 237 | | drop (n, $\$$Cons (x, s)) = drop (n-1, s) 238 | \end{lstlisting} 239 | или, более эффективно, как 240 | \begin{lstlisting} 241 | fun lazy drop (n, s) = let fun drop' (0, s) = s 242 | | drop' (n, $\$$Nil) = $\$$Nil 243 | | drop' (n-1, $\$$Cons (x, s)) = drop' (n-1, s) 244 | in drop' (n, s) end 245 | \end{lstlisting} 246 | Эта функция монолитна, поскольку рекурсивные вызовы \lstinline!drop'! 247 | никогда не задерживаются~--- вычисление первой же ячейки результата 248 | требует выполнения всей функции целиком. Здесь аннотация 249 | \lstinline!lazy! используется, чтобы задержать исходный вызов 250 | \lstinline!drop'!, а не сопоставление с образцом. 251 | 252 | \begin{exercise}\label{ex:4.1} 253 | Покажите, используя эквивалентность \lstinline!force ($\$e$)! и $e$, 254 | что два определения \lstinline!drop! эквивалентны. 255 | \end{exercise} 256 | 257 | Ещё одна часто используемая монолитная функция над потоками~--- 258 | \lstinline!reverse!. 259 | \begin{lstlisting} 260 | fun lazy reverse s = 261 | let fun reverse' ($\$$Nil, r) = r 262 | | reverse' ($\$$Cons (x, s), r) = reverse' (s, $\$$Cons (x, r)) 263 | in reverse' (s, $\$$Nil) end 264 | \end{lstlisting} 265 | Здесь рекурсивные вызовы \lstinline!reverse'! никогда не 266 | задерживаются. Обратите внимание, однако, что каждый такой вызов 267 | создает задержку вида \lstinline!$\$$Cons (x, r)!. Может показаться, 268 | что \lstinline!reverse! на самом деле не производит всю работу за один 269 | раз. Однако задержки такого вида, где тело содержит лишь 270 | несколько конструкторов и переменных, называются 271 | \term{тривиальными}{trivial}. Тривиальные задержки создаются не из 272 | каких-то алгоритмических соображений, а для того, чтобы удовлетворить 273 | систему типов. Можно считать, что тело тривиальной задержки 274 | выполняется в момент ее создания. На самом деле, при минимальной 275 | оптимизации компилятором подобные задержки создаются уже в 276 | мемоизированном виде. В любом случае, вынуждение тривиальной 277 | задержки никогда не занимает больше, чем $O(1)$ времени. 278 | 279 | Несмотря на распространенность монолитных функций над потоками вроде 280 | \lstinline!drop! и \lstinline!reverse!, смыслом существования потоков 281 | являются пошаговые функции вроде $\concat$ и \lstinline!take!. Каждая 282 | задержка несет с собой небольшие, но существенные расходы, поэтому для 283 | максимальной эффективности ленивость следует использовать только тогда, 284 | когда для этого есть серьезные основания. Если все операции над 285 | ленивыми списками в каком-то приложении монолитны, то в этом 286 | приложении лучше пользоваться обыкновенными ленивыми списками, а не 287 | потоками. 288 | 289 | На Рис.~\ref{fig:4.1} потоковые функции собраны в единый модуль на 290 | Стандартном ML. Заметим, что в модуле не экспортируются, как можно 291 | было бы ожидать, функции вроде \lstinline!isEmpty! и 292 | \lstinline!cons!. Вместо этого мы намеренно выставляем для обозрения 293 | внутреннее представление, чтобы поддержать для потоков сопоставление с 294 | образцом. 295 | 296 | \begin{figure} 297 | \centering 298 | \begin{lstlisting} 299 | signature STREAM = sig 300 | datatype $\alpha$ StreamCell $=$ Nil | Cons of $\alpha \times \alpha$ Stream 301 | withtype $\alpha$ Stream $=$ $\alpha$ StreamCell susp 302 | 303 | val $\concat$ : $\alpha$ Stream $\times \alpha$ Stream $\rightarrow \alpha$ Stream /*$\mbox{ Конкатенация потоков }$*/ 304 | val take : int $\times \alpha$ Stream $\rightarrow \alpha$ Stream 305 | val drop : int $\times \alpha$ Stream $\rightarrow \alpha$ Stream 306 | val reverse : $\alpha$ Stream $\rightarrow \alpha$ Stream 307 | end 308 | 309 | structure Stream: STREAM = struct 310 | datatype $\alpha$ StreamCell $=$ Nil | Cons of $\alpha \times \alpha$ Stream 311 | withtype $\alpha$ Stream $=$ $\alpha$ StreamCell susp 312 | 313 | fun lazy ($\$$Nil) ++ t = t 314 | | ($\$$Cons(x,s)) ++ t $= \$$Cons(x,s++t) 315 | 316 | fun lazy take (0,s) $= \$$Nil 317 | | take (n, $\$$Nil) $= \$$Nil 318 | | take (n, $\$$Cons(x,s)) $= \$$Cons(x,take(n-1,s)) 319 | 320 | fun lazy drop (n, s) = 321 | let fun drop' (0, s) = s 322 | | drop' (n, $\$$Nil) $= \$$Nil 323 | | drop' (n-1, $\$$Cons (x, s)) $=$ drop' (n-1, s) 324 | in drop' (n, s) en 325 | 326 | fun lazy reverse s = 327 | let fun reverse' ($\$$Nil, r) $=$ r 328 | | reverse' ($\$$Cons(x, s), r) $=$ reverse' (s, $\$$Cons(x, r)) 329 | in reverse' (s, $\$$Nil) end 330 | end 331 | \end{lstlisting} 332 | \centering 333 | \caption{Небольшой пакет потоков.} 334 | \label{fig:4.1} 335 | \end{figure} 336 | 337 | \begin{exercise}\label{ex:4.2} 338 | Реализуйте сортировку вставками для потоков. Покажите, что 339 | извлечение первых $k$ элементов \lstinline!sort xs! требует лишь 340 | $O (n \cdot k)$ времени, где $n$~--- длина \lstinline!xs!, а не 341 | $O(n^2)$, как можно было бы ожидать от сортировки вставками. 342 | \end{exercise} 343 | 344 | \section{Примечания} 345 | \label{sc:4.3} 346 | 347 | \textbf{Ленивое вычисление.} Ленивое вычисление было изобретено 348 | Уодсвортом \cite{Wadsworth1971} как оптимизация нормального порядка 349 | редукции в лямбда-исчислении. Позже Виллемин \cite{Vuillemin1974} 350 | показал, что при некоторым образом ограниченных условиях ленивое 351 | вычисление является оптимальной стратегией вычисления. Формальная 352 | семантика ленивого вычисления подробно исследовалась в 353 | \cite{Josephs1989, Launchbury1993, OkasakiLeeTarditi1994, Ariola-etal1995}. 354 | 355 | \noindent 356 | \textbf{Потоки.} Потоки изобрел Ландин \cite{Landin1965}, но без 357 | мемоизации. Фридман и Уайз \cite{FriedmanWise1976} и Хендерсон и 358 | Моррис \cite{HendersonMorris1976} расширили потоки Ландина 359 | мемоизацией. 360 | 361 | \noindent 362 | \textbf{Мемоизация.} Термин <<мемоизация>> придумал Мичи 363 | \cite{Michie1968}, чтобы называть так кэширование пар 364 | аргумент-результат у функции. Поле аргумента можно отбросить при мемоизации 365 | задержек, если рассматривать задержки как нульместные функции, то 366 | есть функции с нулем аргументов. Позднее Хьюз \cite{Hughes1985} 367 | применил мемоизацию в исходном смысле Мичи к функциональным 368 | программам. 369 | 370 | \noindent 371 | \textbf{Алгоритмика.} Обе компоненты ленивых вычислений~--- задержка 372 | вычисления и мемоизация результатов,~--- имеют долгую историю в науке 373 | построения алгоритмов, хотя и не всегда в сочетании друг с 374 | другом. Идея задержки вычислений, которые могут оказаться дорогими 375 | (часто это уничтожение элементов) с пользой используется в 376 | хэш-таблицах \cite{vanWykVitter1986}, очередях с приоритетами 377 | \cite{SleatorTarjan1986b, FredmanTarjan1987} и деревьях поиска 378 | \cite{Driscoll-etal1989}. В свою очередь, мемоизация является основой 379 | таких методик, как динамическое программирование \cite{Bellman1957} и 380 | сжатие путей \cite{HopcroftUllman1973, TarjanvanLeeuwen1984}. 381 | 382 | %%% Local Variables: 383 | %%% mode: latex 384 | %%% TeX-master: "pfds" 385 | %%% End: 386 | -------------------------------------------------------------------------------- /chapter11.tex: -------------------------------------------------------------------------------- 1 | \chapter{Неявное рекурсивное замедление} 2 | \label{ch:11} 3 | 4 | В Разделе~\ref{sc:9.2.3} мы видели, что избыточное ленивое 5 | представление двоичных чисел может поддерживать как функцию 6 | увеличения, так и уменьшения за амортизированное время $O(1)$. В 7 | Разделе~\ref{sc:10.1.2} мы видели, что гетерогенные типы и полиморфная 8 | рекурсия позволяют строить чрезвычайно простые реализации числовых 9 | представлений, например, двоичных списков с произвольным доступом. В 10 | этой главе мы сочетаем и расширяем эти идеи, получая в результате 11 | методику, называемую \term{неявное рекурсивное замедление}{implicit 12 | recursive slowdown}. 13 | 14 | Каплан и Тарждан \cite{KaplanTarjan1995, KaplanTarjan1996b, 15 | KaplanTarjan1996a} исследовали родственную методику под названием 16 | \term{рекурсивное замедление}{recursive slowdown}, основанную, в 17 | отличие от нашей, не на ленивых двоичных числах, а на сегментированных 18 | двоичных числах (Раздел~\ref{sc:9.2.4}). Сходства и различия 19 | реализаций, основанных на рекурсивном замедлении и на неявном 20 | рекурсивном замедлении, в сущности, аналогичны сходствам и различиям 21 | между этими двумя системами счисления. 22 | 23 | \section{Очереди и деки} 24 | \label{sc:11.1} 25 | 26 | Напомним устройство двоичных списков с произвольным доступом из 27 | Раздела~\ref{sc:10.1.2}, имеющих тип 28 | \begin{lstlisting} 29 | datatype $\alpha$ RList = 30 | Nil | Zero of ($\alpha$ $\times$ $\alpha$) RList | One of $\alpha$ $\times$ ($\alpha$ $\times$ $\alpha$) RList 31 | \end{lstlisting} 32 | Чтобы упростить дальнейшее обсуждение, давайте заменим этот тип на 33 | \begin{lstlisting} 34 | datatype $\alpha$ Digit = Zero | One of $\alpha$ 35 | datatype $\alpha$ RList = Shallow of $\alpha$ Digit | Deep of $\alpha$ Digit $\times$ ($\alpha$ $\times$ $\alpha$) RList 36 | \end{lstlisting} 37 | Мелкий (\lstinline!Shallow!) список содержит от нуля до одного 38 | элемента. Глубокий (\lstinline!Deep!) список содержит ноль или один 39 | элемент, а также список пар. С этим типом мы можем играть во многие из 40 | игр, освоенных нами при рассмотрении двоичных списков с произвольным 41 | доступом в Главе~\ref{ch:9}. Например, можно поддержать функцию 42 | \lstinline!head! за время $O(1)$, переключившись на безнулевое 43 | представление вроде 44 | \begin{lstlisting} 45 | datatype $\alpha$ Digit = Zero | One of $\alpha$ | Two of $\alpha$ $\times$ $\alpha$ 46 | datatype $\alpha$ RList = Shallow of $\alpha$ Digit | Deep of $\alpha$ Digit $\times$ ($\alpha$ $\times$ $\alpha$) RList 47 | \end{lstlisting} 48 | В этом представлении все цифры в глубоком (\lstinline!Deep!) узле 49 | должны быть единицами или двойками. Конструктор ноль-\lstinline!Zero! 50 | используется только в пустом списке \lstinline!Shallow Zero!. 51 | 52 | Подобным образом, задержав список пар в каждом глубоком узле, мы можем 53 | заставить либо \lstinline!cons!, либо \lstinline!tail! работать за 54 | амортизированное время $O(1)$, а вторую из этих операций за 55 | амортизированное время $O(\log n)$. 56 | \begin{lstlisting} 57 | datatype $\alpha$ RList = 58 | Shallow of $\alpha$ Digit 59 | | Deep of $\alpha$ Digit $\times$ ($\alpha$ $\times$ $\alpha$) RList susp 60 | \end{lstlisting} 61 | Позволив выбирать из трёх ненулевых цифр в каждом глубоком узле, мы 62 | можем заставить все три функции \lstinline!cons!, \lstinline!head! и 63 | \lstinline!tail! работать за время $O(1)$. 64 | \begin{lstlisting} 65 | datatype $\alpha$ Digit = 66 | Zero | One of $\alpha$ | Two of $\alpha$ $\times$ $\alpha$ | Three of $\alpha$ $\times$ $\alpha$ $\times$ $\alpha$ 67 | \end{lstlisting} 68 | Как и прежде, конструктор \lstinline!Zero! используется только в 69 | пустом списке. 70 | 71 | Чтобы расширить эту схему для поддержки очередей и деков, достаточно 72 | добавить вторую цифру в каждый глубокий узел. 73 | \begin{lstlisting} 74 | datatype $\alpha$ Queue = 75 | Shallow of $\alpha$ Digit 76 | | Deep of $\alpha$ Digit $\times$ ($\alpha$ $\times$ $\alpha$) Queue susp $\times$ $\alpha$ Digit 77 | \end{lstlisting} 78 | Первая цифра представляет первые несколько элементов очереди, а 79 | вторая~--- последние несколько элементов. Оставшиеся элементы хранятся 80 | в задержанной очереди пар, которую мы называем \term{срединной 81 | очередью}{middle queue}. 82 | 83 | Выбор типа цифры зависит от того, какие функции мы хотим поддерживать 84 | на каждом конце очереди. В следующей таблице приведены разрешённые 85 | значения для головной цифры очереди, поддерживающей каждое данное 86 | сочетание функций. 87 | $$ 88 | \begin{array}{c|c} 89 | \mbox{поддерживаемые функции} & \mbox{разрешённые цифры} \\ 90 | \hline 91 | \lstinline!cons! & \lstinline!Zero!, \lstinline!One! \\ 92 | \lstinline!cons/head! & \lstinline!One!, \lstinline!Two! \\ 93 | \lstinline!head/tail! & \lstinline!One!, \lstinline!Two! \\ 94 | \lstinline!cons/head/tail! & \lstinline!One!, \lstinline!Two!, \lstinline!Three! \\ 95 | \end{array} 96 | $$ 97 | Те же правила выбора относятся и к хвостовой цифре. 98 | 99 | В качестве конкретного примера давайте разработаем реализацию 100 | очередей, поддерживающую \lstinline!snoc! на хвостовом конце и 101 | \lstinline!head! и \lstinline!tail! на головном (т.~е., обыкновенных 102 | очередей-FIFO). Обратившись к таблице, мы решаем, что головная цифра 103 | глубокого узла может быть единица-\lstinline!One! или 104 | двойка-\lstinline!Two!, а хвостовая цифра может быть 105 | ноль-\lstinline!Zero! или единица-\lstinline!One!. Цифра в мелком узле 106 | может быть \lstinline!Zero! или \lstinline!One!. 107 | 108 | Чтобы добавить к глубокой очереди новый элемент \lstinline!y! через 109 | \lstinline!snoc!, мы смотрим на хвостовую цифру. Если это ноль 110 | (\lstinline!Zero!), мы заменяем хвостовую цифру на 111 | единицу-\lstinline!One y!. Если это \lstinline!One x!, то мы заменяем её на \lstinline!Zero! 112 | и добавляем пару \lstinline!(x, y)! к срединной очереди. Кроме того, 113 | требуется выписать несколько особых случаев для добавления элементов к 114 | мелкой очереди. 115 | \begin{lstlisting} 116 | fun snoc (Shallow Zero, y) = Shallow (One y) 117 | | snoc (Shallow (One x), y) = Deep (Two (x, y), $\$$empty, Zero) 118 | | snoc (Deep (f, m, Zero), y) = Deep (f, m, One y) 119 | | snoc (Deep (f, m, One x), y) = 120 | Deep (f, $\$$snoc (force m, (x, y)), Zero) 121 | \end{lstlisting} 122 | 123 | Чтобы удалить элемент из глубокой очереди через 124 | \lstinline!tail!, мы смотрим на головную цифру. Если это 125 | \lstinline!Two (x, y)!, мы отбрасываем \lstinline!x! и устанавливаем 126 | головную цифру в \lstinline!One y!. Если это \lstinline!One x!, мы 127 | <<занимаем>> в срединной очереди пару \lstinline!(y, z)! и 128 | устанавливаем головную цифру в \lstinline!Two (y, z)!. Опять же, нужно 129 | учесть ещё несколько особых случаев для работы с мелкими очередями. 130 | \begin{lstlisting} 131 | fun tail (Shallow (One x)) = empty 132 | | tail (Deep (Two (x, y), m, r)) = Deep (One y, m, r) 133 | | tail (Deep (One x, $\$$q, r)) = 134 | if isEmpty q then Shallow r 135 | else let val (y, z) = head q 136 | in Deep (Two (y, z), $\$$tail q, r) end 137 | \end{lstlisting} 138 | Заметим, что в последнем варианте \lstinline!tail! мы вынуждаем 139 | срединную очередь. Полный код приведен на Рис.~\ref{fig:11.1}. 140 | 141 | \begin{figure} 142 | \centering 143 | 144 | \caption{Очереди на основе неявного рекурсивного замедления.} 145 | \label{fig:11.1} 146 | \end{figure} 147 | 148 | Теперь мы хотим показать, что \lstinline!snoc! и \lstinline!tail! 149 | работают за амортизированное время $O(1)$. Заметим, что 150 | \lstinline!snoc! никак не обращается к головной цифре, а 151 | \lstinline!tail!~--- к хвостовой цифре. Если мы рассматриваем каждую 152 | из функций по отдельности, то \lstinline!snoc! оказывается аналогичен 153 | функции \lstinline!inc! для ленивых двоичных чисел, а \lstinline!tail! 154 | оказывается аналогичен функции \lstinline!dec! для безнулевых ленивых 155 | двоичных чисел. Модифицируя доказательства для \lstinline!inc! и 156 | \lstinline!dec!, мы легко можем показать, что \lstinline!snoc! и 157 | \lstinline!tail! работают за амортизированное время $O(1)$, если 158 | каждая из них используется отдельно от другой. 159 | 160 | Основная идея неявного рекурсивного замедления состоит в том, что 161 | когда функции вроде \lstinline!snoc! и \lstinline!tail! \emph{почти} 162 | независимы друг от друга, мы можем сочетать их доказательства, просто 163 | сложив долги, используемые в каждом из доказательств. Доказательство 164 | для \lstinline!snoc! использует одну единицу долга, если хвостовая 165 | цифра равна \lstinline!Zero!, и ноль единиц, если хвостовая цифра равна 166 | \lstinline!One!. Доказательство для \lstinline!tail! использует одну 167 | единицу долга, если головная цифра равна \lstinline!Two! и ноль 168 | единиц, если головная цифра равна \lstinline!One!. Нижеследующее 169 | доказательство сочетает эти два понятия долга. 170 | 171 | \begin{theorem}\label{th:11.1} 172 | Функции \lstinline!snoc! и \lstinline!tail! работают за 173 | амортизированное время $O(1)$. 174 | 175 | \noindent 176 | \emph{Доказательство.} Мы анализируем реализацию очередей, используя 177 | метод банкира. Долг присваивается каждой задержке; задержки у нас 178 | всегда находятся в среднем поле какой-либо глубокой очереди. Мы 179 | принимаем инвариант долга, позволяющий каждой задержке иметь размер 180 | долга, зависящий от цифр в головном и хвостовом поле. Среднее поле 181 | глубокой очереди может иметь до $|f| - |r|$ единиц долга, где $|f|$ 182 | равно одному или двум, а $|r|$ равно нулю или одному. 183 | 184 | Нераздельная стоимость каждой из функций равна $O(1)$, так что нам 185 | остаётся показать, что ни одна из функций не высвобождает больше, 186 | чем $O(1)$ единиц долга. Мы приводим только доказательство для 187 | \lstinline!tail!. Доказательство для \lstinline!snoc! немного проще. 188 | 189 | Мы проводим рассуждения методом передачи долга, который 190 | близкородствен методу наследования долга. Каждый раз, когда 191 | вложенная задержка получает больше долга, чем ей разрешено иметь, мы 192 | передаём этот долг объемлющей задержке, которая служит средним полем 193 | предыдущего узла \lstinline!Deep!. Передача долга является 194 | безопасной операцией, поскольку объемлющая задержка всегда 195 | вынуждается раньше вложенной. Передача ответственности за 196 | высвобождение долга от вложенной задержки к объемлющей гарантирует, 197 | что этот долг будет высвобожден прежде, чем будет вынуждена 198 | объемлющая задержка, а следовательно, и раньше, чем может быть вынуждена 199 | внутренняя. 200 | 201 | Мы показываем, что каждый вызов \lstinline!tail! передает одну 202 | единицу долга в объемлющую задержку, кроме самого внешнего вызова, у 203 | которого объемлющей задержки нет. Этот вызов просто высвобождает 204 | лишний долг. 205 | 206 | Каждый каскад вызовов \lstinline!tail! заканчивается на вызове, 207 | заменяющем \lstinline!Two! на 208 | \lstinline!One!. (Для простоты описания, мы сейчас не учитываем 209 | возможность добраться до мелкой очереди.) Это уменьшает разрешённый 210 | размер долга для \lstinline!m! на один, так что мы передаём эту 211 | лишнюю единицу в объемлющую задержку. 212 | 213 | Всякий промежуточный вызов \lstinline!tail! заменяет \lstinline!f! с 214 | единицы-\lstinline!One! на двойку-\lstinline!Two! и вызывает 215 | \lstinline!tail! рекурсивно. Есть два подслучая: 216 | \begin{itemize} 217 | \item \lstinline!r! равно \lstinline!Zero!. Очередь \lstinline!m! имеет одну 218 | единицу долга, и эту единицу требуется высвободить, прежде чем мы можем 219 | вынудить \lstinline!m!. Мы передаём эту единицу в объемлющую 220 | задержку. Кроме того, создаём единицу долга, чтобы покрыть 221 | нераздельную стоимость рекурсивного вызова. Наконец, нашей 222 | задержке передаётся одна единицы долга из рекурсивного вызова. 223 | Поскольку нашей задержке разрешено иметь до двух единиц долга, 224 | баланс оказывается в порядке. 225 | \item \lstinline!r! равно \lstinline!One!. Очередь \lstinline!m! не 226 | имеет долга, так что мы бесплатно можем её вынудить. Создаём одну 227 | единицу долга, чтобы покрыть нераздельную стоимость рекурсивного 228 | вызова. Кроме того, из рекурсивного вызова нам передаётся ещё одна 229 | единица долга. Поскольку разрешённый размер долга для текущей 230 | задержки равен одному, мы одну единицу долга оставляем у себя, а 231 | другую передаём в объемлющую задержку. 232 | \end{itemize} 233 | \end{theorem} 234 | 235 | \begin{exercise}\label{ex:11.1} 236 | Реализуйте для этих очередей функции \lstinline!lookup! и 237 | \lstinline!update!. Эти функции должны работать за амортизированное 238 | время $O(\log i)$. Может быть полезно снабдить каждую очередь 239 | полем, содержащим её размер. 240 | \end{exercise} 241 | 242 | \begin{exercise}\label{ex:11.2} 243 | С помощью методик, описанных в этом разделе, реализуйте двусторонние 244 | очереди. 245 | \end{exercise} 246 | 247 | \section{Двусторонние очереди с конкатенацией} 248 | \label{sc:11.2} 249 | 250 | Наконец, мы реализуем с помощью неявного рекурсивного замедления 251 | двусторонние очереди с конкатенацией, чья сигнатура приведена на 252 | Рис.~\ref{sc:11.2}. Сначала мы описываем относительно простую 253 | реализацию, поддерживающую $\concat$ за амортизированное время $O(\log 254 | n)$, а остальные операции за амортизированное время $O(1)$. Затем мы 255 | строим намного более сложную реализацию, которая улучшает время работы 256 | $\concat$ до $O(1)$. 257 | 258 | \begin{figure} 259 | \centering 260 | 261 | (*\mbox{ Возбуждает }Empty\mbox{, если дек пуст }*)\\ 262 | (*\mbox{ Возбуждает }Empty\mbox{, если дек пуст }*)\\ 263 | (*\mbox{ Возбуждает }Empty\mbox{, если дек пуст }*)\\ 264 | (*\mbox{ Возбуждает }Empty\mbox{, если дек пуст }*)\\ 265 | 266 | \caption{Сигнатура для двусторонних очередей с конкатенацией.} 267 | \label{fig:11.2} 268 | \end{figure} 269 | 270 | Рассмотрим следующую реализацию двусторонних очередей с конкатенацией, 271 | или c-деков. C-дек является либо \term{мелким}{shallow}, либо 272 | \term{глубоким}{deep}. Мелкий c-дек~--- это просто обыкновенный дек, 273 | например, дек по методу банкира из Раздела~\ref{sc:8.4.2}. Глубокий 274 | c-дек состоит из трёх частей: \term{front}{голова}, 275 | \term{середина}{middle} и \term{хвост}{rear}. Голова и хвост являются 276 | обыкновенными деками, содержащими не меньше двух элементов 277 | каждый. Середина является c-деком с обыкновенными деками в качестве 278 | элементов, каждый из которых не короче двух. Мы предполагаем, что есть 279 | реализация \lstinline!D!, реализующая сигнатуру \lstinline!Deque!, и 280 | все её функции работают за время $O(1)$ (амортизированное или 281 | жёсткое). 282 | \begin{lstlisting} 283 | datatype $\alpha$ Cat = 284 | Shallow of $\alpha$ D.Queue 285 | | Deep of $\alpha$ D.Queue $\times$ $\alpha$ D.Deque Cat susp $\times$ $\alpha$ D.Queue 286 | \end{lstlisting} 287 | Заметим, что это определение предполагает полиморфную рекурсию. 288 | 289 | Чтобы добавить элемент к какому-либо концу, мы просто добавляем его в 290 | головной или хвостовой дек. Например, \lstinline!cons! реализован как 291 | \begin{lstlisting} 292 | fun cons (x, Shallow d) = Shallow (D.cons (x, d)) 293 | | cons (x, Deep (f, m, r)) = Deep (D.cons (x, f), m, r) 294 | \end{lstlisting} 295 | Чтобы уничтожить элемент на каком-либо конце, мы уничтожаем элемент из 296 | головного либо хвостового дека. Если при этом длина этого дека падает 297 | ниже двух, мы извлекаем следующий дек из середины и делаем его новой 298 | головой либо хвостом. С добавлением остающегося элемента из старого 299 | дека новый дек содержит по крайней мере три элемента. Например, код 300 | \lstinline!tail! выглядит как 301 | \begin{lstlisting} 302 | fun tail (Shallow d) = Shallow (D.tail d) 303 | | tail (Deep (f, m, r) = 304 | let f' = D.tail f 305 | in 306 | if not (tooSmall f') then Deep (f', m, r) 307 | else if isEmpty (force m) then Shallow (dappendL (f', r)) 308 | else Deep (dappendL (f', head (force m)), $\$$tail (force m), r) 309 | end 310 | \end{lstlisting} 311 | где функция \lstinline!tooSmall! возвращает истину, если длина дека 312 | меньше двух, а \lstinline!dappendL! добавляет дек длины один или два к 313 | деку произвольной длины. 314 | 315 | Заметим, что вызовы \lstinline!tail! распространяются на следующий 316 | уровень c-дека только в том случае, когда длина головного дека равна 317 | двум. В терминах из Раздела~\ref{sc:9.2.3} мы можем сказать, что дек 318 | длиной три или более \term{безопасен}{safe}, а дек длиной два 319 | \term{опасен}{dangerous}. Каждый раз, когда \lstinline!tail! 320 | рекурсивно себя вызывает на следующем уровне, он переводит головной 321 | дек из опасного состояния в безопасное, так что ни на каком уровне 322 | c-дека два последовательных вызова \lstinline!tail! не могут 323 | распространиться на следующий уровень. Мы легко можем доказать, что 324 | \lstinline!tail! работает за амортизированное время $O(1)$, позволив 325 | безопасному деку иметь одну единицу долга, а опасному ноль. 326 | 327 | \begin{exercise}\label{ex:11.3} 328 | Докажите, что \lstinline!tail! и \lstinline!init! вместе работают за 329 | амортизированное время $O(1)$, сочетая их правила накопления долга 330 | согласно методике неявного рекурсивного замедления. 331 | \end{exercise} 332 | 333 | Как реализовать конкатенацию? Чтобы сконкатенировать два глубоких 334 | c-дека \lstinline!c$_1$! и \lstinline!c$_2$!, мы сохраняем голову 335 | \lstinline!c$_1$! как новую голову, хвост \lstinline!c$_2$! как новый 336 | хвост, а из оставшихся элементов собираем новую середину: хвост 337 | \lstinline!c$_1$! вставляем в середину \lstinline!c$_1$!, голову 338 | \lstinline!c$_2$! в середину \lstinline!c$_2$!, а затем конкатенируем 339 | результаты. 340 | \begin{lstlisting} 341 | fun (Deep (f$_1$, m$_1$, r$_1$)) $\concat$ (Deep (f$_2$, m$_2$, r$_2$)) = 342 | Deep (f$_1$, $\$$(snoc (force m$_1$, r$_1$) $\concat$ cons (f$_2$, force m$_2$)), r$_2$) 343 | \end{lstlisting} 344 | (Разумеется, есть ещё варианты, когда \lstinline!c$_1$! или 345 | \lstinline!c$_2$! являются мелкими.) Заметим, что глубина рекурсии 346 | $\concat$ равна глубине более мелкого c-дека. Кроме того, $\concat$ 347 | создаёт $O(1)$ долга на каждом уровне, и весь этот долг нужно 348 | немедленно высвободить, чтобы восстановить инвариант долга для 349 | \lstinline!tail! и \lstinline!init!. Следовательно, время работы 350 | $\concat$ равно $O(\min (\log n_1, \log n_2))$, где $n_i$~--- длина 351 | \lstinline!c$_i$!. 352 | 353 | Полный код этой реализации c-деков приведён на Рис.~\ref{fig:11.3}. 354 | 355 | \begin{figure} 356 | \centering 357 | (* $\mbox{предполагает полиморфную рекурсию!}$ *) \\ 358 | \mbox{\ldots{} \lstinline!snoc!, \lstinline!last! и \lstinline!init! 359 | определяются симметричным образом \ldots}\\ 360 | 361 | \caption{Простые деки с конкатенацией.} 362 | \label{fig:11.3} 363 | \end{figure} 364 | 365 | Чтобы улучшить время работы $\concat$ до $O(1)$, мы изменяем 366 | представление c-деков так, чтобы операция $\concat$ не вызывала сама 367 | себя рекурсивно. Основная идея состоит в том, чтобы $\concat$ каждого уровня 368 | обращалась на следующем уровне только к \lstinline!cons! и 369 | \lstinline!snoc!. Вместо трёх сегментов мы теперь заставляем глубокие 370 | c-деки содержать пять сегментов: $(f, a, m, b, r)$. $f$, $m$ и $r$ 371 | представляют собой обыкновенные деки; $f$ и $r$ содержат при этом не 372 | менее трёх элементов каждый, а $m$ не менее двух элементов. $a$ и $b$ 373 | представляют собой c-деки \term{составных элементов}{compound 374 | elements}. Вырожденный составной элемент является обыкновенным деком, 375 | содержащим не менее двух элементов. Полный составной элемент содержит 376 | три сегмента: $(f, c, r)$, где $f$ и $r$~--- обыкновенные деки, 377 | содержащие не меньше чем по два элемента каждый, а $m$~--- c-дек 378 | составных элементов. Этот тип данных может быть записан на Стандартном 379 | ML (с полиморфной рекурсией) так: 380 | \begin{lstlisting} 381 | datatype $\alpha$ Cat = 382 | Shallow of $\alpha$ D.Queue 383 | | Deep of $\alpha$ D.Queue (* $\ge 3$ *) 384 | $\times$ $\alpha$ CmpdElem Cat susp 385 | $\times$ $\alpha$ D.Queue (* $\ge 2$ *) 386 | $\times$ $\alpha$ CmpdElem Cat susp 387 | $\times$ $\alpha$ D.Queue (* $\ge 3$ *) 388 | and $\alpha$ CmpdElem = 389 | Simple of $\alpha$ D.Queue (* $\ge 2$ *) 390 | | Cmpd of $\alpha$ D.Queue (* $\ge 2$ *) 391 | $\times$ $\alpha$ CmpdElem Cat susp 392 | $\times$ $\alpha$ D.Queue (* $\ge 2$ *) 393 | \end{lstlisting} 394 | Если нам даны глубокие c-деки 395 | \lstinline!c$_1$ = Deep (f$_1$, a$_1$, m$_1$, b$_1$, r$_1$)! и 396 | \lstinline!c$_2$ = Deep (f$_2$, a$_2$, m$_2$, b$_2$, r$_2$)!, их 397 | конкатенация вычисляется следующим образом: прежде всего, 398 | \lstinline!f$_1$! сохраняется как голова результата, а 399 | \lstinline!r$_2$! как хвост результата. Затем мы строим новый 400 | срединный дек из последнего элемента \lstinline!r$_1$! и первого 401 | элемента \lstinline!f$_2$!. Затем мы порождаем составной элемент из 402 | \lstinline!m$_1$!, \lstinline!b$_1$! и остатка \lstinline!r$_1$!, и 403 | прицепляем его к концу \lstinline!a$_1$! через \lstinline!snoc!. Это 404 | будет сегмент \lstinline!a! результата. Наконец, мы порождаем 405 | составной элемент из остатка \lstinline!f$_2$!, \lstinline!a$_2$! и 406 | \lstinline!m$_2$!, и присоединяем его к началу \lstinline!b$_2$!. Это 407 | будет сегмент \lstinline!b! результата. Вся реализация выглядит как 408 | \begin{lstlisting} 409 | fun (Deep (f$_1$, a$_1$, m$_1$, b$_1$, r$_1$)) $\concat$ (Deep (f$_2$, a$_2$, m$_2$, b$_2$, r$_2$)) = 410 | let val (r$'_1$, m, f$'_2$) = share (r$_1$, f$_2$) 411 | val a$'_1$ - $\$$snoc (force a$_1$, Cmpd (m$_1$, b$_1$, r$'_1$)) 412 | val b$'_2$ = $\$$cons (Cmpd (f$'_2$, a$_2$, m$_2$), force b$_2$) 413 | in Deep (f$_1$, a$'_1$, m, b$'_2$, r$_2$) end 414 | \end{lstlisting} 415 | где 416 | \begin{lstlisting} 417 | fun share (f, r) = 418 | let val m = D.cons (D.last f, D.cons (D.head r, D.empty)) 419 | in (D.init f, m, D.tail r) 420 | fun cons (x, Deep (f, a, m, b, r)) = Deep (D.cons (x, f), a, m, b, r) 421 | fun snoc (Deep (f, a, m, b, r), x) = Deep (f, a, m, b, D.snoc (r, x)) 422 | \end{lstlisting} 423 | (Ради простоты описания мы опускаем варианты с участием мелких 424 | c-деков.) 425 | 426 | К сожалению, \lstinline!tail! и \lstinline!init! в этой реализации 427 | устроены весьма коряво. Поскольку эти две функции симметричны, мы 428 | описываем только \lstinline!tail!. Если у нас есть c-дек 429 | \lstinline!Deep (f, a, m, b, r)!, возможны шесть вариантов: 430 | \begin{itemize} 431 | \item $|\lstinline!f!| > 3$ 432 | \item $|\lstinline!f!| = 3$ 433 | \begin{itemize} 434 | \item \lstinline!a! непуст. 435 | \begin{itemize} 436 | \item Первый составной элемент \lstinline!a! вырожден. 437 | \item Первый составной элемент \lstinline!a! невырожден. 438 | \end{itemize} 439 | \item \lstinline!a! пуст, а \lstinline!b! непуст. 440 | \begin{itemize} 441 | \item Первый составной элемент \lstinline!b! вырожден. 442 | \item Первый составной элемент \lstinline!b! невырожден. 443 | \end{itemize} 444 | \item \lstinline!a! и \lstinline!b! оба пусты. 445 | \end{itemize} 446 | \end{itemize} 447 | Мы описываем поведение \lstinline!tail c! в первых трёх 448 | случаях. Код для оставшихся случаев можно найти в полной реализации, 449 | приведенной на Рис.~\ref{fig:11.4} и \ref{fig:11.5}. Если 450 | $|\lstinline!f!| > 3$, мы просто заменяем \lstinline!f! на 451 | \lstinline!D.tail f!. Если $|\lstinline!f!| = 3$, то 452 | уничтожение первого элемента \lstinline!f! сделает его размер меньше 453 | разрешённого. Следовательно, нам нужно вынуть новый головной дек из 454 | \lstinline!a! и состыковать его с остающимися в \lstinline!f! двумя 455 | элементами. Новый \lstinline!f! содержит не меньше четырёх элементов, 456 | так что следующий вызов \lstinline!tail! пойдёт по ветке 457 | $|\lstinline!f!| > 3$. 458 | 459 | \begin{figure} 460 | \centering 461 | 462 | (* \mbox{Предполагается, что \lstinline!D! поддерживает функцию \lstinline!size!} *)\\ 463 | \mbox{\ldots{} \lstinline!snoc! и \lstinline!last! определяются симметричным образом \ldots}\\ 464 | 465 | \caption{Деки с конкатенацией, использующие неявное рекурсивное замедление (часть I).} 466 | \label{fig:11.4} 467 | \end{figure} 468 | 469 | \begin{figure} 470 | \centering 471 | \mbox{\ldots{} \lstinline!replaceLast! и \lstinline!init! определяются симметричным образом \ldots}\\ 472 | 473 | \caption{Деки с конкатенацией, использующие неявное рекурсивное замедление (часть II).} 474 | \label{fig:11.5} 475 | \end{figure} 476 | 477 | Когда мы извлекаем первый составной элемент из \lstinline!a!, чтобы 478 | построить новый головной дек, этот составной элемент может быть 479 | вырожденным или невырожденным. Если он вырожденный (т.~е., 480 | обыкновенный дек), новым значением \lstinline!a! будет 481 | \lstinline!$\$$tail (force a)!. Если же мы получаем полный составной 482 | элемент \lstinline!Cmpd (f', c', r')!, то \lstinline!f'! оказывается 483 | новым значением \lstinline!f! (вместе с остающимися элементами старого 484 | \lstinline!f!), а новое значение \lstinline!a! будет 485 | $$ 486 | \lstinline!$\$$(force c' $\concat$ cons (Simple r', tail (force a)))! 487 | $$ 488 | Заметим, однако, что в результате комбинации \lstinline!cons! и 489 | \lstinline!tail! мы просто заменяем первый элемент 490 | \lstinline!a!. Можно сделать это напрямую, избежав тем самым ненужного 491 | вызова \lstinline!tail!, с помощью функции \lstinline!replaceHead!. 492 | \begin{lstlisting} 493 | fun replaceHead (x, Shallow d) = Shallow (D.cons (x, D.tail d)) 494 | | replaceHead (x, Deep (f, a, m, b, r) = 495 | Deep (D.cons (x, D.tail f), a, m, b, r) 496 | \end{lstlisting} 497 | Оставшиеся варианты \lstinline!tail! устроены похожим образом; 498 | каждый из них производит $O(1)$ работы, а затем делает максимум один 499 | вызов \lstinline!tail!. 500 | 501 | \begin{remark} 502 | Этот код можно записать намного короче и намного понятнее с 503 | использованием языковой конструкции, называемой 504 | \term{взгляды}{views} \cite{Wadler1987, BurtonCameron1993, 505 | PalaoGostanzaPenaNunez1996}, позволяющей устраивать сопоставление 506 | с образцом на абстрактных типах данных. Детали можно найти в 507 | \cite{Okasaki1997}. В Стандартном ML взгляды не поддерживаются. 508 | \end{remark} 509 | 510 | В функциях \lstinline!cons!, \lstinline!snoc!, \lstinline!head! и 511 | \lstinline!last! ленивое вычисление не используется, и легко видеть, 512 | что все они работают за время $O(1)$. Остальные функции мы анализируем 513 | методом банкира с использованием передачи долга. 514 | 515 | Как всегда, мы присваиваем долг каждой задержке. Задержки содержатся в 516 | сегментах \lstinline!a! и \lstinline!b! глубокого c-дека, а также в 517 | средних сегментах (\lstinline!c!) составных элементов. Каждому полю 518 | \lstinline!c! мы разрешаем иметь до четырёх единиц долга, а полям 519 | \lstinline!a! и \lstinline!b! мы позволяем иметь от нуля до пяти 520 | единиц, в зависимости от длины полей \lstinline!f! и 521 | \lstinline!r!. Базовый лимит полей \lstinline!a! и \lstinline!b! равен 522 | нулю. Если в поле \lstinline!f! содержится более трёх элементов, то 523 | лимит поля \lstinline!a! увеличивается на четыре, а лимит поля 524 | \lstinline!b! на одну единицу. Подобным образом, если поле 525 | \lstinline!r! содержит более трёх элементов, то лимит поля 526 | \lstinline!b! увеличивается на четыре, а лимит поля \lstinline!a! на 527 | одну единицу. 528 | 529 | \begin{theorem}\label{th:11.2} 530 | Функции $\concat$, \lstinline!tail! и \lstinline!init! работают за 531 | амортизированное время $O(1)$. 532 | 533 | \noindent 534 | \emph{Доказательство.} ($\concat$) Интересный случай~--- 535 | конкатенация двух c-деков 536 | \lstinline!Deep (f$_1$, a$_1$, m$_1$, b$_1$, r$_1$)! и 537 | \lstinline!Deep (f$_2$, a$_2$, m$_2$, b$_2$, r$_2$)!. 538 | В этом случае $\concat$ производит $O(1)$ нераздельной работы и 539 | высвобождает не более четырёх единиц долга. Во-первых, мы создаём 540 | две единицы долга для задержанных вызовов \lstinline!snoc! и 541 | \lstinline!cons! для \lstinline!a! и \lstinline!b! 542 | соответственно. Эти две единицы мы всегда высвобождаем. Кроме того, 543 | если на \lstinline!b$_1$! или \lstinline!a$_2$! висит пять единиц 544 | долга, нам нужно высвободить одну единицу, когда этот сегмент 545 | становится серединой составного элемента. Наконец, если в 546 | \lstinline!f$_1$! содержится только три элемента, а в 547 | \lstinline!f$_2$! более трёх элементов, нам нужно высвободить 548 | единицу долга из \lstinline!b$_2$!, поскольку он становится новым 549 | \lstinline!b!; и то же самое справедливо для \lstinline!r$_1$! и 550 | \lstinline!r$_2$!. Заметим, однако, что если на \lstinline!b$_1$! 551 | висит пять единиц долга, то \lstinline!f$_1$! содержит более трёх 552 | элементов, а если на \lstinline!a$_2$! висит пять единиц долга, то 553 | \lstinline!r$_2$! содержит более трёх элементов. Следовательно, 554 | всего нам нужно высвободить не больше четырёх единиц 555 | долга, или, по крайней мере, передать этот долг объемлющей задержке. 556 | 557 | (\lstinline!tail! и \lstinline!init!) Поскольку функции 558 | \lstinline!tail! и \lstinline!init! симметричны, мы приводим 559 | рассуждение только для \lstinline!tail!. Простым просмотром можно 560 | убедиться, что \lstinline!tail! совершает $O(1)$ нераздельной 561 | работы, так что нам остаётся показать, что она высвобождает не более 562 | $O(1)$ долга. Мы покажем, что размер высвобождаемого долга не 563 | превышает пять единиц. 564 | 565 | Поскольку \lstinline!tail! может вызывать сама себя рекурсивно, нам 566 | нужно учитывать возможность каскада вызовов \lstinline!tail!. Мы 567 | используем в рассуждениях передачу долга. Пусть у нас есть глубокий 568 | c-дек \lstinline!Deep (f, a, m, b, r)!. Нужно рассмотреть каждый 569 | вариант поведения \lstinline!tail!. 570 | 571 | Если $|\lstinline!f!| > 3$, мы находимся в конце каскада. Нового 572 | долга не создаётся, но извлечение элемента из \lstinline!f! может 573 | уменьшить разрешённый размер долга для \lstinline!a! на четыре 574 | единицы, а \lstinline!b! на одну единицу, так что мы передаём этот 575 | долг объемлющей задержке. 576 | 577 | Если $|\lstinline!f!| > 3$, то предположим, что \lstinline!a! 578 | непуст. (Случаи с пустым \lstinline!a! не содержат принципиальных 579 | отличий.) Если $|\lstinline!r!| > 3$, то \lstinline!a! 580 | может иметь одну единицу долга, которую мы передаём в объемлющую 581 | задержку. В противном случае \lstinline!a! не должен иметь 582 | долга. Если голова \lstinline!a! является вырожденным составным 583 | элементом (т.~е., простым деком элементов), то он становится новым 584 | значением \lstinline!f! вместе с оставшимися элементами старого 585 | \lstinline!f!. Новое значение \lstinline!a! представляет собой 586 | задержку от результата применения \lstinline!tail! к старому 587 | \lstinline!a!. Эта задержка получает до пяти единиц долга из 588 | рекурсивного вызова \lstinline!tail!. Поскольку новый разрешённый 589 | размер долга для \lstinline!a! не меньше четырёх, мы передаём не 590 | более одной единицы долга в объемлющую задержку, и всего 591 | передаваемых единиц долга получается не больше двух. (На самом деле, 592 | размер передаваемого долга не больше одного, поскольку здесь мы 593 | передаём одну единицу в точности в тех случаях, когда нам не нужно 594 | было передавать одну единицу долга из исходного \lstinline!a!.) 595 | 596 | Если же голова \lstinline!a! является невырожденным составным 597 | элементом \lstinline!Cmpd (f', c', r')!, то \lstinline!f'! 598 | становится новым значением \lstinline!f! вместе с остающимися 599 | элементами старого \lstinline!f!. Вычисление нового \lstinline!a! 600 | требует вызовов $\concat$ и \lstinline!replaceHead!. Полное число 601 | получаемых при этом единиц долга равно девяти: четыре от 602 | \lstinline!c'!, четыре от $\concat$ и одна свежесозданная единица 603 | долга от \lstinline!replaceHead!. Разрешённый размер долга для 604 | нового \lstinline!a! равен либо четырём, либо пяти, так что либо 605 | четыре, либо пять единиц долга мы передаём объемлющей 606 | задержке. Поскольку четыре единицы требуется передавать ровно в тех 607 | случаях, когда одну единицу нужно было передать из старого значения 608 | \lstinline!a!, всего требуется передать не более пяти единиц долга. 609 | \end{theorem} 610 | 611 | \begin{exercise}\label{ex:11.4} 612 | Пусть имеется реализация \lstinline!D! деков без 613 | конкатенации. Реализуйте списки с конкатенацией, используя тип 614 | \begin{lstlisting} 615 | datatype $\alpha$ Cat = 616 | Shallow of $\alpha$ D.Queue 617 | | Deep of $\alpha$ D.Queue $\times$ $\alpha$ CmpdElem Cat susp $\times$ $\alpha$ D.Queue 618 | and $\alpha$ CmpdElem = Cmpd of $\alpha$ D.Queue $\times$ $\alpha$ CmpdElem Cat susp 619 | \end{lstlisting} 620 | причём как головной дек глубокого (\lstinline!Deep!) узла, так и дек 621 | в узле \lstinline!Cmpd! должны содержать не менее двух 622 | элементов. Докажите, что все функции в вашей реализации работают за 623 | амортизированное время $O(1)$ при условии, что все функции в 624 | \lstinline!D! работают за время $O(1)$ (ограничение может быть 625 | жёстким или амортизированным). 626 | \end{exercise} 627 | 628 | \section{Примечания} 629 | \label{sc:11.3} 630 | 631 | \noindent 632 | \textbf{Рекурсивное замедление} Понятие рекурсивного замедления было 633 | введено Капланом и Тарьяном в \cite{KaplanTarjan1995} и снова 634 | использовано ими же в \cite{KaplanTarjan1996b}, но оно 635 | близкородственно ограничениям регулярности у Гибаса и 636 | др. \cite{Guibas-etal1977}. Бродал \cite{Brodal1995} пользовался 637 | похожим методом при реализации куч. 638 | 639 | \textbf{Деки с конкатенацией} Бухсбаум и Тарьян 640 | \cite{BuchsbaumTarjan1995} представляют чисто функциональную 641 | реализацию деков с конкатенацией, которая поддерживает 642 | \lstinline!tail! и \lstinline!init! за время $O(\log^* n)$ в худшем 643 | случае, а все остальные операции за $O(1)$ в худшем случае. Наша 644 | реализация улучшает этот показатель до $O(1)$ для всех операций, но 645 | ограничения получаются амортизированными, а не жёсткими. Независимо от 646 | нас, Каплан и Тарьян разработали похожую реализацию с жёсткими 647 | показателями $O(1)$. Однако детали их реализации весьма сложны. 648 | 649 | %%% Local Variables: 650 | %%% mode: latex 651 | %%% TeX-master: "pfds" 652 | %%% End: 653 | -------------------------------------------------------------------------------- /figures/fig.10.4.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}, level distance=3cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \def\rstep{5cm} 5 | 6 | \huge 7 | 8 | \tikzstyle{level 1}=[sibling distance=3cm] 9 | \tikzstyle{level 2}=[sibling distance=1.5cm] 10 | \tikzstyle{level 3}=[sibling distance=1cm] 11 | 12 | \node[tnode] (a) {} 13 | child { node[tnode] (b) {} 14 | child { node[tnode] (c) {} } 15 | child { node[tnode] (d) {} 16 | child { node[tnode] (e) {} } 17 | child { node[tnode] (f) {} } 18 | child { node[tnode] (g) {} } 19 | child { node[tnode] (h) {} } 20 | } 21 | child { node[tnode] (i) {} } 22 | } 23 | child { 24 | node[tnode] (j) {} 25 | child { node[tnode] (k) {} } 26 | } 27 | child { node[tnode] (l) {} 28 | child { node[tnode] (m) {} 29 | child { node[tnode] (n) {} } 30 | child { node[tnode] (o) {} } 31 | child { node[tnode] (p) {} } 32 | } 33 | child[missing] {} 34 | child { node[tnode] (q) {} 35 | child { node[tnode] (r) {} } 36 | child { node[tnode] (s) {} } 37 | child { node[tnode] (t) {} } 38 | } 39 | } 40 | ; 41 | \foreach \i in {c, e, f, g, h, i, k, n, o, p, r, s, t} { 42 | \node[below=8mm, anchor=base] at (\i) {$\i$}; 43 | } 44 | \foreach \i in {a, b, d, j, m} { 45 | \node[above left] at (\i) {$\i$}; 46 | } 47 | \foreach \i in {l, q} { 48 | \node[above right] at (\i) {$\i$}; 49 | } 50 | 51 | 52 | 53 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.10.5.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=2cm] 2 | 3 | \newcommand{\subtrg}[2]{\draw[thick] (#1.center) -- ++(0.6cm, -2cm) -- ++(-1.2cm, 0) -- cycle; \node[yshift=-1.8cm] at (#1.center) {#2}} 4 | 5 | 6 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 7 | \tikzstyle{tnodes}=[circle, fill=black, inner sep=1.7mm, shift={(1.73cm, -1.73cm)}] 8 | \tikzstyle{level 1}=[sibling distance=2.3cm] 9 | 10 | \huge 11 | 12 | \begin{scope} 13 | 14 | \node[tnode] (x) {} 15 | child foreach \i in {a, b, c, d} { node[tnode] (\i) {} } 16 | ; 17 | \foreach \i in {a, b, c, d} { 18 | \subtrg{\i}{}; 19 | } 20 | \node[above left] at (x) {$x$}; 21 | \foreach \i in {a, b} { 22 | \node[left=2mm] at (\i) {$\i$}; 23 | } 24 | \foreach \i in {c, d} { 25 | \node[right=2mm] at (\i) {$\i$}; 26 | } 27 | \foreach \i in {0, 1, 2, 3} { 28 | \node[xshift=\i * 2.3cm, yshift=-2.5cm] at (a) {$t_\i$}; 29 | } 30 | 31 | \end{scope} 32 | 33 | \node (rar) at (6cm, -2cm) {$\Longrightarrow$}; 34 | \node[above=2mm] at (rar) {\texttt{tail}}; 35 | \begin{scope}[shift={(8cm,2cm)}] 36 | \node[tnode] (a) {} 37 | child { node[tnodes] (b) at (a) {} 38 | child { node[tnodes] (c) at (b) {} 39 | child { node[tnodes] (d) at (c) {} } 40 | } 41 | } 42 | ; 43 | \foreach \i in {a, b, c, d} { 44 | \subtrg{\i}{}; 45 | \node[above right] at (\i) {$\i$}; 46 | } 47 | \foreach \i in {0, 1, 2, 3} { 48 | \node[xshift=\i * 1.73cm, yshift=-2.5cm - \i * 1.73cm] at (a) {$t_\i$}; 49 | } 50 | \end{scope} 51 | 52 | \end{tikzpicture} 53 | -------------------------------------------------------------------------------- /figures/fig.10.trie.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}, level distance=1.7cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \def\rstep{5cm} 5 | 6 | \huge 7 | \tt 8 | 9 | \tikzstyle{level 1}=[sibling distance=1.7cm] 10 | \tikzstyle{level 3}=[sibling distance=1cm] 11 | 12 | \node[tnode] {} 13 | child { node[tnode] {} 14 | child { node[tnode] {} 15 | child { node[tnode] {} 16 | child { node[tnode] {} 17 | edge from parent node[left] {t} 18 | } 19 | edge from parent node[left] {r} 20 | } 21 | child { node[tnode] {} 22 | edge from parent node[right] {t} 23 | } 24 | edge from parent node[left] {a} 25 | } 26 | edge from parent node[left] {c} 27 | } 28 | child { node[tnode] {} 29 | child { node[tnode] {} 30 | child { node[tnode] {} 31 | edge from parent node[right] {g} 32 | } 33 | edge from parent node[right] {o} 34 | } 35 | edge from parent node[right] {d} 36 | } 37 | 38 | ; 39 | 40 | 41 | 42 | 43 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.4.after.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | 5 | \begin{scope} 6 | 7 | \foreach \x/\y in {0/0, 0/-2, 3/-2, 6/-2} { 8 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 9 | } 10 | \draw[marrs] (-1.5, 0) -> +(1, 0); 11 | \draw[marrs] (0, 0) -> +(0, -1.5); 12 | \draw[marrs] (1, -2) -> +(1.5, 0); 13 | \draw[marrs] (4, -2) -> +(1.5, 0); 14 | \draw[marrs] (1, 0) -- (17.5, 0) .. controls (17.75, 0) and (18, -0.25) .. (18, -0.5) -- (18, -1.5); 15 | \draw[marrs] (7, -2) -- +(4.5, 0); 16 | 17 | { \huge 18 | \draw (-2, 0) node {$zs$}; 19 | \draw (0, -2) node {$0$}; 20 | \draw (3, -2) node {$1$}; 21 | \draw (6, -2) node {$2$}; 22 | } 23 | 24 | \end{scope} 25 | 26 | \begin{scope}[xshift=12cm] 27 | 28 | \foreach \x/\y in {0/-2, 3/-2, 6/-2} { 29 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 30 | } 31 | 32 | \draw[marrs] (1, -2) -> +(1.5, 0); 33 | \draw[marrs] (4, -2) -> +(1.5, 0); 34 | 35 | { \huge 36 | \draw (0, -2) node {$3$}; 37 | \draw (3, -2) node {$4$}; 38 | \draw (6, -2) node {$5$}; 39 | \draw (7, -2) node {$\cdot$}; 40 | } 41 | \end{scope} 42 | 43 | 44 | 45 | \end{tikzpicture} 46 | -------------------------------------------------------------------------------- /figures/fig.2.4.before.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | \begin{scope} 5 | 6 | \foreach \x/\y in {0/0, 0/-2, 3/-2, 6/-2} { 7 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 8 | } 9 | \draw[marrs] (-1.5, 0) -> +(1, 0); 10 | \draw[marrs] (0, 0) -> +(0, -1.5); 11 | \draw[marrs] (1, -2) -> +(1.5, 0); 12 | \draw[marrs] (4, -2) -> +(1.5, 0); 13 | \draw[marrs] (1, 0) -- (5.5, 0) .. controls (5.75, 0) and (6, -0.25) .. (6, -0.5) -- (6, -1.5); 14 | 15 | { \huge 16 | \draw (-2, 0) node {$xs$}; 17 | \draw (0, -2) node {$0$}; 18 | \draw (3, -2) node {$1$}; 19 | \draw (6, -2) node {$2$}; 20 | \draw (7, -2) node {$\cdot$}; 21 | } 22 | 23 | \end{scope} 24 | 25 | \begin{scope}[xshift=12cm] 26 | 27 | \foreach \x/\y in {0/0, 0/-2, 3/-2, 6/-2} { 28 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 29 | } 30 | \draw[marrs] (-1.5, 0) -> +(1, 0); 31 | \draw[marrs] (0, 0) -> +(0, -1.5); 32 | \draw[marrs] (1, -2) -> +(1.5, 0); 33 | \draw[marrs] (4, -2) -> +(1.5, 0); 34 | \draw[marrs] (1, 0) -- (5.5, 0) .. controls (5.75, 0) and (6, -0.25) .. (6, -0.5) -- (6, -1.5); 35 | 36 | { \huge 37 | \draw (-2, 0) node {$ys$}; 38 | \draw (0, -2) node {$3$}; 39 | \draw (3, -2) node {$4$}; 40 | \draw (6, -2) node {$5$}; 41 | \draw (7, -2) node {$\cdot$}; 42 | } 43 | \end{scope} 44 | 45 | 46 | 47 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.5.after.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | 5 | 6 | \begin{scope} 7 | 8 | \foreach \x/\y in {0/0, 3/0, 6/0} { 9 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 10 | } 11 | \draw[marrs] (-1.5, 0) -> +(1, 0); 12 | \draw[marrs] (1, 0) -> +(1.5, 0); 13 | \draw[marrs] (4, 0) -> +(1.5, 0); 14 | 15 | { \huge 16 | \draw (-2, 0) node {$xs$}; 17 | \draw (0, 0) node {$0$}; 18 | \draw (3, 0) node {$1$}; 19 | \draw (6, 0) node {$2$}; 20 | \draw (7, 0) node {$\cdot$}; 21 | } 22 | 23 | \end{scope} 24 | 25 | \begin{scope}[xshift=12cm] 26 | 27 | \foreach \x/\y in {0/0, 3/0, 6/0} { 28 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 29 | } 30 | \draw[marrs] (-1.5, 0) -> +(1, 0); 31 | \draw[marrs] (1, 0) -> +(1.5, 0); 32 | \draw[marrs] (4, 0) -> +(1.5, 0); 33 | 34 | { \huge 35 | \draw (-2, 0) node {$ys$}; 36 | \draw (0, 0) node {$3$}; 37 | \draw (3, 0) node {$4$}; 38 | \draw (6, 0) node {$5$}; 39 | \draw (7, 0) node {$\cdot$}; 40 | } 41 | \end{scope} 42 | 43 | \begin{scope}[yshift=2cm] 44 | 45 | \foreach \x/\y in {0/0, 3/0, 6/0} { 46 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 47 | } 48 | \draw[marrs] (-1.5, 0) -> +(1, 0); 49 | \draw[marrs] (1, 0) -> +(1.5, 0); 50 | \draw[marrs] (4, 0) -> +(1.5, 0); 51 | 52 | { \huge 53 | \draw (-2, 0) node {$zs$}; 54 | \draw (0, 0) node {$0$}; 55 | \draw (3, 0) node {$1$}; 56 | \draw (6, 0) node {$2$}; 57 | 58 | \draw[marrs] (7, 0) -- (11.5, 0) .. controls (11.75, 0) and (12, -0.25) .. (12, -0.5) -- (12, -1.5); 59 | } 60 | 61 | \end{scope} 62 | 63 | 64 | 65 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.5.before.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | \begin{scope} 5 | 6 | \foreach \x/\y in {0/0, 3/0, 6/0} { 7 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 8 | } 9 | \draw[marrs] (-1.5, 0) -> +(1, 0); 10 | \draw[marrs] (1, 0) -> +(1.5, 0); 11 | \draw[marrs] (4, 0) -> +(1.5, 0); 12 | 13 | { \huge 14 | \draw (-2, 0) node {$xs$}; 15 | \draw (0, 0) node {$0$}; 16 | \draw (3, 0) node {$1$}; 17 | \draw (6, 0) node {$2$}; 18 | \draw (7, 0) node {$\cdot$}; 19 | } 20 | 21 | \end{scope} 22 | 23 | \begin{scope}[xshift=12cm] 24 | 25 | \foreach \x/\y in {0/0, 3/0, 6/0} { 26 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 27 | } 28 | \draw[marrs] (-1.5, 0) -> +(1, 0); 29 | \draw[marrs] (1, 0) -> +(1.5, 0); 30 | \draw[marrs] (4, 0) -> +(1.5, 0); 31 | 32 | { \huge 33 | \draw (-2, 0) node {$ys$}; 34 | \draw (0, 0) node {$3$}; 35 | \draw (3, 0) node {$4$}; 36 | \draw (6, 0) node {$5$}; 37 | \draw (7, 0) node {$\cdot$}; 38 | } 39 | \end{scope} 40 | 41 | 42 | 43 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.6.after.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | 5 | 6 | \begin{scope} 7 | 8 | \foreach \x/\y in {0/0, 3/0, 6/0, 9/0, 12/0} { 9 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 10 | } 11 | \draw[marrs] (-1.5, 0) -> +(1, 0); 12 | \foreach \x in {1, 4, 7, 10} { 13 | \draw[marrs] (\x, 0) -> +(1.5, 0); 14 | } 15 | 16 | { \huge 17 | \draw (-2, 0) node {$xs$}; 18 | \foreach \x/\y in {0/0, 3/1, 6/2, 9/3, 12/4} { 19 | \draw (\x, 0) node {$\y$}; 20 | } 21 | \draw (13, 0) node {$\cdot$}; 22 | } 23 | 24 | \end{scope} 25 | 26 | \begin{scope}[yshift=2cm] 27 | 28 | \foreach \x/\y in {0/0, 3/0, 6/0} { 29 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 30 | } 31 | \draw[marrs] (-1.5, 0) -> +(1, 0); 32 | \draw[marrs] (1, 0) -> +(1.5, 0); 33 | \draw[marrs] (4, 0) -> +(1.5, 0); 34 | 35 | { \huge 36 | \draw (-2, 0) node {$ys$}; 37 | \draw (0, 0) node {$0$}; 38 | \draw (3, 0) node {$1$}; 39 | \draw (6, 0) node {$7$}; 40 | 41 | \draw[marrs] (7, 0) -- (8.5, 0) .. controls (8.75, 0) and (9, -0.25) .. (9, -0.5) -- (9, -1.5); 42 | } 43 | 44 | \end{scope} 45 | 46 | 47 | 48 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.6.before.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5}] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | 4 | \begin{scope} 5 | 6 | \foreach \x/\y in {0/0, 3/0, 6/0, 9/0, 12/0} { 7 | \draw (\x - 0.5, \y - 0.5) rectangle +(1, 1); \draw (\x + 1 - 0.5, \y - 0.5) rectangle +(1, 1); 8 | } 9 | \draw[marrs] (-1.5, 0) -> +(1, 0); 10 | \foreach \x in {1, 4, 7, 10} { 11 | \draw[marrs] (\x, 0) -> +(1.5, 0); 12 | } 13 | 14 | { \huge 15 | \draw (-2, 0) node {$xs$}; 16 | \foreach \x/\y in {0/0, 3/1, 6/2, 9/3, 12/4} { 17 | \draw (\x, 0) node {$\y$}; 18 | } 19 | \draw (13, 0) node {$\cdot$}; 20 | } 21 | 22 | \end{scope} 23 | 24 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.8.after.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=3cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, draw=black,node distance=1.7cm] 4 | \tikzstyle{level 1}=[sibling distance=5.6cm] 5 | \tikzstyle{level 2}=[sibling distance=3.4cm] 6 | 7 | \huge 8 | 9 | \draw (0, 2.5) node {$xs$}; 10 | \draw[marrs] (0, 2) -- +(0, -1.3); 11 | 12 | \draw (1.7, 2.5) node {$ys$}; 13 | \draw[marrs] (1.7, 2) -- +(0, -1.3); 14 | 15 | \node[tnode] (n_d) {d} 16 | child {node[tnode] (n_b) {b} 17 | child {node[tnode] (n_a) {a}} 18 | child {node[tnode] (n_c) {c}} 19 | } 20 | child {node[tnode] (n_g) {g} 21 | child {node[tnode] (n_f) {f} 22 | node[tnode] (n_f') [right of=n_f] {f} [clockwise from=250] 23 | child {node[tnode] (n_e) {e}} 24 | } 25 | child {node[tnode] (n_h) {h}} 26 | node[tnode] (n_g') [right of=n_g] {g} 27 | } 28 | node[tnode] (n_d') [right of=n_d]{d}; 29 | 30 | \path (n_d') edge (n_b); 31 | \path (n_d') edge (n_g'); 32 | \path (n_g') edge (n_f'); 33 | \path (n_g') edge (n_h); 34 | 35 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.2.8.before.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=3cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, draw=black,node distance=1.7cm] 4 | \tikzstyle{level 1}=[sibling distance=5.6cm] 5 | \tikzstyle{level 2}=[sibling distance=3.4cm] 6 | 7 | 8 | \huge 9 | 10 | \draw (0, 2.5) node {$xs$}; 11 | \draw[marrs] (0, 2) -- (0, 0.7); 12 | \node[tnode] {d} 13 | child {node[tnode] {b} 14 | child {node[tnode] {a}} 15 | child {node[tnode] {c}} 16 | } 17 | child {node[tnode] {g} 18 | child {node[tnode] {f}} 19 | child {node[tnode] {h}} 20 | }; 21 | 22 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.3.3.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},grow via three points={% 2 | one child at (0,-1.5) and two children at (0,-1.5) and (-0.8,-1.5)} 3 | ] 4 | \tikzstyle{marrs}=[very thick,-latex] 5 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 6 | \def\rstep{5cm} 7 | 8 | \huge 9 | 10 | \node[tnode] (0, 0) {}; 11 | child { node[tnode] {} } 12 | child { node[tnode] {} }; 13 | 14 | \begin{scope} 15 | \draw (0, 1) node {Ранг 0}; 16 | \node[tnode] (0, 0) {}; 17 | \end{scope} 18 | 19 | \begin{scope}[xshift=\rstep] 20 | \draw (0, 1) node {Ранг 1}; 21 | \node[tnode] {} 22 | child {node[tnode] {} }; 23 | \end{scope} 24 | 25 | \begin{scope}[xshift=2 * \rstep] 26 | \draw (0, 1) node {Ранг 2}; 27 | \node[tnode] {} 28 | child {node[tnode] {} } 29 | child {node[tnode] {} 30 | child {node[tnode] {} }}; 31 | 32 | 33 | \end{scope} 34 | 35 | \begin{scope}[xshift=3 * \rstep] 36 | \draw (0, 1) node {Ранг 3}; 37 | \node[tnode] {} 38 | child {node[tnode] {} } 39 | child {node[tnode] {} 40 | child {node[tnode] {} } 41 | } 42 | child {node[tnode] {} 43 | child {node[tnode] {} } 44 | child {node[tnode] {} 45 | child {node[tnode] {} } 46 | } 47 | }; 48 | 49 | 50 | \end{scope} 51 | 52 | 53 | \end{tikzpicture} 54 | -------------------------------------------------------------------------------- /figures/fig.3.5.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=2.5cm, sibling distance=2cm] 2 | \tikzstyle{tblack}=[circle, line width=1mm, draw=black] 3 | \tikzstyle{tred}=[circle, draw=black] 4 | \def\xstep{7cm} 5 | \def\ystep{10cm} 6 | 7 | \huge 8 | 9 | \begin{scope}[xshift=7cm, yshift=7cm] 10 | \def\inse{3.5mm} 11 | % \draw (-1, -2.5) rectangle (5, 1); 12 | 13 | \node[tblack, inner sep=\inse] at (0,0) {}; 14 | \node[tred, inner sep=\inse] at (0,-1.6) {}; 15 | \node[right=1pt] at (1,0) { -- черный}; 16 | \node[right=1pt] at (1,-1.6) { -- красный}; 17 | \end{scope} 18 | 19 | \begin{scope}[yshift=\ystep] 20 | \node[tblack] {z} 21 | child { node[tred] {x} 22 | child { node {a} } 23 | child { node[tred] {y} 24 | child {node {b}} 25 | child {node {c}} 26 | } 27 | } 28 | child { node {d} }; 29 | \end{scope} 30 | 31 | \begin{scope}[xshift=\xstep] 32 | \node[tblack] {x} 33 | child { node {a} } 34 | child { node[tred] {y} 35 | child { node {b} } 36 | child { node[tred] {z} 37 | child {node {c}} 38 | child {node {d}} 39 | } 40 | }; 41 | \end{scope} 42 | 43 | \begin{scope}[xshift=-\xstep] 44 | \node[tblack] {z} 45 | child { node[tred] {y} 46 | child { node[tred] {x} 47 | child {node {a}} 48 | child {node {b}} 49 | } 50 | child { node {c} } 51 | } 52 | child { node {d} }; 53 | \end{scope} 54 | 55 | \begin{scope}[yshift=-\ystep] 56 | \node[tblack] {x} 57 | child { node {a} } 58 | child { node[tred] {z} 59 | child { node[tred] {y} 60 | child {node {b}} 61 | child {node {c}} 62 | } 63 | child { node {d} } 64 | }; 65 | \end{scope} 66 | 67 | \begin{scope}[yshift=-1.5cm] 68 | \tikzstyle{level 1}=[sibling distance=3cm] 69 | \tikzstyle{level 2}=[sibling distance=2cm] 70 | \node[tred] {y} 71 | child { node[tblack] {x} 72 | child { node {a} } 73 | child { node {b} } 74 | } 75 | child { node[tblack] {z} 76 | child { node {c} } 77 | child { node {d} } 78 | }; 79 | \end{scope} 80 | \Huge 81 | \draw (0, 0.5cm) node[rotate=-90] {$\Rightarrow$}; 82 | \draw (0, -8cm) node[rotate=90] {$\Rightarrow$}; 83 | \draw (-4cm, -4cm) node[rotate=0] {$\Rightarrow$}; 84 | \draw (4cm, -4cm) node[rotate=180] {$\Rightarrow$}; 85 | 86 | 87 | 88 | \end{tikzpicture} 89 | -------------------------------------------------------------------------------- /figures/fig.5.4.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},grow via three points={% 2 | one child at (-0.5,-1.8) and two children at (-0.5,-1.8) and (0.5,-1.8)}] 3 | \tikzstyle{tblack}=[circle, line width=1mm, draw=black] 4 | \tikzstyle{tred}=[circle, draw=black] 5 | \def\xstep{7cm} 6 | \def\ystep{10cm} 7 | 8 | \huge 9 | 10 | \begin{scope} 11 | \node {7} 12 | child { node {6} 13 | child { node {5} 14 | child { node {4} 15 | child { node {3} 16 | child { node {2} 17 | child { node {1} 18 | }}}}}} 19 | ; 20 | \end{scope} 21 | 22 | \begin{scope}[xshift=5cm] 23 | \node {6} 24 | child { node {4} 25 | child { node {2} 26 | child { node {1} } 27 | child { node {3} } 28 | } 29 | child { node {5} } 30 | } 31 | child { node {7} }; 32 | \end{scope} 33 | 34 | \Huge 35 | \draw (1.5, -3.6) node[rotate=0] {$\Rightarrow$}; 36 | 37 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.9.2a.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=1.7cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \tikzstyle{temp}=[inner sep=0mm] 5 | \def\rstep{5cm} 6 | 7 | \huge 8 | 9 | \tikzstyle{level 1}=[sibling distance = 4cm]; 10 | \tikzstyle{level 2}=[sibling distance = 2cm]; 11 | \tikzstyle{level 3}=[sibling distance = 1cm]; 12 | 13 | 14 | \node[tnode] {} 15 | child foreach \xi in {1, 2} { node[tnode] {} 16 | child foreach \yi in {1, 2} { node[tnode] {} 17 | child foreach \zi in {1, 2} { node[tnode] {} 18 | node[tnode] {} 19 | } 20 | } 21 | }; 22 | 23 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.9.2b.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},grow via three points={% 2 | one child at (0,-1.7) and two children at (0,-1.7) and (-0.8,-1.7)} 3 | ] 4 | \tikzstyle{marrs}=[very thick,-latex] 5 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 6 | \def\rstep{5cm} 7 | 8 | \huge 9 | 10 | \node[tnode] {} 11 | child {node[tnode] {} } 12 | child {node[tnode] {} 13 | child {node[tnode] {} } 14 | } 15 | child {node[tnode] {} 16 | child {node[tnode] {} } 17 | child {node[tnode] {} 18 | child {node[tnode] {} } 19 | } 20 | }; 21 | 22 | \end{tikzpicture} 23 | -------------------------------------------------------------------------------- /figures/fig.9.2c.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=1.7cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \tikzstyle{temp}=[inner sep=0mm] 5 | \def\rstep{5cm} 6 | 7 | \huge 8 | 9 | \tikzstyle{level 1}=[sibling distance = 4cm]; 10 | \tikzstyle{level 2}=[sibling distance = 2cm]; 11 | \tikzstyle{level 3}=[sibling distance = 1cm]; 12 | 13 | 14 | \node[tnode] {} 15 | child foreach \xi in {1} { node[tnode] {} 16 | child foreach \yi in {1, 2} { node[tnode] {} 17 | child foreach \zi in {1, 2} { node[tnode] {} 18 | node[tnode] {} 19 | } 20 | } 21 | }; 22 | 23 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/fig.9.3a.tex: -------------------------------------------------------------------------------- 1 | 2 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=1.3cm] 3 | 4 | \newcommand{\subtrg}[2]{\draw[thick] (#1.center) -- ++(0.85cm, -2.2cm) -- ++(-1.7cm, 0) -- cycle; \node[yshift=-1.8cm] at (#1.center) {#2}} 5 | 6 | 7 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 8 | \tikzstyle{level 1}=[sibling distance=2.3cm] 9 | 10 | \huge 11 | 12 | \def\xstep{1.3} 13 | \coordinate (l) at (0,1.5) {}; 14 | \coordinate (r) at (2 * \xstep,1.5) {}; 15 | \node at (1 * \xstep, 0.4) {$\oplus$}; 16 | \node at (3 * \xstep, 0.4) {$\rightarrow$}; 17 | \coordinate (u) at (5 * \xstep, 2.8) 18 | child { coordinate (ul) } 19 | child { coordinate (ur) }; 20 | 21 | \subtrg{l}{$r$}; 22 | \subtrg{r}{$r$}; 23 | \subtrg{ul}{$r$}; 24 | \subtrg{ur}{$r$}; 25 | 26 | \end{tikzpicture} 27 | -------------------------------------------------------------------------------- /figures/fig.9.3b.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},grow via three points={% 2 | one child at (-1.9cm,-1.9cm) and two children at (0,-1.5) and (-0.8,-1.5)}] 3 | \newcommand{\subtrg}[2]{\draw[thick] (#1.center) -- ++(0.85cm, -2.2cm) -- ++(-1.7cm, 0) -- cycle; \node[yshift=-1.8cm] at (#1.center) {#2}} 4 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 5 | \tikzstyle{level 1}=[sibling distance=2.7cm] 6 | 7 | \huge 8 | 9 | \def\xstep{1.3} 10 | \node[tnode] (l) at (0,1.5) {}; 11 | \node[tnode] (r) at (2 * \xstep,1.5) {}; 12 | \node at (1 * \xstep, 0.4) {$\oplus$}; 13 | \node at (3 * \xstep, 0.4) {$\rightarrow$}; 14 | \node[tnode] (u) at (5.5 * \xstep, 3.4) {} 15 | child { node[tnode] (ul) {} }; 16 | 17 | \subtrg{l}{$r$}; 18 | \subtrg{r}{$r$}; 19 | \subtrg{u}{$r$}; 20 | \subtrg{ul}{$r$}; 21 | 22 | \end{tikzpicture} 23 | -------------------------------------------------------------------------------- /figures/fig.9.3c.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=1.3cm] 2 | \newcommand{\subtrg}[2]{\draw[thick] (#1.center) -- ++(0.85cm, -2.2cm) -- ++(-1.7cm, 0) -- cycle; \node[yshift=-1.8cm] at (#1.center) {#2}} 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \tikzstyle{level 2}=[sibling distance=2.3cm] 5 | 6 | \huge 7 | 8 | \def\xstep{1.3} 9 | \node[tnode] at (0, 1.5 + 1.3) {} child { 10 | node[tnode] (l) {} 11 | }; 12 | \node[tnode] at (2 * \xstep, 1.5 + 1.3) {} child { 13 | node[tnode] (r) {} 14 | }; 15 | 16 | \node at (\xstep, 0.4) {$\oplus$}; 17 | \node at (3 * \xstep, 0.4) {$\rightarrow$}; 18 | \node[tnode] (u1) at (5 * \xstep, 1.5 + 1.3 * 2) {} 19 | child { node[tnode] (u2) {} 20 | child { node[tnode] (ul) {} } 21 | child { node[tnode] (ur) {} } 22 | }; 23 | 24 | \subtrg{l}{r$-$1}; 25 | \subtrg{r}{r$-$1}; 26 | \subtrg{ur}{r$-$1}; 27 | \subtrg{ul}{r$-$1}; 28 | 29 | \end{tikzpicture} 30 | -------------------------------------------------------------------------------- /figures/fig.9.5.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},level distance=1.3cm] 2 | \tikzstyle{marrs}=[very thick,-latex] 3 | \tikzstyle{tnode}=[circle, fill=black, inner sep=1.5mm] 4 | \tikzstyle{tempty}=[inner sep=0mm, line width=0mm, draw=black, circle,fill=black] 5 | \def\rstep{5cm} 6 | 7 | \huge 8 | 9 | \tikzstyle{level 1}=[sibling distance = 4cm]; 10 | \tikzstyle{level 2}=[sibling distance = 2cm]; 11 | \tikzstyle{level 3}=[sibling distance = 1cm]; 12 | 13 | \node[tnode] at (0, 0) {}; 14 | \node at (1, 0) {\bf ,}; 15 | { 16 | \tikzstyle{level 1}=[sibling distance = 1cm]; 17 | \node[tempty] at (2.5, 1.3) {} 18 | child foreach \x in {1,2} { node[tnode] {} } 19 | ; 20 | } 21 | \node at (4, 0) {\bf ,}; 22 | { 23 | \tikzstyle{level 1}=[sibling distance = 2cm]; 24 | \tikzstyle{level 2}=[sibling distance = 1cm]; 25 | \node[tempty] at (6.5, 2.6) {} 26 | child foreach \x in {1,2} { 27 | child foreach \x in {1,2} { 28 | node[tnode] {} 29 | } 30 | } 31 | ; 32 | } 33 | \newcommand{\noop}{} 34 | \foreach \x/\v in {0/0, 1/\noop, 2/1, 3/2, 4/\noop, 5/3, 6/4, 7/5, 8/6} { 35 | \node at (\x, -0.7) {$\v$}; 36 | } 37 | 38 | \draw[very thick] (-0.8, -0.2) -- ++(-0.2, 0) -- ++(0, 3.2) -- ++(0.2, 0); 39 | \draw[very thick] (8.8, -0.2) -- ++(0.2, 0) -- ++(0, 3.2) -- ++(-0.2, 0); 40 | 41 | 42 | \end{tikzpicture} -------------------------------------------------------------------------------- /figures/formula.theorem.5.2.tex: -------------------------------------------------------------------------------- 1 | \begin{tikzpicture}[thick,scale=0.5, every node/.style={scale=0.5},grow via three points={% 2 | one child at (-0.8,-2.3) and two children at (-0.8,-1.8) and (0.8,-1.8)}] 3 | \tikzstyle{tblack}=[circle, line width=1mm, draw=black] 4 | \tikzstyle{tred}=[circle, draw=black] 5 | \def\xstep{7cm} 6 | \def\ystep{10cm} 7 | 8 | \huge 9 | 10 | \begin{scope}[xshift=0.8cm] 11 | \node (x) {$x$} 12 | child {node (y) {$y$} 13 | child {node {$u$}} 14 | child {node {$c$}} 15 | node[left of=y, below=-0.6cm] {$t = $} 16 | } 17 | child {node {$d$}} 18 | node[left of=x] {$s = $}; 19 | 20 | \end{scope} 21 | 22 | \begin{scope}[xshift=4cm] 23 | \node at (0, -1.8){$a$}; 24 | \node at (1, -1.8) {$||$}; 25 | \node (y) at (3, 0) {$y$} 26 | child {node {$b$}} 27 | child {node (x) {$x$} 28 | child {node {$c$}} 29 | child {node {$d$}} 30 | node [right of=x, below=-0.6cm] {$ = t'$} 31 | } 32 | node [right of=y, below=-0.7cm] {$ = s'$}; 33 | \end{scope} 34 | 35 | \Huge 36 | \draw (2.8, -1.8) node[rotate=0] {$\Rightarrow$}; 37 | 38 | \end{tikzpicture} -------------------------------------------------------------------------------- /haskell/AltBinaryRandomAccessList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module AltBinaryRandomAccessList (BinaryList) where 3 | 4 | import Prelude hiding (head, tail, lookup) 5 | import RandomAccessList 6 | 7 | data BinaryList a 8 | = Nil 9 | | Zero (BinaryList (a, a)) 10 | | One a (BinaryList (a, a)) 11 | 12 | uncons:: BinaryList a -> (a, BinaryList a) 13 | uncons Nil = error "empty list" 14 | uncons (One x Nil) = (x, Nil) 15 | uncons (One x ps) = (x, Zero ps) 16 | uncons (Zero ps) = let ((x, y), ps') = uncons ps in (x, One y ps') 17 | 18 | fupdate :: (a -> a) -> Int -> BinaryList a -> BinaryList a 19 | fupdate f i Nil = error "bad subscript" 20 | fupdate f 0 (One x ps) = One (f x) ps 21 | fupdate f i (One x ps) = cons x (fupdate f (i-1) (Zero ps)) 22 | fupdate f i (Zero ps) = Zero (fupdate f' (i `div` 2) ps) 23 | where f' (x,y) = if i `mod` 2 == 0 then (f x, y) else (x, f y) 24 | 25 | instance RandomAccessList BinaryList where 26 | empty = Nil 27 | isEmpty Nil = True 28 | isEmpty _ = False 29 | 30 | cons x Nil = One x Nil 31 | cons x (Zero ps) = One x ps 32 | cons x (One y ps) = Zero (cons (x, y) ps) 33 | 34 | head xs = fst (uncons xs) 35 | tail xs = snd (uncons xs) 36 | 37 | lookup i Nil = error "bad subscript" 38 | lookup 0 (One x ps) = x 39 | lookup i (One x ps) = lookup (i-1) (Zero ps) 40 | lookup i (Zero ps) = if i `mod` 2 == 0 then x else y 41 | where (x, y) = lookup (i `div` 2) ps 42 | 43 | update i y xs = fupdate (\x -> y) i xs 44 | \end{code} 45 | -------------------------------------------------------------------------------- /haskell/BankersDeque.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BankersDeque (BankersDeque) where 3 | 4 | import Prelude hiding (head, tail, last, init) 5 | import Deque 6 | 7 | data BankersDeque a = BD Int [a] Int [a] 8 | 9 | c = 3 10 | 11 | check lenf f lenr r = 12 | if lenf > c*lenr + 1 then 13 | let i = (lenf + lenr) `div` 2 14 | j = lenf + lenr - i 15 | f' = take i f 16 | r' = r ++ reverse (drop i f) 17 | in BD i f' j r' 18 | else if lenr > c*lenf + 1 then 19 | let j = (lenf+lenr) `div` 2 20 | i = lenf + lenr - i 21 | r' = take j r 22 | f' = f ++ reverse (drop j r) 23 | in BD i f' j r' 24 | else BD lenf f lenr r 25 | 26 | instance Deque BankersDeque where 27 | empty = BD 0 [] 0 [] 28 | isEmpty (BD lenf f lenr r) = (lenf+lenr == 0) 29 | 30 | cons x (BD lenf f lenr r) = check (lenf+1) (x:f) lenr r 31 | 32 | head (BD lenf [] lenr r) = error "empty deque" 33 | head (BD lenf (x:f') lenr r) = x 34 | 35 | tail (BD lenf [] lenr r) = error "empty deque" 36 | tail (BD lenf (x:f') lenr r) = check (lenf-1) f' lenr r 37 | 38 | snoc (BD lenf f lenr r) x = check lenf f (lenr+1) (x:r) 39 | 40 | last (BD lenf f lenr [] ) = error "empty deque" 41 | last (BD lenf f lenr (x:r')) = x 42 | 43 | init (BD lenf f lenr [] ) = error "empty deque" 44 | init (BD lenf f lenr (x:r')) = check lenf f (lenr-1) r' 45 | \end{code} 46 | -------------------------------------------------------------------------------- /haskell/BankersQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BankersQueue (BankersQueue) where 3 | 4 | import Prelude hiding (head, tail) 5 | import Queue 6 | 7 | data BankersQueue a = BQ Int [a] Int [a] 8 | 9 | check lenf f lenr r = 10 | if lenr <= lenf 11 | then BQ lenf f lenr r 12 | else BQ (lenf + lenr) (f ++ reverse r) 0 [] 13 | 14 | instance Queue BankersQueue where 15 | empty = BQ 0 [] 0 [] 16 | isEmpty (BQ lenf f lenr r) = (lenf == 0) 17 | 18 | snoc (BQ lenf f lenr r) x = check lenf f (lenr + 1) (x:r) 19 | 20 | head (BQ lenf [] lenr r) = error "empty queue" 21 | head (BQ lenf (x:f') lenr r) = x 22 | 23 | tail (BQ lenf [] lenr r) = error "empty queue" 24 | tail (BQ lenf (x:f') lenr r) = check (lenf - 1) f' lenr r 25 | \end{code} 26 | -------------------------------------------------------------------------------- /haskell/BatchedQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BatchedQueue (BatchedQueue) where 3 | 4 | import Prelude hiding (head, tail) 5 | import Queue 6 | 7 | data BatchedQueue a = BQ [a] [a] 8 | 9 | check [] r = BQ (reverse r) [] 10 | check f r = BQ f r 11 | 12 | instance Queue BatchedQueue where 13 | empty = BQ [] [] 14 | isEmpty (BQ f r) = null f 15 | 16 | snoc (BQ f r) x = check f (x:r) 17 | 18 | head (BQ [] _) = error "empty queue" 19 | head (BQ (x:f) r) = x 20 | 21 | tail (BQ [] _) = error "empty queue" 22 | tail (BQ (x:f) r) = check f r 23 | \end{code} 24 | -------------------------------------------------------------------------------- /haskell/BinaryRandomAccessList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BinaryRandomAccessList (BinaryList) where 3 | 4 | import Prelude hiding (head, tail, lookup) 5 | import RandomAccessList 6 | 7 | data Tree a = Leaf a | Node Int (Tree a) (Tree a) 8 | data Digit a = Zero | One (Tree a) 9 | newtype BinaryList a = BL [Digit a] 10 | 11 | size (Leaf x) = 1 12 | size (Node w t1 t2) = w 13 | 14 | link t1 t2 = Node (size t1 + size t2) t1 t2 15 | 16 | consTree t [] = [One t] 17 | consTree t (Zero : ts) = One t : ts 18 | consTree t1 (One t2 : ts) = Zero : consTree (link t1 t2) ts 19 | 20 | unconsTree [] = error "empty list" 21 | unconsTree [One t] = (t, []) 22 | unconsTree (One t:ts) = (t, Zero : ts) 23 | unconsTree (Zero:ts) = (t1, One t2 : ts') 24 | where (Node _ t1 t2, ts') = unconsTree ts 25 | 26 | instance RandomAccessList BinaryList where 27 | empty = BL [] 28 | isEmpty (BL ts) = null ts 29 | 30 | cons x (BL ts) = BL (consTree (Leaf x) ts) 31 | head (BL ts) = let (Leaf x, _) = unconsTree ts in x 32 | tail (BL ts) = let (_, ts') = unconsTree ts in BL ts' 33 | 34 | lookup i (BL ts) = look i ts 35 | where 36 | look i [] = error "bad subscript" 37 | look i (Zero : ts) = look i ts 38 | look i (One t : ts) = 39 | if i < size t then lookTree i t 40 | else look (i - size t) ts 41 | 42 | lookTree 0 (Leaf x) = x 43 | lookTree i (Leaf x) = error "bad subscript" 44 | lookTree i (Node w t1 t2) = 45 | if i < w `div` 2 then lookTree i t1 46 | else lookTree (i - w `div` 2) t2 47 | 48 | update i y (BL ts) = BL (upd i ts) 49 | where 50 | upd i [] = error "bad subscript" 51 | upd i (Zero : ts) = Zero : upd i ts 52 | upd i (One t : ts) = 53 | if i < size t then One (updTree i t) : ts 54 | else One t : upd (i - size t) ts 55 | 56 | updTree 0 (Leaf x) = Leaf y 57 | updTree i (Leaf x) = error "bad subscript" 58 | updTree i (Node w t1 t2) = 59 | if i < w `div` 2 then Node w (updTree i t1) t2 60 | else Node w t1 (updTree (i - w `div` 2) t2) 61 | \end{code} 62 | -------------------------------------------------------------------------------- /haskell/BinomialHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BinomialHeap (BinomialHeap) where 3 | 4 | import Heap 5 | 6 | data Tree a = Node Int a [Tree a] 7 | newtype BinomialHeap a = BH [Tree a] 8 | 9 | rank (Node r x c) = r 10 | root (Node r x c) = x 11 | 12 | link t1@(Node r x1 c1) t2@(Node _ x2 c2) = 13 | if x1 <= x2 14 | then Node (r+1) x1 (t2 : c1) 15 | else Node (r+1) x2 (t1 : c2) 16 | 17 | insTree t [] = [t] 18 | insTree t ts@(t' : ts') = 19 | if rank t < rank t' then t:ts else insTree (link t t') ts' 20 | 21 | mrg ts1 [] = ts1 22 | mrg [] ts2 = ts2 23 | mrg ts1@(t1:ts'1) ts2@(t2:ts'2) 24 | | rank t1 < rank t2 = t1 : mrg ts'1 ts2 25 | | rank t2 < rank t1 = t2 : mrg ts1 ts'2 26 | | otherwise = insTree (link t1 t2) (mrg ts'1 ts'2) 27 | 28 | removeMinTree [] = error "empty heap" 29 | removeMinTree [t] = (t, []) 30 | removeMinTree (t:ts) = 31 | if root t < root t' then (t, ts) else (t', t:ts') 32 | where (t', ts') = removeMinTree ts 33 | 34 | instance Heap BinomialHeap where 35 | empty = BH [] 36 | isEmpty (BH ts) = null ts 37 | 38 | insert x (BH ts) = BH (insTree (Node 0 x []) ts) 39 | merge (BH ts1) (BH ts2) = BH (mrg ts1 ts2) 40 | findMin (BH ts) = root t 41 | where (t, _) = removeMinTree ts 42 | 43 | deleteMin (BH ts) = BH (mrg (reverse ts1) ts2) 44 | where (Node _ x ts1, ts2) = removeMinTree ts 45 | \end{code} 46 | -------------------------------------------------------------------------------- /haskell/BootstrapHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BootstrapHeap (BootstrapHeap) where 3 | 4 | import Heap 5 | 6 | data BootstrapHeap h a = E | H a (h (BootstrapHeap h a)) 7 | 8 | instance Eq a => Eq (BootstrapHeap h a) where 9 | (H x _) == (H y _) = (x == y) 10 | 11 | instance Ord a => Ord (BootstrapHeap h a) where 12 | (H x _) <= (H y _) = (x <= y) 13 | 14 | instance Heap h => Heap (BootstrapHeap h) where 15 | empty = E 16 | isEmpty E = True 17 | isEmpty _ = False 18 | 19 | insert x h = merge (H x empty) h 20 | 21 | merge E h = h 22 | merge h E = h 23 | merge h1@(H x p1) h2@(H y p2) = 24 | if x <= y 25 | then H x (insert h2 p1) 26 | else H y (insert h1 p2) 27 | 28 | 29 | findMin E = error "empty heap" 30 | findMin (H x p) = x 31 | 32 | deleteMin E = error "empty heap" 33 | deleteMin (H x p) = 34 | if isEmpty p then E 35 | else let H y p1 = findMin p 36 | p2 = deleteMin p 37 | in H y (merge p1 p2) 38 | \end{code} 39 | -------------------------------------------------------------------------------- /haskell/BootstrappedQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BootstrappedQueue (BootstrappedQueue) where 3 | 4 | import Prelude hiding (head, tail) 5 | import Queue 6 | 7 | data BootstrappedQueue a 8 | = E 9 | | Q Int [a] (BootstrappedQueue [a]) Int [a] 10 | 11 | checkQ, checkF :: Int -> 12 | [a] -> 13 | BootstrappedQueue [a] -> 14 | Int -> 15 | [a] -> 16 | BootstrappedQueue a 17 | 18 | checkQ lenfm f m lenr r = 19 | if lenr < lenfm 20 | then checkF lenfm f m lenr r 21 | else checkF (lenfm+lenr) f (snoc m (reverse r)) 0 [] 22 | 23 | checkF lenfm [] E lenr r = E 24 | checkF lenfm [] m lenr r = Q lenfm (head m) (tail m) lenr r 25 | checkF lenfm f m lenr r = Q lenfm f m lenr r 26 | 27 | instance Queue BootstrappedQueue where 28 | empty = Q 0 [] E 0 [] 29 | isEmpty E = True 30 | isEmpty _ = False 31 | 32 | snoc E x = Q 1 [x] E 0 [] 33 | snoc (Q lenfm f m lenr r) x = checkQ lenfm f m (lenr+1) (x:r) 34 | 35 | head E = error "empty queue" 36 | head (Q lenfm (x:f') m lenr r) = x 37 | 38 | tail E = error "empty queue" 39 | tail (Q lenfm (x:f') m lenr r) = checkQ (lenfm-1) f' m lenr r 40 | \end{code} 41 | -------------------------------------------------------------------------------- /haskell/BottomUpMergeSort.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module BottomUpMergeSort (MergeSort) where 3 | 4 | import Sortable 5 | 6 | data MergeSort a = MS Int [[a]] 7 | 8 | mrg [] ys = ys 9 | mrg xs [] = xs 10 | mrg xs@(x:xs') ys@(y:ys') = 11 | if x <= y 12 | then x : mrg xs' ys 13 | else y : mrg xs ys' 14 | 15 | instance Sortable MergeSort where 16 | empty = MS 0 [] 17 | 18 | add x (MS size segs) = MS (size+1) (addSeg [x] segs size) 19 | where addSeg seg segs size = 20 | if size `mod` 2 == 0 then seg : segs 21 | else addSeg (mrg seg (head segs)) (tail segs) (size `div` 2) 22 | 23 | sort (MS size segs) = foldl mrg [] segs 24 | \end{code} 25 | -------------------------------------------------------------------------------- /haskell/CatList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module CatList (CatList) where 3 | 4 | import Prelude hiding (head, tail, (++)) 5 | import CatenableList 6 | import Queue (Queue) 7 | import qualified Queue 8 | 9 | data CatList q a 10 | = E 11 | | C a (q (CatList q a)) 12 | 13 | link (C x q) s = C x (Queue.snoc q s) 14 | 15 | instance Queue q => CatenableList (CatList q) where 16 | empty = E 17 | isEmpty E = True 18 | isEmpty _ = False 19 | 20 | xs ++ E = xs 21 | E ++ xs = xs 22 | xs ++ ys = link xs ys 23 | 24 | cons x xs = C x Queue.empty ++ xs 25 | snoc xs x = xs ++ C x Queue.empty 26 | head E = error "empty list" 27 | 28 | head (C x q) = x 29 | tail E = error "empty list" 30 | tail (C x q) = if Queue.isEmpty q then E else linkAll q 31 | where linkAll q = if Queue.isEmpty q' then t 32 | else link t (linkAll q') 33 | where t = Queue.head q 34 | q' = Queue.tail q 35 | \end{code} 36 | -------------------------------------------------------------------------------- /haskell/CatenableDeque.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module CatenableDeque ( 3 | CatenableDeque(..), 4 | Deque(..) 5 | ) where 6 | 7 | import Prelude hiding (head, tail, last, init, (++)) 8 | import Deque 9 | 10 | class Deque d => CatenableDeque d where 11 | (++) :: d a -> d a -> d a 12 | \end{code} 13 | -------------------------------------------------------------------------------- /haskell/CatenableList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module CatenableList (CatenableList(..)) where 3 | 4 | import Prelude hiding (head, tail, (++)) 5 | 6 | class CatenableList c where 7 | empty :: c a 8 | isEmpty :: c a -> Bool 9 | 10 | cons :: a -> c a -> c a 11 | snoc :: c a -> a -> c a 12 | (++) :: c a -> c a -> c a 13 | 14 | head :: c a -> a 15 | tail :: c a -> c a 16 | \end{code} 17 | -------------------------------------------------------------------------------- /haskell/Deque.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Deque (Deque(..)) where 3 | 4 | import Prelude hiding (head, tail, last, init) 5 | 6 | class Deque q where 7 | empty :: q a 8 | isEmpty :: q a -> Bool 9 | 10 | cons :: a -> q a -> q a 11 | head :: q a -> a 12 | tail :: q a -> q a 13 | 14 | snoc :: q a -> a -> q a 15 | last :: q a -> a 16 | init :: q a -> q a 17 | \end{code} 18 | -------------------------------------------------------------------------------- /haskell/FiniteMap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | module FiniteMap (FiniteMap(..)) where 4 | 5 | class FiniteMap m k where 6 | empty :: m k a 7 | bind :: k -> a -> m k a -> m k a 8 | lookup :: k -> m k a -> Maybe a 9 | \end{code} 10 | -------------------------------------------------------------------------------- /haskell/Heap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Heap (Heap(..)) where 3 | 4 | class Heap h where 5 | empty :: Ord a => h a 6 | isEmpty :: Ord a => h a -> Bool 7 | 8 | insert :: Ord a => a -> h a -> h a 9 | merge :: Ord a => h a -> h a -> h a 10 | 11 | findMin :: Ord a => h a -> a 12 | deleteMin :: Ord a => h a -> h a 13 | \end{code} 14 | -------------------------------------------------------------------------------- /haskell/HoodMelvilleQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module HoodMelvilleQueue (HoodMelvilleQueue) where 3 | 4 | import Prelude hiding (head, tail) 5 | import Queue 6 | 7 | data RotationState a 8 | = Idle 9 | | Reversing Int [a] [a] [a] [a] 10 | | Appending Int [a] [a] 11 | | Done [a] 12 | 13 | data HoodMelvilleQueue a = HM Int [a] (RotationState a) Int [a] 14 | 15 | exec (Reversing ok (x:f) f' (y:r) r') = Reversing (ok+1) f (x:f') r (y:r') 16 | exec (Reversing ok [] f' [y] r') = Appending ok f' (y:r') 17 | exec (Appending 0 f' r') = Done r' 18 | exec (Appending ok (x:f') r') = Appending (ok-1) f' (x:r') 19 | exec state = state 20 | 21 | invalidate (Reversing ok f f' r r') = Reversing (ok-1) f f' r r' 22 | invalidate (Appending 0 f' (x:r')) = Done r' 23 | invalidate (Appending ok f' r') = Appending (ok-1) f' r' 24 | invalidate state = state 25 | 26 | exec2 lenf f state lenr r = 27 | case exec (exec state) of 28 | Done newf -> HM lenf newf Idle lenr r 29 | newstate -> HM lenf f newstate lenr r 30 | 31 | check lenf f state lenr r = 32 | if lenr < lenf 33 | then exec2 lenf f state lenr r 34 | else let newstate = Reversing 0 f [] r [] 35 | in exec2 (lenf+lenr) f newstate 0 [] 36 | 37 | instance Queue HoodMelvilleQueue where 38 | empty = HM 0 [] Idle 0 [] 39 | isEmpty (HM lenf f state lenr r) = (lenf == 0) 40 | 41 | snoc (HM lenf f state lenr r) x = check lenf f state (lenr+1) (x:r) 42 | 43 | head (HM _ [] _ _ _) = error "empty queue" 44 | head (HM _ (x:f') _ _ _) = x 45 | 46 | tail (HM lenf [] state lenr r) = error "empty queue" 47 | tail (HM lenf (_:f') state lenr r) = 48 | check (lenf-1) f' (invalidate state) lenr r 49 | \end{code} 50 | -------------------------------------------------------------------------------- /haskell/ImplicitCatenableDeque.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module ImplicitCatenableDeque ( 3 | Sized(..), 4 | ImplicitCatDeque 5 | ) where 6 | 7 | import Prelude hiding (head, tail, last, init, (++)) 8 | import CatenableDeque 9 | 10 | class Sized d where 11 | size :: d a -> Int 12 | 13 | data ImplicitCatDeque d a 14 | = Shallow (d a) 15 | | Deep (d a) (ImplicitCatDeque d (CmpdElem d a)) (d a) 16 | (ImplicitCatDeque d (CmpdElem d a)) (d a) 17 | 18 | data CmpdElem d a 19 | = Simple (d a) 20 | | Cmpd (d a) (ImplicitCatDeque d (CmpdElem d a)) (d a) 21 | 22 | share f r = (init f, m, tail r) 23 | where m = cons (last f) (cons (head r) empty) 24 | 25 | dappendL d1 d2 = 26 | if isEmpty d1 then d2 27 | else dappendL (init d1) (cons (last d1) d2) 28 | 29 | dappendR d1 d2 = 30 | if isEmpty d2 then d1 31 | else dappendR (snoc d1 (head d2)) (tail d2) 32 | 33 | replaceHead x (Shallow d) = Shallow (cons x (tail d)) 34 | replaceHead x (Deep f a m b r) = Deep (cons x (tail f)) a m b r 35 | 36 | instance (Deque d, Sized d) => Deque (ImplicitCatDeque d) where 37 | empty = Shallow empty 38 | isEmpty (Shallow d) = isEmpty d 39 | isEmpty _ = False 40 | 41 | cons x (Shallow d) = Shallow (cons x d) 42 | cons x (Deep f a m b r) = Deep (cons x f) a m b r 43 | 44 | head (Shallow d) = head d 45 | head (Deep f a m b r) = head f 46 | 47 | tail (Shallow d) = Shallow (tail d) 48 | tail (Deep f a m b r) 49 | | size f > 3 = Deep (tail f) a m b r 50 | | not (isEmpty a) = 51 | case head a of 52 | Simple d -> Deep f' (tail a) m b r 53 | where f' = dappendL (tail f) d 54 | Cmpd f' c' r' -> Deep f'' a'' m b r 55 | where f'' = dappendL (tail f) f' 56 | a'' = c' ++ replaceHead (Simple r') a 57 | | not (isEmpty b) = 58 | case head b of 59 | Simple d -> Deep f' empty d (tail b) r 60 | where f' = dappendL (tail f) m 61 | Cmpd f' c' r' -> Deep f'' a'' r' (tail b) r 62 | where f'' = dappendL (tail f) m 63 | a'' = cons (Simple f') c' 64 | | otherwise = Shallow (dappendL (tail f) m) ++ Shallow r 65 | 66 | -- snoc, last, and init defined symmetrically... 67 | 68 | instance (Deque d, Sized d) => CatenableDeque (ImplicitCatDeque d) where 69 | (Shallow d1) ++ (Shallow d2) 70 | | size d1 < 4 = Shallow (dappendL d1 d2) 71 | | size d2 < 4 = Shallow (dappendR d1 d2) 72 | | otherwise = let (f, m, r) = share d1 d2 in Deep f empty m empty r 73 | (Shallow d) ++ (Deep f a m b r) 74 | | size d < 4 = Deep (dappendL d f) a m b r 75 | | otherwise = Deep d (cons(Simple f) a) m b r 76 | (Deep f a m b r) ++ (Shallow d) 77 | | size d < 4 = Deep f a m b (dappendR r d) 78 | | otherwise = Deep f a m (snoc b (Simple r)) d 79 | (Deep f1 a1 m1 b1 r1) ++ (Deep f2 a2 m2 b2 r2) = Deep f1 a'1 m b'2 r2 80 | where (r'1, m, f'2) = share r1 f2 81 | a'1 = snoc a1 (Cmpd m1 b1 r'1) 82 | b'2 = cons (Cmpd f'2 a2 m2) b2 83 | \end{code} 84 | -------------------------------------------------------------------------------- /haskell/ImplicitQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module ImplicitQueue (ImplicitQueue) where 3 | 4 | import Prelude hiding (head, tail) 5 | import Queue 6 | 7 | data Digit a = Zero | One a | Two a a 8 | 9 | data ImplicitQueue a 10 | = Shallow (Digit a) 11 | | Deep (Digit a) (ImplicitQueue (a, a)) (Digit a) 12 | 13 | instance Queue ImplicitQueue where 14 | empty = Shallow Zero 15 | isEmpty (Shallow Zero) = True 16 | isEmpty _ = False 17 | 18 | snoc (Shallow Zero) y = Shallow (One y) 19 | snoc (Shallow (One x)) y = Deep (Two x y) empty Zero 20 | snoc (Deep f m Zero) y = Deep f m (One y) 21 | snoc (Deep f m (One x)) y = Deep f (snoc m (x, y)) Zero 22 | 23 | head (Shallow Zero) = error "empty queue" 24 | head (Shallow (One x)) = x 25 | head (Deep (One x) m r) = x 26 | head (Deep (Two x y) m r) = x 27 | 28 | tail (Shallow Zero) = error "empty queue" 29 | tail (Shallow (One x)) = empty 30 | tail (Deep (Two x y) m r) = Deep (One y) m r 31 | tail (Deep (One x) m r) = 32 | if isEmpty m then Shallow r 33 | else Deep (Two y z) (tail m) r 34 | where (y, z) = head m 35 | \end{code} 36 | -------------------------------------------------------------------------------- /haskell/LazyPairingHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module LazyPairingHeap (PairingHeap) where 3 | 4 | import Heap 5 | 6 | data PairingHeap a = E | T a (PairingHeap a) (PairingHeap a) 7 | 8 | link (T x E m) a = T x a m 9 | link (T x b m) a = T x E (merge (merge a b) m) 10 | 11 | instance Heap PairingHeap where 12 | empty = E 13 | isEmpty E = True 14 | isEmpty _ = False 15 | 16 | insert x a = merge (T x E E) a 17 | merge a E = a 18 | merge E b = b 19 | merge a@(T x _ _) b@(T y _ _) = if x <= y 20 | then link a b 21 | else link b a 22 | 23 | findMin E = error "empty heap" 24 | findMin (T x a m) = x 25 | 26 | deleteMin E = error "empty heap" 27 | deleteMin (T x a m) = merge a m 28 | \end{code} 29 | -------------------------------------------------------------------------------- /haskell/LeftistHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module LeftistHeap (LeftistHeap) where 3 | 4 | import Heap 5 | 6 | data LeftistHeap a = E | T Int a (LeftistHeap a) (LeftistHeap a) 7 | 8 | rank E = 0 9 | rank (T r _ _ _) = r 10 | 11 | makeT x a b = if rank a >= rank b 12 | then T (rank b + 1) x a b 13 | else T (rank a + 1) x b a 14 | 15 | instance Heap LeftistHeap where 16 | empty = E 17 | isEmpty E = True 18 | isEmpty _ = False 19 | 20 | insert x h = merge (T 1 x E E) h 21 | 22 | merge h E = h 23 | merge E h = h 24 | merge h1@(T _ x a1 b1) h2@(T _ y a2 b2) = 25 | if x <= y 26 | then makeT x a1 (merge b1 h2) 27 | else makeT y a2 (merge h1 b2) 28 | 29 | findMin E = error "empty heap" 30 | findMin (T _ x a b) = x 31 | 32 | deleteMin E = error "empty heap" 33 | deleteMin (T _ x a b) = merge a b 34 | \end{code} 35 | -------------------------------------------------------------------------------- /haskell/PairingHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module PairingHeap (PairingHeap) where 3 | 4 | import Heap 5 | 6 | data PairingHeap a = E | T a [PairingHeap a] 7 | 8 | mergePairs [] = E 9 | mergePairs [h] = h 10 | mergePairs (h1:h2:hs) = merge (merge h1 h2) (mergePairs hs) 11 | 12 | instance Heap PairingHeap where 13 | empty = E 14 | isEmpty E = True 15 | isEmpty _ = False 16 | 17 | insert x h = merge (T x []) h 18 | merge h E = h 19 | merge E h = h 20 | merge h1@(T x hs1) h2@(T y hs2) = 21 | if x < y 22 | then T x (h2 : hs1) 23 | else T y (h1 : hs2) 24 | 25 | findMin E = error "empty heap" 26 | findMin (T x hs) = x 27 | 28 | deleteMin E = error "empty heap" 29 | deleteMin (T x hs) = mergePairs hs 30 | \end{code} 31 | -------------------------------------------------------------------------------- /haskell/PhysicistsQueue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module PhysicistsQueue (PhysicistsQueue) where 3 | 4 | import Prelude as P 5 | import Queue 6 | 7 | data PhysicistsQueue a = PQ [a] Int [a] Int [a] 8 | 9 | check w lenf f lenr r = 10 | if lenr <= lenf 11 | then checkw w lenf f lenr r 12 | else checkw f (lenf + lenr) (f ++ reverse r) 0 [] 13 | 14 | checkw [] lenf f lenr r = PQ f lenf f lenr r 15 | checkw w lenf f lenr r = PQ w lenf f lenr r 16 | 17 | instance Queue PhysicistsQueue where 18 | empty = PQ [] 0 [] 0 [] 19 | isEmpty (PQ w lenf f lenr r) = (lenf == 0) 20 | 21 | snoc (PQ w lenf f lenr r) x = check w lenf f (lenr+1) (x:r) 22 | 23 | head (PQ [] lenf f lenr r) = error "empty queue" 24 | head (PQ (x:w) lenf f lenr r) = x 25 | 26 | tail (PQ [] lenf f lenr r) = error "empty queue" 27 | tail (PQ (x:w) lenf f lenr r) = check w (lenf-1) (P.tail f) lenr r 28 | \end{code} 29 | -------------------------------------------------------------------------------- /haskell/Queue.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Queue (Queue(..)) where 3 | 4 | import Prelude hiding (head, tail) 5 | 6 | class Queue q where 7 | empty :: q a 8 | isEmpty :: q a -> Bool 9 | 10 | snoc :: q a -> a -> q a 11 | head :: q a -> a 12 | tail :: q a -> q a 13 | \end{code} 14 | -------------------------------------------------------------------------------- /haskell/RandomAccessList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module RandomAccessList (RandomAccessList(..)) where 3 | 4 | import Prelude hiding (head, tail, lookup) 5 | 6 | class RandomAccessList r where 7 | empty :: r a 8 | isEmpty :: r a -> Bool 9 | 10 | cons :: a -> r a -> r a 11 | head :: r a -> a 12 | tail :: r a -> r a 13 | 14 | lookup :: Int -> r a -> a 15 | update :: Int -> a -> r a -> r a 16 | \end{code} 17 | -------------------------------------------------------------------------------- /haskell/RedBlackSet.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 3 | module RedBlackSet (RedBlackSet) where 4 | 5 | import Set 6 | 7 | data Color = R | B 8 | data RedBlackSet a = E | T Color (RedBlackSet a) a (RedBlackSet a) 9 | 10 | balance B (T R (T R a x b) y c) z d = T R (T B a x b) y (T B c z d) 11 | balance B (T R a x (T R b y c)) z d = T R (T B a x b) y (T B c z d) 12 | balance B a x (T R (T R b y c) z d) = T R (T B a x b) y (T B c z d) 13 | balance B a x (T R b y (T R c z d)) = T R (T B a x b) y (T B c z d) 14 | balance color a x b = T color a x b 15 | 16 | instance Ord a => Set RedBlackSet a where 17 | empty = E 18 | 19 | member x E = False 20 | member x (T _ a y b) = if x < y then member x a 21 | else if x > y then member x b 22 | else True 23 | 24 | insert x s = T B a y b 25 | where ins E = T R E x E 26 | ins s@(T color a y b) = 27 | if x < y then balance color (ins a) y b 28 | else if x > y then balance color a y (ins b) 29 | else s 30 | T _ a y b = ins s -- guaranteed to be non-empty 31 | \end{code} 32 | -------------------------------------------------------------------------------- /haskell/Set.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | module Set (Set(..)) where 4 | 5 | class Set s a where 6 | empty :: s a 7 | insert :: a -> s a -> s a 8 | member :: a -> s a -> Bool 9 | \end{code} 10 | -------------------------------------------------------------------------------- /haskell/SimpleCatenableDeque.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module SimpleCatenableDeque (SimpleCatDeque) where 3 | 4 | import Prelude hiding (head, tail, last, init, (++)) 5 | import CatenableDeque 6 | 7 | data SimpleCatDeque d a 8 | = Shallow (d a) 9 | | Deep (d a) (SimpleCatDeque d (d a)) (d a) 10 | 11 | tooSmall d = isEmpty d || isEmpty (tail d) 12 | 13 | dappendL d1 d2 = if isEmpty d1 then d2 else cons (head d1) d2 14 | dappendR d1 d2 = if isEmpty d2 then d1 else snoc d1 (head d2) 15 | 16 | instance Deque d => Deque (SimpleCatDeque d) where 17 | empty = Shallow empty 18 | isEmpty (Shallow d) = isEmpty d 19 | isEmpty _ = False 20 | 21 | cons x (Shallow d) = Shallow (cons x d) 22 | cons x (Deep f m r) = Deep (cons x f) m r 23 | 24 | head (Shallow d) = head d 25 | head (Deep f m r) = head f 26 | 27 | tail (Shallow d) = Shallow (tail d) 28 | tail (Deep f m r) 29 | | not (tooSmall f) = Deep f m r 30 | | isEmpty m = Shallow (dappendL f r) 31 | | otherwise = Deep (dappendL f (head m)) (tail m) r 32 | where f = tail f 33 | -- snoc, last, and init defined symmetrically... 34 | 35 | instance Deque d => CatenableDeque (SimpleCatDeque d) where 36 | (Shallow d1) ++ (Shallow d2) 37 | | tooSmall d1 = Shallow (dappendL d1 d2) 38 | | tooSmall d2 = Shallow (dappendR d1 d2) 39 | | otherwise = Deep d1 empty d2 40 | (Shallow d) ++ (Deep f m r) 41 | | tooSmall d = Deep (dappendL d f) m r 42 | | otherwise = Deep d (cons f m) r 43 | (Deep f m r) ++ (Shallow d) 44 | | tooSmall d = Deep f m (dappendR r d) 45 | | otherwise = Deep f (snoc m r) d 46 | (Deep f1 m1 r1) ++ (Deep f2 m2 r2) = 47 | Deep f1 (snoc m1 r1 ++ cons f2 m2) r2 48 | \end{code} 49 | -------------------------------------------------------------------------------- /haskell/SkewBinaryRandomAccessList.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module SkewBinaryRandomAccessList (SkewList) where 3 | 4 | import Prelude hiding (head, tail, lookup) 5 | import RandomAccessList 6 | 7 | data Tree a = Leaf a | Node a (Tree a) (Tree a) 8 | newtype SkewList a = SL [(Int, Tree a)] 9 | 10 | instance RandomAccessList SkewList where 11 | empty = SL [] 12 | isEmpty (SL ts) = null ts 13 | 14 | cons x (SL ((w1, t1):(w2, t2):ts)) 15 | | w1 == w2 = SL ((1+w1+w2, Node x t1 t2):ts) 16 | cons x (SL ts) = SL ((1, Leaf x) : ts) 17 | 18 | head (SL []) = error "empty list" 19 | head (SL ((1, Leaf x):ts)) = x 20 | head (SL ((w, Node x t1 t2):ts)) = x 21 | 22 | tail (SL []) = error "empty list" 23 | tail (SL ((1, Leaf x):ts)) = SL ts 24 | tail (SL ((w, Node x t1 t2):ts)) = SL ((w `div` 2, t1):(w `div` 2, t2):ts) 25 | 26 | lookup i (SL ts) = look i ts 27 | where 28 | look i [] = error "bad subscript" 29 | look i ((w, t):ts) = 30 | if i < w then lookTree w i t else look (i-w) ts 31 | 32 | lookTree 1 0 (Leaf x) = x 33 | lookTree 1 i (Leaf x) = error "bad subscript" 34 | lookTree w 0 (Node x t1 t2) = x 35 | lookTree w i (Node x t1 t2) = 36 | if i <= w' then lookTree w' (i-1) t1 37 | else lookTree w' (i-1-w') t2 38 | where w' = w `div` 2 39 | 40 | update i y (SL ts) = SL (upd i ts) 41 | where 42 | upd i [] = error "bad subscript" 43 | upd i ((w, t):ts) = 44 | if i < w then (w, updTree w i t): ts 45 | else (w, t) : upd (i-w) ts 46 | 47 | updTree 1 0 (Leaf x) = Leaf y 48 | updTree 1 i (Leaf x) = error "bad subscript" 49 | updTree w 0 (Node x t1 t2) = Node y t1 t2 50 | updTree w i (Node x t1 t2) = 51 | if i <= w' then Node x (updTree w' (i-1) t1) t2 52 | else Node x t1 (updTree w' (i-1-w') t2) 53 | where w' = w `div` 2 54 | \end{code} 55 | -------------------------------------------------------------------------------- /haskell/SkewBinomialHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module SkewBinomialHeap (SkewBinomialHeap) where 3 | 4 | import Heap 5 | 6 | data Tree a = Node Int a [a] [Tree a] 7 | newtype SkewBinomialHeap a = SBH [Tree a] 8 | 9 | rank (Node r x xs c) = r 10 | root (Node r x xs c) = x 11 | 12 | link t1@(Node r x1 xs1 c1) t2@(Node _ x2 xs2 c2) = 13 | if x1 <= x2 14 | then Node (r+1) x1 xs1 (t2 : c1) 15 | else Node (r+1) x2 xs2 (t1 : c2) 16 | 17 | skewLink x t1 t2 = 18 | let Node r y ys c = link t1 t2 19 | in if x <= y 20 | then Node r x (y : ys) c 21 | else Node r y (x : ys) c 22 | 23 | insTree t [] = [t] 24 | insTree t ts@(t':ts') = 25 | if rank t < rank t' then t:ts else insTree (link t t') ts' 26 | 27 | mrg ts1 [] = ts1 28 | mrg [] ts2 = ts2 29 | mrg ts1@(t1:ts'1) ts2@(t2:ts'2) 30 | | rank t1 < rank t2 = t1 : mrg ts'1 ts2 31 | | rank t2 < rank t2 = t2 : mrg ts1 ts'2 32 | | otherwise = insTree (link t1 t2) (mrg ts'1 ts'2) 33 | 34 | normalize [] = [] 35 | normalize (t:ts) = insTree t ts 36 | 37 | removeMinTree [] = error "empty heap" 38 | removeMinTree [t] = (t, []) 39 | removeMinTree (t: ts) = if root t < root t' then (t, ts) else (t', t:ts') 40 | where (t', ts') = removeMinTree ts 41 | 42 | instance Heap SkewBinomialHeap where 43 | empty = SBH [] 44 | isEmpty (SBH ts) = null ts 45 | 46 | insert x (SBH (t1:t2:ts)) 47 | | rank t1 == rank t2 = SBH (skewLink x t1 t2:ts) 48 | insert x (SBH ts) = SBH (Node 0 x [] [] : ts) 49 | 50 | merge (SBH ts1) (SBH ts2) = SBH (mrg (normalize ts1) (normalize ts2)) 51 | 52 | findMin (SBH ts) = root t 53 | where (t, _) = removeMinTree ts 54 | 55 | deleteMin (SBH ts) = foldr insert (SBH ts') xs 56 | where (Node _ x xs ts1, ts2) = removeMinTree ts 57 | ts' = mrg (reverse ts1) (normalize ts2) 58 | \end{code} 59 | -------------------------------------------------------------------------------- /haskell/Sortable.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Sortable (Sortable(..)) where 3 | 4 | class Sortable s where 5 | empty :: Ord a => s a 6 | add :: Ord a => a -> s a -> s a 7 | sort :: Ord a => s a -> [a] 8 | \end{code} 9 | -------------------------------------------------------------------------------- /haskell/SplayHeap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module SplayHeap (SplayHeap) where 3 | 4 | import Heap 5 | 6 | data SplayHeap a = E | T (SplayHeap a) a (SplayHeap a) 7 | 8 | partition pivot E = (E, E) 9 | partition pivot t@(T a x b) = 10 | if x <= pivot then 11 | case b of 12 | E -> (t, E) 13 | T b1 y b2 -> 14 | if y <= pivot then 15 | let (small, big) = partition pivot b2 16 | in (T (T a x b) y small, big) 17 | else 18 | let (small, big) = partition pivot b1 19 | in (T a x small, T big y b2) 20 | else 21 | case a of 22 | E -> (E, t) 23 | T a1 y a2 -> 24 | if y <= pivot then 25 | let (small, big) = partition pivot a2 26 | in (T a1 y small, T big x b) 27 | else 28 | let (small, big) = partition pivot a1 29 | in (small, T big y (T a2 x b)) 30 | 31 | instance Heap SplayHeap where 32 | empty = E 33 | isEmpty E = True 34 | isEmpty _ = False 35 | 36 | insert x t = T a x b 37 | where (a, b) = partition x t 38 | 39 | merge E t = t 40 | merge (T a x b) t = T (merge ta a) x (merge tb b) 41 | where (ta, tb) = partition x t 42 | 43 | findMin E = error "empty heap" 44 | findMin (T E x b) = x 45 | findMin (T a x b) = findMin a 46 | 47 | deleteMin E = error "empty heap" 48 | deleteMin (T E x b) = b 49 | deleteMin (T (T E x b) y c) = T b y c 50 | deleteMin (T (T a x b) y c) = T (deleteMin a) x (T b y c) 51 | \end{code} 52 | -------------------------------------------------------------------------------- /haskell/Trie.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 3 | module Trie (Trie) where 4 | 5 | import Prelude hiding (lookup) 6 | import FiniteMap 7 | 8 | data Trie mk ks a = Trie (Maybe a) (mk (Trie mk ks a)) 9 | 10 | instance FiniteMap m k => FiniteMap (Trie (m k)) [k] where 11 | empty = Trie Nothing empty 12 | 13 | lookup [] (Trie b m) = b 14 | lookup (k:ks) (Trie b m) = lookup k m >>= \m' -> lookup ks m' 15 | 16 | bind [] x (Trie b m) = Trie (Just x) m 17 | bind (k:ks) x (Trie b m) = 18 | let t = case lookup k m of 19 | Just t -> t 20 | Nothing -> empty 21 | t' = bind ks x t 22 | in Trie b (bind k t' m) 23 | \end{code} 24 | -------------------------------------------------------------------------------- /haskell/TrieOfTrees.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 3 | module TrieOfTrees (Tree(..), Trie) where 4 | 5 | import Prelude hiding (lookup) 6 | import FiniteMap 7 | 8 | data Tree a = E | T a (Tree a) (Tree a) 9 | data Trie mk ks a = Trie (Maybe a) (mk (Trie mk ks (Trie mk ks a))) 10 | 11 | instance FiniteMap m k => FiniteMap (Trie (m k)) (Tree k) where 12 | empty = Trie Nothing empty 13 | 14 | lookup E (Trie v m) = v 15 | lookup (T k a b) (Trie v m) = 16 | lookup k m >>= \m' -> 17 | lookup a m' >>= \m'' -> 18 | lookup b m'' 19 | 20 | bind E x (Trie v m) = Trie (Just x) m 21 | bind (T k a b) x (Trie v m) = 22 | let tt = case lookup k m of 23 | Just tt -> tt 24 | Nothing -> empty 25 | t = case lookup a tt of 26 | Just t -> t 27 | Nothing -> empty 28 | t' = bind b x t 29 | tt' = bind a t' tt 30 | in Trie v (bind k tt' m) 31 | \end{code} 32 | -------------------------------------------------------------------------------- /haskell/UnbalancedSet.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 3 | module UnbalancedSet (UnbalancedSet) where 4 | 5 | import Set 6 | 7 | data UnbalancedSet a = E | T (UnbalancedSet a) a (UnbalancedSet a) 8 | 9 | instance Ord a => Set UnbalancedSet a where 10 | empty = E 11 | 12 | member x E = False 13 | member x (T a y b) = if x < y then member x a 14 | else if x > y then member x b 15 | else True 16 | 17 | insert x E = T E x E 18 | insert x s@(T a y b) = if x < y then T (insert x a) y b 19 | else if x > y then T a y (insert x b) 20 | else s 21 | \end{code} 22 | -------------------------------------------------------------------------------- /pfds.tex: -------------------------------------------------------------------------------- 1 | \documentclass{book} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[russian]{babel} 4 | \usepackage{amsmath} 5 | \usepackage{amssymb} 6 | \usepackage{listings} 7 | 8 | \usepackage{caption} 9 | \usepackage{subcaption} 10 | \usepackage{tikz} 11 | \usetikzlibrary{trees} 12 | 13 | \usepackage[arrow,curve,matrix,frame]{xy} 14 | 15 | %% See http://www.haskell.org/haskellwiki/Literate_programming for details 16 | \lstloadlanguages{Haskell} 17 | \lstnewenvironment{code} 18 | {\lstset{}% 19 | \csname lst@SetFirstLabel\endcsname} 20 | {\csname lst@SaveFirstLabel\endcsname} 21 | \lstset{ 22 | basicstyle=\small, 23 | flexiblecolumns=false, 24 | basewidth={0.5em,0.45em}, 25 | literate={+}{{$+$}}1 {/}{{$/$}}1 {*}{{$*$}}1 {=}{{$=$}}1 26 | {>}{{$>$}}1 {<}{{$<$}}1 {\\}{{$\lambda$}}1 27 | {\\\\}{{\char`\\\char`\\}}1 28 | {->}{{$\rightarrow$}}2 {>=}{{$\geq$}}2 {<-}{{$\leftarrow$}}2 29 | {<=}{{$\leq$}}2 {=>}{{$\Rightarrow$}}2 30 | {\ .}{{$\circ$}}2 {\ .\ }{{$\circ$}}2 31 | {>>}{{>>}}2 {>>=}{{>>=}}2 32 | {|}{{$\mid$}}1 33 | } 34 | \newcommand{\codesep}{\noindent\hfil\rule{0.5\textwidth}{.4pt}\hfil} 35 | 36 | \lstset{language=ml,mathescape=true} 37 | 38 | \makeatletter 39 | \newcommand{\translationnotemark}{{\renewcommand{\@makefnmark}{\mbox{$^*$}}\footnotemark{}}} 40 | \newcommand{\translationnotetext}[1]{{\renewcommand{\@makefnmark}{\mbox{$^*$}}\footnotetext{#1 --- {\it прим. перев.}}}\addtocounter{footnote}{-1}} 41 | \newcommand{\translationnote}[1]{\translationnotemark{}\translationnotetext{#1}} 42 | \makeatother 43 | 44 | 45 | \newcommand{\term}[2]{\textit{#1} (#2)} 46 | 47 | \newcommand{\concat}{\ensuremath{+\!\!\!+\,}} 48 | 49 | \newtheorem{remark}{\textbf{Замечание}}[chapter] 50 | \newtheorem{exercise}{\textbf{Упражнение}}[chapter] 51 | 52 | \newtheorem{hint}{\textbf{Указание разработчикам}}[chapter] 53 | 54 | \newtheorem{theorem}{\textbf{Теорема}}[chapter] 55 | \newtheorem{lemma}[theorem]{\textbf{Лемма}} 56 | 57 | \newtheorem{definition}{\textbf{Определение}}[chapter] 58 | 59 | \author{Крис Окасаки} 60 | \title{Чисто функциональные структуры данных} 61 | \setcounter{tocdepth}{4} 62 | \begin{document} 63 | \maketitle 64 | \tableofcontents 65 | \input{preface.tex} 66 | 67 | \input{chapter01.tex} 68 | \input{chapter02.tex} 69 | \input{chapter03.tex} 70 | \input{chapter04.tex} 71 | \input{chapter05.tex} 72 | \input{chapter06.tex} 73 | \input{chapter07.tex} 74 | \input{chapter08.tex} 75 | \input{chapter09.tex} 76 | \input{chapter10.tex} 77 | \input{chapter11.tex} 78 | 79 | \input{app-a.tex} 80 | \input{bibliography.tex} 81 | 82 | \end{document} 83 | -------------------------------------------------------------------------------- /preface.tex: -------------------------------------------------------------------------------- 1 | \chapter*{Предисловие} 2 | 3 | Я впервые познакомился с языком Стандартный ML в 1989 году. Мне всегда 4 | нравилось программировать эффективные реализации структур данных, 5 | и я немедленно занялся переводом некоторых своих любимых программ 6 | на Стандартный ML. Для некоторых структур перевод оказался достаточно 7 | простым и, к моему большому удовольствию, получался код значительно более краткий 8 | и ясный, чем предыдущие версии, написанные мной на C, Pascal или 9 | Ada. Однако не всегда результат оказывался столь приятным. Раз за 10 | разом мне приходилось использовать разрушающее присваивание, которое в 11 | Стандартном ML не приветствуется, а во многих других функциональных 12 | языках вообще запрещено. Я пытался обращаться к литературе, но 13 | нашел лишь несколько разрозненных статей. Понемногу я стал понимать, 14 | что столкнулся с неисследованной областью, и начал искать новые 15 | способы решения задач. 16 | 17 | Сейчас, восемь лет спустя, мой поиск продолжается. Всё ещё есть много 18 | примеров структур данных, которые я просто не знаю как эффективно 19 | реализовать на функциональном языке. Однако за это время я получил 20 | множество уроков о том, что в функциональных языках 21 | \textit{работает}. Эта книга является попыткой записать выученные 22 | уроки, и я надеюсь, что она послужит справочником для функциональных 23 | программистов, а также как текст для тех, кто хочет больше узнать о 24 | структурах данных в функциональном окружении. 25 | 26 | \textbf{Стандартный ML.} Несмотря на то, что структуры данных из этой 27 | книги можно реализовать практически на любом функциональном языке, я 28 | во всех примерах буду использовать Стандартный ML. У этого языка 29 | имеются следующие преимущества для моих целей: (1) энергичный 30 | порядок вычислений, что значительно упрощает рассуждения о том, 31 | сколько времени потребует тот или иной алгоритм, и (2) замечательная 32 | система модулей, идеально подходящая для выражения абстрактных типов 33 | данных. Однако пользователи других языков, например, Haskell или 34 | Lisp, смогут без труда адаптировать мои примеры к своим вычислительным 35 | окружениям. (В приложении я привожу переводы большинства примеров на 36 | Haskell.) Даже программисты на C или Java должны быть способны 37 | реализовать эти структуры данных, хотя в случае C отсутствие 38 | автоматической сборки мусора иногда будет доставлять неприятности. 39 | 40 | Читателям, незнакомым со Стандартным ML, я рекомендую в качестве 41 | введения книги \textit{ML для программиста-практика} Полсона 42 | \cite{Paulson1996} или \textit{Элементы программирования на ML} 43 | Ульмана \cite{Ullman1994}. 44 | 45 | \textbf{Прочие предварительные требования.} Эта книга не рассчитана 46 | служить первоначальным общим введением в структуры данных. Я 47 | предполагаю, что читателю достаточно знакомы основные абстрактные 48 | структуры данных~--- стеки, очереди, кучи (приоритетные очереди) и 49 | конечные отображения (словари). Кроме того, я предполагаю знакомство 50 | с основами анализа алгоритмов, особенно с нотацией <<большого O>> 51 | (напр., $O(n \log n)$). Обычно эти вопросы рассматриваются во втором 52 | курсе для студентов, изучающих информатику. 53 | 54 | \textbf{Благодарности.} Мое понимание функциональных структур данных 55 | чрезвычайно обогатилось в результате дискуссий со многими 56 | специалистами на протяжении многих лет. Мне бы особенно хотелось 57 | поблагодарить Питера Ли, Генри Бейкера, Герта Бродала, Боба Харпера, 58 | Хаима Каплана, Грэма Мосса, Саймона Пейтон Джонса и Боба Тарждана. 59 | 60 | 61 | 62 | %%% Local Variables: 63 | %%% mode: latex 64 | %%% TeX-master: "pfds" 65 | %%% End: 66 | -------------------------------------------------------------------------------- /terminology.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[russian]{babel} 4 | \begin{document} 5 | 6 | \begin{tabular}{p{3cm}|p{4cm}|p{5cm}} 7 | Выражение & Перевод & Возможные варианты/\textit{вопросы} \\ 8 | \hline 9 | accumulated savings & текущие накопления \\ 10 | actual cost & реальная стоимость \\ 11 | banker's method & метод банкира & банковский? \\ 12 | batched rebuilding & порционная перестройка \\ 13 | bootstrapping & развёртка & раскрутка \\ 14 | complete cost & полная стоимость \\ 15 | debit & единица долга \\ 16 | execution trace & трассировка вычисления \\ 17 | force & вынудить \\ 18 | incremental computation & пошаговое вычисление \\ 19 | leftist heaps & левоориентированные кучи \\ 20 | node & узел & вершина \\ 21 | non-uniformly recursive types & гетерогенно рекурсивные типы \\ 22 | pairing heaps & парные кучи & спаривательные кучи, кучи со спариванием 23 | \\ 24 | pennant & подвешенное дерево \\ 25 | persistence & устойчивость \\ 26 | physicist's method & метод физика & физический \\ 27 | raise an exception & возбудить исключение \\ 28 | relaxed heaps & расслабленные кучи & ослабленные? \\ 29 | release debit & высвободить долг & выплатить \\ 30 | rotate a queue & провернуть очередь \\ 31 | rotation & проворот \\ 32 | schedule & расписание & \textit{можно было бы говорить 33 | \emph{scheduling} `планирование', но не хочется плодить 34 | разнокоренные переводы} \\ 35 | shared cost & разделяемая стоимость \\ 36 | sharing & совместное использование \\ 37 | skew binary numbers & скошенные двоичные числа \\ 38 | splay heaps & расширяющиеся кучи & \textit{См. перевод Cormen-Leiserson-Rivest}\\ 39 | strict evaluation & энергичный порядок вычислений & строгий \\ 40 | suspend & задержать & подвесить, заморозить \\ 41 | suspension & задержка \\ 42 | tries & префиксные деревья & \textit{слово <<префиксные>> ъорошо 43 | подходит, когда речь идёт о строках, но плохо, когда о деревьях} \\ 44 | uniformly recursive types & гомогенно рекурсивные типы \\ 45 | unshared cost & нераздельная стоимость & неразделяемая \\ 46 | view & взгляд & перспектива, отражение \\ 47 | worst-case limit & жёсткая оценка, оценка для худшего случая & 48 | \textit{было бы приятнее иметь одно выражение вместо двух, неочевидно 49 | синонимичных} \\ 50 | 51 | \end{tabular} 52 | 53 | \end{document} 54 | --------------------------------------------------------------------------------