From 8d2cce53cc99835efb599a96455d17778e329ca0 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Fri, 7 Aug 2020 19:33:28 -0300 Subject: [PATCH 01/66] Use explicitly python2 --- latex/snarf-python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/snarf-python.py b/latex/snarf-python.py index fe8f3c08..35082707 100755 --- a/latex/snarf-python.py +++ b/latex/snarf-python.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 """Touch up a LaTeX file Snarf up a LaTeX input file importing appropriate code where requested From 916e4b4af8eb8a282746b4e6cc93d20e558f7619 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Fri, 7 Aug 2020 19:34:29 -0300 Subject: [PATCH 02/66] Use explicitly Python 2 --- latex/figs-python/snarf-fig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/figs-python/snarf-fig.py b/latex/figs-python/snarf-fig.py index 4968bdaf..24500654 100755 --- a/latex/figs-python/snarf-fig.py +++ b/latex/figs-python/snarf-fig.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 import sys import re From 559b5962d2dffb25723c5d9ae55ca6e2b253362f Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Fri, 7 Aug 2020 19:38:37 -0300 Subject: [PATCH 03/66] Update Makefile --- latex/images/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latex/images/Makefile b/latex/images/Makefile index 76e2e1c2..66a03bf8 100644 --- a/latex/images/Makefile +++ b/latex/images/Makefile @@ -12,10 +12,10 @@ bigoh-2.tex : bigoh-2.gp gnuplot $< %.pdf : %.svg - inkscape --export-pdf=$@ $< +inkscape --export-type=pdf --export-filename=$@ $< %.eps : %.svg - inkscape --export-eps=$@ $< +inkscape --export-type=eps --export-filename=$@ $< clean: rm -f $(pdfs) From 1c8c8e5f0c569f201e237dd845324833ca488206 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Sat, 8 Aug 2020 23:00:45 -0300 Subject: [PATCH 04/66] Starting translation to brazilian portuguese --- latex/ods.tex | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/latex/ods.tex b/latex/ods.tex index 9d117861..6f20fbb5 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -96,57 +96,57 @@ % Referencing commands \newcommand{\chaplabel}[1]{\label{chap:#1}} -\newcommand{\Chapref}[1]{Chapter~\ref{chap:#1}} -\newcommand{\chapref}[1]{Chapter~\ref{chap:#1}} +\newcommand{\Chapref}[1]{Capítulo~\ref{chap:#1}} +\newcommand{\chapref}[1]{Capítulo~\ref{chap:#1}} \newcommand{\seclabel}[1]{\label{sec:#1}} -\newcommand{\Secref}[1]{Section~\ref{sec:#1}} -\newcommand{\secref}[1]{Section~\ref{sec:#1}} +\newcommand{\Secref}[1]{Seção~\ref{sec:#1}} +\newcommand{\secref}[1]{Seção~\ref{sec:#1}} \newcommand{\sref}[1]{\textsection~\ref{sec:#1}} \newcommand{\alglabel}[1]{\label{alg:#1}} -\newcommand{\Algref}[1]{Algorithm~\ref{alg:#1}} -\newcommand{\algref}[1]{Algorithm~\ref{alg:#1}} +\newcommand{\Algref}[1]{Algoritmo~\ref{alg:#1}} +\newcommand{\algref}[1]{Algoritmo~\ref{alg:#1}} \newcommand{\applabel}[1]{\label{app:#1}} -\newcommand{\Appref}[1]{Appendix~\ref{app:#1}} -\newcommand{\appref}[1]{Appendix~\ref{app:#1}} +\newcommand{\Appref}[1]{Apêndice~\ref{app:#1}} +\newcommand{\appref}[1]{Apêndice~\ref{app:#1}} \newcommand{\tablabel}[1]{\label{tab:#1}} -\newcommand{\Tabref}[1]{Table~\ref{tab:#1}} -\newcommand{\tabref}[1]{Table~\ref{tab:#1}} +\newcommand{\Tabref}[1]{Tabela~\ref{tab:#1}} +\newcommand{\tabref}[1]{Tabela~\ref{tab:#1}} \newcommand{\figlabel}[1]{\label{fig:#1}} -\newcommand{\Figref}[1]{Figure~\ref{fig:#1}} -\newcommand{\figref}[1]{Figure~\ref{fig:#1}} +\newcommand{\Figref}[1]{Figura~\ref{fig:#1}} +\newcommand{\figref}[1]{Figura~\ref{fig:#1}} \newcommand{\eqlabel}[1]{\label{eq:#1}} \newcommand{\myeqref}[1]{(\ref{eq:#1})} -\newcommand{\Eqref}[1]{Equation~(\ref{eq:#1})} +\newcommand{\Eqref}[1]{Equação~(\ref{eq:#1})} % Theorem-like environments \theoremstyle{plain} \newtheorem{thm}{Theorem}[chapter] \newcommand{\thmlabel}[1]{\label{thm:#1}} -\newcommand{\thmref}[1]{Theorem~\ref{thm:#1}} +\newcommand{\thmref}[1]{Teorema~\ref{thm:#1}} \newtheorem{lem}{Lemma}[chapter] \newcommand{\lemlabel}[1]{\label{lem:#1}} -\newcommand{\lemref}[1]{Lemma~\ref{lem:#1}} +\newcommand{\lemref}[1]{Lema~\ref{lem:#1}} \newtheorem{cor}{Corollary}[chapter] \newcommand{\corlabel}[1]{\label{cor:#1}} -\newcommand{\corref}[1]{Corollary~\ref{cor:#1}} +\newcommand{\corref}[1]{Corolário~\ref{cor:#1}} \theoremstyle{definition} \newtheorem{exc}{Exercise}[chapter] \newcommand{\exclabel}[1]{\label{exc:#1}} -\newcommand{\excref}[1]{Exercise~\ref{exc:#1}} +\newcommand{\excref}[1]{Exercício~\ref{exc:#1}} \newtheorem{prp}{Property}[chapter] \newcommand{\prplabel}[1]{\label{prp:#1}} -\newcommand{\prpref}[1]{Property~\ref{prp:#1}} +\newcommand{\prpref}[1]{Propriedade~\ref{prp:#1}} % Miscellaneous commands \newcommand{\etal}{\emph{et al.}} @@ -174,16 +174,16 @@ \hypersetup{colorlinks=true, linkcolor=linkblue, anchorcolor=linkblue,% citecolor=linkblue, filecolor=linkblue, menucolor=linkblue,% urlcolor=linkblue,% - pdfauthor={Pat Morin},% - pdftitle={Open Data Structures},% + pdfauthor={Pat Morin. Tradução e adaptação: Marcelo Keese Albertini},% + pdftitle={Open Data Structures. Estruturas de Dados Abertas},% pdfsubject={Computer Science, Data Structures},% pdfkeywords={Data structures, algorithms}} \DeclareMathOperator{\bdiv}{div} % Title page content -\title{Open Data Structures (in \lang)} -\author{Pat Morin} +\title{Open Data Structures (in \lang). Estruturas de Dados Abertas (em \lang)} +\author{Autor: Pat Morin.\\Tradução e adaptações por: Marcelo Keese Albertini} \date{% Edition 0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} \htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} From 1610914c339451d754cacc031391627bf1a60240 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Sun, 9 Aug 2020 14:01:07 -0300 Subject: [PATCH 05/66] Translation of 1st chapter: introduction --- latex/intro.tex | 1541 +++++++++++++++++++++-------------------------- 1 file changed, 680 insertions(+), 861 deletions(-) diff --git a/latex/intro.tex b/latex/intro.tex index 238af3bb..ca38dbfc 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -1,218 +1,151 @@ -\chapter{Introduction} +\chapter{Introdução} \pagenumbering{arabic} +Todo currículo de Ciência da Computação no mundo inclui ao menos uma disciplina sobre estruturas de dados e algoritmos. +Estruturas de dados são \emph{realmente} importantes; +elas melhoram nossa qualidade de vida e até salvam vidas com certa frequência. +Muitas companhias que valem múltiplos milhões de dados and algumas que valem bilhões foram construídas em torno de estruturas de dados. -Every computer science curriculum in the world includes a course on data -structures and algorithms. Data structures are \emph{that} important; -they improve our quality of life and even save lives on a regular basis. -Many multi-million and several multi-billion dollar companies have been -built around data structures. +Como isso é possível? Se pararmos para pensar nisso, percebemos que interagimos com estruturas de dados constantemente. -How can this be? If we stop to think about it, we realize that we -interact with data structures constantly. \begin{itemize} - \item Open a file: File system - \index{file system}% - data structures are used to locate - the parts of that file on disk so they can be retrieved. This isn't - easy; disks contain hundreds of millions of blocks. The contents of your - file could be stored on any one of them. - \item Look up a contact on your phone: A data - structure is used to look up a phone number in your contact list - \index{contact list}% - based on partial - information even before you finish dialing/typing. This isn't easy; - your phone may contain information about a lot of people---everyone - you have ever contacted via phone or email---and your phone doesn't - have a very fast processor or a lot of memory. - \item Log in to your favourite social network: - \index{social network}% - The network servers - use your login information to look up your account information. - This isn't easy; the most popular social networks have hundreds of - millions of active users. - \item Do a web search: - \index{web search}% - The search engine uses data structures to find - the web pages containing your search terms. This isn't easy; there - are over 8.5 billion web pages on the Internet and each page contains - a lot of potential search terms. - \item Phone emergency services (9-1-1): - \index{emergency services}\index{9-1-1}% - The emergency services network - looks up your phone number in a data structure that maps phone numbers - to addresses so that police cars, ambulances, or fire trucks can be - sent there without delay. This is important; the person making the - call may not be able to provide the exact address they are calling - from and a delay can mean the difference between life or death. + \item Abrir um arquivo: Sistema de arquivos + \index{sistema de arquivos}% + estruturas de dados são usadas para encontrar + as partes daquele arquivo em disco para serem recuperads. + Isso não é fácil; discos contém centenas de milhões de blocos. + O conteúdo do seu arquivo pode estar armazenado em qualquer um deles. + \item Procurar contato no telefone: Uma estrutura de dados é usada para procurar por um número de telefone na sua lista de contatos + \index{lista de contatos}% + baseada em informação parcial mesmo antes de você terminar de discar/digitar + Isso não é fácil; +seu telefone pode ter informações sobre muitas pessoas---todos que você já entrou em contato via telefone ou email---e seu telefone não tem um processador muito rápido ou muita memória. + \item Entrar na sua rede social preferida: + \index{rede social}% + Os servidores da rede + usam sua informação de login para procurar as informações da sua conta. + Isso não é fácil; as redes sociais mais populares tem centenas de milhões de usuários ativos. + \item Fazer uma busca na web: + \index{busca na web}% + O motor de busca usa estruturas de dados para buscar + as páginas na web que contêm os seus termos de busca. + Isso não é fácil; existem mais de 8.5 bilhões de páginas web na Internet + e cada página tem muitos potenciais termos de busca. + \item Serviços de telefone de emergência (9-1-1): + \index{serviços de emergência}\index{9-1-1}% + Os serviços de emergência procuram pelo seu número de telefone em uma estrutura de dados que mapeia números de telefone para endereços de forma tal que carros de polícia, ambulâncias ou os bombeiros possam ser enviados imediatamente. + Isso é importante; a pessoa fazendo a chamada pode não conseguir fornecer o exato endereço onde está e um atraso pode significar a diferença entre vida e morte. \end{itemize} -\section{The Need for Efficiency} - -In the next section, we look at the operations supported by the most -commonly used data structures. Anyone with a bit of programming -experience will see that these operations are not hard to implement -correctly. We can store the data in an array or a linked list and each -operation can be implemented by iterating over all the elements of the -array or list and possibly adding or removing an element. - -This kind of implementation is easy, but not very efficient. Does this -really matter? Computers are becoming faster and faster. Maybe the -obvious implementation is good enough. Let's do some rough calculations -to find out. - -\paragraph{Number of operations:} Imagine an application with a -moderately-sized data set, say of one million ($10^6$), items. It is -reasonable, in most applications, to assume that the application will -want to look up each item at least once. This means we can expect to do -at least one million ($10^6$) searches in this data. If each of these -$10^6$ searches inspects each of the $10^6$ items, this gives a total -of $10^6\times 10^6=10^{12}$ (one thousand billion) inspections. - -\paragraph{Processor speeds:} At the time of writing, even a very fast -desktop computer can not do more than one billion ($10^9$) operations per -second.\footnote{Computer speeds are at most a few gigahertz (billions -of cycles per second), and each operation typically takes a few cycles.} -This means that this application will take at least $10^{12}/10^9 = 1000$ -seconds, or roughly 16 minutes and 40 seconds. Sixteen minutes is an -eon in computer time, but a person might be willing to put up with it -(if he or she were headed out for a coffee break). - -%Consider, now an application like the human genome project. Completed in -%2003 (when computers were quite a bit slower than they are now), this -%was considered an extraordinary scientific accomplishment. The human -%genome consists of 3 billion ($3\times 10^6$) base pairs. By our -%rough calculations, doing even a single search on this data would take -%3 seconds. Doing a million searches would take $3$ million seconds, -%or roughly 34 days. This is starting to get beyond reasonable. - -\paragraph{Bigger data sets:} Now consider a company like Google, +\section{A Necessidade de Eficiência} +Na próxima seção, nós veremos as operações aceitas pelas estruturas de dados mais comuns. +Qualquer um com um pouco de experiência em programação verá que essas operações não são difíceis de implementar corretamente. +Podemos implementar ao percorrer/iterar todos os elementos de um array ou lista e possivelmente adicionar ou remover um elemento. +Esse tipo de implementação é fácil, mas não muito eficiente. +Isso realmente importa? Computadores estão ficando cada vez mais rápidos. +Talvez a implementação mais óbvia é boa o suficiente. +Vamos fazer alguns cálculos aproximados para verificar isso. + +\paragraph{Número de operações:} +Imagine uma aplicação com um conjunto de dados de tamanho moderado, digamos de um milhão ($10^6$) de items. +É razoável, na maior parte das aplicações, presumir que a aplicação vai precisar verificar cada item pelo menos uma vez. Isso significa que podemos esperar fazermos pelo menos um milhão ($10^6$) de buscas nesses dados. Se cada uma dessas $10^6$ buscas inspecionas cada um dos $10^6$ itens, isso resulta em um total de $10^6\times 10^6=10^{12}$ (mil bilhões) inspeções. + +\paragraph{Velocidades do Processador:} No momento de escrita, mesmo um computador de mesa bem rápido não pode fazer mais de um bilhão ($10^9$) de operações por segundo.\footnote{Velocidades de computadores são até pouco gigaherts (bilhões de ciclos por segundo) e cada operação tipicamente exige alguns ciclos.} +Isso significa que essa aplicação vai levar pelo menos $10^{12}/10^9 = 1000$ +segundos, ou aproximadamente 16 minutos e 40 segundos. Dezesseis minutos é uma eternidade em tempos computacionais, mas uma pessoa pode estar disposta a suportá-lo (se ela estiver indo para um café). + +\paragraph{Conjuntos de dados maiores:} Agora considere que uma empresa como a Google, \index{Google}% -that -indexes over 8.5 billion web pages. By our calculations, doing any kind -of query over this data would take at least 8.5 seconds. We already -know that this isn't the case; web searches complete in much less than -8.5 seconds, and they do much more complicated queries than just asking -if a particular page is in their list of indexed pages. At the time -of writing, Google receives approximately $4,500$ queries per second, -meaning that they would require at least $4,500\times 8.5=38,250$ very -fast servers just to keep up. - -\paragraph{The solution:} -These examples tell us that the obvious implementations of data structures -do not scale well when the number of items, #n#, in the data structure -and the number of operations, $m$, performed on the data structure -are both large. In these cases, the time (measured in, say, machine -instructions) is roughly $#n#\times m$. - -The solution, of course, is to carefully organize data within the data -structure so that not every operation requires every data item to be -inspected. Although it sounds impossible at first, we will see data -structures where a search requires looking at only two items on average, -independent of the number of items stored in the data structure. In our -billion instruction per second computer it takes only $0.000000002$ -seconds to search in a data structure containing a billion items (or a -trillion, or a quadrillion, or even a quintillion items). - -We will also see implementations of data structures that keep the -items in sorted order, where the number of items inspected during an -operation grows very slowly as a function of the number of items in -the data structure. For example, we can maintain a sorted set of one -billion items while inspecting at most 60 items during any operation. -In our billion instruction per second computer, these operations take -$0.00000006$ seconds each. - -The remainder of this chapter briefly reviews some of the main concepts -used throughout the rest of the book. \secref{interfaces} describes -the interfaces implemented by all of the data structures described in -this book and should be considered required reading. The remaining -sections discuss: +que indexa mais de 8.5 bilhões de páginas web. +De acordo com nossos cálculos, fazer qualquer tipo de consulta nesses dados levaria pelo menos 8.5 segundos. +Nós já sabemos que esse não é o caso; buscas web são feitas em bem menos de 8.5 segundos, e elas fazem consultas bem mais complicadas que somente pedindo se uma dadoa página está na lista de páginas indexadas. +No momento de escrita, Google recebe aproximadamente $4,500$ consultas por segundo, o que significa que eles precisariam de pelo menos $4,500 \times 8.5 =38,250$ servidores muito rápidos somente para manter esse tempo de resposta. + +\paragraph{A solução:} +Esses exemplos nos dizem que as implementações mais óbvias de estruturas de dados não escalam bem quando o número de itens, #n#, na estrutura de dados e o número de operações $m$, feitos na estrutura de dados são altos. +Nesses casos, o tempo (medido em, digamos, instruções de máquina) é aproximadamente $#n#\times m$. + +A solução, é claro, cuidadosamente organizar dados na estrutura de dados de forma que nem toda operação requer que todos os itens de dados sejam inspecionados. +Embora pareça inicialmente impossível, nós veremos estruturas de dados em que a busca requer olhar em apenas dois itens em média, independentemente do número de itens guardados na estrutura de dados. No nosso computador de um bilhão de instruções por segundo leva somente $0.000000002$ +segundos para buscar em uma estrutura de dados contendo um bilhão de itens (ou um trilhão ou um quatrilhão ou mesmo um quintilhão de items) + +Nós veremos também implementações de estruturas de dados que mantêm os itens em ordem, onde o número de itens inspecionados durante uma operação cresce muito lentamente em função do número de itens na estrutura de dados. +Por exemplo, podemos manter um conjunto ordenado com um bilhão de itens e inspecionar no máximo 60 itens para qualquer operação. +No nosso computador de um bilhão de instruções por segundo, essas operações levam $0.00000006$ segundos cada. + +O resto deste capítulo revisa brevemente alguns dos principais conceitos usados ao longo do resto do livro. \secref{interfaces} descreve as interfaces implementadas por todas as estruturas de dados descritas neste livro e +deve ser consideradas como leitura obrigatória. + +O restante das seções discute: + \begin{itemize} - \item some mathematical review including exponentials, logarithms, - factorials, asymptotic (big-Oh) notation, probability, and randomization; - \item the model of computation; - \item correctness, running time, and space; - \item an overview of the rest of the chapters; and - \item the sample code and typesetting conventions. + \item revisão de conceitos matemáticos incluindo exponenciais, logaritmos, fatoriais, notação assintótica (big-Oh), probabilidades e aleatorização; + \item modelo de computação; + \item corretudo, tempo de execução e espaço; + \item uma visão geral do resto dos capítulos; e + \item um código de amostra juntamente com convenções de escrita de código. \end{itemize} -A reader with or without a background in these areas can easily skip -them now and come back to them later if necessary. +Um leitor com ou sem um experiência nessas áreas pode simplesmente pulá-las agora e voltar se necessário. \section{Interfaces} \seclabel{interfaces} -When discussing data structures, it is important to understand the -difference between a data structure's interface and its implementation. -An interface describes what a data structure does, while an implementation -describes how the data structure does it. +Ao discutir sobre estruturas de dados, é importante entender a diferença entre a interface de uma estrutura de dados e sua imeplementação. +Uma interface descreve o que uma estrutura de dados faz, enquanto uma implementação descreve como a estrutura o faz. -An \emph{interface}, +Uma \emph{interface}, \index{interface}% -\index{abstract data type|see{interface}}% -sometimes also called an \emph{abstract data -type}, defines the set of operations supported by a data structure and -the semantics, or meaning, of those operations. An interface tells us -nothing about how the data structure implements these operations; it only -provides a list of supported operations along with specifications about -what types of arguments each operation accepts and the value returned -by each operation. - -A data structure \emph{implementation}, on the other hand, includes the -internal representation of the data structure as well as the definitions -of the algorithms that implement the operations supported by the data -structure. Thus, there can be many implementations of a single interface. -For example, in \chapref{arrays}, we will see implementations of the -#List# interface using arrays and in \chapref{linkedlists} we will -see implementations of the #List# interface using pointer-based data -structures. Each implements the same interface, #List#, -but in different ways. - -\subsection{The #Queue#, #Stack#, and #Deque# Interfaces} - -The #Queue# interface represents a collection of elements to which we -can add elements and remove the next element. More precisely, the operations -supported by the #Queue# interface are +\index{tipo abstrato de dados|see{interface}}% +às vezes também chamada de \emph{tipo abstrato de dados}(\emph{abstract data type}, em inglês), +define o conjunto de operações aceitas por uma estrutura de dados e a semântica, ou significado, dessas operações. + +Uma interface nos diz nada sobre como a estrutura de dados implementa essas operações; ela somente provê uma lsita de operações aceitas juntamente com as especificações sobre quais tipos de argumentos cada operação aceita e o valor retornado por cada operação. + +A \emph{implementação} de uma estrutura de dados, por outro lado, inclui a representação interna da estrutura de dados assim como as definições dos algoritmos que implementam as operações aceitas pela estrutura de dados. +Então, pode haver muitas implemetações de uma dada interface. +Por exemplo, em \chapref{arrays}, veremos implementações da interface #List# usando arrays e em \chapref{linkedlists} veremos implementações da interface #List# usando estruturas de dados baseadas em ponteiros. Ambas implementam a mesma interface, #List#, mas de modos distintos. + +\subsection{As Interfaces #Queue#, #Stack# e #Deque#} + +A interface #Queue# (Fila, em português) representa uma coleção de elementos à qual podemos +adicionar elementos e remover o próximo elemento. Mais precisamente, as operações +aceitas pela interface #Queue# são \begin{itemize} - \item #add(x)#: add the value #x# to the #Queue# - \item #remove()#: remove the next (previously added) value, #y#, from the #Queue# and return #y# + \item #add(x)#: adiciona o valor #x# à #Queue# + \item #remove()#: remove o próximo (previamente adicionado) valor, #y#, da #Queue# e retorna #y# \end{itemize} -Notice that the #remove()# operation takes no argument. The #Queue#'s -\emph{queueing discipline} decides which element should be removed. -There are many possible queueing disciplines, the most common of which -include FIFO, priority, and LIFO. +Note que +a operação #remove()# não recebe argumentos. +A \emph{política de enfileiramento} da #Queue# decide qual é o próximo elemento a ser removido. + +Existem muitas políticas de enfileiramento possíveis, sendo que entre as mais comuns estão FIFO, por prioridades e LIFO. -A \emph{FIFO (first-in-first-out) #Queue#}, +Uma \emph{#Queue# FIFO (first-in-first-out -- primeiro a entrar, primeiro a sair) }, \index{FIFO queue}% \index{queue!FIFO}% -which is illustrated in -\figref{queue}, removes items in the same order they were added, much -in the same way a queue (or line-up) works when checking out at a cash -register in a grocery store. This is the most common kind of #Queue# -so the qualifier FIFO is often omitted. In other texts, the #add(x)# -and #remove()# operations on a FIFO #Queue# are often called #enqueue(x)# -and #dequeue()#, respectively. +que é ilustrada em +\figref{queue}, remove itens na mesma ordem em que são adicionados, do meio jeito que uma fila de compras em um supermercado funciona. +Esse é o tipo mais comum de #Queue# de forma que o qualificador FIFO é frequentemente omitido. +Em outros textos, as operações #add(x)# e #remove()# em uma #Queue# FIFO são frequentemente chamadas de #enqueue(x)# e #dequeue()#, respectivamente. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/queue}} - \caption[A FIFO queue]{A FIFO #Queue#.} + \caption[Uma queue (fila) FIFO]{Uma #Queue# FIFO.} \figlabel{queue} \end{figure} -A \emph{priority #Queue#}, +Uma \emph{#Queue# com prioridades}, \index{priority queue}% +\index{fila com prioridades}% \index{priority queue|seealso{heap}}% \index{queue!priority}% -illustrated in \figref{prioqueue}, always -removes the smallest element from the #Queue#, breaking ties arbitrarily. -This is similar to the way in which patients are triaged in a hospital -emergency room. As patients arrive they are evaluated and then placed in -a waiting room. When a doctor becomes available he or she first treats -the patient with the most life-threatening condition. The #remove()# -operation on a priority #Queue# is usually called #deleteMin()# in -other texts. +ilustrada em \figref{prioqueue}, sempre +remove o menor elemento da #Queue#, deciding empates arbitrariamente. +Isso é similar ao modo no qual pacientes passam por triagem em uma sala de emergência de um hospital. Conforme pacientes chegam eles são avalias e então +vão à sala de espera. Quando um médico torna-se disponível, ele primeiro trata o paciente na situação mais grave. A operação #remove()# em um #Queue# com prioridades é geralmente chamada de #deleteMin()# em outros textos. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/prioqueue}} - \caption[A priority queue]{A priority #Queue#.} + \caption[Uma fila com prioridades (no inglês, priority queue)]{Uma #Queue# com prioridades.} \figlabel{prioqueue} \end{figure} @@ -221,326 +154,312 @@ \subsection{The #Queue#, #Stack#, and #Deque# Interfaces} %When a business-class seat becomes available it is given to the most %important customer waiting on an upgrade. -A very common queueing discipline is the LIFO (last-in-first-out) +Uma política de enfileiramento muito comum é a política LIFO (last-in-first-out, último-entrar-primeiro-a-sair, em português) \index{LIFO queue}% \index{LIFO queue|seealso{stack}}% +\index{fila LIFO}% \index{queue!LIFO}% \index{stack}% -discipline, illustrated in \figref{stack}. In a \emph{LIFO Queue}, -the most recently added element is the next one removed. This is best -visualized in terms of a stack of plates; plates are placed on the top of -the stack and also removed from the top of the stack. This structure is -so common that it gets its own name: #Stack#. Often, when discussing a -#Stack#, the names of #add(x)# and #remove()# are changed to #push(x)# -and #pop()#; this is to avoid confusing the LIFO and FIFO queueing -disciplines. +\index{pilha}% +, ilustrada em \figref{stack}. Em uma \emph{Queue LIFO}, +o elemento mais recentemente adicionado é o próximo a ser removido. +Isso é melhor vizualiados em termos de uma pilha de pratos; pratos são +posicionados no topo da pilha a também removidos do topo. Essa estrutura +é tão comum que recebe seu próprio nome: #Stack# (pilha, em português). Frequentemente, ao referenciar uma #Stack#, as operações #add(x)# e #remove()# +recebem os nomes de #push(x)# e #pop()#; isso é para evitar confusões entre as políticas LIFO e FIFO. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/stack}} - \caption[A stack]{A stack.} + \caption[Uma stack (pilha, em português]{Uma stack (pilha, em português).} \figlabel{stack} \end{figure} -A #Deque# +Uma #Deque# \index{deque}% -is a generalization of both the FIFO #Queue# and LIFO #Queue# (#Stack#). -A #Deque# represents a sequence of elements, with a front and a back. -Elements can be added at the front of the sequence or the back of the -sequence. The names of the #Deque# operations are self-explanatory: -#addFirst(x)#, #removeFirst()#, #addLast(x)#, and #removeLast()#. It is -worth noting that a #Stack# can be implemented using only #addFirst(x)# -and #removeFirst()# while a FIFO #Queue# can be implemented using -#addLast(x)# and #removeFirst()#. +é uma generalização de ambos #Queue# FIFO e #Queue# LIFO (#Stack#). +Uma #Deque# representa uma sequência de elementos, com uma frente e verso (um início e um fim). +Elementos podem ser adicionados na frente da sequência ou no final da sequência. +Os nomes das operações da #Deque# são auto-explicativos: +#addFirst(x)#, #removeFirst()#, #addLast(x)# e #removeLast()#. +Vale notar que uma #Stack# pode ser implementada usando somente #addFirst(x)# +e #removeFirst()# enquanto uma #Queue# FIFO pode ser implementada usando +#addLast(x)# e #removeFirst()#. -\subsection{The #List# Interface: Linear Sequences} +\subsection{A Interface #List#: Sequências Lineares} -This book will talk very little about the FIFO #Queue#, #Stack#, or -#Deque# interfaces. This is because these interfaces are subsumed by the -#List# interface. A #List#, +Este livro vai falar muito pouco sobre +as interfaces #Queue# FIFO, #Stack#, ou #Deque#. Isso porque essas interfaces são englobadas pela interface +#List#. Uma #List#, \index{List@#List#}% -illustrated in \figref{list}, represents a -sequence, $#x#_0,\ldots,#x#_{#n#-1}$, of values. The #List# interface -includes the following operations: +ilustrada em \figref{list}, representa uma +sequência, $#x#_0,\ldots,#x#_{#n#-1}$, de valores. A interface #List# inclui as operações a seguir: \begin{enumerate} - \item #size()#: return #n#, the length of the list - \item #get(i)#: return the value $#x#_{#i#}$ - \item #set(i,x)#: set the value of $#x#_{#i#}$ equal to #x# - \item #add(i,x)#: add #x# at position #i#, displacing + \item #size()#: retorna #n#, o comprimento da lista + \item #get(i)#: retorna o valor $#x#_{#i#}$ + \item #set(i,x)#: atribui o valor $#x#_{#i#}$ igual a #x# + \item #add(i,x)#: adicionar #x# à posição #i#, deslocando $#x#_{#i#},\ldots,#x#_{#n#-1}$; \\ - Set $#x#_{j+1}=#x#_j$, for all - $j\in\{#n#-1,\ldots,#i#\}$, increment #n#, and set $#x#_i=#x#$ - \item #remove(i)# remove the value $#x#_{#i#}$, displacing + Atribua $#x#_{j+1}=#x#_j$, para todo + $j\in\{#n#-1,\ldots,#i#\}$, incremente #n#, and faça $#x#_i=#x#$ + \item #remove(i)# remove o valor $#x#_{#i#}$, deslocando $#x#_{#i+1#},\ldots,#x#_{#n#-1}$; \\ - Set $#x#_{j}=#x#_{j+1}$, for all - $j\in\{#i#,\ldots,#n#-2\}$ and decrement #n# + Atribua $#x#_{j}=#x#_{j+1}$, para todo + $j\in\{#i#,\ldots,#n#-2\}$ e decremente #n# \end{enumerate} -Notice that these operations are easily sufficient to implement the -#Deque# interface: +Note que essas operações are facilmente suficientes para implementar +a interface #Deque#: \begin{eqnarray*} #addFirst(x)# &\Rightarrow& #add(0,x)# \\ #removeFirst()# &\Rightarrow& #remove(0)# \\ #addLast(x)# &\Rightarrow& #add(size(),x)# \\ #removeLast()# &\Rightarrow& #remove(size()-1)# \end{eqnarray*} - +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CONTINUAR \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/list}} - \caption[A List]{A #List# represents a sequence indexed by - $0,1,2,\ldots,#n#-1$. In this #List# a call to #get(2)# would return - the value $c$.} + \caption[Uma List]{Uma #List# representa uma sequência indexada por + $0,1,2,\ldots,#n#-1$. Nessa #List# uma chamada para #get(2)# retornaria o valor $c$.} \figlabel{list} \end{figure} -Although we will normally not discuss the #Stack#, #Deque# and FIFO -#Queue# interfaces in subsequent chapters, the terms #Stack# and #Deque# -are sometimes used in the names of data structures that implement the -#List# interface. When this happens, it highlights the fact that -these data structures can be used to implement the #Stack# or #Deque# -interface very efficiently. For example, the #ArrayDeque# class is an -implementation of the #List# interface that implements all the #Deque# -operations in constant time per operation. +Embora normalmente não discutiremos as interfaces #Stack#, #Deque# e #Queue# FIFO nos seguintes capítulos, os termos +#Stack# e #Deque# +são às vezes usados nos nomes das estruturas de dados que implementam a interface #List#. Quando isso acontece, destaca-se que essas estruturas de dados podem ser usadas para implementar a interface #Stack# ou #Deque# eficientemente. +Por exemplo, a classe #ArrayDeque# é uma implementação da interface #List# que implementa todas as operações #Deque# em tempo constante por operação. +\subsection{A Interface #USet#: Unordered Sets (Conjuntos Desordenados)} -\subsection{The #USet# Interface: Unordered Sets} - -The #USet# +A interface #USet# \index{USet@#USet#}% -interface represents an unordered set of unique elements, which -mimics a mathematical \emph{set}. A #USet# contains #n# \emph{distinct} -elements; no element appears more than once; the elements are in no -specific order. A #USet# supports the following operations: +representa um conjunto desordenado de elementos únicos (sem repetições), que simulam um \emph{conjunto} (em inglês, \emph{set}) matemático. +Um #USet# contém #n# elementos \emph{distintos}; nenhum elemento aparece mais de uma vez; os elementos não estão em nenhuma ordem específica. +Um #USet# aceita as seguintes operações: \begin{enumerate} - \item #size()#: return the number, #n#, of elements in the set - \item #add(x)#: add the element #x# to the set if not already present; \\ - Add #x# to the set provided that there - is no element #y# in the set such that #x# equals #y#. Return #true# - if #x# was added to the set and #false# otherwise. - \item #remove(x)#: remove #x# from the set; \\ - Find an element #y# in the set such that #x# equals - #y# and remove #y#. Return #y#, or #null# if no such element exists. - \item #find(x)#: find #x# in the set if it exists; \\ - Find an element #y# in the set such that #y# equals - #x#. Return #y#, or #null# if no such element exists. + \item #size()#: retorna o número, #n#, de elementos no conjunto + \item #add(x)#: adiciona o elemento #x# ao conjunto se não já presente; \\ + Adiciona #x# ao conjunto considerando que não há elemento #y# no conjunto tal que #x# é considerado igual a #y#. Retorna #true# se #x# foi adicionado ao conjunto e #false# caso contrário. + \item #remove(x)#: remove #x# do conjunto; \\ + Achar um elemento #y# no conjunto tal que #x# iguala + a #y# e remove #y#. Retorna #y#, ou #null# se tal elemento não existe. + \item #find(x)#: achar #x# no conjunto se existir; \\ + Achar um elemento #y# no conjunto tal que #y# seja igual a #x#. Retornar #y# ou #null# se tal elemento não exista no conjunto. \end{enumerate} -These definitions are a bit fussy about distinguishing #x#, the element -we are removing or finding, from #y#, the element we may remove or find. -This is because #x# and #y# might actually be distinct objects that -are nevertheless treated as equal.\javaonly{\footnote{In Java, this is -done by overriding the class's #equals(y)# and #hashCode()# methods.}} -Such a distinction is useful because it allows for the creation of -\emph{dictionaries} or \emph{maps} that map keys onto values. +Essas definições são um pouco cuidados ao distinguir #x#, o elemento que estamos a remover ou buscar, de #y#, o elemento que poderemos remover ou achar. +Isso é porque #x# e #y# podem ser na realidade objetos distintos que são, para todos os efeitos nessa situação, tratados como iguais. + +\javaonly{\footnote{Em Java, isso é realizado ao sobrescrever os métodos da classe dos elementos #equals(y)# e #hashCode()#.}} +Tal distinção é útil porque permite a criação de +\emph{dicionários} ou \emph{mapas} que mapeiam chaves em valores. \index{dictionary}% +\index{dicionário}% \index{map}% +\index{mapa}% -To create a dictionary/map, one forms compound objects called #Pair#s, +Para criar um dicionário/mapa, forma-se objetos chamados #Pair# (par, em português) \index{pair}% -each of which contains a \emph{key} and a \emph{value}. Two #Pair#s -are treated as equal if their keys are equal. If we store some pair -$(#k#,#v#)$ in a #USet# and then later call the #find(x)# method using -the pair $#x#=(#k#,#null#)$ the result will be $#y#=(#k#,#v#)$. In other -words, it is possible to recover the value, #v#, given only the key, #k#. - - -\subsection{The #SSet# Interface: Sorted Sets} +cada qual contém uma \emph{chave} (\emph{key}) e um \emph{valor} (\emph{valor}). +Dois pares #Pair# são tratados como iguais se suas chaves forem iguais. +Se armazenamos algum par +$(#k#,#v#)$ +em um #USet# e então depois chamamos o método #find(x)# usando +o par +$#x#=(#k#,#null#)$ o resultado será $#y#=(#k#,#v#)$. +Em outras palavras, é possível recuperar o valor, #v#, usando somente a chave #k#. + +\subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} \seclabel{sset} \index{SSet@#SSet#}% -The #SSet# interface represents a sorted set of elements. An #SSet# -stores elements from some total order, so that any two elements #x# -and #y# can be compared. In code examples, this will be done with a -method called #compare(x,y)# in which +A interface #SSet# representa um conjunto ordenado de elementos. +Um #SSet# guarda elementos provientes de alguma ordem total, tal que quaisquer dois elementos #x# e #y# podem ser comparados. Em exemplos de código, isso será feito com um método chamado #compare(x,y)# no qual \[ #compare(x,y)# \begin{cases} - {}<0 & \text{if $#x#<#y#$} \\ - {}>0 & \text{if $#x#>#y#$} \\ - {}=0 & \text{if $#x#=#y#$} + {}<0 & \text{se $#x#<#y#$} \\ + {}>0 & \text{se $#x#>#y#$} \\ + {}=0 & \text{se $#x#=#y#$} \end{cases} \] \index{compare@#compare(x,y)#}% -An #SSet# supports the #size()#, #add(x)#, and #remove(x)# methods with -exactly the same semantics as in the #USet# interface. The difference -between a #USet# and an #SSet# is in the #find(x)# method: +Um #SSet# aceita os métodos #size()#, #add(x)# e #remove(x)# com +exatamente a mesma semântica que a interface #USet#. A diferença +entre um #USet# e um #SSet# está no método #find(x)#: \begin{enumerate} \setcounter{enumi}{3} -\item #find(x)#: locate #x# in the sorted set; \\ - Find the smallest element #y# in the set such that $#y# \ge #x#$. - Return #y# or #null# if no such element exists. +\item #find(x)#: localizar #x# no conjunto ordenado; \\ + Achar o menor elemento $y$ no conjunto tal que $#y# \ge #x#$. + Retornar #y# ou #null# se o elemento não existir. \end{enumerate} -This version of the #find(x)# operation is sometimes referred to -as a \emph{successor search}. +Essa versão da operação #find(x)# é às vezes chamada de \emph{busca de sucessor} (em inglês, \emph{successor search}). \index{successor search}% -It differs in a fundamental way from -#USet.find(x)# since it returns a meaningful result even when there is -no element equal to #x# in the set. - -The distinction between the #USet# and #SSet# #find(x)# operations is -very important and often missed. The extra functionality provided -by an #SSet# usually comes with a price that includes both a larger -running time and a higher implementation complexity. For example, most -of the #SSet# implementations discussed in this book all have #find(x)# -operations with running times that are logarithmic in the size of the set. -On the other hand, the implementation of a #USet# as a #ChainedHashTable# -in \chapref{hashing} has a #find(x)# operation that runs in constant -expected time. When choosing which of these structures to use, one should -always use a #USet# unless the extra functionality offered by an #SSet# -is truly needed. +\index{busca de sucessor}% +Ela difere de um modo fundamental de #USet.find(x)# pois retorna um resultado mesmo quando não há elemento igual a #x# no conjunto. -% \subsection{The DynamicString Interface} +A distinção entre a operação #find(x)# de #USet# e de #SSet# é muito importante e frequentemente esquecida ou não percebida. A funcionalidade extra provida por um #SSet# frequentemente vem com um preço que inclui tanto tempo de execução mais alto quanto uma complexidade de implementação maior. +Por exemplo, a maior parte das implementações #SSet# discutidas neste livro tem operações #find(x)# com tempo de execução que são logaritmicas no tamanho do conjunto. +Por outro lado, a implementação de um #USet# com uma #ChainedHashTable# em +\chapref{hashing} tem uma operação #find(x)# que roda em tempo esperado constante. +Ao escolher quais dessas estruturas usar, deve-se sempre usar um #USet# a menos que a funcionalidade extra oferecida por um #SSet# é verdadeiramente necessária. -\section{Mathematical Background} +% \subsection{The DynamicString Interface} -In this section, we review some mathematical notations and tools -used throughout this book, including logarithms, big-Oh notation, and -probability theory. This review will be brief and is not intended as -an introduction. Readers who feel they are missing this background are -encouraged to read, and do exercises from, the appropriate sections of -the very good (and free) textbook on mathematics for computer science +\section{Conceitos Matemáticos} +Nesta seção, revisaremos algumas noções matemáticas e ferramentas usadas +ao longo deste livro, incluindo logaritmos, notação big-Oh, e +teoria das probabilidades. Esta revisão será breve e não tem a intenção +de ser uma introdução. Leitores que acham que precisam saber mais destes conceitos +são encorajados a ler, e fazer os exercícios relacionados, as seções apropriadas +do excelente (e livre) texto didático sobre Matemática para Ciência da Computação \cite{llm11}. -\subsection{Exponentials and Logarithms} +\subsection{Exponenciais e Logaritmos} \index{exponential}% -The expression $b^x$ denotes the number $b$ raised to the power of $x$. -If $x$ is a positive integer, then this is just the value of $b$ -multiplied by itself $x-1$ times: +\index{exponencial}% +A expressão $b^x$ denota o número $b$ elevado à potência $x$. +Se $x$ é um inteiro positivo, então o resultado é somente o valor de $b$ multiplicado por ele mesmo $x-1$ vezes: \[ b^x = \underbrace{b\times b\times \cdots \times b}_{x} \enspace . \] -When $x$ is a negative integer, $b^{-x}=1/b^{x}$. When $x=0$, $b^x=1$. -When $b$ is not an integer, we can still define exponentiation in terms -of the exponential function $e^x$ (see below), which is itself defined in -terms of the exponential series, but this is best left to a calculus text. - -\index{logarithm}% -In this book, the expression $\log_b k$ denotes the \emph{base-$b$ logarithm} -of $k$. That is, the unique value $x$ that satisfies +Quando $x$ é um inteiro negativo, $b^{-x}=1/b^{x}$. +Quando $x=0$, $b^x=1$. +Quando $b$ não é um inteiro, podemos ainda definir a exponenciação em termos da função exponencial +$e^x$ (ver a seguir), que é definida em termos de uma série exponencial, mas isso é melhor deixar para um texto sobre cálculo. + +\index{logaritmo}% +Neste livro, a expressão +$\log_b k$ denota o \emph{logaritmo base $b$} +de $k$. Isto é, o único valor $x$ que satisfaz \[ b^{x} = k \enspace . \] -Most of the logarithms in this book are base 2 (\emph{binary logarithms}). -\index{binary logarithm}% -\index{logarithm!binary}% -For these, we omit the base, so that $\log k$ is shorthand for +A maior parte dos logaritmos neste livro são base 2 (\emph{logaritmos binários}). +\index{logaritmo binário}% +\index{logaritmo!binário}% +Nesse caso, omitiremos a base, de forma que +$\log k$ é uma abreviação para $\log_2 k$. -An informal, but useful, way to think about logarithms is to think -of $\log_b k$ as the number of times we have to divide $k$ by $b$ -before the result is less than or equal to 1. For example, when one -does binary search, each comparison reduces the number of possible -answers by a factor of 2. This is repeated until there is at most one -possible answer. Therefore, the number of comparison done by binary -search when there are initially at most $n+1$ possible answers is at -most $\lceil\log_2(n+1)\rceil$. - -\index{natural logarithm}% -\index{logarithm!natural}% -Another logarithm that comes up several times in this book is the -\emph{natural logarithm}. Here we use the notation $\ln k$ to denote -$\log_e k$, where $e$ --- \emph{Euler's constant} --- is given by -\index{Euler's constant}% -\index{e@$e$ (Euler's constant)}% +Um jeito informal, mas útil, de pensar sobre logaritmos é pensar +de $\log_b k$ como o número de vezes que temos que dividir $k$ por $b$ +antes do resultador ser menor ou igual a 1. Por exemplo, quando alguém realiza +busca binária, cada comparação reduz o número de possíveis respostas por +um fator de 2. Isso é repetido até que existe no máximo uma única resposta +possível. Portanto, o número de comparações feitas pela busca binária +existem inicialmente até $n+1$ possíveis respostas é, no máximo, +$\lceil\log_2(n+1)\rceil$. + +\index{logaritmo natural}% +\index{logaritmo!natural}% +Outro logaritmo que aparece várias vezes neste livro é o +\emph{logaritmo natural}. Aqui usamos a notação $\ln k$ para denotar +$\log_e k$, onde $e$ --- \emph{Constante de Euler} --- é dado por +\index{Constante de Euler}% +\index{e@$e$ (Constante de Euler)}% \[ e = \lim_{n\rightarrow\infty} \left(1+\frac{1}{n}\right)^n \approx 2.71828 \enspace . \] -The natural logarithm comes up frequently because it is the value -of a particularly common integral: +O logaritmo natural apareces frequentemente porque é o valor +de uma integral particularmente comum: \[ \int_{1}^{k} 1/x\,\mathrm{d}x = \ln k \enspace . \] -Two of the most common manipulations we do with logarithms are removing -them from an exponent: +Duas das manipulações mais comuns que fazemos com logaritmos são +removê-los de um expoente: \[ b^{\log_b k} = k \] -and changing the base of a logarithm: +e trocar a base de um logaritmo: \[ \log_b k = \frac{\log_a k}{\log_a b} \enspace . \] -For example, we can use these two manipulations to compare the natural and binary logarithms +Por exemplo, podemos usar essas duas manipulações para comparar os logaritmos natural e binário: \[ \ln k = \frac{\log k}{\log e} = \frac{\log k}{(\ln e)/(\ln 2)} = (\ln 2)(\log k) \approx 0.693147\log k \enspace . \] -\subsection{Factorials} -\seclabel{factorials} +\subsection{Fatoriais} +\seclabel{fatoriais} -\index{factorial}% -In one or two places in this book, the \emph{factorial} function is used. -For a non-negative integer $n$, the notation $n!$ (pronounced ``$n$ factorial'') is defined to mean +\index{fatorial}% +Em uma ou duas partes deste livro, +a função \emph{fatorial} é usada. +Para um inteiro não-negativo $n$, a notação $n!$ (pronunciada ``$n$ fatorial'') é definida para representar \[ n! = 1\cdot2\cdot3\cdot\cdots\cdot n \enspace . \] -Factorials appear because $n!$ counts the number of distinct -permutations, i.e., orderings, of $n$ distinct elements. -\index{permutation}% -For the special case $n=0$, $0!$ is defined as 1. +Fatoriais aparecem porque $n!$ conta o número de permutações distintas, i.e., ordenações, de $n$ elementos distintos. +\index{permutação}% +Para o caso especial $n=0$, $0!$ é definido como 1. -\index{Stirling's Approximation}% -The quantity $n!$ can be approximated using \emph{Stirling's Approximation}: +\index{Aproximação de Stirling}% +A quantidade $n!$ pode ser aproximada usando a \emph{Aproximação de Stirling}: \[ n! = \sqrt{2\pi n}\left(\frac{n}{e}\right)^{n}e^{\alpha(n)} \enspace , \] -where +onde \[ \frac{1}{12n+1} < \alpha(n) < \frac{1}{12n} \enspace . \] -Stirling's Approximation also approximates $\ln(n!)$: +a Aproximação de Stirling também aproxima $\ln(n!)$: \[ \ln(n!) = n\ln n - n + \frac{1}{2}\ln(2\pi n) + \alpha(n) \] -(In fact, Stirling's Approximation is most easily proven by approximating -$\ln(n!)=\ln 1 + \ln 2 + \cdots + \ln n$ by the integral +(De fato, a Aproximação de Stirling é mais facilmente provada por aproximando +$\ln(n!)=\ln 1 + \ln 2 + \cdots + \ln n$ pela integral $\int_1^n \ln n\,\mathrm{d}n = n\ln n - n +1$.) -\index{binomial coefficients}% -Related to the factorial function are the \emph{binomial coefficients}. -For a non-negative integer $n$ and an integer $k\in\{0,\ldots,n\}$, -the notation $\binom{n}{k}$ denotes: +\index{coeficientes binomiais}% +Relacionados à função fatorial são os +\emph{coeficientes binomiais}. +Para um inteiro não-negativo $n$ e um inteiro $k\in\{0,\ldots,n\}$, +a notação $\binom{n}{k}$ denota: \[ \binom{n}{k} = \frac{n!}{k!(n-k)!} \enspace . \] -The binomial coefficient $\binom{n}{k}$ (pronounced ``$n$ choose $k$'') -counts the number of subsets of an $n$ element set that have size $k$, -i.e., the number of ways of choosing $k$ distinct integers from the -set $\{1,\ldots,n\}$. +O coeficiente binomial + $\binom{n}{k}$ (pronunciado ``$n$, escolhidos $k$ a $k$'') + conta o número de subconjuntos com $k$ elementos de um conjunto de tamanho $n$ +i.e., o número de forma de escolher $k$ inteiros distintos do conjunto $\{1,\ldots,n\}$. -\subsection{Asymptotic Notation} +\subsection{Notação assintótica} -\index{asymptotic notation}% -\index{big-Oh notation}% +\index{notação assintótica}% +\index{notação big-Oh}% \index{O@$O$ notation}% -When analyzing data structures in this book, we want to talk about -the running times of various operations. The exact running times will, -of course, vary from computer to computer and even from run to run on an -individual computer. When we talk about the running time of an operation -we are referring to the number of computer instructions performed during -the operation. Even for simple code, this quantity can be difficult to -compute exactly. Therefore, instead of analyzing running times exactly, -we will use the so-called \emph{big-Oh notation}: For a function $f(n)$, -$O(f(n))$ denotes a set of functions, +Ao analisar estruturas de dados neste livro, queremos discutir +os tempos de execução de várias operações. O exato tempo de execução irá, +é claro, variar de computar a computador e até entre diferentes execuções +no mesmo computador. +Ao discutirmos sobre o tempo de execução de uma operação estamos nos +referindo ao número de intruções de computador executadas durante a operação. +Mesmo para códigos simples, essa quantidade pode ser difícil para computar com exatidão. +Portanto, em vez de analisar tempos de execução exatos, iremos usar a famosa \emph{notação big-Oh}: Para uma função $f(n)$, $O(f(n))$ denota um conjunto de funções, + \[ O(f(n)) = \left\{ \begin{array}{l} - g(n):\mbox{there exists $c>0$, and $n_0$ such that} \\ - \quad\mbox{$g(n) \le c\cdot f(n)$ for all $n\ge n_0$} + g(n):\mbox{existe $c>0$, e $n_0$ tal que} \\ + \quad\mbox{$g(n) \le c\cdot f(n)$ para todo $n\ge n_0$} \end{array} \right\} \enspace . \] -Thinking graphically, this set consists of the functions $g(n)$ where -$c\cdot f(n)$ starts to dominate $g(n)$ when $n$ is sufficiently large. +Pensando graficamente, esse conjunto consiste das funções $g(n)$ +onde +$c\cdot f(n)$ começa a dominar $g(n)$ quando $n$ é suficientemente grande. + +Geralmente usamos notação assintótica para simplificar funções. Por exemplo, -We generally use asymptotic notation to simplify functions. For example, -in place of $5n\log n + 8n - 200$ we can write $O(n\log n)$. -This is proven as follows: +em vez de usar $5n\log n + 8n - 200$ podemos escrever $O(n\log n)$. +Isso é provado da seguinte forma: \begin{align*} 5n\log n + 8n - 200 & \le 5n\log n + 8n \\ @@ -548,97 +467,97 @@ \subsection{Asymptotic Notation} \\ & \le 13n\log n \enspace . \end{align*} -This demonstrates that the function $f(n)=5n\log n + 8n - 200$ is in -the set $O(n\log n)$ using the constants $c=13$ and $n_0 = 2$. +Isso demontra que a função $f(n)=5n\log n + 8n - 200$ está no conjunto +$O(n\log n)$ usando as constantes $c=13$ e $n_0 = 2$. -A number of useful shortcuts can be applied when using asymptotic -notation. First: +Vários atalhos podem ser aplicados ao usar notação assintótica. +Primeiro: \[ O(n^{c_1}) \subset O(n^{c_2}) \enspace ,\] -for any $c_1 < c_2$. Second: For any constants $a,b,c > 0$, +para qualquer $c_1 < c_2$. Segundo: para quaisquer constantes $a,b,c > 0$, \[ O(a) \subset O(\log n) \subset O(n^{b}) \subset O({c}^n) \enspace . \] -These inclusion relations can be multiplied by any positive value, -and they still hold. For example, multiplying by $n$ yields: +Essas relações de inclusão podem ser multiplicadas por qualquer valor positivo, +e eles ainda valerão. +Por exemplo, multiplicar por $n$ chegamos a: \[ O(n) \subset O(n\log n) \subset O(n^{1+b}) \subset O(n{c}^n) \enspace . \] -Continuing in a long and distinguished tradition, we will abuse this -notation by writing things like $f_1(n) = O(f(n))$ when what we really -mean is $f_1(n) \in O(f(n))$. We will also make statements like ``the -running time of this operation is $O(f(n))$'' when this statement should -be ``the running time of this operation is \emph{a member of} $O(f(n))$.'' -These shortcuts are mainly to avoid awkward language and to make it -easier to use asymptotic notation within strings of equations. - -A particularly strange example of this occurs when we write statements like +Continuando uma longa e notável tradição, iremos abusar dessa notação +ao escrever +coisas do tipo $f_1(n) = O(f(n))$ quando o que realmente queremos +expressar é $f_1(n) \in O(f(n))$. +Também iremos fazer afirmações do tipo ``o tempo de execução dessa operação +é $O(f(n))$'' quando essa afirmação deveria ser na verdade +``o tempo de execução dessa operação é \emph{um membro de} $O(f(n))$.'' +Esses atalhos servem principalmente para evitar frases estranhas e tornar +mais fácil o uso de notação assintótica em manipulações sequenciais de equações. + +A exemplo particulamente estranho disso ocorre quando escrevemos afirmações tipo \[ T(n) = 2\log n + O(1) \enspace . \] -Again, this would be more correctly written as +De novo, isso seria mais corretamente escrito da forma \[ - T(n) \le 2\log n + [\mbox{some member of $O(1)$]} \enspace . + T(n) \le 2\log n + [\mbox{algum membro de $O(1)$]} \enspace . \] -The expression $O(1)$ also brings up another issue. Since there is -no variable in this expression, it may not be clear which variable is -getting arbitrarily large. Without context, there is no way to tell. -In the example above, since the only variable in the rest of the equation -is $n$, we can assume that this should be read as $T(n) = 2\log n + -O(f(n))$, where $f(n) = 1$. - -Big-Oh notation is not new or unique to computer science. It was -used by the number theorist Paul Bachmann as early as 1894, and is -immensely useful for describing the running times of computer algorithms. -Consider the following piece of code: +A expressão $O(1)$ também traz à tona outra questão. +Como não tem nenhuma variável nessa expressão, pode não ser claro qual variável está aumentando. Sem contexto, não tem como dizer. + +No exemplo anterior, como a única variável no resto da equação é $n$, podemos assumir que isso deve ser lido como $T(n)=2\log n+O(f(n))$, onde $f(n) = 1$. + +A notação +Big-Oh não é nova nem exclusiva à Ciência da Computação. Ela foi +usada pelo matemático especialista em Teoria dos Números +Paul Bachmann desde pelo menos 1894, e é imensamente útil +para descrever o tempo de execução de algoritmos de computadores. +Considere o seguinte trecho de código: \javaimport{junk/Simple.snippet()} \cppimport{ods/Simple.snippet()} -One execution of this method involves +Uma execução desse método involve: \begin{itemize} - \item $1$ assignment (#int\, i\, =\, 0#), - \item $#n#+1$ comparisons (#i < n#), - \item #n# increments (#i++#), - \item #n# array offset calculations (#a[i]#), and - \item #n# indirect assignments (#a[i] = i#). + \item $1$ atribuição (#int\, i\, =\, 0#), + \item $#n#+1$ comparações (#i < n#), + \item #n# incrementos (#i++#), + \item #n# cálculo de deslocamentos em array (#a[i]#), e + \item #n# atribuições indiretas (#a[i] = i#). \end{itemize} -So we could write this running time as +Então podemos escrever esse tempo de execução como \[ T(#n#)=a + b(#n#+1) + c#n# + d#n# + e#n# \enspace , \] -where $a$, $b$, $c$, $d$, and $e$ are constants that depend on the -machine running the code and represent the time to perform assignments, -comparisons, increment operations, array offset calculations, and indirect -assignments, respectively. However, if this expression represents the -running time of two lines of code, then clearly this kind of analysis -will not be tractable to complicated code or algorithms. Using big-Oh -notation, the running time can be simplified to +onde $a$, $b$, $c$, $d$ e $e$ são constantes que dependem +da máquina rodando o código e que representam o tempo para realizar atribuições, +comparações, incrementos, cálculos de deslocamento em array, e atribuições indiretas, respectivamente. +Entretanto, se essa expressão representa o tempo de execução de duas linhas de +código, então claramente esse tpo de análise não será viável para códigos ou algoritmos complicados. +Ao usar a notação big-Oh, o tempo de execução pode ser simplificado a \[ T(#n#)= O(#n#) \enspace . \] -Not only is this more compact, but it also gives nearly as much -information. The fact that the running time depends on the constants $a$, -$b$, $c$, $d$, and $e$ in the above example means that, in general, it -will not be possible to compare two running times to know which is faster -without knowing the values of these constants. Even if we make the -effort to determine these constants (say, through timing tests), then -our conclusion will only be valid for the machine we run our tests on. - -Big-Oh notation allows us to reason at a much higher level, making -it possible to analyze more complicated functions. If two algorithms -have the same big-Oh running time, then we won't know which is faster, -and there may not be a clear winner. One may be faster on one machine, -and the other may be faster on a different machine. However, if the -two algorithms have demonstrably different big-Oh running times, then -we can be certain that the one with the smaller running time will be -faster \emph{for large enough values of #n#}. - -An example of how big-Oh notation allows us to compare two different -functions is shown in \figref{intro-asymptotics}, which compares the rate -of growth of $f_1(#n#)=15#n#$ versus $f_2(n)=2#n#\log#n#$. It might be -that $f_1(n)$ is the running time of a complicated linear time algorithm -while $f_2(n)$ is the running time of a considerably simpler algorithm -based on the divide-and-conquer paradigm. This illustrates that, -although $f_1(#n#)$ is greater than $f_2(n)$ for small values of #n#, -the opposite is true for large values of #n#. Eventually $f_1(#n#)$ -wins out, by an increasingly wide margin. Analysis using big-Oh notation -told us that this would happen, since $O(#n#)\subset O(#n#\log #n#)$. +Isso não somente é mais compacto, mas também provê praticamente a mesma informação. +O fato de que o tempo de execução depende das constantes +$a$, $b$, $c$, $d$ e $e$ +no exemplo anterior significa que, em geral, não será possível comparar +dois tempos de execução para saber qual é mais rápido sem saber os valores dessas constantes. +Mesmo se fizermos esforços para determinar essas constantes (digamos, usando medindo o tempo de testes), então nossa conclusão será válida somente para a máquina +na qual rodamos nossos testes. + +Notação Big-Oh nos permite avaliar a situação a um nível mais alto, +possibilitando a análise de funções mais complicadas. +Se dois algoritmos tem o tempo tempo Big-Oh, então não saberemos qual é mais rápido +e que não pode não haver um ganhador em todas as situações. Um algoritmo pode ser mais rápido em uma máquina enquanto o outro em uma máquina diferente. Porém, se os dois algoritmos tem tempos de execução big-Oh distintos, então +teremos certeza que aquele com menor função Big-Oh será mais rápido \emph{para valores #n# grandes o suficientes}. + +Um exemplo de como a notação big-Oh nos permite comparar duas diferentes funções é mostrado em \figref{intro-asymptotics}, que compara a taxa de crescimento +de $f_1(#n#)=15#n#$ versus $f_2(n)=2#n#\log#n#$. +Hipotéticamente, $f_1(n)$ seria o tempo de execução de um complicado algoritmo de tempo linear enquanto $f_2(n)$ é o tempo de execução de um algoritmo bem mais simples baseado no paradigma de divisão e conquista. +Isso exemplica essa situação, +embora + $f_1(#n#)$ seja maior que $f_2(n)$ para valores baixos de #n#, + o oposto é verdade para valores mais altos de #n#. +Eventualmente $f_1(#n#)$ ganha, e por uma margem crescente. +Análise usando notação big-Oh +nos indica que isso aconteceria, pois + $O(#n#)\subset O(#n#\log #n#)$. \begin{figure} \begin{center} @@ -647,185 +566,155 @@ \subsection{Asymptotic Notation} \resizebox{\tmpa}{!}{\input{images/bigoh-1.tex}}\\[4ex] \resizebox{.98\linewidth}{!}{\input{images/bigoh-2.tex}} \end{center} - \caption{Plots of $15#n#$ versus $2#n#\log#n#$.} + \caption{Comparação de $15#n#$ versus $2#n#\log#n#$.} \figlabel{intro-asymptotics} \end{figure} -In a few cases, we will use asymptotic notation on functions with more -than one variable. There seems to be no standard for this, but for our -purposes, the following definition is sufficient: +Em alguns casos, usaremos notação assintótica em funções com mais de uma variável. +Pode parecer que não padrão para isso, mas para nossos objetivos, a seguinte definição é suficiente: \[ O(f(n_1,\ldots,n_k)) = \left\{\begin{array}{@{}l@{}} - g(n_1,\ldots,n_k):\mbox{there exists $c>0$, and $z$ such that} \\ + g(n_1,\ldots,n_k):\mbox{existe $c>0$, e $z$ tal que} \\ \quad \mbox{$g(n_1,\ldots,n_k) \le c\cdot f(n_1,\ldots,n_k)$} \\ - \qquad \mbox{for all $n_1,\ldots,n_k$ such that $g(n_1,\ldots,n_k)\ge z$} + \qquad \mbox{para todo $n_1,\ldots,n_k$ tal que $g(n_1,\ldots,n_k)\ge z$} \end{array}\right\} \enspace . \] -This definition captures the situation we really care about: when the -arguments $n_1,\ldots,n_k$ make $g$ take on large values. This definition -also agrees with the univariate definition of $O(f(n))$ when $f(n)$ -is an increasing function of $n$. The reader should be warned that, -although this works for our purposes, other texts may treat multivariate -functions and asymptotic notation differently. - - -\subsection{Randomization and Probability} +Essa definição captura a situação que mais nos importa: +quando os argumentos +$n_1,\ldots,n_k$ faz $g$ assumir valores altos. +Essa definição também está de acordo com a definição univariada de +$O(f(n))$ quando $f(n)$ é uma função crescente de $n$. +O leitor deve ficar alerta de que, embora isso funciona para nosso objetivo neste livro, outros textos podem tratar funções multivariadas e notação assintótica diferentemente. + +\subsection{Randomização e Probabilidades} \seclabel{randomization} -\index{randomization}% -\index{probability}% +\index{aleatorização}% +\index{probabilidade}% \index{randomized data structure}% +\index{estrutura de dados aleatorizada}% \index{randomized algorithm}% -Some of the data structures presented in this book are \emph{randomized}; -they make random choices that are independent of the data being stored -in them or the operations being performed on them. For this reason, -performing the same set of operations more than once using these -structures could result in different running times. When analyzing these -data structures we are interested in their average or \emph{expected} -running times. -\index{expected running time}% -\index{running time!expected}% - -Formally, the running time of an operation on a randomized data structure -is a random variable, and we want to study its \emph{expected value}. -\index{expected value}% -For -a discrete random variable $X$ taking on values in some countable -universe $U$, the expected value of $X$, denoted by $\E[X]$, is given -by the formula +Algumas das estruturas de dados apresentadas neste livro são \emph{randomizadas}\footnote{Randomização é um neologismo para indicar o uso de números aleatórios que influencia na execução de um algoritmo}; +eles fazem escolhas aleatórias que são independentes dos dados nelas armazenadas +os nas operações realizadas neles. Por essa razão, +realizar o mesmo conjunto de operações mais de uma vez usando essas +estruturas pode resultar em tempos de execução variáveis. Ao analisar essas +estruturas de dados estamos interessados no seu tempo de execução médio ou \emph{esperado}. +\index{tempo de execução esperado}% +\index{tempo de execução!esperado}% + +Formalmente, o tempo de execução de uma operação em uma estrutura de dados randomizada e queremos estudar o seu \emph{valor esperado}; +\index{valor esperado}% +Para uma variável aleatória discreta + $X$ assumindo valores em um universo contável +$U$, o valor esperado de $X$, denotado $\E[X]$, é dado pela fórmula \[ \E[X] = \sum_{x\in U} x\cdot\Pr\{X=x\} \enspace . \] -Here $\Pr\{\mathcal{E}\}$ denotes the probability that the event -$\mathcal{E}$ occurs. In all of the examples in this book, these -probabilities are only with respect to the random choices made by the -randomized data structure; there is no assumption that the data stored -in the structure, nor the sequence of operations performed on the -data structure, is random. - -One of the most important properties of expected values is \emph{linearity -of expectation}. -\index{linearity of expectation}% -For any two random variables $X$ and $Y$, + +Aqui $\Pr\{\mathcal{E}\}$ denota a probabilidade que o evento +$\mathcal{E}$ ocorre. Em todos os exemplos deste livro, essas probabilidades +são somente em relação às escolhas aleatórias feitas pela estrutura de dados randomizada; não supomos que os dados armazenados na estrutura, nem a sequência de operações realizadas na estrutura de dados, seja aleatória. + +Uma das propriedades mais importantes dos valores esperados é +\emph{linearidade da esperança}. +\index{linearidade da esperança}% +Para duas quaisquer variáveis aleatórias $X$ e $Y$, \[ \E[X+Y] = \E[X] + \E[Y] \enspace . \] -More generally, for any random variables $X_1,\ldots,X_k$, +De forma geral, para quaisquer variáveis aleatórias $X_1,\ldots,X_k$, \[ \E\left[\sum_{i=1}^k X_i\right] = \sum_{i=1}^k \E[X_i] \enspace . \] -Linearity of expectation allows us to break down complicated random variables (like the left hand sides of the above equations) into sums of simpler random variables (the right hand sides). - -A useful trick, that we will use repeatedly, is defining \emph{indicator -random variables}. -\index{indicator random variable}% -These binary variables are useful when we want to -count something and are best illustrated by an example. Suppose we toss -a fair coin $k$ times and we want to know the expected number of times -the coin turns up as heads. -\index{coin toss}% -Intuitively, we know the answer is $k/2$, -but if we try to prove it using the definition of expected value, we get +Linearidade da esperança nos permite quebrar variáveis aleatórias complicadas (como os lados esquerdos das equações acima) em somas de variáveis aleatória mais simples (lados direitos). + +Um truque útil, que usaremos repetidamente, é definir \emph{variáveis aleatórias indicadoras}. +\index{variável aleatória indicadora}% +Essas variáveis binárias são úteis quando queremos contar algo e são melhor ilustradas por um exemplo. Suponha we lancemos uma moeda honesta $k$ vezes e que queremos saber o número esperado de vezes que o lado cara aparece. +\index{lançamento de moeda}% +Intuitivamente, sabemos que a resposta é $k/2$, +mas se tentamos provar usando a definição de valor esperado, temos \begin{align*} \E[X] & = \sum_{i=0}^k i\cdot\Pr\{X=i\} \\ & = \sum_{i=0}^k i\cdot\binom{k}{i}/2^k \\ & = k\cdot \sum_{i=0}^{k-1}\binom{k-1}{i}/2^k \\ & = k/2 \enspace . \end{align*} -This requires that we know enough to calculate that $\Pr\{X=i\} -= \binom{k}{i}/2^k$, and that we know the binomial identities -$i\binom{k}{i}=k\binom{k-1}{i-1}$ and $\sum_{i=0}^{k} \binom{k}{i} = 2^{k}$. +Isso requer que saibamos o suficiente para calcular que $\Pr\{X=i\} += \binom{k}{i}/2^k$, e que saibamos as identidades binomiais +$i\binom{k}{i}=k\binom{k-1}{i-1}$ e $\sum_{i=0}^{k} \binom{k}{i} = 2^{k}$. -Using indicator variables and linearity of expectation makes things -much easier. For each $i\in\{1,\ldots,k\}$, define the indicator -random variable +Usando variáveis indicadoras e linearidade da esperança torna esse trabalho bem mais fácil. Para cada +$i\in\{1,\ldots,k\}$, defina a variável aleatória indicadora \[ I_i = \begin{cases} - 1 & \text{if the $i$th coin toss is heads} \\ - 0 & \text{otherwise.} + 1 & \text{se o $i$-ésimo lançamento de moeda é cara} \\ + 0 & \text{caso contrário.} \end{cases} \] -Then +Então \[ \E[I_i] = (1/2)1 + (1/2)0 = 1/2 \enspace . \] -Now, $X=\sum_{i=1}^k I_i$, so +Agora, $X=\sum_{i=1}^k I_i$, então \begin{align*} \E[X] & = \E\left[\sum_{i=1}^k I_i\right] \\ & = \sum_{i=1}^k \E[I_i] \\ & = \sum_{i=1}^k 1/2 \\ & = k/2 \enspace . \end{align*} -This is a bit more long-winded, but doesn't require that we know any -magical identities or compute any non-trivial probabilities. Even better, -it agrees with the intuition that we expect half the coins to turn up as -heads precisely because each individual coin turns up as heads with -a probability of $1/2$. +Esse caminho é mais longo, mas não exige que saibamos identidades mágicas ou que obtenhamos expressões não triviais de probabilidades. Melhor ainda, +ele vai ao encontro à intuição que temos sobre metade das moedas cairem do lado da cara precisamente porque cada moeda individual cai como moeda com probabilidade $1/2$. -\section{The Model of Computation} +\section{O Modelo de Computação} \seclabel{model} -In this book, we will analyze the theoretical running times of operations -on the data structures we study. To do this precisely, we need a -mathematical model of computation. For this, we use the \emph{#w#-bit +Neste libro, iremos analisar o tempo teórico de execução das operações das estruturas de dados que estudamos. Para fazer precisamente isso, precisamos um modelo matemático de computação. Para isso, usamos +o modelo \emph{#w#-bit word-RAM} \index{word-RAM}% \index{RAM}% -model. RAM stands for Random Access Machine. In this model, we -have access to a random access memory consisting of \emph{cells}, each of -which stores a #w#-bit \emph{word}. +. RAM é uma sigla para Random Access Machine --- Máquina de Acesso Aleatório. Nesse modelo, temos acesso a uma memória de acesso aleatório consistindo de \emph{cells}, cada qual armazena +uma #w#-bit \emph{word}, ou seja, um apalavra com #w# bits de memória. \index{word}% -This implies that a memory cell can -represent, for example, any integer in the set $\{0,\ldots,2^{#w#}-1\}$. - -In the word-RAM model, basic operations on words take constant time. -This includes arithmetic operations (#+#, #-#, #*#, #/#, #%#), comparisons -($<$, $>$, $=$, $\le$, $\ge$), and bitwise boolean operations (bitwise-AND, -OR, and exclusive-OR). - -Any cell can be read or written in constant time. A computer's memory -is managed by a memory management system from which we can allocate or -deallocate a block of memory of any size we would like. Allocating a -block of memory of size $k$ takes $O(k)$ time and returns a reference -(a pointer) to the newly-allocated memory block. This reference is -small enough to be represented by a single word. - -The word-size #w# is a very important parameter of this model. The only -assumption we will make about #w# is the lower-bound $#w# \ge \log #n#$, -where #n# is the number of elements stored in any of our data structures. -This is a fairly modest assumption, since otherwise a word is not even -big enough to count the number of elements stored in the data structure. - -Space is measured in words, so that when we talk about the amount of -space used by a data structure, we are referring to the number of words -of memory used by the structure. All of our data structures store values of -a generic type #T#, and we assume an element of type #T# occupies one word -of memory. \javaonly{(In reality, we are storing references to objects -of type #T#, and these references occupy only one word of memory.)} - -\javaonly{The #w#-bit word-RAM model is a fairly close match for the -(32-bit) Java Virtual Machine (JVM) when $#w#=32$.}\cpponly{The #w#-bit -word-RAM model is a fairly close match for modern desktop computers when -$#w#=32$ or $#w#=64$.} The data structures presented in this book don't -use any special tricks that are not implementable \javaonly{on the JVM and most -other architectures.}\cpponly{in C++ on most architectures.} - -\section{Correctness, Time Complexity, and Space Complexity} - -When studying the performance of a data structure, there are three things -that matter most: +Isso implica que uma célula de memória pode representar, por exemplo, qualquer inteiro no conjunto $\{0,\ldots,2^{#w#}-1\}$. + +No modelo word-RAM, operações básicas em words levam tempo constante. +Isso inclui operações aritméticas (#+#, #-#, #*#, #/#, #%#), comparisons +($<$, $>$, $=$, $\le$, $\ge$) e operações booleana bit-a-bit (AND, OR e OR exclusivo bit-a-bit). + +Qualquer célula pode ler lida com escrita em tempo constante. +A memória do computador é gerenciada por um sistema gerenciador de memória a partir do qual podemos alocar or desalocar um bloco de memória de qualquer tamanho que quisermos. Alocar um bloco de memória de tamanho $k$ leva $O(k)$ tempo e retorna uma referência (um ponteiro) para o bloco recém alocado. Essa referência é pequena o suficiente para ser representada por uma única word. + +O tamanho da word #w# é um parâmetro muito importante desse modelo. +A única premissa que faremos sobre #w# é um limitante inferior $#w# \ge \log #n#$, +onde #n# é o número de elmentos guardados em qualquer estrutura de dados. +Essa premissa é bem razoável, pois caso contrário uma word não seria +grande o suficiente para contar o número de elementos guardados na estrutura +de dados. + +Espaço é medido em words, de forma que quando falarmos sobre a quantidade de espaço usado por uma estrutura de dados, estamos nos referindo ao número de words de memória usada pela estrutura. +Todas as nossas estruturas de dados guardam valores de um tipo genérico #T#, +e nós presumimos que um elemento de tipo #T# ocupa uma word de memória. +\javaonly{(Na realidade, estamos guardando referencias para objetos do tipo #T#, e essas referencias ocupam somente uma word de memória.)} + +\javaonly{O modelo word-RAM com #w# bits é bem próximo à Java Virtual Machine (JVM) de 32 bits quando $#w#=32$.} +\cpponly{O modelo word-RAM com #w# bits é bem representativo a computadores desktop modernos quando $#w#=32$ or $#w#=64$.} +As estruturas de dados apresentadas neste livro não usam truqes especiais que não são implementáveis \javaonly{na JVM em mais parte de outras arquiteturas.}\cpponly{em C++ na maior parte das arquiteturas.} + +\section{Corretude, Complexidade de Tempo e Complexidade de Espaço} + +Ao estudar o desempenho de um estrutura de dados, existem três coisas mais importantes: \begin{description} - \item[Correctness:] The data structure should correctly implement - its interface. - \index{correctness}% - \item[Time complexity:] The running times of operations on the data - structure should be as small as possible. - \index{time complexity}% - \index{complexity!time}% - \item[Space complexity:] The data structure should use as little memory - as possible. - \index{space complexity}% - \index{complexity!space}% + \item[Corretude:] A estrutura de dados deve implementar corretamente sua interface. + \index{corretude}% + \item[Complexidade de tempo:] Os tempos de execução de operações na estrutura de dados deve ser o menor possível. + \index{complexidade de tempo}% + \index{complexidade!tempo}% + \item[Complexidade de espaço:] A estrutura de dados deve usar o mínimo de memória possível. + \index{complexidade de espaço}% + \index{complexidade!espaço}% \end{description} %Sometimes these three requirements are in conflict with each other. For @@ -834,185 +723,168 @@ \section{Correctness, Time Complexity, and Space Complexity} %or uses less space, if the data structure is allowed to make (hopefully %occasional) mistakes. -In this introductory text, we will take correctness as a given; we -won't consider data structures that give incorrect answers to queries or -don't perform updates properly. We will, however, see data structures -that make an extra effort to keep space usage to a minimum. This won't -usually affect the (asymptotic) running times of operations, but can -make the data structures a little slower in practice. +Neste texto introdutório, consideraremos a corretude como presumida; não consideramos estruturas de dados que dão respostas incorretas a consultas ou que não realizam atualizações corretamente. Iremos, entretanto, ver estruturas de dados que fazem um esforço extra para manter uso de espaço a um mínimo. +Isso não irá em geral afetar o tempo (assintótico) de execução de operações, mas pode fazer as estruturas de dados um pouco mais lentas na prática. -When studying running times in the context of data structures we tend to -come across three different kinds of running time guarantees: +Ao estudar tempos de execução no contexto de estruturas de dados tendemos a encontrar três tipos diferentes de garantias de tempo de execução: \begin{description} -\item[Worst-case running times:] - \index{running time}% - \index{running time!worst-case}% - \index{worst-case running time}% - These are the strongest kind of running - time guarantees. If a data structure operation has a worst-case - running time of $f(#n#)$, then one of these operations \emph{never} - takes longer than $f(#n#)$ time. -\item[Amortized running times:] - \index{running time!amortized}% - \index{amortized running time}% - If we say that the amortized running - time of an operation in a data structure is $f(#n#)$, then this means that - the cost of a typical operation is at most $f(#n#)$. More precisely, - if a data structure has an amortized running time of $f(#n#)$, - then a sequence of $m$ operations takes at most $mf(#n#)$ time. - Some individual operations may take more than $f(#n#)$ time but the - average, over the entire sequence of operations, is at most $f(#n#)$. -\item[Expected running times:] - \index{running time!expected}% - \index{expected running time}% - If we say that the expected running time - of an operation on a data structure is $f(#n#)$, this means that the - actual running time is a random variable (see \secref{randomization}) - and the expected value of this random variable is at most $f(#n#)$. - The randomization here is with respect to random choices made by the - data structure. +\item[Tempos de execução no pior caso:] + \index{tempo de execução}% + \index{tempo de execução!pior caso}% + \index{tempo de execução no pior caso}% + Esse é o tipo mais forte de garantia de tempo de execução. + Se uma operação de uma estrutura de dados tem tempo + de execução no pior-caso de + $f(#n#)$, então uma dessas operações \emph{nunca} + leva mais tempo que +$f(#n#)$. +\item[Tempo de execução amortizado:] + \index{tempo de execução!amortizado}% + \index{tempo de execução amortizado}% + Se dizemos que o tempo de execução amortizado de uma operação em uma estrutura de dados é $f(#n#)$, então isso significa que o custo de uma operação típica é no máximo $f(#n#)$. Mais precisamente, se uma estrutura de dados tem tempo de execução amortizado de + $f(#n#)$, + então uma sequência de $m$ operações leva de tempo, no máximo, + $mf(#n#)$. + Algumas operações individuais podem levar mais tempo que + $f(#n#)$ mas a média, sobre a sequência inteira de operações, é até $f(#n#)$. +\item[Tempo de Execução Esperado:] + \index{tempo de execução!esperado}% + \index{tempo de execução esperado}% + Se dizemos que o tempo de esperado de execução de uma operação em uma estrutura de dados é + $f(#n#)$, isso significa que o tempo real de execução é uma variável aleatória + (veja \secref{randomization}) + e o valor esperado dessa variável aleatória é no máximo + $f(#n#)$. +A randomização aqui é em respeito a escolha aleatória feitas pela estrutura de dados. \end{description} -To understand the difference between worst-case, amortized, and expected -running times, it helps to consider a financial example. Consider the -cost of buying a house: -\paragraph{Worst-case versus amortized cost:} -\index{amortized cost}% -Suppose that a home costs \$120\,000. In order to buy this home, -we might get a 120 month (10 year) mortgage with monthly payments of -\$1\,200 per month. In this case, the worst-case monthly cost of paying -this mortgage is \$1\,200 per month. - -If we have enough cash on hand, we might choose to buy the house outright, -with one payment of \$120\,000. In this case, over a period of 10 years, -the amortized monthly cost of buying this house is +Para entender a diferença entre pior caso, amortizado e tempo esperado, ajuda a considerar um exemplo financeiro. Considere o custo de comprar uma casa: + +\paragraph{Pior caso versus custo amortizado:} +\index{custo amortizado}% +Suponha que uma casa custa \$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de +\$1\,200. Nesse caso, o custo mensal no pior caso de pagar o empréstimo é +\$1\,200. + +Se temos suficiente dinheiro em mãos, podemos escolher comprar a casa sem empréstimo, com um pagamento de \$120\,000. +Nesse caso, para o período de 10 anos, o custo amortizado mensal de comprar essa casa é \[ - \$120\,000 / 120\text{ months} = \$1\,000\text{ per month} \enspace . + \$120\,000 / 120\text{ meses} = \$1\,000\text{ por mês} \enspace . \] -This is much less than the \$1\,200 per month we would have to pay if -we took out a mortgage. - -\paragraph{Worst-case versus expected cost:} -\index{expected cost}% -Next, consider the issue of fire insurance on our \$120\,000 home. -By studying hundreds of thousands of cases, insurance companies have -determined that the expected amount of fire damage caused to a home -like ours is \$10 per month. This is a very small number, since most -homes never have fires, a few homes may have some small fires that -cause a bit of smoke damage, and a tiny number of homes burn right to -their foundations. Based on this information, the insurance company -charges \$15 per month for fire insurance. - -Now it's decision time. Should we pay the \$15 worst-case monthly cost -for fire insurance, or should we gamble and self-insure at an expected -cost of \$10 per month? Clearly, the \$10 per month costs less \emph{in -expectation}, but we have to be able to accept the possibility that -the \emph{actual cost} may be much higher. In the unlikely event that the -entire house burns down, the actual cost will be \$120\,000. - -These financial examples also offer insight into why we sometimes settle -for an amortized or expected running time over a worst-case running time. -It is often possible to get a lower expected or amortized running time -than a worst-case running time. At the very least, it is very often possible -to get a much simpler data structure if one is willing to settle for -amortized or expected running times. - -\section{Code Samples} +Esse valor é bem menor que os \$1\,200 mensais que teríamos que pagar se pegássemos o empréstimo. + +\paragraph{Pior caso versus custo esperado:} +\index{custo esperado}% +A seguir, considere o caso de um seguro de incêndio na nossa casa de \$120\,000. +Por analisar centenas de milhares de casos, companias de seguro tem determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de +\$10 por mês. +Esse é uma valor bem baixo, pois a maior parte das casa nunca pegam fogo, algumas poucas tem incêndios pequenos que causam algum dano e um número minúsculo de casa queimam até as cinzas. Baseada nessa informação, a seguradora cobra +\$15 mensais para a contratação de um seguro. + +Agora é o momento da decisão. Devemos pagar os + \$15 referente ao custo de pior caso mensalmente para o seguro ou devemos +apostar fazer uma poupança anti-incêndio ao custo esperado de +\$10 mensal? Claramente, \$10 por mês custa menos \emph{em expectativa}, +mas temos que aceitar que a possibilidade de \emph{custo real} possa ser bem maior. +No evento improvável que a casa queime inteira, o custo real será de \$120\,000. + +Esses exemplos financeiros também oferecem a oportunidade de vermos porque às vezes aceitamos um tempo de execução amortizado ou esperado em vez de considerarmos o tempo de execução no pior caso. Frequentemente é possível obter um tempo de execução esperado ou amortizado menor que o obtido no pior caso. +No mínimo, frequentemente é possível obter uma estrutura de dados bem mais simples se estamos dispostos a aceitar tempo de execução amortizado ou esperado. + + +\section{Trechos de Código} \pcodeonly{ -The code samples in this book are written in pseudocode. + Os trechos de código neste livro são escritos em pseudocódigo. + \index{pseudocode}% -These should be easy enough to read for anyone who has any programming experience in any of the most common programming languages of the last 40 years. To get an idea of what the pseudocode in this book looks like, here is a function that computes the average of an array, #a#: +Eles devem ser de fácil leitura para qualquer um que teve alguma experiência de programação em qualquer linguagens de programação comum dos últimos 40 anos. +Para ter uma ideia do que o pseudocódigo neste livro parece, segue uma função que computa a média de um array, #a#: \pcodeimport{ods/Algorithms.average(a)} -As this code illustrates, assigning to a variable is done using the $\gets$ -notation. +Como esse código exemplifica, a atribuição a uma variável é feita usando a notação + $\gets$. % WARNING: graphic typesetting of assignment operator -We use the convention that the length of an -array, #a#, is denoted by #len(a)# and array indices start at zero, -so that #range(len(a))# are the valid indices for #a#. To shorten -code, and sometimes make it easier to read, our pseudocode allows for -(sub)array assignments. The following two functions are equivalent: +Nós usamos a convenção de que o comprimento de uma array, #a#, é denotado por +#len(a)# e os índices do array começam com zero, +tal que #range(len(a))# são índices válidos para #a#. +Para encurtar o código, e algumas vezes facilitar a leitura, nosso pseudocódigo permite atribuições de subarrays. +As duas funções a seguir são equivalentes: \pcodeimport{ods/Algorithms.left_shift_a(a).left_shift_b(a)} -The following code sets all the values in an array to zero: +O código a seguir atribui todos os valores em um array para zero: \pcodeimport{ods/Algorithms.zero(a)} -When analyzing the running time of code like this, we have to be careful; -statements like +Ao analysis o tempo de execução de um código desse tipo, é necessário cuidado; +comandos como #a[0:len(a)] = 1# -or +ou #a[1:len(a)] = a[0:len(a)-1]# -do not run in constant time. They run in $O(#len(a)#)$ time. +não rodam em tempo constante. Eles rodam em tempo $O(#len(a)#)$. -We take similar shortcuts with variable assignments, so that the code -#x,y=0,1# sets #x# to zero and #y# to 1 and the code #x,y = y,x# swaps -the values of the variables #x# and #y#. +Usamos atalhos similares com atribuições a variáveis, tal que o código +#x,y=0,1# atribui #x# a zero e #y# a 1 e o código #x,y = y,x# troca +os valores das variáveis #x# e #y#. \index{swap} -Our pseudocode uses a few operators that may be unfamiliar. As is -standard in mathematics, (normal) division is denoted by the $/$ operator. -In many cases, we want to do integer division instead, in which case we -use the $#//#$ operator, so that $#a//b# = \lfloor a/b\rfloor$ is the -integer part of $a/b$. So, for example, $3/2=1.5$ but $#3//2# = 1$. -\index{integer division}% -\index{div operator}% -Occasionally, we also use the $\bmod$ operator to obtain the -remainder from integer division, but this will be defined when it is used. -\index{mod operator}% -\index{div operator}% -Later in the book, we may use some bitwise operators including left-shift (#<<#), right shift (#>>#), bitwise and (#&#), and bitwise exclusive-or (#^#). +Nosso pseudocódigo usa alguns operadores que podem não ser conhecidos. +Como é padrão em matemática, divisão (normal) é denotada pelo operador $/$ +Em muitos casos, queremos fazer divisão inteira e, nesse caso, usamos +o operador +$#//#$, tal que $#a//b# = \lfloor a/b\rfloor$ é a parte inteira de +$a/b$. Então, por exemplo, $3/2=1.5$ mas $#3//2# = 1$. +\index{divisão inteira}% +\index{operador div}% +Ocasionalmente, também usamos o operador $\bmod$ para obter o resto +da divisão inteira, mas isso será definido quando for o caso. +\index{operador mod}% +\index{operador div}% +Mais adiante no livro, podemos usar alguns operadores bit-a-bit incluindo o deslocamento à esquerda (#<<#), à direita (#>>#), E bit-a-bit (#&#) e XOR bit-a-bit (#^#). +\index{deslocamento à esquerda}% \index{left shift}% \index{#<<#|see {left shift}}% +\index{#<<#|see {deslocamento à esquerda}}% +\index{deslocamento à direita}% \index{right shift}% \index{#>>#|see {right shift}}% +\index{#>>#|see {deslocamente à direita}}% \index{bitwise and}% +\index{E bit-a-bit}% \index{#&#|see {bitwise and}}% +\index{#&#|see {E bit-a-bit}}% \index{#^#|see {bitwise exclusive-or}}% +\index{#^#|see {XOR bit-a-bit}}% -The pseudocode samples in this book are machine translations -of Python code that can be downloaded from the book's website.\footnote{ \url{http://opendatastructures.org}} If you ever encounter an ambiguity in the pseudocode that you can't resolve yourself, then you can always refer -to the corresponding Python code. If you don't read Python, the code is -also available in Java and C++. If you can't decipher the pseudocode, -or read Python, C++, or Java, then you may not be ready for this book. +Os trechos de pseudocódigo neste livro são traduções de máquina do código Python que pode ser baixado do website do livro. +\footnote{ \url{http://opendatastructures.org}} +Se você encontrar qualquer ambiguidade no pseudocódigo que você não consegue resolver por si só, então você pode resolver essa questões com o correspondente código em Python. +Se você não lê Python, o código também está disponível em Java e C++. Se você não consegue decifrar o pseudocódigo, ou ler Python, C++, ou Java, então você pode não estar pronto para este livro. } \notpcode{ -The code samples in this book are written in the \lang\ programming -language. However, to make the book accessible to readers not familiar -with all of \lang's constructs and keywords, the code samples have -been simplified. For example, a reader won't find any of the keywords -#public#, #protected#, #private#, or #static#. A reader also won't find -much discussion about class hierarchies. Which interfaces a particular -class implements or which class it extends, if relevant to the discussion, -should be clear from the accompanying text. - -These conventions should make the code samples understandable by -anyone with a background in any of the languages from the ALGOL tradition, -including B, C, C++, C\#, Objective-C, D, Java, JavaScript, and so on. -Readers who want the full details of all implementations are encouraged -to look at the \lang\ source code that accompanies this book. - -This book mixes mathematical analyses of running times with \lang\ -source code for the algorithms being analyzed. This means that -some equations contain variables also found in the source code. -These variables are typeset consistently, both within the source code -and within equations. The most common such variable is the variable #n# +Os trechos de código neste livro são escritos na linguagem + \lang +. Entretanto, para fazer o livro acessível para leitores não familiares com todas as construções e keywords definidas pela linguagem \lang, os trechos de código foram simplificados. Por exemplo, um leitor não irá encontrar keywords como +#public#, #protected#, #private# ou #static#. +O leitor também não encontrará discussão aprofundada sobre hierarquia de classes. +Quais interfaces uma determinada classes implementa ou qual classes ela extende, se relevante para a discussão, deve estar claro do texto do contexto em questão. + +Essas convenções deve fazer os trechos de código mais fáceis de entender por qualquer um com background linguagens que variantes da linha ALGOL, incluindo B, C, C++, C\#, Objective-C, D, Java, JavaScript, e assim por diante. +Leitores em busca de detalhes de todas as implementações são encorajados a olhar no código fonte da linguagem \lang\ que acompanha este livro. + +Este livro mixtura análise matemática de tempos de execução com o código-fonte da linguagem \lang\ para os algoritmos em análise. Isso significa que algumas equações contêm variáveis variáveis também encontradas no código-fonte. + +Essas variáveis são formatadas consistentemente, tanto dentro do código-fonte quanto nas equações. A variável mais comum desse tipo é a variável #n# \index{n@#n#}% -that, without exception, always refers to the number of items currently -stored in the data structure. +que, sem exceção, sempre refere-se ao número de itens atualmente armazenados na estrutura de dados. } -\section{List of Data Structures} +\section{Lista de Estruturas de Dados} -Tables~\ref{tab:summary-i} and \ref{tab:summary-ii} summarize the -performance of data structures in this book that implement each of the -interfaces, #List#, #USet#, and #SSet#, described in \secref{interfaces}. -\Figref{dependencies} shows the dependencies between various chapters in -this book. -\index{dependencies}% -A dashed arrow indicates only a weak dependency, in which -only a small part of the chapter depends on a previous chapter or only -the main results of the previous chapter. +Tabelas~\ref{tab:summary-i} e \ref{tab:summary-ii} resumem o desempenho +das estruturas de dados neste livro que implementam cada uma das interfaces, #List#, #Uset# e #SSet#, descritas em \secref{interfaces}. +\Figref{dependencies} mostra as dependências entre vários capítulos neste livro. +\index{dependências}% +Uma linha tracejada indica somente uma dependência fraca, na qual somente uma pequena parte do capítulo depende em um capítulo anterior ou somente nos resultados principais do capítulo anterior. \begin{table} \vspace{56pt} @@ -1020,7 +892,7 @@ \section{List of Data Structures} \resizebox{.98\textwidth}{!}{ \begin{threeparttable} \begin{tabular}{|l|l|l|l|} \hline -\multicolumn{4}{|c|}{#List# implementations} \\ \hline +\multicolumn{4}{|c|}{Implementações de #List# } \\ \hline & #get(i)#/#set(i,x)# & #add(i,x)#/#remove(i)# & \\ \hline #ArrayStack# & $O(1)$ & $O(1+#n#-#i#)$\tnote{A} & \sref{arraystack} \\ #ArrayDeque# & $O(1)$ & $O(1+\min\{#i#,#n#-#i#\})$\tnote{A} & \sref{arraydeque} \\ @@ -1030,18 +902,18 @@ \section{List of Data Structures} #SEList# & $O(1+\min\{#i#,#n#-#i#\}/#b#)$ & $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$\tnote{A} & \sref{selist} \\ #SkiplistList# & $O(\log #n#)$\tnote{E} & $O(\log #n#)$\tnote{E} & \sref{skiplistlist} \\ \hline \multicolumn{4}{c}{} \\[2ex] \hline -\multicolumn{4}{|c|}{#USet# implementations} \\ \hline +\multicolumn{4}{|c|}{Implementações de #USet#} \\ \hline & #find(x)# & #add(x)#/#remove(x)# & \\ \hline #ChainedHashTable# & $O(1)$\tnote{E} & $O(1)$\tnote{A,E} & \sref{hashtable} \\ #LinearHashTable# & $O(1)$\tnote{E} & $O(1)$\tnote{A,E} & \sref{linearhashtable} \\ \hline \end{tabular} \begin{tablenotes} -\item[A]{Denotes an \emph{amortized} running time.} -\item[E]{Denotes an \emph{expected} running time.} +\item[A]{Denota um tempo de execução \emph{amortizado}.} +\item[E]{Denota um tempo de execução \emph{esperado}.} \end{tablenotes} \end{threeparttable}} \end{center} -\caption[Summary of List and USet implementations.]{Summary of #List# and #USet# implementations.} +\caption[Resumo de implementações de List e USet.]{Resumo de implementações de #List# e #USet#.} \tablabel{summary-i} \end{table} @@ -1049,7 +921,7 @@ \section{List of Data Structures} \begin{center} \begin{threeparttable} \begin{tabular}{|l|l|l|l|} \hline -\multicolumn{4}{|c|}{#SSet# implementations} \\ \hline +\multicolumn{4}{|c|}{Implementações de #SSet#} \\ \hline & #find(x)# & #add(x)#/#remove(x)# & \\ \hline #SkiplistSSet# & $O(\log #n#)$\tnote{E} & $O(\log #n#)$\tnote{E} & \sref{skiplistset} \\ #Treap# & $O(\log #n#)$\tnote{E} & $O(\log #n#)$\tnote{E} & \sref{treap} \\ @@ -1061,19 +933,19 @@ \section{List of Data Structures} \javaonly{#BTree# & $O(\log #n#)$ & $O(B+\log #n#)$\tnote{A} & \sref{btree} \\ #BTree#\tnote{X} & $O(\log_B #n#)$ & $O(\log_B #n#)$ & \sref{btree} \\ } \hline \multicolumn{4}{c}{} \\[2ex] \hline -\multicolumn{4}{|c|}{(Priority) #Queue# implementations} \\ \hline + \multicolumn{4}{|c|}{Implementações de #Queue# (de prioridade)} \\ \hline & #findMin()# & #add(x)#/#remove()# & \\ \hline #BinaryHeap# & $O(1)$ & $O(\log #n#)$\tnote{A} & \sref{binaryheap} \\ #MeldableHeap# & $O(1)$ & $O(\log #n#)$\tnote{E} & \sref{meldableheap} \\ \hline \end{tabular} \begin{tablenotes} -\item[I]{This structure can only store #w#-bit integer data.} -\javaonly{\item[X]{This denotes the running time in the external-memory model; see \chapref{btree}.}} +\item[I]{Essa estrutura pode somente guardar dados inteiros de #w#-bit.} +\javaonly{\item[X]{Isso denota o tempo de execução no modelo de memória externa; veja \chapref{btree}.}} \end{tablenotes} %\renewcommand{\thefootnote}{\arabic{footnote}} \end{threeparttable} \end{center} -\caption[Summary of SSet and priority Queue implementations.]{Summary of #SSet# and priority #Queue# implementations.} +\caption[Resumo das implementações SSet e priority Queue.]{Resumo de implementações de #SSet# e priority #Queue#.} \tablabel{summary-ii} \end{table} @@ -1081,160 +953,107 @@ \section{List of Data Structures} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/dependencies} \end{center} - \caption{The dependencies between chapters in this book.} + \caption{Dependências entre capítulos neste livro.} \figlabel{dependencies} \end{figure} -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -The #List#, #USet#, and #SSet# interfaces described in -\secref{interfaces} are influenced by the Java Collections Framework +As interfaces +#List#, #USet# e #SSet# descritas em +\secref{interfaces} são influenciadas pela Java Collections Framework \cite{oracle_collections}. \index{Java Collections Framework}% -These are essentially simplified versions of -the #List#, #Set#, #Map#, #SortedSet#, and #SortedMap# interfaces found in -the Java Collections Framework. \javaonly{The accompanying source -code includes wrapper classes for making #USet# and #SSet# implementations -into #Set#, #Map#, #SortedSet#, and #SortedMap# implementations.} - -For a superb (and free) treatment of the mathematics discussed in this -chapter, including asymptotic notation, logarithms, factorials, Stirling's -approximation, basic probability, and lots more, see the textbook by -Leyman, Leighton, and Meyer \cite{llm11}. For a gentle calculus text -that includes formal definitions of exponentials and logarithms, see the -(freely available) classic text by Thompson \cite{t14}. - -For more information on basic probability, especially as it relates to -computer science, see the textbook by Ross \cite{r01}. Another good -reference, which covers both asymptotic notation and probability, is -the textbook by Graham, Knuth, and Patashnik \cite{gkp94}. - -\javaonly{Readers wanting to brush up on their Java programming can find -many Java tutorials online \cite{oracle_tutorials}.} +Elas são essencialmente versões simplificadas das interfaces +#List#, #Set#, #Map#, #SortedSet# e #SortedMap# encontradas no +Java Collections Framework. \javaonly{O código-fonte que acompanha este livro inclui +classes auxiliares para fazer das implementações #USet# e #SSet# +implementaçoes #Set#, #Map#, #SortedSet# e #SortedMap#.} + +Para um soberbo (e livre) tratamento da matemática discutida neste capítulo, incluindo notação assintótica, logaritmos, fatoriais, aproximação de Sterling, probabilidade básica e muito mais, veja o livro texto por +Leyman, Leighton e Meyer \cite{llm11}. +Para um texto de cálculo suave que inclui definições formais de exponenciais e logaritmos, seja o (disponível livremente) texto clássico de Thompson \cite{t14}. + +Para maiores informações em probabilidade básica, especialmente como ela se relaciona com Ciência da Computação, veja o livro didático de Ross \cite{r01}. +Outra boa referência, que cobre notação assintótica e probabilidades, é o livro-texto de Graham, Knuth e Patashnik \cite{gkp94}. + +\javaonly{Leitures que desejam afinar suas habilidades de programação Java +podem achar muitos tutoriais online em \cite{oracle_tutorials}.} \begin{exc} - This exercise is designed to help familiarize the reader with choosing - the right data structure for the right problem. If implemented, the - parts of this exercise should be done by making use of an implementation - of the relevant interface (#Stack#, #Queue#, #Deque#, #USet#, or #SSet#) - provided by the \javaonly{Java Collections Framework}\cpponly{C++ + Este exercício é planejado para auxiliar o leitor a se familiarizar na escolha da estrutura de dados certa para o problema correto. Se implementado, partes deste exercício devem ser realizadas usando uma implementação da interface relevante + (#Stack#, #Queue#, #Deque#, #USet# ou #SSet#) + providas pela \javaonly{Java Collections Framework}\cpponly{C++ Standard Template Library}. - Solve the following problems by reading a text file one line at a - time and performing operations on each line in the appropriate data - structure(s). Your implementations should be fast enough that even - files containing a million lines can be processed in a few seconds. + Resolva os seguintes problemas fazendo a leitura de um arquivo de texto uma linha por vez e executando operações em cada linha nas estrutura(s) de dados adequada(s). +Suas implementações devem ser rápidas o suficiente tal que até arquivos com um milhão de linhas podem ser processadas em alguns segundos. \begin{enumerate} - \item Read the input one line at a time and then write the lines out - in reverse order, so that the last input line is printed first, - then the second last input line, and so on. - - \item Read the first 50 lines of input and then write them out in - reverse order. Read the next 50 lines and then write them out in - reverse order. Do this until there are no more lines left to read, - at which point any remaining lines should be output in reverse - order. - - In other words, your output will start with the 50th line, then - the 49th, then the 48th, and so on down to the first line. This - will be followed by the 100th line, followed by the 99th, and so - on down to the 51st line. And so on. - - Your code should never have to store more than 50 lines at any - given time. - - \item Read the input one line at a time. - At any point after reading the first 42 lines, if some line is blank - (i.e., a string of length 0), then output the line that occured - 42 lines prior to that one. For example, if Line 242 is blank, - then your program should output line 200. This program should - be implemented so that it never stores more than 43 lines of the - input at any given time. - - \item Read the input one line at a time and write each line to the - output if it is not a duplicate of some previous input line. Take - special care so that a file with a lot of duplicate lines does not - use more memory than what is required for the number of unique lines. - - \item Read the input one line at a time and write each line to the - output only if you have already read this line before. (The end - result is that you remove the first occurrence of each line.) - Take special care so that a file with a lot of duplicate lines - does not use more memory than what is required for the number of - unique lines. - - \item Read the entire input one line at a time. Then output all lines - sorted by length, with the shortest lines first. In the case where - two lines have the same length, resolve their order using the usual - ``sorted order.'' Duplicate lines should be printed only once. - - \item Do the same as the previous question except that duplicate lines - should be printed the same number of times that they appear in the input. - - \item Read the entire input one line at a time and then output the - even numbered lines (starting with the first line, line 0) followed - by the odd-numbered lines. - - \item Read the entire input one line at a time and randomly permute - the lines before outputting them. To be clear: You should not - modify the contents of any line. Instead, the same collection of - lines should be printed, but in a random order. + \item Leia a entrada uma linha por vez e então escreva as linha em ordem invertida, tal que a última linha lida é a primeira imprimida, e então a penúltima lida é a segunda a ser imprimida e assim por diante. + \item Leia as primeiras 50 linhas da entrada e então as escreva na saída em ordem reserva. Leia as seguintes 50 linhas e então escreva na saída em ordem reserva. Faça isso até que não haja mais linhas a serem linhas. Nesse ponto as linhas restantes lidas também devem ser imprimidas invertidas. +Em outras palavras, sua saída inicirá com a 50-ésima linha, então 49-ésima linha, então a 48-ésima e assim até a primeira linha. Isso deverá ser seguido pela centésima linha, seguida pela 99-ésima até a 51-ésima linha. Assim por diante. +O seu código nunca deverá manter mais de 50 linhas a qualquer momento. + +\item Leia a entrada uma linha por vez. + A qualquer momento após ler as primeiras 42 linhas, se alguma linha está em branco (i.e., uma string de comprimento 0), então produza a linha que ocorreu 42 linhas antes dela. Por exemplo, se Linha 242 está em branco, então o seu programa deve imprimir a linha 200. Esse programa deve ser implementado tal que ele nunca guarda mais que 43 linhas da entrada a qualquer momento. + +\item Leia a entrada uma linha por vez e imprima cada linha se não for uma duplicata de alguma linha anterior. Tenha cuidado especial de forma que um arquivo com muitas linhas duplicadas não use mais memória do que é necessário para o número de linhas únicas. + +\item Leia a entrada uma linha por vez e imprima cada linha somente se você já encontrou uma linha igual antes. (O resultado final é que você remove a primeira ocorrência de cada linha.). Tenha cuidado tal que um arquivo com minhas linhas duplicadas não usa mais memória que é necessário para o número de linhas únicas. + +\item Leia a entrada uma linha por vez. Então, imprima todas as linhas ordenadas por comprimento, com as linhas mais curtas primeiro. No caso em que duas linhas + tem o mesmo tamanho, decida sua ordem usando a ordem usual de texto. Linhas duplicadas devem ser impressas somente uma vez. + +\item Faça o mesmo que a questão anterior exceto que linhas duplicadas devem ser impressas o mesmo número de vezes que elas aparecem na entrada. + +\item Leia a entrada uma linha por vez e então imprima as linhas pares (começando com a primeira linha, linha 0) seguidas das linhas ímpares. + +\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimí-las. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser imprimida, mas em ordem aleatória. + \end{enumerate} \end{exc} \begin{exc} - \index{Dyck word}% - A \emph{Dyck word} is a sequence of +1's and -1's with the property that - the sum of any prefix of the sequence is never negative. For example, - $+1,-1,+1,-1$ is a Dyck word, but $+1,-1,-1,+1$ is not a Dyck word - since the prefix $+1-1-1<0$. Describe any relationship - between Dyck words and #Stack# #push(x)# and #pop()# operations. + \index{Palavra Dyck}% + Uma \emph{palavra Dyck} é uma sequência de +1s e -1s com propriedade de que soma de qualquer prefix da sequência nunca é negativa. Por exemplo, + $+1,-1,+1,-1$ é uma palavra Dyck, mas $+1,-1,-1,+1$ não é uma palavra Dyck + pois o prefixo $+1-1-1<0$. Descreva qualquer relação entre + palavras Dyck e as operações da interface #Stack#, #push(x)# e #pop()#. \end{exc} \begin{exc} - \index{matched string}% - \index{string!matched}% - A \emph{matched string} is a sequence of \{, \}, (, ), [, and ] - characters that are properly matched. For example, ``\{\{()[]\}\}'' - is a matched string, but this ``\{\{()]\}'' is not, since the second \{ - is matched with a ]. Show how to use a stack so that, given a string - of length #n#, you can determine if it is a matched string in $O(#n#)$ - time. + \index{string pareada}% + \index{string!pareada}% + Uma \emph{string pareada} é uma sequência de caracteres \{, \}, (, ), [, e ] + que são apropriadamente pareados. Por exemplo, ``\{\{()[]\}\}'' + é uma string pareada, mas este ``\{\{()]\}'' não é, uma vez que a segunda \{ + é pareada ]. Mostre como usar uma stack tal que, dada uma string de comprimento + #n#, você pode determinar se é uma string pareada em tempo $O(#n#)$. \end{exc} \begin{exc} - Suppose you have a #Stack#, #s#, that supports only the #push(x)# - and #pop()# operations. Show how, using only a FIFO #Queue#, #q#, - you can reverse the order of all elements in #s#. + Suponha que você tem uma + #Stack#, #s#, que aceita somente as operações #push(x)# + e #pop()#. Mostre como, usando somente uma #Queue# FIFO, #q#, + você pode inverter a ordem de todos os elementos em #s#. \end{exc} \begin{exc} \index{Bag@#Bag#}% - Using a #USet#, implement a #Bag#. A #Bag# is like a #USet#---it - supports the #add(x)#, #remove(x)# and #find(x)# methods---but it allows - duplicate elements to be stored. The #find(x)# operation in a #Bag# - returns some element (if any) that is equal to #x#. In addition, - a #Bag# supports the #findAll(x)# operation that returns a list of - all elements in the #Bag# that are equal to #x#. +Usando #USet#, implemente uma #Bag#. Uma #Bag# é como um #USet#---ele aceita os métodos +#add(x)#, #remove(x)# e #find(x)# --- mas ele aceita armazenar elementos duplicados. A operação #find(x)# em uma #Bag# retorna + algum elemento (se tiver) que é igual a #x#. Em adição, uma + #Bag# aceita a operação #findAll(x)# que retorna uma lista de todos os elementos + na #Bag# que são iguais a #x#. \end{exc} \begin{exc} - From scratch, write and test implementations of the #List#, #USet# - and #SSet# interfaces. These do not have to be efficient. They can - be used later to test the correctness and performance of more efficient - implementations. (The easiest way to do this is to store the elements - in an array.) + A partir do zero, escreva e teste implementações das interfaces #List#, #USet# + e #SSet#. Essas implementações não precisam ser eficientes. Elas podem ser usadas depois para testar a corretude e desempenho de implementações mais eficientes. (O jeito mais fácil de fazer isso é guardar os elementos em um array.) \end{exc} \begin{exc} - Work to improve the performance of your implementations from the - previous question using any tricks you can think of. Experiment and - think about how you could improve the performance of #add(i,x)# and - #remove(i)# in your #List# implementation. Think about how you could - improve the performance of the #find(x)# operation in your #USet# - and #SSet# implementations. This exercise is designed to give you a - feel for how difficult it can be to obtain efficient implementations - of these interfaces. +Trabalhe para melhorar o desempenho das suas implementações da questão anterior usando quaisquer truques que puder imaginar. Experimente e pense sonre como você pode melhorar o desempenho de #add(i,x)# e #remove(i)# na sua implementação de #List#. Pense como você poderia melhorar o desempenho da operação #find(x)# na suas implementações dos #USet# e #SSet#. Este exercício é pensado para te dar uma ideia da dificuldade de conseguir implementações eficientes dessas interfaces. \end{exc} From 719ed18a9770e96bde718e97147d5a8a128ca5b0 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Mon, 10 Aug 2020 18:09:50 -0300 Subject: [PATCH 06/66] translating to PT-BR --- latex/arrays.tex | 272 ++++++++++++++++++++++-------------------- latex/ods.tex | 4 +- latex/snarf-python.py | 66 +++++----- latex/why.tex | 96 ++++++++------- 4 files changed, 228 insertions(+), 210 deletions(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index 21f30a41..212d93f8 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -1,12 +1,11 @@ -\chapter{Array-Based Lists} +\chapter{Listas Baseadas em Array} \chaplabel{arrays} -In this chapter, we will study implementations of the #List# and #Queue# -interfaces where the underlying data is stored in an array, called the -\emph{backing array}. +Neste capítulo, iremos estudar implementações das interfaces #List# e #Queue#, +onde os dados são armazenados em um array chamado de \emph{backing array} ou \emph{array de apoio}. \index{backing array}% -The following table summarizes the running times -of operations for the data structures presented in this chapter: +\index{array de apoio}% +A tabela a seguir resume o tempo de execução de operações para estruturas de dados apresentadas nestes capítulo: \newlength{\tabsep} \setlength{\tabsep}{\itemsep} \addtolength{\tabsep}{\parsep} @@ -22,197 +21,212 @@ \chapter{Array-Based Lists} \end{tabular} \vspace{\tabsep} \end{center} -Data structures that work by storing data in a single array have many -advantages and limitations in common: +Estruturas de dados que funcionam armazenando em um único array têm muitas vantagens e limitações em comum: \index{arrays}% \begin{itemize} - \item Arrays offer constant time access to any value in the array. - This is what allows #get(i)# and #set(i,x)# to run in constant time. - - \item Arrays are not very dynamic. Adding or removing an element - near the middle of a list means that a large number of elements in the - array need to be shifted to make room for the newly added element or - to fill in the gap created by the deleted element. This is why the - operations #add(i,x)# and #remove(i)# have running times that depend - on #n# and #i#. - - \item Arrays cannot expand or shrink. When the number of elements in - the data structure exceeds the size of the backing array, a new array needs - to be allocated and the data from the old array needs to be copied - into the new array. This is an expensive operation. + \item Arrays permitem acesso em tempo constante a qualquer valor no array. + Isso é o que permite #get(i)# e #set(i,x)# rodarem em tempo constante. + + \item Arrays não são muito dinâmicos. Adicionar ou remover um elemento perto do meio de uma lista significa que um grande número de elemento no array precisam +ser deslocados para abrir espaço para o elemento recentemente adicionado ou +preencher a lacuna criada pelo elemento removido. Essa é a razão pela qual as operações + #add(i,x)# e #remove(i)# tem tempos de execução que dependem de + #n# e #i#. + + \item Arrays não podem expandir ou encolher por si só. Quando o número de elementos na + estrutura de dados excede o tamanho do array de apoio, um novo array precisa + ser alocado e os dados do antigo array precisa ser copiado + no novo array. Essa é uma operação cara. \end{itemize} -The third point is important. The running times cited in the table -above do not include the cost associated with growing and shrinking -the backing array. We will see that, if carefully managed, the cost of -growing and shrinking the backing array does not add much to the cost of -an \emph{average} operation. More precisely, if we start with an empty -data structure, and perform any sequence of $m$ #add(i,x)# or #remove(i)# -operations, then the total cost of growing and shrinking the backing -array, over the entire sequence of $m$ operations is $O(m)$. Although -some individual operations are more expensive, the amortized cost, -when amortized over all $m$ operations, is only $O(1)$ per operation. +Um terceiro ponto é importante. Os tempos de execução citados na tabela +acima não incluem o custo associado com expandir ou encolher o array de apoio. +Veremos que, se não gerenciado com cuidado, o custo de expandir ou encolher o array de apoio não aumenta muito o custo de uma operação \emph{média}. +Mais precisamente, se iniciarmos com um estrutura de dados vazia e +realizarmos qualquer sequência de $m$ operações #add(i,x)# ou #remove(i)# +, então o custo total de expandir e encolher o array de apoio, sobre a sequência inteira de $m$ operações é $O(m)$. Embora algumas operações individuais sejam mais caras, o custo amortizado, quando dividido por todas as $m$ operações, é somente $O(1)$ por operação. \cpponly{ -In this chapter, and throughout this book, it will be convenient to -have arrays that keep track of their size. The usual C++ arrays do -not do this, so we have defined a class, #array#, that keeps track -of its length. The implementation of this class is straightforward. -It is implemented as a standard C++ array, #a#, and an integer, #length#:} +Neste capítulo, e ao longo deste livro, seria conveniente ter arrays que guardam seus tamanhos. Os arrays típicos do C++ não fazem isso, então definimos uma classe, #array#, que registra seu tamanho. A implementação dessa classe direta. Ela é feita como um array C++ padrão, #a#, e um inteiro, #length#: +} \cppimport{ods/array.a.length} \cpponly{ -The size of an #array# is specified at the time of creation: +O tamanho de um #array# é especificado no momento de criação: } \cppimport{ods/array.array(len)} -\cpponly{The elements of an array can be indexed:} +\cpponly{Os eleementos de um array podem ser indexados:} \cppimport{ods/array.operator[]} -\cpponly{Finally, when one array is assigned to another, this is just -a pointer manipulation that takes constant time:} +\cpponly{Finalmente, quando um array é atribuído para outro, ocorre apenas uma manipulação de ponteiros que leva um tempo constante:} \cppimport{ods/array.operator=} -\section{#ArrayStack#: Fast Stack Operations Using an Array} +\section{#ArrayStack#: Operações de Stack Rápida Usando um Array} \seclabel{arraystack} \index{ArrayStack@#ArrayStack#}% -An #ArrayStack# implements the list interface using an array #a#, called -the \emph{backing array}. The list element with index #i# is stored -in #a[i]#. At most times, #a# is larger than strictly necessary, -so an integer #n# is used to keep track of the number of elements -actually stored in #a#. In this way, the list elements are stored in -#a[0]#,\ldots,#a[n-1]# and, at all times, $#a.length# \ge #n#$. +Um +#ArrayStack# implementa a interface lista usando um array #a#, chamado de +\emph{array de apoio}. O elemento da lista com índice #i# é armazenado +em #a[i]#. Na maior parte do tempo, #a# é maior que o estritamente necessário, +então um inteiro +#n# é usado para registrar o número de elementos realmente armazenados em #a#. +Dessa maneira, os elementos da lista são guardados em +#a[0]#,\ldots,#a[n-1]# e, sempre, $#a.length# \ge #n#$. \codeimport{ods/ArrayStack.a.n.size()} -\subsection{The Basics} +\subsection{O Básico} -Accessing and modifying the elements of an #ArrayStack# using #get(i)# -and #set(i,x)# is trivial. After performing any necessary bounds-checking -we simply return or set, respectively, #a[i]#. +Acessar e modificar os elementos de uma +#ArrayStack# usando #get(i)# e #set(i,x)# é trivial. +Após realizar as verificações de limites necessárias, simplesmente retornamos ou atribuímos, respectivamente, #a[i]#. \codeimport{ods/ArrayStack.get(i).set(i,x)} -The operations of adding and removing elements from an #ArrayStack# -are illustrated in \figref{arraystack}. To implement the #add(i,x)# -operation, we first check if #a# is already full. If so, we call -the method #resize()# to increase the size of #a#. How #resize()# -is implemented will be discussed later. For now, it is sufficient to -know that, after a call to #resize()#, we can be sure that $#a.length# -> #n#$. With this out of the way, we now shift the elements -$#a[i]#,\ldots,#a[n-1]#$ right by one position to make room for #x#, -set #a[i]# equal to #x#, and increment #n#. +As operações de adicionar e remover elementos de um + #ArrayStack# +estão ilustradas em + \figref{arraystack}. Para implementar a operação #add(i,x)#, +primeiro verificamos se #a# está cheio. Caso positivo, chamamos o método +#resize()# para aumentar o tamanho de #a#. Como #resize()# +é implementado será discutido depois. Por ora, é suficiente +saber que, após uma chamada para #resize()#, temos certeza que $#a.length# +> #n#$. +Com isso resolvido, agora nós deslocamos os elementos +$#a[i]#,\ldots,#a[n-1]#$ para uma posição à direita para +abrir espaço para #x#, atribuir +#a[i]# igual a #x#, e incrementar #n#. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/arraystack} \end{center} - \caption[Adding to an ArrayStack]{A sequence of #add(i,x)# and #remove(i)# operations on an - #ArrayStack#. Arrows denote elements being copied. Operations that - result in a call to #resize()# are marked with an asterisk.} + \caption[Adicionando a um ArrayStack]{Uma sequência de operações #add(i,x)# e #remove(i)# em um + #ArrayStack#. Flechas denotam elementos sendo copiados. Operações que + resultam em uma chamada para + #resize()# são marcados em um arterisco.} \figlabel{arraystack} \end{figure} \codeimport{ods/ArrayStack.add(i,x)} -If we ignore the cost of the potential call to #resize()#, then the cost -of the #add(i,x)# operation is proportional to the number of elements we -have to shift to make room for #x#. Therefore the cost of this operation -(ignoring the cost of resizing #a#) is $O(#n#-#i#)$. - -Implementing the #remove(i)# operation is similar. We shift the elements -$#a[i+1]#,\ldots,#a[n-1]#$ left by one position (overwriting #a[i]#) and -decrease the value of #n#. After doing this, we check if #n# is getting -much smaller than #a.length# by checking if $#a.length# \ge 3#n#$. If so, -then we call #resize()# to reduce the size of #a#. +Se ignorarmos o custo de uma potencial chamada a +#resize()#, então o cusot da operação +#add(i,x)# é proporcional ao número de elementos que temos que deslocar para +abrir espaço para + #x#. Portanto o custo dessa operação +(ignorando o custo de redimensionar #a#) é $O(#n#-#i#)$. + +Implementar a operação +#remove(i)# é similar. Desloca-se os elementos +$#a[i+1]#,\ldots,#a[n-1]#$ à esquerda por uma posição (sobrescrevendo #a[i]#) +e decrementar o valor de + #n#. Após fazer isso, verificamos se #n# muito menor + que #a.length# ao verificar se $#a.length# \ge 3#n#$. +Caso positivo, então chamamos #resize()# para reduzir o tamanho de #a#. \codeimport{ods/ArrayStack.remove(i)} % TODO: Add shifting figure -If we ignore the cost of the #resize()# method, the cost of a #remove(i)# -operation is proportional to the number of elements we shift, which -is $O(#n#-#i#)$. +Ao ignorar o custo do método #resize()#, o custo de uma operação #remove(i)# +é proporcional ao número de elementos que deslocamos, que é $O(#n#-#i#)$. -\subsection{Growing and Shrinking} +\subsection{Expansão e Redução} -The #resize()# method is fairly straightforward; it allocates a new -array #b# whose size is $2#n#$ and copies the #n# elements of #a# into -the first #n# positions in #b#, and then sets #a# to #b#. Thus, after a call to #resize()#, $#a.length# = 2#n#$. +O método + #resize()# razoavelmente direto; ele aloca um novo array +#b# de tamanho $2#n#$ e copia os #n# elementos de #a# nas primeiras +#n# posições em #b#, e então atribui #a# em #b#. Então, após isso faz uma chamada a #resize()#, $#a.length# = 2#n#$. \codeimport{ods/ArrayStack.resize()} -Analyzing the actual cost of the #resize()# operation is easy. It -allocates an array #b# of size $2#n#$ and copies the #n# elements of #a# -into #b#. This takes $O(#n#)$ time. +Analisar o custo real da operação +#resize()# é fácil. +Ela aloca um array + #b# de tamanho $2#n#$ e copia os #n# elementos de #a# em +#b#. Isso leva $O(#n#)$ de tempo. -The running time analysis from the previous section ignored the cost -of calls to #resize()#. In this section we analyze this cost using a -technique known as \emph{amortized analysis}. This technique does not -try to determine the cost of resizing during each individual #add(i,x)# -and #remove(i)# operation. Instead, it considers the cost of all calls to -#resize()# during a sequence of $m$ calls to #add(i,x)# or #remove(i)#. -In particular, we will show: +A análise de tempo de execução da seção anterior ignorou o custo de chamadas a +#resize()#. Nesta seção analisaremos esse custo usando uma técnica chamada de +\emph{análise amortizada}. Essa técnica não tenta determinar o custo de +redimensionar o array durante cada operação +#add(i,x)# e #remove(i)#. Em vez disso, ela considera o custo de todas as chamadas a +#resize()# durante a sequência de $m$ chamadas a #add(i,x)# ou #remove(i)#. +Em particular, mostraremos que: \begin{lem}\lemlabel{arraystack-amortized} - If an empty #ArrayStack# is created and any sequence of $m\ge 1$ calls - to #add(i,x)# and #remove(i)# are performed, then the total time spent - during all calls to #resize()# is $O(m)$. + Se um + #ArrayStack# vazio é criado e qualquer sequência de $m\ge 1$ chamadas a + #add(i,x)# e #remove(i)# são executadas, então o tempo total gasto durante + todas as chamadas a + #resize()# é $O(m)$. \end{lem} \begin{proof} - We will show that any time #resize()# is called, the number of calls - to #add# or #remove# since the last call to #resize()# is at least - $#n#/2-1$. Therefore, if $#n#_i$ denotes the value of #n# during the - $i$th call to #resize()# and $r$ denotes the number of calls to - #resize()#, then the total number of calls to #add(i,x)# or - #remove(i)# is at least + Nós iremos mostra que em qualquer momento que + #resize()# é chamada, o número de chamadas a + #add# ou #remove# desde a última chamada a #resize()# é pelo menos + $#n#/2-1$. Portanto, se $#n#_i$ denota o valor de #n# durante a + $i$-ésima chamada a #resize()# e $r$ denota o número de chamadas a + #resize()#, então o número total de chamadas a #add(i,x)# ou + #remove(i)# é pelo menos \[ \sum_{i=1}^{r} (#n#_i/2-1) \le m \enspace , \] - which is equivalent to + o que é equivalente a \[ \sum_{i=1}^{r} #n#_i \le 2m + 2r \enspace . \] - On the other hand, the total time spent during all calls to #resize()# is + Por outro lado, o tempo total gasto durante todas as chamadas a #resize()# é \[ \sum_{i=1}^{r} O(#n#_i) \le O(m+r) = O(m) \enspace , \] - since $r$ is not more than $m$. All that remains is to show that the - number of calls to #add(i,x)# or #remove(i)# between the $(i-1)$th - and the $i$th call to #resize()# is at least $#n#_i/2$. - - There are two cases to consider. In the first case, #resize()# is - being called by #add(i,x)# because the backing array #a# is full, i.e., - $#a.length# = #n#=#n#_i$. Consider the previous call to #resize()#: - after this previous call, the size of #a# was #a.length#, but the - number of elements stored in #a# was at most $#a.length#/2=#n#_i/2$. - But now the number of elements stored in #a# is $#n#_i=#a.length#$, - so there must have been at least $#n#_i/2$ calls to #add(i,x)# since - the previous call to #resize()#. + uma vez que + $r$ não é maior que $m$. O que resta é mostrar que o número de chamadas a + #add(i,x)# ou #remove(i)# entre a $(i-1)$-ésima + e a $i$-ésima chamada a #resize()# é de pelo menos $#n#_i/2$. + + Há dois casos a considerar. No primeiro caso, + #resize()# está sendo chamado por +#add(i,x)# pois o array de apoio #a# está cheio, i.e., + $#a.length# = #n#=#n#_i$. Considere a chamada anterior a #resize()#: + após essa chamada prévia, o tamanho de + #a# era #a.length#, mas o número de elementos guardados em #a# + era no máximo $#a.length#/2=#n#_i/2$. + Mas agora o número de elementos guardados em + #a# é $#n#_i=#a.length#$, então deve ter havido pelo menos +$#n#_i/2$ chamadas a #add(i,x)# desde a chamada anterior a + #resize()#. % TODO: Add figure - The second case occurs when #resize()# is being called by - #remove(i)# because $#a.length# \ge 3#n#=3#n#_i$. Again, after the - previous call to #resize()# the number of elements stored in #a# was - at least $#a.length/2#-1$.\footnote{The ${}-1$ in this formula accounts for - the special case that occurs when $#n#=0$ and $#a.length# = 1$.} Now there - are $#n#_i\le#a.length#/3$ elements stored in #a#. Therefore, the number - of #remove(i)# operations since the last call to #resize()# is at least + O segundo caso ocorre quando + #resize()# está sendo chamado por + #remove(i)# porque $#a.length# \ge 3#n#=3#n#_i$. Novamente, após a + chamada anterior a + #resize()# o número de elementos guardados em #a# era pelo menos + $#a.length/2#-1$.\footnote{O ${}-1$ nessa fórmula inclui o caso especial que ocorre quando + $#n#=0$ e $#a.length# = 1$.} Agora há + $#n#_i\le#a.length#/3$ elementos guardados em #a#. Portanto, o número de + operações #remove(i)# desde a última chamada a #resize()# é pelo menos \begin{align*} R & \ge #a.length#/2 - 1 - #a.length#/3 \\ & = #a.length#/6 - 1 \\ & = (#a.length#/3)/2 - 1 \\ & \ge #n#_i/2 -1\enspace . \end{align*} - In either case, the number of calls to #add(i,x)# or #remove(i)# that - occur between the $(i-1)$th call to #resize()# and the $i$th call to - #resize()# is at least $#n#_i/2-1$, as required to complete the proof. +Nos dois casos, o número de chamadas a + #add(i,x)# ou #remove(i)# que ocorrem + entre + $(i-1)$-ésima chamada a #resize()# e a $i$-ésima chamada a + #resize()# é pelo menos $#n#_i/2-1$, conforme exigido para completar a prova. \end{proof} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of an #ArrayStack#: +O teorema a seguir resume o desempenho de uma #ArrayStack#: \begin{thm}\thmlabel{arraystack} - An #ArrayStack# implements the #List# interface. Ignoring the cost of - calls to #resize()#, an #ArrayStack# supports the operations + Uma + #ArrayStack# implementa a interface #List#. Ignorando o custo de chamadas a + #resize()#, uma #ArrayStack# aceita as operações \begin{itemize} - \item #get(i)# and #set(i,x)# in $O(1)$ time per operation; and + \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e \item #add(i,x)# and #remove(i)# in $O(1+#n#-#i#)$ time per operation. \end{itemize} Furthermore, beginning with an empty #ArrayStack# and performing any diff --git a/latex/ods.tex b/latex/ods.tex index 6f20fbb5..d977165d 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -183,7 +183,7 @@ % Title page content \title{Open Data Structures (in \lang). Estruturas de Dados Abertas (em \lang)} -\author{Autor: Pat Morin.\\Tradução e adaptações por: Marcelo Keese Albertini} +\author{Autor: Pat Morin.\\Tradução e adaptação por: Marcelo Keese Albertini} \date{% Edition 0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} \htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} @@ -243,7 +243,7 @@ \thispagestyle{empty} \cleardoublepage -\fancyhead[CE]{\small Why This Book?} % chapter title, left center +\fancyhead[CE]{\small Por que este livro?} % chapter title, left center \include{why} \cleardoublepage diff --git a/latex/snarf-python.py b/latex/snarf-python.py index 35082707..604abc13 100755 --- a/latex/snarf-python.py +++ b/latex/snarf-python.py @@ -28,7 +28,7 @@ def matches(line, methods): sig = name + args sys.stderr.write("signature: %s %r\n" % (sig, methods)) return sig in methods - + def translate_code(line): """Translate a snippet of Python into LaTeX markup for pseudocode""" @@ -55,22 +55,22 @@ def translate_code(line): line = re.sub(r'super\([^)]*\)', 'super', line) # a += b => a = a + b --- handles +=, -=, *=, and /= - line = re.sub(r'([^\s]+)\s*([+*-/])=\s*(.*)$', r'\1 = \1 \2 \3', line) + line = re.sub(r'([^\s]+)\s*([+*-/])=\s*(.*)$', r'\1 = \1 \2 \3', line) # turn assignment operator (=) into \gets line = re.sub(r'([^-*/<>!=+])=([^=])', r'\1\\gets \2', line) - # This backstop makes sure we ensuremath any assignment + # This backstop makes sure we ensuremath any assignment line = re.sub(r'(\s*)([^#:]*\\gets[^\w][^#;]*)', - r'\1\ensuremath{\2}', line) + r'\1\ensuremath{\2}', line) # for _ in range(n) => repeat n times line = re.sub(r'for\s+_\s+in\s+range\s*\((\w+)\)', r'repeat \1 times', line) # for _ in range(a, b, -1) => repeat b-a times - line = re.sub(r'for\s+_\s+in\s+range\s*\((\w+)\s*,\s*(\w+)\s*,\s*-1\)', + line = re.sub(r'for\s+_\s+in\s+range\s*\((\w+)\s*,\s*(\w+)\s*,\s*-1\)', r'repeat \1-\2 times', line) - + # for : => for do # while : => while do # Note: The space after 'do' is important here (see [1]) @@ -78,11 +78,11 @@ def translate_code(line): # if : => if then # Note: The space after 'then' is important here (see [1]) - line = re.sub(r'\bif (.*):', r'if \1 then ', line) + line = re.sub(r'\bif (.*):', r'if \1 then ', line) # else: => else # Note: The space after 'then' is important here (see [1]) - line = re.sub(r'\belse:', r'else ', line) + line = re.sub(r'\belse:', r'else ', line) # len() => length() line = re.sub(r'\blen\s*\(\s*(\w+)\s*\)', r'length(\1)', line) @@ -95,25 +95,25 @@ def translate_code(line): r'\1,\1-1,\1-2,\ldots,\2+1', line) # range(a, b-1) => a,...,b-2 - #line = re.sub(r'\brange\s*\(\s*(.*),\s*(.*)\s*\-\s*1\s*\)', + #line = re.sub(r'\brange\s*\(\s*(.*),\s*(.*)\s*\-\s*1\s*\)', # r'\1,\1+1,\1+2,\ldots,\2-2', line) # range(a, b) => a,...,b-1 - line = re.sub(r'\brange\s*\(\s*(.*),\s*(.*)\s*\)', + line = re.sub(r'\brange\s*\(\s*(.*),\s*(.*)\s*\)', r'\1,\1+1,\1+2,\ldots,\2-1', line) # range(a) => 0,...,a-1 - #re.sub(r'\brange\s*\(\s*([^),]+(\([^)]*\)[^)]*)?)\s*\)', + #re.sub(r'\brange\s*\(\s*([^),]+(\([^)]*\)[^)]*)?)\s*\)', line = re.sub(r'\brange\s*\((.*)\)', r'0,1,2,\ldots,\1-1', line) # a[x:y] => a[x,x+1,\ldots,y-1] line = re.sub(r'\[([^\]]+):([^\]]+)\]', r'[\ensuremath{\1,\1+1,\ldots,\2-1}]', line) - + # some dumb arithmetic line = re.sub(r'\+\s*1\s*-\s*1', '', line) line = re.sub(r'-\s*1\s*-\s*1', '-2', line) line = re.sub(r'0\s*\+\s*1', '1', line) - + # -1-1 => -2 and similar arithmetic line = re.sub(r'-\s*1\s*-\s*1', r'-2', line) @@ -146,7 +146,7 @@ def translate_code(line): # a is b => a == b (our code doesn't care about equality vs. identity line = re.sub(r'\bis\s+not\b', r'!=', line) - line = re.sub(r'\bis\b', '==', line) + line = re.sub(r'\bis\b', '==', line) # recognize mathematical expression and \ensuremath them @@ -154,7 +154,7 @@ def translate_code(line): basic = r'\b\w+\b' fncall = r'%s(\([^\)]*\))?' % basic indexed = r'%s(\[[^\]]+\])?' % fncall - operand = r'(-?%s)' % indexed + operand = r'(-?%s)' % indexed op = r'(,\\ldots,|&|>=|\.|,|//|<=|==|!=|=|\gets|%|<<|>>|<|>|\+|-|/|\*|\^|\&)' expr0 = r'(%s(\s*%s\s*%s)*)' % (operand, op, operand) parenexpr = r'(%s|\(%s\))' % (expr0, expr0) @@ -163,12 +163,12 @@ def translate_code(line): # turn Python math operators into LaTeX math operators - line = re.sub(r'>=', r'\\ge', line) + line = re.sub(r'>=', r'\\ge', line) line = re.sub(r'<=', r'\le', line) - line = re.sub(r'%', r'\\bmod ', line) + line = re.sub(r'%', r'\\bmod ', line) line = re.sub(r'\*', r'\cdot ', line) line = re.sub(r'!=', r'\\ne', line) - line = re.sub(r'==', r'\eq', line) + line = re.sub(r'==', r'\eq', line) line = re.sub(r'(^|[^\\])&', r'\1 \\wedge ', line) line = re.sub(r'//', r'\\bdiv ', line) @@ -179,8 +179,8 @@ def translate_code(line): line = re.sub(r'\(?\s*1\s*<<\s*(\w+)\s*\)?', r'2^{\1}', line) # these are hacks and should eventually be fixed - line = re.sub(r'\>\>', r'\ensuremath{\\gg}', line) - line = re.sub(r'\<\<', r'\ensuremath{\\ll}', line) + line = re.sub(r'\>\>', r'\ensuremath{\\gg}', line) + line = re.sub(r'\<\<', r'\ensuremath{\\ll}', line) # del is a python keyword, but we have a variable called del in Ch. 5 line = re.sub(r'\bdl\b', r'\mathit{del}', line) @@ -191,30 +191,30 @@ def translate_code(line): line = re.sub(r'False', r'\ensuremath{\mathit{false}}', line) # Camelcase variable names to underscores - line = re.sub(r'\b([a-z_][a-z0-9_]*)([A-Z])', + line = re.sub(r'\b([a-z_][a-z0-9_]*)([A-Z])', lambda m: m.group(1) + '_' + m.group(2).lower(), line) line = re.sub(r'_[A-Z]_', lambda m: m.group(0).lower(), line) # None/null => \textbf{nil} line = re.sub(r'\b(None|null)\b', r'nil', line) - # typeset function names in mathrm + # typeset function names in mathrm line = re.sub(r'\b(\w+)\(', r'\\mathrm{\1}(', line) # typeset variables in mathit # this loop is a dirty hack to deal with expressions like 'i-front.size()' - for i in range(3): + for i in range(3): # This RE is delicate. It interacts with [1] line = re.sub(r'(^|[^\\\w])([a-z_][a-z0-9_]*)([^{}\w]|}?$)', \ r'\1\ensuremath{\mathit{\2}}\3', line) - + # undo mathit for any keywords we accidentally hit line = re.sub(r'\\ensuremath{\\mathit{' + keywords + '}}', r'\1', line) - + # don't be afraid to use l as a variable name line = re.sub(r'\bell\b', r'\ell', line) line = re.sub(r'\bl\b', r'\ell', line) - + # typeset class names in mathrm line = re.sub(r'([A-Z]\w+)', r'\mathrm{\1}', line) @@ -223,12 +223,12 @@ def translate_code(line): # remove trailing underscores line = re.sub(r'([a-z])_+\b', r'\1', line) - + # escape underscores line = re.sub(r'_', r'\_', line) - + # use subscripts on variable names that end with a single digit - line = re.sub(r'(\\mathit{\w+)(\d})', r'\1_\2', line) + line = re.sub(r'(\\mathit{\w+)(\d})', r'\1_\2', line) # Some hex constants look better in binary #line = re.sub(r'\b0xff\b', '11111111_2', line) @@ -253,7 +253,7 @@ def touchup_code_line(line): # add \\ at the end of each line line = re.sub(r'$', r'\\\\', line) return line - + def print_code(clazz, methods): """Print out the methods in clazz that are listed in methods""" @@ -283,14 +283,14 @@ def print_code(clazz, methods): print touchup_code_line(translate_code(line)) except IOError: print "Unable to open %s" % filename - if not printed: + if not printed: print r'NO OUTPUT (looking for \verb+' + str(methods) + "+)" print r'\end{flushleft}' print [r'\end{oframed}', r'\end{leftbar}'][html] def fig_subs(line): - return re.sub(r'(\includegraphics\[[^\]]*\]){figs/', + return re.sub(r'(\includegraphics\[[^\]]*\]){figs/', r'\1{figs-python/', line) def code_subs(line): @@ -323,7 +323,7 @@ def snarf(infile): methods = m.group(2).lstrip('.').split('.') if [m for m in methods if re.match(r'^\w+$', m)]: sys.stderr.write('looking for initialize() in %s because of %s\n' \ - % (clazz, m)) + % (clazz, m)) methods.append('initialize()') sys.stderr.write('looking for %r in %s\n' \ % (methods, clazz)) diff --git a/latex/why.tex b/latex/why.tex index ff11af55..fcfd0cd3 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -1,49 +1,53 @@ -\chapter*{Why This Book?} -\addcontentsline{toc}{chapter}{Why This Book?} - -There are plenty of books that teach introductory data structures. -Some of them are very good. Most of them cost money, and the vast -majority of computer science undergraduate students will shell out at -least some cash on a data structures book. - -Several free data structures books are available online. Some are very -good, but most of them are getting old. The majority of these books -became free when their authors and/or publishers decided to stop updating -them. Updating these books is usually not possible, for two reasons: -(1)~The copyright belongs to the author and/or publisher, either of whom -may not allow it. (2)~The \emph{source code} for these books is often -not available. That is, the Word, WordPerfect, FrameMaker, or \LaTeX\ -source for the book is not available, and even the version of the software -that handles this source may not be available. - -The goal of this project is to free undergraduate computer science -students from having to pay for an introductory data structures book. -I have decided to implement this goal by treating this book like an -Open Source -\index{Open Source} -software project. The \LaTeX\ source, \lang\ source, and -build scripts for the book are available to download from the -author's website\footnote{\url{http://opendatastructures.org}} -and also, more importantly, on a reliable source code management -site.\footnote{\url{https://github.com/patmorin/ods}} - -The source code available there is released under a Creative Commons -Attribution license, meaning that anyone is free to \emph{share}: -\index{share} -to copy, distribute and -transmit the work; and to \emph{remix}: -\index{remix} -to adapt the work, including the -right to make commercial use of the work. The only condition on these -rights is \emph{attribution}: you must acknowledge that the derived work -contains code and/or text from \url{opendatastructures.org}. - -Anyone can contribute corrections/fixes using the \texttt{git} +\chapter*{Porquê este livro?} +\addcontentsline{toc}{chapter}{Porquê este livro?} + +Existem muitos livros introdutórios a estruturas de dados. +Alguns deles são muito bons. A maior parte deles são pagos, e a +grande maioridade de estudantes de Ciência da Computação e Sistemas de +Informação irá gastar algum dinheiro em um livro de estruturas de dados. + +Vários livros gratuitos sobre estruturas de dados estão disponíveis online. +Alguns muito bons, mas a maior parte está ficando desatualizada. Grande +parte deles se torna gratuita quando seus autores e/ou editores decidem +parar de atualizá-los. Atualizar esses livros frequentemente não é possível, +por duas razões: (1)~Os direitos autorais pertencem ao autor e/ou editor, +os quais podem não autorizar tais atualizações. (2)~O \emph{código-fonte} desses +livros muitas vezes não está disponível. Isto é os arquivos Word, WordPerfect, +FrameMaker, ou \LaTeX para o livro não está acessível e, ainda, a versão do +software que processa tais arquivo pode não estar mais disponível. + +A meta deste projeto é libertar estudantes de graduação de Ciência da Computação +de ter que pagar por um livro introdutório a estruturas de dados. + +Eu\footnote{Eu, Marcelo Keese Albertini, o tradutor para o Português, agradeço imensamente ao autor original Pat Morin por ter tomado tal decisão que permite a disponibilização de um livro de qualidade, gratuito e de livre distribuição na língua nativa de meus alunos brasileiros.}\footnote{I, Marcelo Keese Albertini, the translator to portuguese, am very grateful to the original author Pat Morin for this decision which allows the availability of a good quality and free book in the +native language of my Brazilian students.} decidi implementar essa meta ao tratar esse livro como um projeto de Software Aberto +\index{Open Source}% +\index{Software Aberta}% +. + +O arquivos-fonte originais em \LaTeX\ , \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} +e também, de modo mais importante, em um site confiável de gerenciamento de códigos-fonte +.\footnote{\url{https://github.com/patmorin/ods} e, em português, \url{https://github.com/albertiniufu/ods}} + +O código-fonte disponível é publicado sob uma licença +Creative Commons Attribution, +o que quer dizer que qualquer um é livre para \emph{compartilhar} +\index{compartilhar} +copiar, distribuir e +transmitir essa obra; e \emph{modificar}: +\index{modificar} +adaptar a obra, incluindo o direito +de fazer uso comercial da obra. +A única condição a esse direitos é \emph{atribuição}: +você deve reconhecer que a obra derivada contém código e/ou texto de \url{opendatastructures.org}. + +Qualquer pessoa pode contribuir com correções usando o sistema de gerenciamento +de código-fonte \texttt{git} \index{git@\texttt{git}} -source-code management system. Anyone can also fork the book's sources to -develop a separate version (for example, in another programming language). -My hope is that, by doing things this way, this book will continue to -be a useful textbook long after my interest in the project, or my pulse, -(whichever comes first) has waned. +. Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fontes +livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). +Minha esperança é que, ao fazer dessee jeito, este livro irá continuar a +ser um livro didático bem depois que o meu interesse no projeto, ou meu pulso, (seja qual for que +aconteça primeiro) esvaneça. From b1df5171834e837344b7150cb129326e58df18a2 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Mon, 10 Aug 2020 18:25:06 -0300 Subject: [PATCH 07/66] Fix tabs on make command --- latex/images/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latex/images/Makefile b/latex/images/Makefile index 66a03bf8..3ee84b51 100644 --- a/latex/images/Makefile +++ b/latex/images/Makefile @@ -12,10 +12,10 @@ bigoh-2.tex : bigoh-2.gp gnuplot $< %.pdf : %.svg -inkscape --export-type=pdf --export-filename=$@ $< + inkscape --export-type=pdf --export-filename=$@ $< %.eps : %.svg -inkscape --export-type=eps --export-filename=$@ $< + inkscape --export-type=eps --export-filename=$@ $< clean: rm -f $(pdfs) From e1a9cb27c1342773c655c86913e979a5fa0bd770 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 10 Aug 2020 18:35:03 -0300 Subject: [PATCH 08/66] minor fixes --- latex/figs-python/snarf-fig.py | 4 ++-- latex/images/Makefile | 4 ++-- latex/why.tex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/latex/figs-python/snarf-fig.py b/latex/figs-python/snarf-fig.py index 24500654..a1873bd6 100755 --- a/latex/figs-python/snarf-fig.py +++ b/latex/figs-python/snarf-fig.py @@ -9,7 +9,7 @@ line = re.sub(r'{\\color{var}', r'\mathit{', line) line = re.sub(r'\\mathtt', r'\mathrm', line) line = re.sub(r'\\texttt', r'\\textrm', line) - line = re.sub(r'(\\mathrm{[a-z_]+)([A-Z]+)', + line = re.sub(r'(\\mathrm{[a-z_]+)([A-Z]+)', lambda m: m.group(1) + '\_' + m.group(2).lower(), line) - line = re.sub(r'(\\mathit{\w+)(\d})', r'\1_\2', line) + line = re.sub(r'(\\mathit{\w+)(\d})', r'\1_\2', line) print line, diff --git a/latex/images/Makefile b/latex/images/Makefile index 66a03bf8..3ee84b51 100644 --- a/latex/images/Makefile +++ b/latex/images/Makefile @@ -12,10 +12,10 @@ bigoh-2.tex : bigoh-2.gp gnuplot $< %.pdf : %.svg -inkscape --export-type=pdf --export-filename=$@ $< + inkscape --export-type=pdf --export-filename=$@ $< %.eps : %.svg -inkscape --export-type=eps --export-filename=$@ $< + inkscape --export-type=eps --export-filename=$@ $< clean: rm -f $(pdfs) diff --git a/latex/why.tex b/latex/why.tex index fcfd0cd3..7aadbe39 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -1,5 +1,5 @@ -\chapter*{Porquê este livro?} -\addcontentsline{toc}{chapter}{Porquê este livro?} +\chapter*{Por que este livro?} +\addcontentsline{toc}{chapter}{Por que este livro?} Existem muitos livros introdutórios a estruturas de dados. Alguns deles são muito bons. A maior parte deles são pagos, e a From d4ae246b66913d8f00d64e2b74c03f10a8415f65 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 10 Aug 2020 23:51:41 -0300 Subject: [PATCH 09/66] Fixing label factorials --- latex/intro.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/intro.tex b/latex/intro.tex index ca38dbfc..5ce45ab3 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -387,7 +387,7 @@ \subsection{Exponenciais e Logaritmos} \] \subsection{Fatoriais} -\seclabel{fatoriais} +\seclabel{factorials} \index{fatorial}% Em uma ou duas partes deste livro, From 60736233378e4cf4c1c6ead0a4091fbf24c2b247 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 10 Aug 2020 23:52:10 -0300 Subject: [PATCH 10/66] translation to portuguese --- latex/ack.tex | 9 +- latex/arrays.tex | 363 +++++++++++++++++++++++++---------------------- 2 files changed, 199 insertions(+), 173 deletions(-) diff --git a/latex/ack.tex b/latex/ack.tex index 14fe42c0..f07e1dcf 100644 --- a/latex/ack.tex +++ b/latex/ack.tex @@ -1,5 +1,10 @@ -\chapter*{Acknowledgments} -\addcontentsline{toc}{chapter}{Acknowledgments} +\chapter*{Agradecimentos} +\addcontentsline{toc}{chapter}{Agradecimentos} + +Agradeço à todos que ajudaram e motivaram a fazer esta tradução. + +Devido à natureza pessoal de agradecimentos, vou manter os agradecimentos originais deste +livro a seguir. I am grateful to Nima~Hoda, who spent a summer tirelessly proofreading many of the chapters in this book; to the students in the Fall 2011 diff --git a/latex/arrays.tex b/latex/arrays.tex index 212d93f8..9e2d7c0f 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -227,32 +227,36 @@ \subsection{Resumo} #resize()#, uma #ArrayStack# aceita as operações \begin{itemize} \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e - \item #add(i,x)# and #remove(i)# in $O(1+#n#-#i#)$ time per operation. + \item #add(i,x)# e #remove(i)# em $O(1+#n#-#i#)$ tempo por operação. \end{itemize} - Furthermore, beginning with an empty #ArrayStack# and performing any - sequence of $m$ #add(i,x)# and #remove(i)# operations results in a - total of $O(m)$ time spent during all calls to #resize()#. + Além disso, ao comerçarmos com um + #ArrayStack# vazio e realizarmos qualquer sequência de $m$ operações + #add(i,x)# e #remove(i)# resulta em um total de + $O(m)$ tempo gasto durante todas as chamadas a #resize()#. \end{thm} -The #ArrayStack# is an efficient way to implement a #Stack#. -In particular, we can implement #push(x)# as #add(n,x)# and #pop()# -as #remove(n-1)#, in which case these operations will run in $O(1)$ -amortized time. +O #ArrayStack# é um jeito eficiente de implementar a #Stack#. +Em especial, podemos implementar + #push(x)# como #add(n,x)# e #pop()# +como #remove(n-1)# e nesse caso essas operações rodarão $O(1)$ +de tempo amortizado. -\section{#FastArrayStack#: An Optimized ArrayStack} +\section{#FastArrayStack#: Uma ArrayStack otimizada} \seclabel{fastarraystack} \index{FastArrayStack@#FastArrayStack#}% -Much of the work done by an #ArrayStack# involves shifting (by -#add(i,x)# and #remove(i)#) and copying (by #resize()#) of data. -\notpcode{In the implementations shown above, this was done using #for# loops.}% -\pcodeonly{In a naive implementation, this would be done using #for# loops.} -It -turns out that many programming environments have specific functions -that are very efficient at copying and moving blocks of data. In the C -programming language, there are the #memcpy(d,s,n)# and #memmove(d,s,n)# -functions. In the C++ language there is the #std::copy(a0,a1,b)# algorithm. -In Java there is the #System.arraycopy(s,i,d,j,n)# method. +Muito do trabalho feito por uma + #ArrayStack# envolver o deslocamento (por +#add(i,x)# e #remove(i)#) e cópias (pelo #resize()#) de dados. +\notpcode{Nas implementações mostradas acima, isso era feito usando laços #for#.}% +\pcodeonly{Em uma implementação naive, isso seria feito usando laços #for#.} +Acontece que muitos ambientes de programação tem funções específicas que são muito +eficientes em copiar e mover blocos de dados. +Na linguagem C, +existem as funções #memcpy(d,s,n)# e #memmove(d,s,n)#. +Na linguagem C++ existe o algoritmo #std::copy(a0,a1,b)#. +Em Java, existe o +método #System.arraycopy(s,i,d,j,n)#. \index{memcpy@#memcpy(d,s,n)#}% \index{std::copy@#std::copy(a0,a1,b)#}% \index{System.arraycopy@#System.arraycopy(s,i,d,j,n)#}% @@ -260,235 +264,252 @@ \section{#FastArrayStack#: An Optimized ArrayStack} \cppimport{ods/FastArrayStack.add(i,x).remove(i).resize()} \javaimport{ods/FastArrayStack.add(i,x).remove(i).resize()} -These functions are usually highly optimized and may even use special -machine instructions that can do this copying much faster than we could by -using a #for# loop. Although using these functions does not asymptotically -decrease the running times, it can still be a worthwhile optimization. +Essas funções são em geral altamente otimizadas e podem usar até mesmo de +instruções de máquina especiais que podem fazer esse copiar muito mais rápido +que poderíamos usando um laço #for#. +Embora o uso dessas funções não diminuam o tempo de execução assintoticamente falando, +pode ser uma otimização que vale a pena. -\pcodeonly{In our C++ and Java implementations, the use of fast array -copying functions} -\notpcode{In the \lang\ implementations here, the use of the native \javaonly{#System.arraycopy(s,i,d,j,n)#}\cpponly{#std::copy(a0,a1,b)#}} -resulted in speedups of a factor between 2 and 3, depending on the types of -operations performed. Your mileage may vary. +\pcodeonly{Nas nossas implementações em C++ e Java, o uso de funções de cópia rápida de arrays +} +\notpcode{Nas implementações em \lang\ aqui, o uso do nativo \javaonly{#System.arraycopy(s,i,d,j,n)#}\cpponly{#std::copy(a0,a1,b)#}} +resultaram em um fator de speedups (aceleração) entre 2 e 3, dependendo dos tipos de operações realizadas. +O resultados podem variar de acordo com o caso. -\section{#ArrayQueue#: An Array-Based Queue} +\section{#ArrayQueue#: Uma Queue Baseada Em Array} \seclabel{arrayqueue} \index{ArrayQueue@#ArrayQueue#}% -In this section, we present the #ArrayQueue# data structure, which -implements a FIFO (first-in-first-out) queue; elements are removed (using -the #remove()# operation) from the queue in the same order they are added -(using the #add(x)# operation). - -Notice that an #ArrayStack# is a poor choice for an implementation of a -FIFO queue. It is not a good choice because we must choose one end of -the list upon which to add elements and then remove elements from the -other end. One of the two operations must work on the head of the list, -which involves calling #add(i,x)# or #remove(i)# with a value of $#i#=0$. -This gives a running time proportional to #n#. - -To obtain an efficient array-based implementation of a queue, we -first notice that the problem would be easy if we had an infinite -array #a#. We could maintain one index #j# that keeps track of the -next element to remove and an integer #n# that counts the number of -elements in the queue. The queue elements would always be stored in + +Nesta seção, apresentamos a estrutura de dados + #ArrayQueue#, que implementa uma queue do tipo + FIFO (first-in-first-out, primeiro-que-chega-primeiro-que-sai); + elementos são removidos (usando a operação +#remove()#) da queue na mesma ordem em que são adicionados +(usando a operação #add(x)#). + +Note uma +#ArrayStack# é uma escolha ruim para uma implementação de uma +queue do tipo FIFO. Não é uma boa escolha porque precisamos escolher um fim da lista ao qual adicionaremos elementos e então remover elementos do outro lado. +Uma das duas operações precisa trabalhar na cabeça da lista, o que envolve chamar +#add(i,x)# ou #remove(i)# com um valor de $#i#=0$. +Isso resulta em um tempo de execução proporcional a #n#. + +Para obter uma implementação de queue eficiente baseada em array, +primeiro observamos que o problema seria fácil se tivéssemos um array #a# infinito. +Poderíamos manter um índice + #j# que guarda qual é o próximo elemento a remover e um inteiro +#n# que conta o número de elementos na queue. +Os elementos da queue sempre seriam guardados em \[ #a[j]#,#a[j+1]#,\ldots,#a[j+n-1]# \enspace . \] -Initially, both #j# and #n# would be -set to 0. To add an element, we would place it in #a[j+n]# and increment #n#. -To remove an element, we would remove it from #a[j]#, increment #j#, and -decrement #n#. - -Of course, the problem with this solution is that it requires an infinite -array. An #ArrayQueue# simulates this by using a finite array #a# -and \emph{modular arithmetic}. -\index{modular arithmetic}% -This is the kind of arithmetic used when -we are talking about the time of day. For example 10:00 plus five -hours gives 3:00. Formally, we say that +Inicialmente, ambos #j# e #n# receberiam o valor 0. +Para adicionar um elemento, teríamos que colocá-lo em #a[j+n]# e incrementar #n#. +Para remover um elemento, o removeríamos de + #a[j]#, incrementando #j#, e +decrementando #n#. + +É claro, o problema com essa solução é que ela requer um array infinito. +Um +#ArrayQueue# simula isso ao usar um array finito #a# +e \emph{aritmética modular}. +\index{aritmética modular}% +Esse é o tipo de aritmética usada quando estamos falando sobre a hora do dia. +Por exemplo, 10:00 mais cinco horas resulta em 3:00. Formalmente, dizemos que \[ 10 + 5 = 15 \equiv 3 \pmod{12} \enspace . \] -We read the latter part of this equation as ``15 is congruent to 3 modulo -12.'' We can also treat $\bmod$ as a binary operator, so that +Lemos a última parte dessa equação da forma ``15 é congruente a 3 módulo 12.'' +Podemos também tratar + $\bmod$ como um operador binário, tal que \[ 15 \bmod 12 = 3 \enspace . \] -More generally, for an integer $a$ and positive integer $m$, $a \bmod m$ -is the unique integer $r\in\{0,\ldots,m-1\}$ such that $a = r + km$ for -some integer $k$. Less formally, the value $r$ is the remainder we get -when we divide $a$ by $m$. -\pcodeonly{In many programming languages, including C, C++, and Java, -the mod operate is represented using the \% symbol.} -\notpcode{In many programming languages, including -\javaonly{Java}\cpponly{C++}, the $\bmod$ operator is represented -using the #%# symbol.\footnote{This is sometimes referred to as the -\emph{brain-dead} mod operator, since it does not correctly implement -the mathematical mod operator when the first argument is negative.}} - -Modular arithmetic is useful for simulating an infinite array, -since $#i#\bmod #a.length#$ always gives a value in the range -$0,\ldots,#a.length-1#$. Using modular arithmetic we can store the -queue elements at array locations +De modo mais geral, para um inteiro +$a$ e um inteiro positivo $m$, $a \bmod m$ +é único inteiro + $r\in\{0,\ldots,m-1\}$ tal que $a = r + km$ para algum inteiro $k$. +Informalmente, o valor $r$ é o resto obtido quando dividimos $a$ por $m$. +\pcodeonly{Em muitas linguagens de programação, incluindo C, C++ e Java, o operador mod é representado +usando o símbolo \%.} +\notpcode{Em muitas linguagens de programação incluindo +\javaonly{Java}\cpponly{C++}, o operador $\bmod$ é representado +usando o símbolo + #%# symbol.\footnote{Isso às vezes é chamado de +operador mod com \emph{morte cerebral}, pois não implementa corretamente +o operador matemático mod quando o primeiro argumento é negativo.}} + +Aritmética modular é útil para simular um array infinito +uma vez que +$#i#\bmod #a.length#$ sempre resulta em um valor no intervalo +$0,\ldots,#a.length-1#$. Usando aritmética modular podemos guardar +os elementos da queue em posições do array \[ #a[j%a.length]#,#a[(j+1)%a.length]#,\ldots,#a[(j+n-1)%a.length]# \enspace. \] -This treats the array #a# like a \emph{circular array} -\index{circular array}% -\index{array!circular}% -in which array indices -larger than $#a.length#-1$ ``wrap around'' to the beginning of -the array. +Desse jeito trata-se o array + #a# como um \emph{array circular} +\index{array circular}% +\index{circular!array}% +no qual os índices do array maiores que +$#a.length#-1$ ``dão a volta'' ao começo do array. % TODO: figure -The only remaining thing to worry about is taking care that the number -of elements in the #ArrayQueue# does not exceed the size of #a#. +A única coisa que falta considerar é cuidar que o número de elementos no + #ArrayQueue# não ultrapasse o tamanho de #a#. \codeimport{ods/ArrayQueue.a.j.n} -A sequence of #add(x)# and #remove()# operations on an #ArrayQueue# is -illustrated in \figref{arrayqueue}. To implement #add(x)#, we first -check if #a# is full and, if necessary, call #resize()# to increase -the size of #a#. Next, we store #x# in -#a[(j+n)%a.length]# and increment #n#. +A sequência de operações +#add(x)# e #remove()# em um #ArrayQueue# é +ilustrado na \figref{arrayqueue}. Para implementar #add(x)#, primeiro +verificamos se + #a# está cheio e, se necessário, chamamos #resize()# para aumentar o tamanho de +#a#. Em seguida, guardamos #x# em +#a[(j+n)%a.length]# e incrementamos #n#. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/arrayqueue} \end{center} - \caption[Adding and removing from an ArrayQueue]{A sequence of #add(x)# and #remove(i)# operations on an - #ArrayQueue#. Arrows denote elements being copied. Operations that - result in a call to #resize()# are marked with an asterisk.} + \caption[Adicionar e remover de um ArrayQueue]{Uma sequência de operações #add(x)# e #remove(i)# em um + #ArrayQueue#. Flechas denotam elementos sendo copiados. Operações que resultam em uma chamada a + #resize()# são marcadas com um asterisco.} \figlabel{arrayqueue} \end{figure} - - \codeimport{ods/ArrayQueue.add(x)} -To implement #remove()#, we first store #a[j]# so that we can return -it later. Next, we decrement #n# and increment #j# (modulo #a.length#) -by setting $#j#=(#j#+1)\bmod #a.length#$. Finally, we return the stored -value of #a[j]#. If necessary, we may call #resize()# to decrease the -size of #a#. +Para implementar +#remove()#, primeiro guardamos #a[j]# para que reutilizá-lo depois. +A seguir, decrementamos #n# e incrementamos #j# (módulo #a.length#) +ao atribuir +$#j#=(#j#+1)\bmod #a.length#$. Finalmente, retornamos o valor guardado de +#a[j]#. Se necessário, podemos chamar #resize()# para diminuir o tamanho de #a#. \codeimport{ods/ArrayQueue.remove()} -Finally, the #resize()# operation is very similar to the #resize()# -operation of #ArrayStack#. It allocates a new array, #b#, of size $2#n#$ -and copies +Finalmente, a operação +#resize()# é muito similar à operação #resize()# +do #ArrayStack#. Ela aloca um novo array, #b#, de tamanho $2#n#$ +e copia \[ #a[j]#,#a[(j+1)%a.length]#,\ldots,#a[(j+n-1)%a.length]# \] -onto +em \[ #b[0]#,#b[1]#,\ldots,#b[n-1]# \] -and sets $#j#=0$. +e atribui $#j#=0$. \codeimport{ods/ArrayQueue.resize()} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of the #ArrayQueue# -data structure: +O teorema a seguir resume o desempenho da estrutura de dados #ArrayQueue#: \begin{thm} -An #ArrayQueue# implements the (FIFO) #Queue# interface. Ignoring the cost of -calls to #resize()#, an #ArrayQueue# supports the operations -#add(x)# and #remove()# in $O(1)$ time per operation. -Furthermore, beginning with an empty #ArrayQueue#, any sequence of $m$ -#add(i,x)# and #remove(i)# operations results in a total of $O(m)$ -time spent during all calls to #resize()#. +Um #ArrayQueue# implementa a interface (FIFO) #Queue#. Ignorando o custo das chamadas a +#resize()#, uma #ArrayQueue# aceita as operações +#add(x)# e #remove()# em tempo $O(1)$ por operação. +Além disso, ao começar com um #ArrayQueue# vazio, qualquer sequência de $m$ +operações #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ tempo gasto +durante todas as chamadas a #resize()#. \end{thm} %TODO: Discuss the use of bitwise-and as a replacement for the mod operator -\section{#ArrayDeque#: Fast Deque Operations Using an Array} +\section{#ArrayDeque#: Operações Rápidas para Deque Usando um Array} \seclabel{arraydeque} \index{ArrayDeque@#ArrayDeque#}% -The #ArrayQueue# from the previous section is a data structure for -representing a sequence that allows us to efficiently add to one -end of the sequence and remove from the other end. The #ArrayDeque# -data structure allows for efficient addition and removal at both ends. -This structure implements the #List# interface by using the same circular -array technique used to represent an #ArrayQueue#. +O #ArrayQueue# da seção anterior é uma estrutura de daos para +representar a sequência que nos permite eficientemente adicionar a +um lado da sequência e remover do outro. + +A estrutura de dados #ArrayDeque# permite a edição e remoção eficiente em ambos lados. +Essa estrutura implementa a interface + #List# ao usar a mesma técnica de array circular +usada para representar um #ArrayQueue#. \codeimport{ods/ArrayDeque.a.j.n} -The #get(i)# and #set(i,x)# operations on an #ArrayDeque# are -straightforward. They get or set the array element $#a[#{#(j+i)#\bmod +As operações #get(i)# e #set(i,x)# em um #ArrayDeque# são simples +. Elas obtém ou atribui a um elemento do array $#a[#{#(j+i)#\bmod #a.length#}#]#$. \codeimport{ods/ArrayDeque.get(i).set(i,x)} -The implementation of #add(i,x)# is a little more interesting. As -usual, we first check if #a# is full and, if necessary, call -#resize()# to resize #a#. Remember that we want this operation to be -fast when #i# is small (close to 0) or when #i# is large (close to -#n#). Therefore, we check if $#i#<#n#/2$. If so, we shift the -elements $#a[0]#,\ldots,#a[i-1]#$ left by one position. Otherwise -($#i#\ge#n#/2$), we shift the elements $#a[i]#,\ldots,#a[n-1]#$ right -by one position. See \figref{arraydeque} for an illustration of -#add(i,x)# and #remove(x)# operations on an #ArrayDeque#. +A implementação de + #add(i,x)# é um pouco mais interessante. Como sempre, primeiro + verificamos se + #a# está cheio e, se necessário, chamados +#resize()# para redimensionar #a#. Lembre-se que queremos que essa operação +seja rápida quando +#i# for pequeno (perto de 0) ou quando #i# é grande (perto de +#n#). Portanto, verificamos se $#i#<#n#/2$. Caso positiov, deslocamos os +elementos $#a[0]#,\ldots,#a[i-1]#$ à esquerda por uma posição. Caso contrário, +($#i#\ge#n#/2$), deslocamos os elementos $#a[i]#,\ldots,#a[n-1]#$ à direito por uma posição +. Veja \figref{arraydeque} para uma ilustração das operações +#add(i,x)# e #remove(x)# em um #ArrayDeque#. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/arraydeque} \end{center} - \caption[Adding and removing from an ArrayDeque]{A sequence of #add(i,x)# and #remove(i)# operations on an - #ArrayDeque#. Arrows denote elements being copied.} + \caption[Adição e remoção de ArrayDeque]{Uma sequência de operações #add(i,x)# e #remove(i)# em um + #ArrayDeque#. Flechas denotam elementos sendo copiados.} \figlabel{arraydeque} \end{figure} \codeimport{ods/ArrayDeque.add(i,x)} -By doing the shifting in this way, we guarantee that #add(i,x)# never -has to shift more than $\min\{ #i#, #n#-#i# \}$ elements. Thus, the running -time of the #add(i,x)# operation (ignoring the cost of a #resize()# -operation) is $O(1+\min\{#i#,#n#-#i#\})$. +Ao deslocar dessa maneira, nós garantimos que #add(i,x)# nunca tem que deslocar mais de + $\min\{ #i#, #n#-#i# \}$ elementos. Então, o tempo de execução da operação +#add(i,x)# (ignorando o custo de uma operação #resize()# +) é $O(1+\min\{#i#,#n#-#i#\})$. -The implementation of the #remove(i)# operation is similar. It either -shifts elements $#a[0]#,\ldots,#a[i-1]#$ right by one position or shifts -the elements $#a[i+1]#,\ldots,#a[n-1]#$ left by one position depending -on whether $#i#<#n#/2$. Again, this means that #remove(i)# never spends -more than $O(1+\min\{#i#,#n#-#i#\})$ time to shift elements. +A implementação da operação #remove(i)# é similar. Ela ou desloca elementos +$#a[0]#,\ldots,#a[i-1]#$ à direita uma posição ou desloca elementos +$#a[i+1]#,\ldots,#a[n-1]#$ à esquerda uma posição dependendo se +$#i#<#n#/2$. Novamente, isso significa que #remove(i)# nunca gasta mais de +$O(1+\min\{#i#,#n#-#i#\})$ tempo para deslocar elementos. \codeimport{ods/ArrayDeque.remove(i)} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of the #ArrayDeque# -data structure: +O teorema a seguir resume o desempenho da estrutura de dados + #ArrayDeque#: \begin{thm}\thmlabel{arraydeque} - An #ArrayDeque# implements the #List# interface. Ignoring the cost of - calls to #resize()#, an #ArrayDeque# supports the operations + Uma #ArrayDeque# implementa a interface #List#. Ignorando o custo de chamadas + a #resize()#, um #ArrayDeque# aceita as operações \begin{itemize} - \item #get(i)# and #set(i,x)# in $O(1)$ time per operation; and - \item #add(i,x)# and #remove(i)# in $O(1+\min\{#i#,#n#-#i#\})$ time - per operation. + \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e + \item #add(i,x)# e #remove(i)# em tempo $O(1+\min\{#i#,#n#-#i#\})$ + por operação. \end{itemize} - Furthermore, beginning with an empty #ArrayDeque#, performing any - sequence of $m$ #add(i,x)# and #remove(i)# operations results in a - total of $O(m)$ time spent during all calls to #resize()#. + Além disso, começar com um + #ArrayDeque# vazio, realizar qualquer sequência de $m$ operações + #add(i,x)# e #remove(i)# resulta em um + total de tempo $O(m)$ gasto durante todas as chamadas a #resize()#. \end{thm} -\section{#DualArrayDeque#: Building a Deque from Two Stacks} +\section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \seclabel{dualarraydeque} \index{DualArrayDeque@#DualArrayDeque#}% -Next, we present a data structure, the #DualArrayDeque# that -achieves the same performance bounds as an #ArrayDeque# by using -two #ArrayStack#s. Although the asymptotic performance of the -#DualArrayDeque# is no better than that of the #ArrayDeque#, it is -still worth studying, since it offers a good example of how to make a -sophisticated data structure by combining two simpler data structures. - -A #DualArrayDeque# represents a list using two #ArrayStack#s. Recall that -an #ArrayStack# is fast when the operations on it modify elements near -the end. A #DualArrayDeque# places two #ArrayStack#s, called #front# -and #back#, back-to-back so that operations are fast at either end. +A seguir, apresentamos uma estrutura de dados, o + #DualArrayDeque# que atinge os mesmos desempenhos +que um #ArrayDeque# ao usar +duas #ArrayStack#s. Embora o desempenho assintótico do +#DualArrayDeque# não é melhor que do #ArrayDeque#, ainda vale estudá-lo +,pois oferece um bom exemplo de como fazer uma estrutura de dados sofisticada pela combinação de duas estruturas de dados mais simples. + +Um #DualArrayDeque# representa uma lista usando duas #ArrayStack#s. Relembre que um +#ArrayStack# é rápido quando as operações dele modificam elementos perto do final. +Uma #DualArrayDeque# posiciona duas #ArrayStack#s, chamadas de #frontal# +and #traseira#, de modo complementar de tal forma que as operações são rápidas em ambas as direções. \codeimport{ods/DualArrayDeque.front.back} @@ -500,12 +521,12 @@ \section{#DualArrayDeque#: Building a Deque from Two Stacks} \codeimport{ods/DualArrayDeque.size()} -The #front# #ArrayStack# stores the list elements that whose indices -are $0,\ldots,#front.size()#-1$, but stores them in reverse order. -The #back# #ArrayStack# contains list elements with indices -in $#front.size()#,\ldots,#size()#-1$ in the normal order. In this way, -#get(i)# and #set(i,x)# translate into appropriate calls to #get(i)# -or #set(i,x)# on either #front# or #back#, which take $O(1)$ time per operation. +A #ArrayStack# #front# guarda os elementos da lista cujos índices são +$0,\ldots,#front.size()#-1$, mas guarda-os em ordem reversa. +O #ArrayStack# #back# contém elementos da lista com índices +em $#front.size()#,\ldots,#size()#-1$ na ordem normal. Desse jeito, +#get(i)# e #set(i,x)# traduzem-se em chamadas apropriadas para #get(i)# +ou #set(i,x)# e ambos #front# ou #back#, que levam tempo $O(1)$ time por operação. \codeimport{ods/DualArrayDeque.get(i).set(i,x)} From 6ae7a1e13d2b5a9183edec47eb50b0726e63d811 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Tue, 11 Aug 2020 11:22:20 -0300 Subject: [PATCH 11/66] PDF em portugues --- README | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README b/README index e208ba26..f6b72198 100644 --- a/README +++ b/README @@ -1,3 +1,10 @@ +Versões PDF em PORTUGUÊS: + - http://www.facom.ufu.br/~albertini/ed2/ods-java.pdf + - http://www.facom.ufu.br/~albertini/ed2/ods-python.pdf + - http://www.facom.ufu.br/~albertini/ed2/ods-cpp.pdf + +------ + latex/ contains the latex sources java/ods contains the java sources cpp contains the C++ sources (still under development) From 11f6b7b103dd63eb5aed24f21f8aa429d4341dc4 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Tue, 11 Aug 2020 18:40:27 -0300 Subject: [PATCH 12/66] continuing translation of arrays.tex --- latex/arrays.tex | 407 ++++++++++++++++++++++++++--------------------- latex/ods.tex | 4 +- 2 files changed, 224 insertions(+), 187 deletions(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index 9e2d7c0f..c3ed9109 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -513,11 +513,13 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \codeimport{ods/DualArrayDeque.front.back} -A #DualArrayDeque# does not explicitly store the number, #n#, -of elements it contains. It doesn't need to, since it contains -$#n#=#front.size()# + #back.size()#$ elements. Nevertheless, when -analyzing the #DualArrayDeque# we will still use #n# to denote the number -of elements it contains. +Um + #DualArrayDeque# não guarda explicitamente o número, #n#, + de elementos contidos. Ele não precisa, pois ele contém +$#n#=#front.size()# + #back.size()#$ elementos. De qualquer forma, ao +analisar o +#DualArrayDeque# iremos usar ainda o #n# para denotar o número de +elementos nele. \codeimport{ods/DualArrayDeque.size()} @@ -530,129 +532,144 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \codeimport{ods/DualArrayDeque.get(i).set(i,x)} -Note that if an index $#i#<#front.size()#$, then it corresponds to the -element of #front# at position $#front.size()#-#i#-1$, since the -elements of #front# are stored in reverse order. +Note que se um índice + $#i#<#front.size()#$, então ele corresponde ao elemento +de #front# na posição $#front.size()#-#i#-1$, pois +elementos de #front# são guardados em ordem inversa. -Adding and removing elements from a #DualArrayDeque# is illustrated in -\figref{dualarraydeque}. The #add(i,x)# operation manipulates either #front# -or #back#, as appropriate: +A adição e remoção de elementos de uma #DualArrayDeque# é ilustrado em +\figref{dualarraydeque}. A operação #add(i,x)# manipula #front# +ou #back#, conforme apropriado: \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/dualarraydeque} \end{center} - \caption[Adding and removing in a DualArrayDeque]{A sequence of #add(i,x)# and #remove(i)# operations on a - #DualArrayDeque#. Arrows denote elements being copied. Operations that - result in a rebalancing by #balance()# are marked with an asterisk.} + \caption[Adição e remoção em um DualArrayDeque]{Uma sequência de operações #add(i,x)# e #remove(i)# em um + #DualArrayDeque#. Flechas denotam elementos sendo copiados. Operações que resultam em um + rebalanceamento por #balance()# são marcados com um asterisco.} \figlabel{dualarraydeque} \end{figure} - - \codeimport{ods/DualArrayDeque.add(i,x)} -The #add(i,x)# method performs rebalancing of the two #ArrayStack#s -#front# and #back#, by calling the #balance()# method. The -implementation of #balance()# is described below, but for now it is -sufficient to know that #balance()# ensures that, unless $#size()#<2$, -#front.size()# and #back.size()# do not differ by more than a factor -of 3. In particular, $3\cdot#front.size()# \ge #back.size()#$ and +O método +#add(i,x)# realiza balanceamento das duas #ArrayStack#s +#front# e #back#, ao chamar o método #balance()#. +A implementação de +#balance()# é descrita a seguir, mas no memento é suficiente +saber que #balance()# garantes que, a não ser que $#size()#<2$, +#front.size()# e #back.size()# não diferem por mais de um fator +de 3. Em particular, $3\cdot#front.size()# \ge #back.size()#$ e $3\cdot#back.size()# \ge #front.size()#$. -Next we analyze the cost of #add(i,x)#, ignoring the cost of calls to -#balance()#. If $#i#<#front.size()#$, then #add(i,x)# gets implemented -by the call to $#front.add(front.size()-i-1,x)#$. Since #front# is an -#ArrayStack#, the cost of this is +A seguir, nós analisamos o custo de + #add(i,x)#, ignorando o custo de chamadas +#balance()#. Se $#i#<#front.size()#$, então #add(i,x)# é implementada +pela chamada a + $#front.add(front.size()-i-1,x)#$. Como #front# é uma +#ArrayStack#, o custo disso é \begin{equation} O(#front.size()#-(#front.size()#-#i#-1)+1) = O(#i#+1) \enspace . \eqlabel{das-front} \end{equation} -On the other hand, if $#i#\ge#front.size()#$, then #add(i,x)# gets -implemented as $#back.add(i-front.size(),x)#$. The cost of this is +Por outro lado, se + $#i#\ge#front.size()#$, então #add(i,x)# é +implementado como $#back.add(i-front.size(),x)#$. O custo disso é \begin{equation} O(#back.size()#-(#i#-#front.size()#)+1) = O(#n#-#i#+1) \enspace . \eqlabel{das-back} \end{equation} -Notice that the first case \myeqref{das-front} occurs when $#i#<#n#/4$. -The second case \myeqref{das-back} occurs when $#i#\ge 3#n#/4$. When -$#n#/4\le#i#<3#n#/4$, we cannot be sure whether the operation affects -#front# or #back#, but in either case, the operation takes -$O(#n#)=O(#i#)=O(#n#-#i#)$ time, since $#i#\ge #n#/4$ and $#n#-#i#> -#n#/4$. Summarizing the situation, we have +Note que o primeiro caso + \myeqref{das-front} ocorre quando $#i#<#n#/4$. + O segundo caso + \myeqref{das-back} ocorre quando $#i#\ge 3#n#/4$. Quando +$#n#/4\le#i#<3#n#/4$, não temos certeza se a operação afeta +#front# ou #back#, mas nos dois casos, a operação leva +$O(#n#)=O(#i#)=O(#n#-#i#)$ de tempo, pois $#i#\ge #n#/4$ e $#n#-#i#> +#n#/4$. Resumindo a situação, temos \[ - \mbox{Running time of } #add(i,x)# \le + \mbox{Tempo de execução } #add(i,x)# \le \begin{cases} - O(1+ #i#) & \mbox{if $#i#< #n#/4$} \\ - O(#n#) & \mbox{if $#n#/4 \le #i# < 3#n#/4$} \\ - O(1+#n#-#i#) & \mbox{if $#i# \ge 3#n#/4$} + O(1+ #i#) & \mbox{se $#i#< #n#/4$} \\ + O(#n#) & \mbox{se $#n#/4 \le #i# < 3#n#/4$} \\ + O(1+#n#-#i#) & \mbox{se $#i# \ge 3#n#/4$} \end{cases} \] -Thus, the running time of #add(i,x)#, if we ignore the cost of the call -to #balance()#, is $O(1+\min\{#i#, #n#-#i#\})$. +Então, o tempo de operação de + #add(i,x)#, se ignorarmos o custo à chamada +#balance()#, é $O(1+\min\{#i#, #n#-#i#\})$. + +A operação #remove(i)# e sua análise lembra a análise de #add(i,x)#. -The #remove(i)# operation and its analysis resemble the #add(i,x)# -operation and analysis. \codeimport{ods/DualArrayDeque.remove(i)} -\subsection{Balancing} +\subsection{Balanceamento} -Finally, we turn to the #balance()# operation performed by #add(i,x)# -and #remove(i)#. This operation ensures that neither #front# nor #back# -becomes too big (or too small). It ensures that, unless there are fewer -than two elements, each of #front# and #back# contain at least $#n#/4$ -elements. If this is not the case, then it moves elements between them -so that #front# and #back# contain exactly $\lfloor#n#/2\rfloor$ elements -and $\lceil#n#/2\rceil$ elements, respectively. +Finalmente, passamos à operação + #balance()# usada por #add(i,x)# +e #remove(i)#. Essa operação garante que nem #front# nem #back# +se tornem grandes demais (ou pequenos demais). +Ela garante que, ao menos que haja menos de dois elementos, o + #front# e o #back# contém $#n#/4$ elementos cada. +Se esse não for o caso, então ele move elementos entre elas +de tal forma que #front# e #back# contêm exatamente $\lfloor#n#/2\rfloor$ elementos +e $\lceil#n#/2\rceil$ elementos, respectivamente. \codeimport{ods/DualArrayDeque.balance()} -Here there is little to analyze. If the #balance()# operation does -rebalancing, then it moves $O(#n#)$ elements and this takes $O(#n#)$ -time. This is bad, since #balance()# is called with each call to -#add(i,x)# and #remove(i)#. However, the following lemma shows that, on -average, #balance()# only spends a constant amount of time per operation. +Aqui há pouco para analisar. Se a operação +#balance()# faz rebalanceamento +, então ela move $O(#n#)$ elementos e isso leva $O(#n#)$ de tempo. +Isso é ruim, pois + #balance()# é chamada a cada operação +#add(i,x)# e #remove(i)#. Porém, o lema a seguir mostra que +na média, #balance()# somente gasta um tempo constante por operação. \begin{lem}\lemlabel{dualarraydeque-amortized} - If an empty #DualArrayDeque# is created and any sequence of $m\ge 1$ calls - to #add(i,x)# and #remove(i)# are performed, then the total time spent - during all calls to #balance()# is $O(m)$. + Se uma + #DualArrayDeque# vazia é criada e qualquer sequência de chamadas $m\ge 1$ a + #add(i,x)# e #remove(i)# são realizadas, então o tempo total gasto + durante todas as chamadas a + #balance()# é $O(m)$. \end{lem} \begin{proof} - We will show that, if #balance()# is forced to shift elements, then - the number of #add(i,x)# and #remove(i)# operations since the last - time any elements were shifted by #balance()# is at least $#n#/2-1$. - As in the proof of \lemref{arraystack-amortized}, this is sufficient - to prove that the total time spent by #balance()# is $O(m)$. - - We will perform our analysis using a technique knows as the - \emph{potential method}. - \index{potential}% - \index{potential method}% - Define the \emph{potential}, $\Phi$, of the - #DualArrayDeque# as the difference in size between #front# and #back#: + Iremos mostrar que, se + #balance()# é forçada a deslocar elementos, então o número de operações + #add(i,x)# e #remove(i)# desde a última vez que elementos foram deslocador por + #balance()# é pelo menos $#n#/2-1$. + Como na prova de + \lemref{arraystack-amortized}, é suficiente provar que o tempo total gasto por + #balance()# é $O(m)$. + + Iremos realizar nossa análise usando uma técnica conhecida como o \emph{método do potencial}. + \index{potencial}% + \index{método do potential}% + Definimos o \emph{potencial}, $\Phi$, do + #DualArrayDeque# como a diferença em tamanho entre #front# e #back#: \[ \Phi = |#front.size()# - #back.size()#| \enspace . \] - The interesting thing about this potential is that a call to #add(i,x)# - or #remove(i)# that does not do any balancing can increase the potential - by at most 1. + A propriedade interessante desse potencial é que uma chamada a + #add(i,x)# + ou #remove(i)# que não faz nenhum balanceamento pode aumentar o potencial em até 1. - Observe that, immediately after a call to #balance()# that shifts - elements, the potential, $\Phi_0$, is at most 1, since + Observe que, imediatamente depois de uma chamada a #balance()# que desloca elementos + , o potencial , $\Phi_0$, é no máximo 1, pois \[ \Phi_0 = \left|\lfloor#n#/2\rfloor-\lceil#n#/2\rceil\right|\le 1 \enspace .\] - Consider the situation immediately before a call to #balance()# that - shifts elements and suppose, without loss of generality, that #balance()# - is shifting elements because $3#front.size()# < #back.size()#$. - Notice that, in this case, + Considere a situação no momento exatamente anterior a uma chamada #balance()# que + desloca elementos e suponha, sem perda de generalidade, que + #balance()# +está deslocando elementos porque $3#front.size()# < #back.size()#$. +Note que, nesse caso \begin{eqnarray*} #n# & = & #front.size()#+#back.size()# \\ & < & #back.size()#/3+#back.size()# \\ & = & \frac{4}{3}#back.size()# \end{eqnarray*} - Furthermore, the potential at this point in time is + Além disso, o potencial nesse momento é \begin{eqnarray*} \Phi_1 & = & #back.size()# - #front.size()# \\ &>& #back.size()# - #back.size()#/3 \\ @@ -660,64 +677,72 @@ \subsection{Balancing} &>& \frac{2}{3}\times\frac{3}{4}#n# \\ &=& #n#/2 \end{eqnarray*} - Therefore, the number of calls to #add(i,x)# or #remove(i)# since - the last time #balance()# shifted elements is at least $\Phi_1-\Phi_0 - > #n#/2-1$. This completes the proof. + Portanto, o número de chamadas a + #add(i,x)# ou #remove(i)# desde a última vez que + #balance()# deslocou elementos é pelo menos $\Phi_1-\Phi_0 + > #n#/2-1$. Isso completa a prova. \end{proof} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the properties of a #DualArrayDeque#: +O teorma a seguir resume as propriedades de uma #DualArrayDeque#: \begin{thm}\thmlabel{dualarraydeque} - A #DualArrayDeque# implements the #List# interface. Ignoring the - cost of calls to #resize()# and #balance()#, a #DualArrayDeque# - supports the operations + Uma + #DualArrayDeque# implementa a interface #List#. Ignorando o custo de chamadas a + #resize()# e #balance()#, uma #DualArrayDeque# + aceita as operações \begin{itemize} - \item #get(i)# and #set(i,x)# in $O(1)$ time per operation; and - \item #add(i,x)# and #remove(i)# in $O(1+\min\{#i#,#n#-#i#\})$ time - per operation. + \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e + \item #add(i,x)# e #remove(i)# em tempo $O(1+\min\{#i#,#n#-#i#\})$ + por operação. \end{itemize} - Furthermore, beginning with an empty #DualArrayDeque#, any sequence of $m$ - #add(i,x)# and #remove(i)# operations results in a total of $O(m)$ - time spent during all calls to #resize()# and #balance()#. + Além disso, ao começar com uma +#DualArrayDeque# vazia, qualquer sequência de $m$ operações + #add(i,x)# e #remove(i)# resulta em um total de tempo $O(m)$ + durante todas as chamadas a #resize()# e #balance()#. \end{thm} -\section{#RootishArrayStack#: A Space-Efficient Array Stack} +\section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} \seclabel{rootisharraystack} \index{RootishArrayStack@#RootishArrayStack#}% -One of the drawbacks of all previous data structures in this chapter is -that, because they store their data in one or two arrays and they avoid -resizing these arrays too often, the arrays frequently are not very full. -For example, immediately after a #resize()# operation on an #ArrayStack#, -the backing array #a# is only half full. Even worse, there are times -when only one third of #a# contains data. - -In this section, we discuss the #RootishArrayStack# data structure, -that addresses the problem of wasted space. The #RootishArrayStack# -stores #n# elements using $O(\sqrt{#n#})$ arrays. In these arrays, at -most $O(\sqrt{#n#})$ array locations are unused at any time. All -remaining array locations are used to store data. Therefore, these -data structures waste at most $O(\sqrt{#n#})$ space when storing #n# -elements. - -A #RootishArrayStack# stores its elements in a list of #r# -arrays called \emph{blocks} that are numbered $0,1,\ldots,#r#-1$. -See \figref{rootisharraystack}. Block $b$ contains $b+1$ elements. -Therefore, all #r# blocks contain a total of +Uma das desvantagem de todas as estruturas de dados anteriores neste capítulo +é que, porque elas guardam os dados um array ou dois e evitam redimensionar +esses arrays com frequência, os arrays frequentemente não estão muito cheios. +Por exemplo, imediatamente após uma operação + #resize()# em uma #ArrayStack#, +o array de apoio #a# tem somente metade do espaço em uso. +E pior, às vezes somente um terço de #a# contém dados. + +Nesta seção, distimos a estrutura de dados +#RootishArrayStack#, que resolve o problema de espaço desperdiçado. +A #RootishArrayStack# guarda +#n# elementos usando arrays de tamanho $O(\sqrt{#n#})$. +Nesses arrays, no máximo +$O(\sqrt{#n#})$ posições do array estão vazias a qualquer momento. +Todo o restante do array está usando para guardar dados. Portanto, +essas estruturas de dados gastam até + $O(\sqrt{#n#})$ de espaço ao guardar #n# +elementos. + +Uma #RootishArrayStack# guarda seus elementos em uma lista de #r# +arrays chamados de \emph{blocos} que são numerados $0,1,\ldots,#r#-1$. +Ver \figref{rootisharraystack}. O bloco $b$ contém $b+1$ elementos. +Então, todos os + #r# blocos contém um total de \[ 1+ 2+ 3+\cdots +#r# = #r#(#r#+1)/2 \] -elements. The above formula can be obtained as shown in \figref{gauss}. +elementos. A fórmula acima pode ser obtida conforme mostrado em \figref{gauss}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/rootisharraystack} \end{center} - \caption[Adding and removing in a RootishArrayStack]{A sequence of #add(i,x)# and #remove(i)# operations on a - #RootishArrayStack#. Arrows denote elements being copied. } + \caption[Adição e remoção em uma RootishArrayStack]{Uma sequÊncia de operações #add(i,x)# e #remove(i)# em uma + #RootishArrayStack#. Flechas denotam elementos sendo copiados. } \figlabel{rootisharraystack} \end{figure} @@ -727,45 +752,49 @@ \section{#RootishArrayStack#: A Space-Efficient Array Stack} \begin{center} \includegraphics[scale=0.90909]{figs/gauss} \end{center} - \caption{The number of white squares is $1+2+3+\cdots+#r#$. The number of - shaded squares is the same. Together the white and shaded squares make a - rectangle consisting of $#r#(#r#+1)$ squares.} + \caption{O número de quadrados brancos é $1+2+3+\cdots+#r#$. O número de quadrados sombreados é o mesmo. + Juntos, os quadrados brancos e sombreados compõem um retângulo consistindo de + $#r#(#r#+1)$ quadrados.} \figlabel{gauss} \end{figure} -As we might expect, the elements of the list are laid out in order -within the blocks. The list element with index 0 is stored in block 0, -elements with list indices 1 and 2 are stored in block 1, elements with -list indices 3, 4, and 5 are stored in block 2, and so on. The main -problem we have to address is that of determining, given an index $#i#$, -which block contains #i# as well as the index corresponding to #i# -within that block. - -Determining the index of #i# within its block turns out to be easy. If -index #i# is in block #b#, then the number of elements in blocks -$0,\ldots,#b#-1$ is $#b#(#b#+1)/2$. Therefore, #i# is stored at location +Como podemos esperar, os elementos da lista estão dispostos em ordem dentro dos blocos. +O elemento da lista com índice 0 é guardado no bloco 0, +elementos com índices 1 e 2 são guardados no bloco 1, elementos +com índices 3, 4 e 5 são guardados no bloco 2 e assim por diante. +O principal problema que temos para resolver é o de determinar, dado um índice + $#i#$, + qual bloco contém + #i# e também o índice correspondente a #i# naquele bloco. + +Determinar o índice de #i# no bloco acaba sendo fácil. +Se o índice +#i# está no bloco #b#, então o número de elementos nos blocos +$0,\ldots,#b#-1$ é $#b#(#b#+1)/2$. Portanto, #i# é guardado na posição \[ #j# = #i# - #b#(#b#+1)/2 \] -within block #b#. Somewhat more challenging is the problem of determining -the value of #b#. The number of elements that have indices less than -or equal to #i# is $#i#+1$. On the other hand, the number of elements -in blocks $0,\ldots,b$ is $(#b#+1)(#b#+2)/2$. Therefore, #b# is the smallest -integer such that +dentro do bloco #b#. Um pouco mais desafiador é o problema de determinar o valor de +#b#. O número de elementos que tem índices menores que ou iguais a +#i# é $#i#+1$. Por outro lado, o número de elementos nos blocos +$0,\ldots,b$ é $(#b#+1)(#b#+2)/2$. Portanto, #b# é o menor inteiro tal que + \[ (#b#+1)(#b#+2)/2 \ge #i#+1 \enspace . \] -We can rewrite this equation as +Podemos reescrever essa equação da forma \[ #b#^2 + 3#b# - 2#i# \ge 0 \enspace . \] -The corresponding quadratic equation $#b#^2 + 3#b# - 2#i# = 0$ has two -solutions: $#b#=(-3 + \sqrt{9+8#i#}) / 2$ and $#b#=(-3 - \sqrt{9+8#i#}) / 2$. -The second solution makes no sense in our application since it always -gives a negative value. Therefore, we obtain the solution $#b# = (-3 + -\sqrt{9+8i}) / 2$. In general, this solution is not an integer, but -going back to our inequality, we want the smallest integer $#b#$ such that -$#b# \ge (-3 + \sqrt{9+8i}) / 2$. This is simply +A equação quadrática correspondente + $#b#^2 + 3#b# - 2#i# = 0$ tem duas soluções +: $#b#=(-3 + \sqrt{9+8#i#}) / 2$ e $#b#=(-3 - \sqrt{9+8#i#}) / 2$. +A segunda solução não faz sentido na nossa aplicação pois é um valor negativo. +Portanto, obtemos a solução +$#b# = (-3 + +\sqrt{9+8i}) / 2$. Em geral, essa solução não é um inteiro, mas retornando à nossa desigualdade, queremos o menor inteiro + $#b#$ tal que +$#b# \ge (-3 + \sqrt{9+8i}) / 2$. Isso é simplesmente \[ #b# = \left\lceil(-3 + \sqrt{9+8i}) / 2\right\rceil \enspace . \] @@ -776,62 +805,70 @@ \section{#RootishArrayStack#: A Space-Efficient Array Stack} \codeimport{ods/RootishArrayStack.get(i).set(i,x)} -If we use any of the data structures in this chapter for representing the #blocks# list, then #get(i)# and #set(i,x)# will each run in constant time. +Se usarmos quaisquer estruturas de dados neste capítulo para representar a lista de +#blocks#, então #get(i)# e #set(i,x)# irão rodar em tempo constante. -The #add(i,x)# method will, by now, look familiar. We first check -to see if our data structure is full, by checking if the number of -blocks, #r#, is such that $#r#(#r#+1)/2 = #n#$. If so, we call #grow()# -to add another block. With this done, we shift elements with indices -$#i#,\ldots,#n#-1$ to the right by one position to make room for the -new element with index #i#: +O método #add(i,x)# irá, a esta altura, parecer familiar. Primeiro verificamos +se nossa estrutura de dados está cheia ao ver se o número de blocos +, #r#, é tal que $#r#(#r#+1)/2 = #n#$. Caso positivo, chamamos #grow()# +para adicionar outro bloco. Com isso feito, deslocamos elementos com índices +$#i#,\ldots,#n#-1$ à direita por uma posição para abrir espaço para o novo elemento com índice + #i#: \codeimport{ods/RootishArrayStack.add(i,x)} -The #grow()# method does what we expect. It adds a new block: +O método #grow()# faz o que esperamos dele. Ele adiciona um novo bloco à estrutura de dados: \codeimport{ods/RootishArrayStack.grow()} -Ignoring the cost of the #grow()# operation, the cost of an #add(i,x)# -operation is dominated by the cost of shifting and is therefore -$O(1+#n#-#i#)$, just like an #ArrayStack#. +Ignorando o custo da operação + #grow()#, o custo da operação #add(i,x)# é dominado pelo custo de deslocamento e é portanto +$O(1+#n#-#i#)$, como um #ArrayStack#. -The #remove(i)# operation is similar to #add(i,x)#. It shifts the -elements with indices $#i#+1,\ldots,#n#$ left by one position and then, -if there is more than one empty block, it calls the #shrink()# method -to remove all but one of the unused blocks: +A operação #remove(i)# é similar a #add(i,x)#. Ela desloca os elementos com índices +$#i#+1,\ldots,#n#$ à esquerda por uma posição e então, se há mais de um bloco vazio, ela chama o método +#shrink()# para remover todos menos um dos blocos não usados: \codeimport{ods/RootishArrayStack.remove(i)} \codeimport{ods/RootishArrayStack.shrink()} -Once again, ignoring the cost of the #shrink()# operation, the cost of -a #remove(i)# operation is dominated by the cost of shifting and is -therefore $O(#n#-#i#)$. - -\subsection{Analysis of Growing and Shrinking} - -The above analysis of #add(i,x)# and #remove(i)# does not account for -the cost of #grow()# and #shrink()#. Note that, unlike the -#ArrayStack.resize()# operation, #grow()# and #shrink()# do not copy -any data. They only allocate or free an array of size #r#. In -some environments, this takes only constant time, while in others, it -may require time proportional to #r#. - -We note that, immediately after a call to #grow()# or #shrink()#, the -situation is clear. The final block is completely empty, and all other -blocks are completely full. Another call to #grow()# or #shrink()# will -not happen until at least $#r#-1$ elements have been added or removed. -Therefore, even if #grow()# and #shrink()# take $O(#r#)$ time, this -cost can be amortized over at least $#r#-1$ #add(i,x)# or #remove(i)# -operations, so that the amortized cost of #grow()# and #shrink()# is -$O(1)$ per operation. - -\subsection{Space Usage} +Novamente, ignorando o custo da operação + #shrink()#, o custo de uma operação +#remove(i)# é dominado pelo custo de deslocamento e portanto +$O(#n#-#i#)$. + +\subsection{Análise de expandir e encolher} + +A análise acima de + #add(i,x)# e #remove(i)# não leva em conta o custo de +#grow()# e #shrink()#. Note que, diferentemente da operação +#ArrayStack.resize()#, #grow()# e #shrink()# não copiam nenhum dado +. Elas simplesmente alocam ou liberam um array de tamanho #r#. +Em alguns ambientes, isso leva apenas tempo constante, enquanto em outros, pode ser necessário tempo proporcional a #r#. + +Note que, imediatamente após uma chamada a +#grow()# ou #shrink()#, a situação é clara +. O bloco final está completamente vazio e todos os outros blocos estão completamente cheios. +Outra chamada a + #grow()# ou #shrink()# não acontecerá até após pelo menos + $#r#-1$ elementos tenham sido adicionados ou removidos. + Portanto, mesmo se +#grow()# e #shrink()# leve tempo $O(#r#)$, esse custo pode ser amortizado sobre pelo menos +$#r#-1$ operações #add(i,x)# ou #remove(i)# +fazendo com que o custo amortizado de + #grow()# e #shrink()# seja +$O(1)$ por operação. + +\subsection{Uso de Espaço} \seclabel{rootishspaceusage} -Next, we analyze the amount of extra space used by a #RootishArrayStack#. -In particular, we want to count any space used by a #RootishArrayStack# that is not an array element currently used to hold a list element. We call all such space \emph{wasted space}. -\index{wasted space}% +A seguir, analisaremos a quantidade de espaço extra usada por uma + #RootishArrayStack#. + +Em particular, queremos contar qualquer espaço usado por uma #RootishArrayStack# que não é um elemento de array usado no momento para guardar um elemento da lista. Chamamos todo esse espaço de \emph{espaço desperdiçado}. +\index{espaço desperdiçado}% +% TODO continuar The #remove(i)# operation ensures that a #RootishArrayStack# never has more than two blocks that are not completely full. The number of blocks, #r#, used by a #RootishArrayStack# that stores #n# elements therefore diff --git a/latex/ods.tex b/latex/ods.tex index d977165d..4ef6d69b 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -34,8 +34,8 @@ %\usepackage{calc} -%\usepackage[mathlines]{lineno} -%\linenumbers +\usepackage[mathlines]{lineno} +\linenumbers %\DeclareGraphicsExtensions{.pdf,.eps} % Leave this here - it gets substituted with language specific stuff From d0e381028a738c43104c6b86863cd17a029a4064 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 12 Aug 2020 00:00:33 -0300 Subject: [PATCH 13/66] finishing translation of arrays.tex --- latex/arrays.tex | 453 +++++++++++++++++++++++++---------------------- 1 file changed, 241 insertions(+), 212 deletions(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index c3ed9109..70e9cd54 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -869,339 +869,368 @@ \subsection{Uso de Espaço} \index{espaço desperdiçado}% % TODO continuar -The #remove(i)# operation ensures that a #RootishArrayStack# never has -more than two blocks that are not completely full. The number of blocks, -#r#, used by a #RootishArrayStack# that stores #n# elements therefore -satisfies +A operação +#remove(i)# assegura que uma #RootishArrayStack# nunca tem mais que dois +blocos que não estão complementamente cheios. +O número de blocos #r#, usados por uma +#RootishArrayStack# que guarda #n# elementos portanto satisfaz \[ (#r#-2)(#r#-1)/2 \le #n# \enspace . \] -Again, using the quadratic equation on this gives +De novo, usando a equação quadrática nisso resulta em \[ #r# \le \frac{1}{2}\left(3+\sqrt{8#n#+1}\right) = O(\sqrt{#n#}) \enspace . \] -The last two blocks have sizes #r# and #r-1#, so the space wasted by these -two blocks is at most $2#r#-1 = O(\sqrt{#n#})$. If we store the blocks -in (for example) an #ArrayStack#, then the amount of space wasted by the -#List# that stores those #r# blocks is also $O(#r#)=O(\sqrt{#n#})$. The -other space needed for storing #n# and other accounting information is $O(1)$. -Therefore, the total amount of wasted space in a #RootishArrayStack# -is $O(\sqrt{#n#})$. - -Next, we argue that this space usage is optimal for any data structure -that starts out empty and can support the addition of one item at -a time. More precisely, we will show that, at some point during the -addition of #n# items, the data structure is wasting at least in $\sqrt{#n#}$ space (though it may be only wasted for a moment). - -Suppose we start with an empty data structure and we add #n# items one -at a time. At the end of this process, all #n# items are stored in -the structure and distributed among a collection of #r# memory blocks. -If $#r#\ge \sqrt{#n#}$, then the data structure must be using #r# -pointers (or references) to keep track of these #r# blocks, and these -pointers are wasted space. On the other hand, if $#r# < \sqrt{#n#}$ -then, by the pigeonhole principle, some block must have size at -least $#n#/#r# > \sqrt{#n#}$. Consider the moment at which this block -was first allocated. Immediately after it was allocated, this block -was empty, and was therefore wasting $\sqrt{#n#}$ space. Therefore, -at some point in time during the insertion of #n# elements, the data -structure was wasting $\sqrt{#n#}$ space. - -\subsection{Summary} - -The following theorem summarizes our discussion of the #RootishArrayStack# -data structure: +Os últimos dois blocos tem tamanhos #r# e #r-1#, então o espaço desperdiçado por esses dois blocos é até + $2#r#-1 = O(\sqrt{#n#})$. Se guardarmos os blocos +em (por exemplo) uma #ArrayStack#, então a quantidade de espaço gasto pela +#List# que guarda esses #r# blocks também é $O(#r#)=O(\sqrt{#n#})$. +O espaço adicional ncessário para guardar #n# e outras informações auxiliares é $O(1)$. +Portanto, a quantidade total de espaço desperdiçado em uma #RootishArrayStack# +é $O(\sqrt{#n#})$. + +A seguir, demonstramos que esse uso de espaço é ótimo qualquer estrutura +de dados que inicia vazia e permite a adição de um item por vez. +Mais precisamente, mostraremos que, em algum momento durante a +adição de #n# itens, a estrutura de dados está desperdiçando pelo menos +um espaço de $\sqrt{#n#}$ (embora possa ser por apenas um curto momento). + +Suponha que iniciamos com uma estrutura de dados vazia e adicionamos #n# itens, +um por vez. No fim desse processo, todos os #n# intens são guardados na +estrutura e distribuídos entre uma coleção de #r# blocos de memória. +Se $#r#\ge \sqrt{#n#}$, então a estrutura de dados precisa usar #r# +ponteiros (ou referências) para gerenciar esses #r# blocos, e esses +ponteiros são espaço desperdiçado. +Por outro lado, se + $#r# < \sqrt{#n#}$ + então, pelo princípio das casas de pombos, algum bloco deve ter tamanho de + pelo menos +$#n#/#r# > \sqrt{#n#}$. Considere o momento no qual esse bloco +foi inicialmente alocado. Imediatamente após alocá-lo, esse +bloco estava vazio e portanto desperdiçando +um espaço de $\sqrt{#n#}$. Portanto, em algum momento durante a inserção de +#n# elementos, a estrutura de dados estava gastando + $\sqrt{#n#}$ de espaço. + +\subsection{Resumo} + +O seguinte teorema resume a discussão sobre a estrutura de dados #RootishArrayStack#: \begin{thm}\thmlabel{rootisharraystack} - A #RootishArrayStack# implements the #List# interface. Ignoring the cost of - calls to #grow()# and #shrink()#, a #RootishArrayStack# supports the operations + Uma #RootishArrayStack# implementa a interface #List#. Ignorando o custo das chamadas +a #grow()# e #shrink()#, uma #RootishArrayStack# aceita as operações \begin{itemize} - \item #get(i)# and #set(i,x)# in $O(1)$ time per operation; and - \item #add(i,x)# and #remove(i)# in $O(1+#n#-#i#)$ time per operation. + \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operações; e + \item #add(i,x)# e #remove(i)# em tempo $O(1+#n#-#i#)$ por operação. \end{itemize} - Furthermore, beginning with an empty #RootishArrayStack#, any sequence of $m$ - #add(i,x)# and #remove(i)# operations results in a total of $O(m)$ - time spent during all calls to #grow()# and #shrink()#. - - The space (measured in words)\footnote{Recall \secref{model} for a - discussion of how memory is measured.} used by a #RootishArrayStack# - that stores #n# elements is $#n# +O(\sqrt{#n#})$. + Além disso, ao começar com uma + #RootishArrayStack# vazia, qualquer sequência de $m$ operações + #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ + tempo gasto durante todas as chamadas de #grow()# e #shrink()#. + O espaço (medido em palavras)\footnote{Reveja \secref{model} para uma discussão de como a memória é medida.} usada por uma #RootishArrayStack# que guarda +#n# elementos é $#n# +O(\sqrt{#n#})$. \end{thm} \notpcode{ -\subsection{Computing Square Roots} - -\index{square roots}% -A reader who has had some exposure to models of computation may notice -that the #RootishArrayStack#, as described above, does not fit into -the usual word-RAM model of computation (\secref{model}) because it -requires taking square roots. The square root operation is generally -not considered a basic operation and is therefore not usually part of -the word-RAM model. - -In this section, we show that the square root operation can be -implemented efficiently. In particular, we show that for any integer -$#x#\in\{0,\ldots,#n#\}$, $\lfloor\sqrt{#x#}\rfloor$ can be computed -in constant-time, after $O(\sqrt{#n#})$ preprocessing that creates two -arrays of length $O(\sqrt{#n#})$. The following lemma shows that we -can reduce the problem of computing the square root of #x# to the square +\subsection{Computando Raizes Quadradas} + +\index{raizes quadradas}% +Um leitor que tenha tido alguma exposição a modelos de computação +podem notar que +a #RootishArrayStack#, conforme descrita anteriormente, +não se encaixa no modelo de computação normal word-RAM +(\secref{model}) porque requer a computação de raizes quadradas. +A operação raiz quadrada não é geralmente considerada uma operação básica e portante não é normalmente parte do modelo +word-RAM. + +Nesta seção, mostraremos que a operação raiz quadrada pode ser +implementada eficientemente. Em particular, mostramos que para qualquer inteiro +$#x#\in\{0,\ldots,#n#\}$, $\lfloor\sqrt{#x#}\rfloor$ pode ser computado +em tempo constante, após um pré-processamento com tempo de execução de +$O(\sqrt{#n#})$ que cria dois array de tamanho +$O(\sqrt{#n#})$. O lema a seguir mostra que podemos reduzir o problema de computar a raiz quadrada de #x# à raiz quadrada de um valor relacionado #x'#. root of a related value #x'#. \begin{lem}\lemlabel{root} -Let $#x#\ge 1$ and let $#x'#=#x#-a$, where $0\le a\le\sqrt{#x#}$. Then +Seja $#x#\ge 1$ e seja $#x'#=#x#-a$, onde $0\le a\le\sqrt{#x#}$. Então $\sqrt{x'} \ge \sqrt{#x#}-1$. \end{lem} \begin{proof} -It suffices to show that +É suficiente mostrar que \[ \sqrt{#x#-\sqrt{#x#}} \ge \sqrt{#x#}-1 \enspace . \] -Square both sides of this inequality to get +Eleva-se ao quadrado ambos lados dessa desigualdade para obter \[ #x#-\sqrt{#x#} \ge #x#-2\sqrt{#x#}+1 \] -and gather terms to get +e juntar termos para obter \[ \sqrt{#x#} \ge 1 \] -which is clearly true for any $#x#\ge 1$. +o que é claramente verdadeiro para qualquer $#x#\ge 1$. \end{proof} -Start by restricting the problem a little, and assume that $2^{#r#} \le -#x# < 2^{#r#+1}$, so that $\lfloor\log #x#\rfloor=#r#$, i.e., #x# is an -integer having $#r#+1$ bits in its binary representation. We can take -$#x'#=#x# - (#x#\bmod 2^{\lfloor r/2\rfloor})$. Now, #x'# satisfies -the conditions of \lemref{root}, so $\sqrt{#x#}-\sqrt{#x'#} \le 1$. -Furthermore, #x'# has all of its lower-order $\lfloor #r#/2\rfloor$ bits -equal to 0, so there are only +Inicia-se restringindo o problema um pouco, e assume que + $2^{#r#} \le +#x# < 2^{#r#+1}$, tal que $\lfloor\log #x#\rfloor=#r#$, isto é, #x# é um +inteiro com +$#r#+1$ bits na sua representação binária. Podemos fazer que +$#x'#=#x# - (#x#\bmod 2^{\lfloor r/2\rfloor})$. Agora, #x'# satisfaz +as condições de \lemref{root}, então $\sqrt{#x#}-\sqrt{#x'#} \le 1$. +Além disso, + #x'# tem todos os seu bits de baixa ordem $\lfloor #r#/2\rfloor$ bits +iguais a 0, então só há \[ 2^{#r#+1-\lfloor #r#/2\rfloor} \le 4\cdot2^{#r#/2} \le 4\sqrt{#x#} \] -possible values of #x'#. This means that we can use an array, #sqrttab#, -that stores the value of $\lfloor\sqrt{#x'#}\rfloor$ for each possible -value of #x'#. A little more precisely, we have +valores possíveis de #x'#. Isso significa que podemos umas um array #sqrttab#, +que guarda o valor de + $\lfloor\sqrt{#x'#}\rfloor$ para cada possível valor de +#x'#. Um pouco mais precisamente, temos \[ #sqrttab#[i] = \left\lfloor \sqrt{i 2^{\lfloor #r#/2\rfloor}} \right\rfloor \enspace . \] -In this way, $#sqrttab#[i]$ is within 2 of $\sqrt{#x#}$ for all +Desse jeito, $#sqrttab#[i]$ tem um diferença de até 2 em relação a $\sqrt{#x#}$ para todo $#x#\in\{i2^{\lfloor #r#/2\rfloor},\ldots,(i+1)2^{\lfloor #r#/2\rfloor}-1\}$. -Stated another way, the array entry -$#s#=#sqrttab#[#x##>>#\lfloor #r#/2\rfloor]$ is either equal to +De outra forma, a entrada do array +$#s#=#sqrttab#[#x##>>#\lfloor #r#/2\rfloor]$ é ou igual a $\lfloor\sqrt{#x#}\rfloor$, -$\lfloor\sqrt{#x#}\rfloor-1$, or -$\lfloor\sqrt{#x#}\rfloor-2$. From #s# we can determine the value -of $\lfloor\sqrt{#x#}\rfloor$ by -incrementing #s# until +$\lfloor\sqrt{#x#}\rfloor-1$, ou +$\lfloor\sqrt{#x#}\rfloor-2$. A partir de #s# podemos determinar o valor + $\lfloor\sqrt{#x#}\rfloor$ ao incrementar +#s# até u $(#s#+1)^2 > #x#$. } % notpcode \javaimport{ods/FastSqrt.sqrt(x,r)} \cppimport{ods/FastSqrt.sqrt(x,r)} \notpcode{ -Now, this only works for $#x#\in\{2^{#r#},\ldots,2^{#r#+1}-1\}$ and -#sqrttab# is a special table that only works for a particular value -of $#r#=\lfloor\log #x#\rfloor$. To overcome this, we could compute -$\lfloor\log #n#\rfloor$ different #sqrttab# arrays, one for each possible -value of $\lfloor\log #x#\rfloor$. The sizes of these tables form an exponential sequence whose largest value is at most $4\sqrt{#n#}$, so the total size of all tables is $O(\sqrt{#n#})$. - -However, it turns out that more than one #sqrttab# array is unnecessary; -we only need one #sqrttab# array for the value $#r#=\lfloor\log -#n#\rfloor$. Any value #x# with $\log#x#=#r'#<#r#$ can be \emph{upgraded} -by multiplying #x# by $2^{#r#-#r'#}$ and using the equation +Isso é funciona para + $#x#\in\{2^{#r#},\ldots,2^{#r#+1}-1\}$ e +#sqrttab# é uma tabela especial que funciona somente para um valor particular de +$#r#=\lfloor\log #x#\rfloor$. Para superar isso, poderíamos computar +$\lfloor\log #n#\rfloor$ arrays #sqrttab# diferentes, um para cada valor possível de +$\lfloor\log #x#\rfloor$. Os tamanhos dessas tabelas formam uma sequência exponencial cujo maior valor é até $4\sqrt{#n#}$, então o tamanho total de todas as tabelas é $O(\sqrt{#n#})$. + +Porém, acontece que mais um array + #sqrttab# é desnecessário; + somente precisamos de um array + #sqrttab# para o valor $#r#=\lfloor\log +#n#\rfloor$. Qualquer valor #x# com $\log#x#=#r'#<#r#$ pode ser \emph{atualizado} +ao multiplicar +#x# por $2^{#r#-#r'#}$ e usar a equação \[ \sqrt{2^{#r#-#r'#}x} = 2^{(#r#-#r#')/2}\sqrt{#x#} \enspace . \] -The quantity $2^{#r#-#r#'}x$ is in the range -$\{2^{#r#},\ldots,2^{#r#+1}-1\}$ so we can look up its square root -in #sqrttab#. The following code implements this idea to compute -$\lfloor\sqrt{#x#}\rfloor$ for all non-negative integers #x# in the -range $\{0,\ldots,2^{30}-1\}$ using an array, #sqrttab#, of size $2^{16}$. +A quantidade +$2^{#r#-#r#'}x$ está no intervalo +$\{2^{#r#},\ldots,2^{#r#+1}-1\}$ então podemos procurar sua raiz quadrada em +#sqrttab#. O código a seguir implementa essa ideia para computar +$\lfloor\sqrt{#x#}\rfloor$ para todo inteiro não negativo #x# no intervalo +$\{0,\ldots,2^{30}-1\}$ usando um array, #sqrttab#, de tamanho $2^{16}$. } % notpcode \javaimport{ods/FastSqrt.sqrt(x)} \cppimport{ods/FastSqrt.sqrt(x)} \notpcode{ -Something we have taken for granted thus far is the question of how -to compute -$#r#'=\lfloor\log#x#\rfloor$. Again, this is a problem that can be solved -with an array, #logtab#, of size $2^{#r#/2}$. In this case, the -code is particularly simple, since $\lfloor\log #x#\rfloor$ is just the -index of the most significant 1 bit in the binary representation of #x#. -This means that, for $#x#>2^{#r#/2}$, we can right-shift the bits of -#x# by $#r#/2$ positions before using it as an index into #logtab#. -The following code does this using an array #logtab# of size $2^{16}$ to compute -$\lfloor\log #x#\rfloor$ for all #x# in the range $\{1,\ldots,2^{32}-1\}$. + Algo que tinhamos tido como certo até agora depende de como computamos +$#r#'=\lfloor\log#x#\rfloor$. De novo, esse problema pode ser resolvido +com um array, #logtab#, de tamanho $2^{#r#/2}$. Nesse caso, o código +é particularmente simples, pois + $\lfloor\log #x#\rfloor$ é somente o índice do bit 1 mais significativo na representação binária de #x#. +Isso significa que, para + $#x#>2^{#r#/2}$, podemos deslocar à direita os bits de +#x# por $#r#/2$ posições antes de usá-lo como um índice em #logtab#. +O código a seguir faz isso usando um array + #logtab# de tamanho $2^{16}$ para computar +$\lfloor\log #x#\rfloor$ para todo #x# no intervalo $\{1,\ldots,2^{32}-1\}$. } % notpcode \javaimport{ods/FastSqrt.log(x)} \cppimport{ods/FastSqrt.log(x)} \notpcode{ -Finally, for completeness, we include the following code that initializes #logtab# and #sqrttab#: + Finalmente, para completude, incluimos o código a seguir que inicializa + #logtab# e #sqrttab#: } % notpcode \javaimport{ods/FastSqrt.inittabs()} \cppimport{ods/FastSqrt.inittabs()} \notpcode{ -To summarize, the computations done by the #i2b(i)# method can be -implemented in constant time on the word-RAM using $O(\sqrt{n})$ extra -memory to store the #sqrttab# and #logtab# arrays. These arrays can be -rebuilt when #n# increases or decreases by a factor of two, and the cost -of this rebuilding can be amortized over the number of #add(i,x)# and -#remove(i)# operations that caused the change in #n# in the same way that -the cost of #resize()# is analyzed in the #ArrayStack# implementation. +Para resumir, as computações feitas pelo método + #i2b(i)# podem ser implementadas em tempo constante no modelo +word-RAM usando $O(\sqrt{n})$ de memória extra para guardar +os arrays #sqrttab# e #logtab#. Esses arrays podem ser recontruídos +quando + #n# aumenta ou diminui por um fator de dois, e o custo dessa reconstrução +pode ser amortizado sobre o número de +operações #add(i,x)# e +#remove(i)# que causaram a mudança do mesmo jeito que o custo de +#resize()# é analisado na implementação da #ArrayStack#. } % notpcode \section{Discussion and Exercises} -Most of the data structures described in this chapter are folklore. They -can be found in implementations dating back over 30 years. For example, -implementations of stacks, queues, and deques, which generalize easily -to the #ArrayStack#, #ArrayQueue# and #ArrayDeque# structures described -here, are discussed by Knuth \cite[Section~2.2.2]{k97v1}. - -Brodnik \etal\ \cite{bcdms99} seem to have been the first to describe -the #RootishArrayStack# and prove a $\sqrt{n}$ lower-bound like that -in \secref{rootishspaceusage}. They also present a different structure -that uses a more sophisticated choice of block sizes in order to avoid -computing square roots in the #i2b(i)# method. Within their scheme, -the block containing #i# is block $\lfloor\log (#i#+1)\rfloor$, which -is simply the index of the leading 1 bit in the binary representation -of $#i#+1$. Some computer architectures provide an instruction for -computing the index of the leading 1-bit in an integer. \javaonly{In -Java, the #Integer# class provides a method #numberOfLeadingZeros(i)# -from which one can easily compute $\lfloor\log (#i#+1)\rfloor$.} - -A structure related to the #RootishArrayStack# is the two-level -\emph{tiered-vector} of Goodrich and Kloss \cite{gk99}. -\index{tiered-vector}% -This structure -supports the #get(i,x)# and #set(i,x)# operations in constant time and -#add(i,x)# and #remove(i)# in $O(\sqrt{#n#})$ time. These running times -are similar to what can be achieved with the more careful implementation -of a #RootishArrayStack# discussed in \excref{rootisharraystack-fast}. +A maior parte das estruturas de daos descritas neste capítula são folclore. +Elas podem ser encontradas em implementações de pelo menos 30 anos atrás. +Por exemplo, implementações de stacks, queues e deques que generalizam +facilmente para as estruturas +#ArrayStack#, #ArrayQueue# e #ArrayDeque# descritas +aqui, são discutidas por Knuth \cite[Section~2.2.2]{k97v1}. + +Brodnik \etal\ \cite{bcdms99} parece ter sido o primeiro a descrever +a #RootishArrayStack# e provar um limitante inferior de $\sqrt{n}$ como em +\secref{rootishspaceusage}. Eles também apresentam uma estrutura diferente que +usa uma escolha de tamanhos de blocos mais sofisticada a fim de evitar a computação de raizes quadradas usando o método +#i2b(i)#. Com o esquema deles, o bloco contendo +#i# é o bloco $\lfloor\log (#i#+1)\rfloor$, que é simplesmente o índice do primeiro bit 1 na representação binária de +$#i#+1$. Algumas arquiteturas de computadores provêm uma instrução para computar o índice do primeiro bit 1 em um inteiro. +\javaonly{Em +Java, a classe #Integer# provê um método chamado #numberOfLeadingZeros(i)# +a partir do qual pode-se facilmente computar + $\lfloor\log (#i#+1)\rfloor$.} + + Uma estrutura relacionada à + #RootishArrayStack# é o \emph{vetor em camadas} +\index{vetor em camadas}% +de Goodrich e Kloss \cite{gk99}. +Essa estrutura suporta as operações +#get(i,x)# e #set(i,x)# em tempo constante e +#add(i,x)# e #remove(i)# em $O(\sqrt{#n#})$ de tempo. +Esses tempos de execução são similare ao que pode ser conseguido com uma implementação mais cuidadosa de uma +#RootishArrayStack# discutida em \excref{rootisharraystack-fast}. \javaonly{ \begin{exc} - In the #ArrayStack# implementation, after the first call to #remove(i)#, - the backing array, #a#, contains $#n#+1$ non-#null# values despite - the fact that the #ArrayStack# only contains #n# elements. Where is - the extra non-#null# value? Discuss any consequences this non-#null# - value might have on the Java Runtime Environment's memory manager. + Na implementação da + #ArrayStack#, após a primeira chamada a #remove(i)#, + o array de apoio +, #a#, contém $#n#+1$ valores não #null# apesar do fato de que + a #ArrayStack# somente contém #n# elementos. Onde está o valor + não #null# extra? Discuta as consequências que esse valor não #null# + possa ter no gerenciador de memória da Java Runtime Environment. \index{Java Runtime Environment}% - \index{memory manager}% + \index{gerenciador de memória}% \end{exc} } \begin{exc} - The #List# method #addAll(i,c)# inserts all elements of the #Collection# - #c# into the list at position #i#. (The #add(i,x)# method is a special - case where $#c#=\{#x#\}$.) Explain why, for the data structures - in this chapter, it is not efficient to implement #addAll(i,c)# by - repeated calls to #add(i,x)#. Design and implement a more efficient - implementation. + O método da + #List# #addAll(i,c)# insere todos os elementos da #Collection# + #c# na posição #i# da lista. (O método #add(i,x)# é um caso especial onde + $#c#=\{#x#\}$.) Explique porque, para as estruturas de dados deste capítulo + , não é eficiente implementar #addAll(i,c)# fazendo chamadas repetidas a + #add(i,x)#. Projete e codifique uma implementação mais eficiente. \end{exc} \begin{exc} - Design and implement a \emph{#RandomQueue#}. + Projete e implemente uma +\emph{#RandomQueue#}. \index{RandomQueue@#RandomQueue#}% - This is an implementation - of the #Queue# interface in which the #remove()# operation removes - an element that is chosen uniformly at random among all the elements - currently in the queue. (Think of a #RandomQueue# as a bag in which - we can add elements or reach in and blindly remove some random element.) - The #add(x)# and #remove()# operations in a #RandomQueue# should run - in amortized constant time per operation. +Essa é uma implementação da interface +#Queue# na qual a operação #remove()# remove um elemento que é escolhido de uma distribuição aleatória uniforme entre todos os elementos atualmente na queue. + (Considere uma #RandomQueue# como sendo uma sacola na qual + podemos adicionar elementos ou por a mão e sem ver remover algum elemento aleatório.) + As operações + #add(x)# e #remove()# em uma #RandomQueue# deve rodar em um tempo constante amortizado por operação. \end{exc} \begin{exc} - Design and implement a #Treque# (triple-ended queue). + Projete e implemente uma + #Treque# (uma queue com três pontas). \index{Treque@#Treque#}% - This is a #List# - implementation in which #get(i)# and #set(i,x)# run in constant time - and #add(i,x)# and #remove(i)# run in time +Essa é uma implementação de uma #List# + na qual #get(i)# e #set(i,x)# rodas em tempo constante + e #add(i,x)# e #remove(i)# rodam em tempo \[ O(1+\min\{#i#, #n#-#i#, |#n#/2-#i#|\}) \enspace . \] - In other words, modifications are fast if they are near either - end or near the middle of the list. + Em outras palabras, modificações são rápidas se elas estão perto ou do fim, ou do meio da lista. \end{exc} \begin{exc} - Implement a method #rotate(a,r)# that ``rotates'' the array #a# - so that #a[i]# moves to $#a#[(#i#+#r#)\bmod #a.length#]$, for all + Implemente um método +#rotate(a,r)# que ``rotaciona'' o array #a# de tal forma que + #a[i]# moves para $#a#[(#i#+#r#)\bmod #a.length#]$, para todo $#i#\in\{0,\ldots,#a.length#\}$. \end{exc} \begin{exc} - Implement a method #rotate(r)# that ``rotates'' a #List# so that - list item #i# becomes list item $(#i#+#r#)\bmod #n#$. When run on - an #ArrayDeque#, or a #DualArrayDeque#, #rotate(r)# should run in - $O(1+\min\{#r#,#n#-#r#\})$ time. + Implemente um método +#rotate(r)# que ``rotaciona'' uma #List# tal que o item da lista + #i# se torna o item $(#i#+#r#)\bmod #n#$. Ao rodar em uma + #ArrayDeque# ou uma #DualArrayDeque#, #rotate(r)# deve rodar em + $O(1+\min\{#r#,#n#-#r#\})$ de tempo. \end{exc} \begin{exc} - \pcodeonly{This exercise is left out of the \lang\ edition.} + \pcodeonly{Esse exercício não é incluído na edição da linguagem\lang\ .} \notpcode{ - Modify the #ArrayDeque# implementation so that the shifting - done by #add(i,x)#, #remove(i)#, and #resize()# is done using - the faster #System.arraycopy(s,i,d,j,n)# method.} + Modifique a implementação + da #ArrayDeque# tal que o deslocamente feito por + #add(i,x)#, #remove(i)#, e #resize()# é realizado usando o método + rápido #System.arraycopy(s,i,d,j,n)#.} \end{exc} \begin{exc} - Modify the #ArrayDeque# implementation so that it does not use the - #%# operator (which is expensive on some systems). Instead, it - should make use of the fact that, if #a.length# is a power of 2, - then + Modifique a implementação da + #ArrayDeque# tal que não use o operador + #%# (que é caro em alguns sistemas). Em vez disso, deve fazer uso do + fato que se + #a.length# é uma potência de 2, + então \[ #k%a.length#=#k&(a.length-1)# \enspace . \] - (Here, #&# is the bitwise-and operator.) + (Aqui, #&# é o operador and bit-a-bit.) \end{exc} \begin{exc} - Design and implement a variant of #ArrayDeque# that does not do any - modular arithmetic at all. Instead, all the data sits in a consecutive - block, in order, inside an array. When the data overruns the beginning - or the end of this array, a modified #rebuild()# operation is performed. - The amortized cost of all operations should be the same as in an + Projete e implemente uma variante da + #ArrayDeque# que não faz uso de aritmética modular. Em vez disso, todos os dados ficam em um bloco consecutivo, em ordem, dentro de um array. + Quando os dados sobrescrevem o começo ou fim desse array, uma operação #rebuild()# modificada é realizada. + O custo amortizado de todas as operações deve ser o mesmo que em uma #ArrayDeque#. - \noindent Hint: Getting this to work is really all about how you implement - the #rebuild()# operation. You would like #rebuild()# to put the data - structure into a state where the data cannot run off either end until - at least $#n#/2$ operations have been performed. + \noindent Dica: Fazer isso funcionar tem a ver como você implementa a operação #rebuild()#. Deve-se fazer #rebuild()# para colocar dados na estrutura de dados em um estado onde os dados não podem sair pelas duas pontas até que pelo menos + $#n#/2$ operações tenham sido realizadas. - Test the performance of your implementation against the #ArrayDeque#. - Optimize your implementation (by using #System.arraycopy(a,i,b,i,n)#) - and see if you can get it to outperform the #ArrayDeque# implementation. + Teste o desempenho da sua implementação em comparação à + #ArrayDeque#. Otimize sua implementação + (usando #System.arraycopy(a,i,b,i,n)#) + e veja se consegue fazê-la mais rápida que a implementação da #ArrayDeque#. \end{exc} \begin{exc} - Design and implement a version of a #RootishArrayStack# that has - only $O(\sqrt{#n#})$ wasted space, but that can perform #add(i,x)# - and #remove(i,x)# operations in $O(1+\min\{#i#,#n#-#i#\})$ time. + Projete e implemente uma versão de uma + #RootishArrayStack# que desperdiça somente + $O(\sqrt{#n#})$ de espaço, mas que pode realizar as operações #add(i,x)# + e #remove(i,x)# de $O(1+\min\{#i#,#n#-#i#\})$ de tempo. \end{exc} \begin{exc}\exclabel{rootisharraystack-fast} - Design and implement a version of a #RootishArrayStack# that has - only $O(\sqrt{#n#})$ wasted space, but that can perform #add(i,x)# - and #remove(i,x)# operations in $O(1+\min\{\sqrt{#n#},#n#-#i#\})$ - time. (For an idea on how to do this, see \secref{selist}.) + Projete e implemente uma versão de + #RootishArrayStack# que desperdiça somente + $O(\sqrt{#n#})$ de espaço, mas que pode rodar as operações #add(i,x)# + e #remove(i,x)# em $O(1+\min\{\sqrt{#n#},#n#-#i#\})$ + de tempo. (Para uma ideia de como fazer isso, veja \secref{selist}.) \end{exc} \begin{exc} - Design and implement a version of a #RootishArrayStack# that has - only $O(\sqrt{#n#})$ wasted space, but that can perform #add(i,x)# and - #remove(i,x)# operations in $O(1+\min\{#i#,\sqrt {#n#},#n#-#i#\})$ time. - (See \secref{selist} for ideas on how to achieve this.) + Projete e implemente uma versão de +uma #RootishArrayStack# que desperdiça somente + $O(\sqrt{#n#})$ de espaço, mas que pode rodar as operações #add(i,x)# e + #remove(i,x)# em $O(1+\min\{#i#,\sqrt {#n#},#n#-#i#\})$ de tempo. + (Veja \secref{selist} para ideia de como conseguir isso.) \end{exc} \begin{exc} - Design and implement a #CubishArrayStack#. + Projete e implemente uma #CubishArrayStack#. \index{CubishArrayStack@#CubishArrayStack#}% - This three level structure - implements the #List# interface using $O(#n#^{2/3})$ wasted space. - In this structure, #get(i)# and #set(i,x)# take constant time; while - #add(i,x)# and #remove(i)# take $O(#n#^{1/3})$ amortized time. +Essa estrutura de três níveis implementa a interface + #List# desperdiçando $O(#n#^{2/3})$ de espaço. + Nessa estrutura, #get(i)# e #set(i,x)# funcionam em tempo constante; enquanto + #add(i,x)# e #remove(i)# levam $O(#n#^{1/3})$ de tempo amortizado. \end{exc} From 67405dc9a5621e682d7c8d5bdba606b597ea71e8 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 12 Aug 2020 14:35:38 -0300 Subject: [PATCH 14/66] small typo --- latex/intro.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/intro.tex b/latex/intro.tex index 5ce45ab3..7751f8a7 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -972,7 +972,7 @@ \section{Discussão e Exercícios} Para um soberbo (e livre) tratamento da matemática discutida neste capítulo, incluindo notação assintótica, logaritmos, fatoriais, aproximação de Sterling, probabilidade básica e muito mais, veja o livro texto por Leyman, Leighton e Meyer \cite{llm11}. -Para um texto de cálculo suave que inclui definições formais de exponenciais e logaritmos, seja o (disponível livremente) texto clássico de Thompson \cite{t14}. +Para um texto de cálculo suave que inclui definições formais de exponenciais e logaritmos, veja o (disponível livremente) texto clássico de Thompson \cite{t14}. Para maiores informações em probabilidade básica, especialmente como ela se relaciona com Ciência da Computação, veja o livro didático de Ross \cite{r01}. Outra boa referência, que cobre notação assintótica e probabilidades, é o livro-texto de Graham, Knuth e Patashnik \cite{gkp94}. From 473a68d374d5088650068c3991f5092d77f417cd Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 12 Aug 2020 18:08:15 -0300 Subject: [PATCH 15/66] translation to brazilian portuguese --- latex/linkedlists.tex | 1017 +++++++++++++++++++++-------------------- 1 file changed, 518 insertions(+), 499 deletions(-) diff --git a/latex/linkedlists.tex b/latex/linkedlists.tex index c32b148a..3a618e84 100644 --- a/latex/linkedlists.tex +++ b/latex/linkedlists.tex @@ -1,142 +1,139 @@ -\chapter{Linked Lists} +\chapter{Listas Ligadas} \chaplabel{linkedlists} \index{linked list}% -In this chapter, we continue to study implementations of the #List# -interface, this time using pointer-based data structures rather than -arrays. The structures in this chapter are made up of nodes that -contain the list items. Using references (pointers), the nodes are -linked together into a sequence. We first study singly-linked lists, -which can implement #Stack# and (FIFO) #Queue# operations in constant -time per operation and then move on to doubly-linked lists, which can -implement #Deque# operations in constant time. - -Linked lists have advantages and disadvantages when compared to array-based -implementations of the #List# interface. The primary disadvantage is that -we lose the ability to access any element using #get(i)# or #set(i,x)# -in constant time. Instead, we have to walk through the list, one element -at a time, until we reach the #i#th element. The primary advantage is -that they are more dynamic: Given a reference to any list node #u#, we -can delete #u# or insert a node adjacent to #u# in constant time. This -is true no matter where #u# is in the list. - - -\section{#SLList#: A Singly-Linked List} +\index{lista ligada}% +Neste capítulo, constinuamos a estudar implementações da +interface #List# e agora usando estruturas de dados baseadas em ponteiros +em vez de arrays. As estruturas neste capítulo são feitas de nodos +que contêm os itens da lista. Usando referências (ponteiros), os nodos são +ligados entre si em uma sequência. Primeiro estudamos listas simplestmente ligadas (listas com somente uma ligação), que podem implementar operação da #Stack# e #Queue# (FIFO) em tempo constante por operação e então seguimos para listas +duplamente ligadas, que podem implementar operações #Deque# em tempo constante. + +Listas ligadas tem vantagens e desvantagens quando comparadas a implementações +baseadas em arrays da interface #List#. A principal desvantagem é que perdemos +a habilidade de acessar qualquer elementos usando +#get(i)# ou #set(i,x)# em tempo constante. +Em vez disso, temos que percorrer a lista, um elemento por vez, até chegarmos no #i#-ésimo elemento. A principal vantagem é que são mais dinâmicos: dada uma referência a qualquer nodo da lista #u#, podemos deletar #u# ou inserir um nodo adjacente a #u# em tempo constante. Isso vale não importa on #u# esteja na lista. + +\section{#SLList#: Uma Lista Simplesmente Ligada} \seclabel{sllist} \index{SLList@#SLList#}% \index{linked list!singly-}% \index{singly-linked list}% -An #SLList# (singly-linked list) is a sequence of #Node#s. Each node -#u# stores a data value #u.x# and a reference #u.next# to the next node in -the sequence. For the last node #w# in the sequence, $#w.next# = #null#$ +\index{lista simplesmente ligada}% +Uma + #SLList# (em inglês, singly-linked list) é uma sequência de #Node#s (em português, nodo). Cada nodo +#u# guarda um valor de dado #u.x# e uma referência #u.next# ao próximo nodo na sequência +. Para o último nodo #w# na sequência vale $#w.next# = #null#$ % TODO: Remove constructors from SLList.Node \javaimport{ods/SLList.Node} \cppimport{ods/SLList.Node} -For efficiency, an #SLList# uses variables #head# and #tail# to keep -track of the first and last node in the sequence, as well as an integer -#n# to keep track of the length of the sequence: +Para eficiência, uma +#SLList# usa variáveis #head# (cabeça) e #tail# (cauda) para guardar +os acessos à primeiro e último nodos na sequência, assim como um inteiro +#n# para guardar o comprimento da sequência: \codeimport{ods/SLList.head.tail.n} -A sequence of #Stack# and #Queue# operations on an #SLList# is -illustrated in \figref{sllist}. +Uma sequência de operações #Stack# e #Queue# em uma #SLList# está ilustrada +em \figref{sllist}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/sllist} \end{center} - \caption[A sequence of Queue and Stack operations on an SLList]{A sequence of #Queue# (#add(x)# and #remove()#) and #Stack# (#push(x)# and #pop()#) operations on an #SLList#.} + \caption[Uma sequência de operações Queue e Stack em uma SLList]{Uma sequência de operações da #Queue# (#add(x)# e #remove()#) e da #Stack# (#push(x)# e #pop()#) em uma #SLList#.} \figlabel{sllist} \end{figure} - -An #SLList# can efficiently implement the #Stack# operations #push()# -and #pop()# by adding and removing elements at the head of the sequence. -The #push()# operation simply creates a new node #u# with data value #x#, -sets #u.next# to the old head of the list and makes #u# the new head -of the list. Finally, it increments #n# since the size of the #SLList# -has increased by one: +Uma #SLList# pode implementar eficientemente as operações da #Stack# #push()# +e #pop()# ao adicionar e remover elementos na cabeça da sequência. +A operação +#push()# simplesmente cria um novo nodo #u# com valor de dado #x#, +atribuído a #u.next# à antiga cabeça da lista e transforma #u# na nova cabeça da lista. +Finalmente, ela incrementa #n# pois o tamanho de #SLList# aumentou em uma unidade: \codeimport{ods/SLList.push(x)} -The #pop()# operation, after checking that the #SLList# is not empty, -removes the head by setting $#head=head.next#$ and decrementing #n#. -A special case occurs when the last element is being removed, in which case #tail# is set to #null#: +A operação #pop()#, após verificar que a + #SLList# não estsá vazia, remove a cabeça fazendo + $#head=head.next#$ e também decrementando #n#. \codeimport{ods/SLList.pop()} -Clearly, both the #push(x)# and #pop()# operations run in $O(1)$ time. +Claramente, ambas operações #push()# e #pop()# roda em tempo #O(1)#. -\subsection{Queue Operations} +\subsection{Operações de Queue} -An #SLList# can also implement the FIFO queue operations #add(x)# and -#remove()# in constant time. Removals are done from the head of the list, -and are identical to the #pop()# operation: +Uma #SLList# também pode implementar as operações de queue FIFO #add(x)# e +#remove()# em tempo constante. Remoções são feitas da cabeça da lista e são idênticas à operação #pop()#: \codeimport{ods/SLList.remove()} -Additions, on the other hand, are done at the tail of the list. In most -cases, this is done by setting $#tail.next#=#u#$, where #u# is the newly -created node that contains #x#. However, a special case occurs when -$#n#=0$, in which case $#tail#=#head#=#null#$. In this case, both #tail# -and #head# are set to #u#. +Adições, por outro lado, são feitas na cauda da lista. Na maior parte dos casos, +isso é feito ao atribuir $#tail.next#=#u#$, onde #u# é mais novo nodo que contém #x#. Entretato, um caso especial ocorre quando $#n#=0$, no qual $#tail#=#head#=#null#$. Nesse caso, tanto #tail# +quanto #head# são atribuídos a #u#. \codeimport{ods/SLList.add(x)} -Clearly, both #add(x)# and #remove()# take constant time. +Claramente, ambos + #add(x)# e #remove()# levam tempo constante. -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of an #SLList#: +O teorema a seguir resume o desempenho de uma #SLList#: \begin{thm}\thmlabel{sllist} - An #SLList# implements the #Stack# and (FIFO) #Queue# interfaces. - The #push(x)#, #pop()#, #add(x)# and #remove()# operations run - in $O(1)$ time per operation. +Uma #SLList# implementa as interfaces #Stack# e #Queue# (FIFO). + As operações #push(x)#, #pop()#, #add(x)# e #remove()# rodam em + $O(1)$ de tempo por operação. \end{thm} -An #SLList# nearly implements the full set of #Deque# operations. -The only missing operation is removing from the tail of an #SLList#. -Removing from the tail of an #SLList# is difficult because it requires -updating the value of #tail# so that it points to the node #w# -that precedes #tail# in the #SLList#; this is the node #w# such that -$#w.next#=#tail#$. Unfortunately, the only way to get to #w# is by -traversing the #SLList# starting at #head# and taking $#n#-2$ steps. - -\section{#DLList#: A Doubly-Linked List} +Uma +#SLList# quase implementa o conjunto completo de operações de uma #Deque#. +A única operação que falta é remoção da cauda de uma + #SLList#. + Remover da cauda de um #SLList# é difícil porquê requer a atualização + do valor da #tail# de forma que ela aponte ao nodo #w# + que precede #tail# na #SLList#; esse é o nodo #w# tal que +$#w.next#=#tail#$. Infelizmente, o único jeito de chegar a #w# é percorrendo +#SLList# a começar da #head# fazendo $#n#-2$ passos. + +\section{#DLList#: Uma Lista Duplamente Ligada} \seclabel{dllist} \index{DLList@#DLList#}% \index{doubly-linked list}% \index{linked list!doubly-}% -A #DLList# (doubly-linked list) is very similar to an #SLList# except -that each node #u# in a #DLList# has references to both the node #u.next# -that follows it and the node #u.prev# that precedes it. +\index{lista ligada!dupla}% +\index{lista duplamente ligada}% + +Uma #DLList# (do inglês, doubly-linked list) é muito similar a uma #SLList# exceto que cada nodo #u# em uma #DLList# tem referências ao nodo que ele precede #u.next# e ao nodo que sucede #u.prev#. \javaimport{ods/DLList.Node} \cppimport{ods/DLList.Node} -When implementing an #SLList#, we saw that there were always several -special cases to worry about. For example, removing the last element -from an #SLList# or adding an element to an empty #SLList# requires care -to ensure that #head# and #tail# are correctly updated. In a #DLList#, -the number of these special cases increases considerably. Perhaps the -cleanest way to take care of all these special cases in a #DLList# is to -introduce a #dummy# node. +Ao implementar uma #SLList#, vimos que haviam vários casos especiais para tomar cuidado. Por exemplo, ao remover o último elemento de uma #SLList# ou adicionar um elemento a uma #SLList# vazia precisamos tomar cuidade especial para garantir +que #head# e #tail# sejam corretamente atualizados. +Em uma #DLList#, o número desses casos especiais aumentam consideravelmente. +Talvez o modo mais limpo de cuidar de todos esses casos especiais em uma +#DLList# é introduzir um nodo #dummy#, também conhecido como \emph{sentinela}. \index{dummy node}% -This is a node that does not contain any data, -but acts as a placeholder so that there are no special nodes; every node -has both a #next# and a #prev#, with #dummy# acting as the node that -follows the last node in the list and that precedes the first node in -the list. In this way, the nodes of the list are (doubly-)linked into -a cycle, as illustrated in \figref{dllist}. +\index{nodo dummy}% +\index{sentinela}% +Esse nodo não contém nenhum dado, mas atua como um marcador para que não tenhamos nenhum nodo especial +; todo nodo tem um #next# e um #prev#, com um #dummy# agindo como o nodo +que sucede o último nodo na lista e que precede o primeiro nodo da lista. +Desse jeito, os nodos da lista são (duplamente) ligados em um ciclo, como +ilustrado na \figref{dllist}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/dllist2} \end{center} - \caption[A DLList]{A #DLList# containing a,b,c,d,e.} + \caption[Uma DLList]{Uma #DLList# contendo a,b,c,d,e.} \figlabel{dllist} \end{figure} @@ -145,27 +142,29 @@ \section{#DLList#: A Doubly-Linked List} \codeimport{ods/DLList.n.dummy.DLList()} -Finding the node with a particular index in a #DLList# is easy; we can -either start at the head of the list (#dummy.next#) and work forward, -or start at the tail of the list (#dummy.prev#) and work backward. -This allows us to reach the #i#th node in $O(1+\min\{#i#,#n#-#i#\})$ time: +Encontrar o nodo com índice em particular em um #DLList# é fácil; +podemos ou começar na cabeça da lista (#dummy.next#) e ir para frente, +ou começar na cauda da lista (#dummy.next#) e ir para trás. +Isso permite alcançarmos o #i#-ésimo nodo em $O(1+\min\{#i#,#n#-#i#\})$ de tempo: \codeimport{ods/DLList.getNode(i)} -The #get(i)# and #set(i,x)# operations are now also easy. We first find the #i#th node and then get or set its #x# value: +As operações +#get(i)# e #set(i,x)# também são fáceis agora. Primeiro achamos o #i#-ésimo nodo e então pegamos ou atribuímos seu valor #x#: \codeimport{ods/DLList.get(i).set(i,x)} -The running time of these operations is dominated by the time it takes -to find the #i#th node, and is therefore $O(1+\min\{#i#,#n#-#i#\})$. +O tempo de execução dessas operações é dominado pelo tempo para +achar o #i#-ésimo nodo, e portanto é + $O(1+\min\{#i#,#n#-#i#\})$. -\subsection{Adding and Removing} +\subsection{Adicionando e Removendo} -If we have a reference to a node #w# in a #DLList# and we want to insert a -node #u# before #w#, then this is just a matter of setting $#u.next#=#w#$, -$#u.prev#=#w.prev#$, and then adjusting #u.prev.next# and #u.next.prev#. (See \figref{dllist-addbefore}.) -Thanks to the dummy node, there is no need to worry about #w.prev# -or #w.next# not existing. +Se temos uma referência a um nodo #w# em uma #DLList# e queremos inserir um nodo #u# antes de #w#, então isso é só uma questão de atribuir + $#u.next#=#w#$, +$#u.prev#=#w.prev#$, e então ajustar #u.prev.next# e #u.next.prev#. (Ver \figref{dllist-addbefore}.) +Graças o nodo dummy, não há necessidade de se preocupar sobre #w.prev# +existir ou não. \codeimport{ods/DLList.addBefore(w,x)} @@ -173,202 +172,203 @@ \subsection{Adding and Removing} \begin{center} \includegraphics[scale=0.90909]{figs/dllist-addbefore} \end{center} - \caption[Adding to a DLList]{Adding the node #u# before the node #w# - in a #DLList#.} + \caption[Adicionando em uma DLList]{Adicionando o nodeo #u# antes do nodo #w# em uma #DLList#.} \figlabel{dllist-addbefore} \end{figure} -Now, the list operation #add(i,x)# is trivial to implement. We find the -#i#th node in the #DLList# and insert a new node #u# that contains #x# -just before it. +Agora, a operação da lista #add(i,x)# é trivial para implementar. +Achamos o #i#-ésimo nodo na #DLList# and inserimos um novo nodo #u# que contém +#x# logo antes dele. \codeimport{ods/DLList.add(i,x)} -The only non-constant part of the running time of #add(i,x)# is the time -it takes to find the #i#th node (using #getNode(i)#). Thus, #add(i,x)# -runs in $O(1+\min\{#i#, #n#-#i#\})$ time. +A única parte não constante do tempo de execução de #add(i,x)# é o tempo +que leva para achar o #i#-ésimo nodo (usando #getNode(i)#). Então, #add(i,x)# +roda em tempo $O(1+\min\{#i#, #n#-#i#\})$. -Removing a node #w# from a #DLList# is easy. We only need to adjust -pointers at #w.next# and #w.prev# so that they skip over #w#. Again, the -use of the dummy node eliminates the need to consider any special cases: +Remover um nodo #w# de uma #DLList# é fácil. Precisamos somente ajustar +ponteiros em #w.next# e #w.prev# tal que eles \emph{saltem} #w#. +De novo, o uso do nodo dummy elimina a necessidade de considerar casos especiais: \codeimport{ods/DLList.remove(w)} -Now the #remove(i)# operation is trivial. We find the node with index #i# and remove it: - +Agora a operação #remove(i)# é trivial. Achamos o nodo com índice #i# e o removemos: \codeimport{ods/DLList.remove(i)} -Again, the only expensive part of this operation is finding the #i#th node -using #getNode(i)#, so #remove(i)# runs in $O(1+\min\{#i#, #n#-#i#\})$ -time. - -\subsection{Summary} +Novamente, a única parte cara dessa operação é achar o #i#-ésimo nodo usando + #getNode(i)#, e então #remove(i)# roda em tempo $O(1+\min\{#i#, #n#-#i#\})$. -The following theorem summarizes the performance of a #DLList#: +\subsection{Resumo} +O teorema a seguir resumo o desempenho de uma #DLList#: \begin{thm}\thmlabel{dllist} - A #DLList# implements the #List# interface. In this implementation, - the #get(i)#, #set(i,x)#, #add(i,x)# and #remove(i)# operations run - in $O(1+\min\{#i#,#n#-#i#\})$ time per operation. + Uma #DLList# implementa a interface #List# interface. Nessa implementação , + as operações #get(i)#, #set(i,x)#, #add(i,x)# e #remove(i)# rodam em + $O(1+\min\{#i#,#n#-#i#\})$ de tempo por operação. \end{thm} -It is worth noting that, if we ignore the cost of the #getNode(i)# -operation, then all operations on a #DLList# take constant time. -Thus, the only expensive part of operations on a #DLList# is finding -the relevant node. Once we have the relevant node, adding, removing, -or accessing the data at that node takes only constant time. - -This is in sharp contrast to the array-based #List# implementations of -\chapref{arrays}; in those implementations, the relevant array -item can be found in constant time. However, addition or removal requires -shifting elements in the array and, in general, takes non-constant time. - -For this reason, linked list structures are well-suited to applications -where references to list nodes can be obtained through external means. -\javaonly{An example of this is the #LinkedHashSet# data structure found in the -Java Collections Framework, in which a set of items is stored in a -doubly-linked list and the nodes of the doubly-linked list are stored -in a hash table (discussed in \chapref{hashing}). When elements -are removed from a #LinkedHashSet#, the hash table is used to find the -relevant list node in constant time and then the list node is deleted -(also in constant time).} -\cpponly{For example, pointers to the nodes of a linked list could be -stored in a #USet#. Then, to remove an item #x# from the linked list, -the node that contains #x# can be found quickly using the #Uset# and -the node can be removed from the list in constant time.} - -\section{#SEList#: A Space-Efficient Linked List} +Vale notar que, se ignoramos o custo de #getNode(i)#, então todas +as operações em uma #DLList# leva tempo constante. +Então, a única parte cara das operações em uma #DLList# é achar o nodo relevante. +Uma vez que temos o nodo de interesse, adicionar, remover ou acessar os +dados naquele nodo leva somente um tempo constante. + +Há um nítido contraste em relação às implementações da #List# baseadas em arrays do \chapref{arrays}; +naquelas implementações, o item buscado no array pode ser achado em tempo constante. +Entretanto, adicionar ou remover requer o deslocamento de elementos no array e, +em geral, leva tempo não constante. + +Por essa razão, as estruturas de listas ligadas são adequadas para +aplicações em que referências a nodos da lista podem ser obtidos por meios +externos. + +\javaonly{Um exemplo disso é a estrutura de dados #LinkedHashSet# existente na +Java Collections Framework, na qual um conjunto de itens é guardado em uma lista duplamente encadeada e os nodos dessa lista são guardados em uma tabela hash +(discutido em \chapref{hashing}). +Quando um elemento é removido de uma +#LinkedHashSet#, a tabela hash é usada para achar o nodo em questão com tempo constante e então o nodo da lista é removido (também em tempo constante).} +\cpponly{Por exemplo, ponteiros aos nodos de uma lista ligada poderiam ser guardados em uma + #USet#. Então para remover um item #x# de uma lista ligada, + o nodo que contém #x# pode ser achado rapidamente usando + #Uset# e o nodo pode ser removido da lista em tempo constante.} + +\section{#SEList#: Uma Lista Ligada Eficiente no Espaço} \seclabel{selist} - \index{SEList@#SEList#}% \index{linked list!space-efficient}% -One of the drawbacks of linked lists (besides the time it takes to access -elements that are deep within the list) is their space usage. Each node -in a #DLList# requires an additional two references to the next and -previous nodes in the list. Two of the fields in a #Node# are dedicated -to maintaining the list, and only one of the fields is for storing data! - -An #SEList# (space-efficient list) reduces this wasted space using -a simple idea: Rather than store individual elements in a #DLList#, -we store a block (array) containing several items. More precisely, an -#SEList# is parameterized by a \emph{block size} #b#. Each individual -node in an #SEList# stores a block that can hold up to #b+1# elements. - -For reasons that will become clear later, it will be helpful if we can -do #Deque# operations on each block. The data structure that we choose -for this is a #BDeque# (bounded deque), +\index{eficiente no espaço!lista ligada} +Uma das limitações das listas encadeadas (além do tempo que ela leva para +acessar elementos que estão no meio da lista) é o seu uso de espaço. +Cada nodo em uma #DLList# requer duas referências adicionais para o próximo nodo e para o nodo anterior na lista. Dois dos campos em um #Node# são dedicados a manter a lista, e somente um dos campos é para guardar dados! + +Uma #SEList# (do inglês, space-efficient list) reduz esse espaço desperdiçado usando uma ideia simples: +em vez de guardar elementos individuais em uma #DLList#, +guardamos um bloco (array) contendo vários itens. +Mais precisamente, uma +#SEList# é parametrizada pelo tamanho do bloco \emph{block size} #b#. +Cada nodo individual em uma #SEList# guarda um bloco que pode guardar até #b+1# elementos. + +Para razões que vão se tornar claras depois, será útil se podermos fazer operações de +#Deque# em cada bloco. + +A estrutura de dados que escolhemos para isso é uma +#BDeque# (do inglês, bounded deque -- deque delimitado), \index{BDeque@#BDeque#}% \index{bounded deque}% \index{deque!bounded}% -derived from the #ArrayDeque# -structure described in \secref{arraydeque}. The #BDeque# differs from the -#ArrayDeque# in one small way: When a new #BDeque# is created, the size -of the backing array #a# is fixed at #b+1# and never grows or shrinks. -The important property of a #BDeque# is that it allows for the addition -or removal of elements at either the front or back in constant time. This -will be useful as elements are shifted from one block to another. +\index{deque!delimitado}% +derivado da estrutura #ArrayDeque# +descrita em \secref{arraydeque}. A #BDeque# difere do #ArrayDeque# +em um detalhe: quando uma nova #BDeque# é criada, o tamanho do array de apoio #a# +é fixo em #b+1# e nunca expande ou escolhe. +A propriedade mais importante de um #BDeque# é que permite a adição ou remoção +de elementos tanto na frente quanto atrás em tempo constante. +Isso será útil quando elementos forem deslocados de um bloco a outro. \javaimport{ods/SEList.BDeque} \cppimport{ods/SEList.BDeque} \notpcode{ -An #SEList# is then a doubly-linked list of blocks: +Uma +#SEList# é então uma lista duplamente encadeada de blocos: } % notpcode \javaimport{ods/SEList.Node} \cppimport{ods/SEList.Node} \javaimport{ods/SEList.n.dummy} \cppimport{ods/SEList.n.dummy} -\pcodeonly{ -An #SEList# is just a doubly-linked list of blocks. In addition to #next# and #prev# pointers, each node #u# in an #SEList# contains a #BDeque#, #u.d#. -} +\pcodeonly{Uma #SEList# é somente uma lista duplamente encadeada de blocos. Além dos ponteiros #next# e #prev#, cada nodo #u# em uma #SEList# contém um #BDeque#, #u.d#. } -\subsection{Space Requirements} +\subsection{Requisitos de Espaço} -An #SEList# places very tight restrictions on the number of elements -in a block: Unless a block is the last block, then that block contains -at least $#b#-1$ and at most $#b#+1$ elements. This means that, if an -#SEList# contains #n# elements, then it has at most +Uma #SEList# impõe restrições bem firmes ao número de elementos em um bloco: +a não ser que o bloco seja o último, então esse bloco contém pelo menos +$#b#-1$ e até $#b#+1$ elementos. Isso significa que, se uma +#SEList# contém #n# elementos, então ela tem até \[ #n#/(#b#-1) + 1 = O(#n#/#b#) \] -blocks. The #BDeque# for each block contains an array of length $#b#+1$ -but, for every block except the last, at most a constant amount of -space is wasted in this array. The remaining memory used by a block is -also constant. This means that the wasted space in an #SEList# is only -$O(#b#+#n#/#b#)$. By choosing a value of #b# within a constant factor -of $\sqrt{#n#}$, we can make the space-overhead of an SEList approach -the $\sqrt{#n#}$ lower bound given in \secref{rootishspaceusage}. - -\subsection{Finding Elements} - -The first challenge we face with an #SEList# is finding the list item -with a given index #i#. Note that the location of an element consists -of two parts: +blocos. A #BDeque# para cada bloco contém um array de tamanho $#b#+1$ +mas para todo bloco exceto o último, no máxima um quantidade constante de +espaço é desperdiçada nesse array. O resto da memória usada por um +bloco também é constante. +Isso significa que o espaço desperdiçado em uma #SEList# é somente +$O(#b#+#n#/#b#)$. Ao escolher um valor de #b# dentro de um fator constante +de +$\sqrt{#n#}$, podemos custos de espaço extras de uma SEList aproximar +o limitante inferior $\sqrt{#n#}$ descrito em \secref{rootishspaceusage}. + +\subsection{Procurando Elementos} + +O primeiro desafio que enfrentamos com uma #SEList# é procurar o item da lista +com um dado índice #i#. Note que a localização de um elemento consiste de duas +partes: + \begin{enumerate} - \item The node #u# that contains the block that contains the element - with index #i#; and - \item the index #j# of the element within its block. + \item O nodo #u# que contém o bloco que contém o elemento com índice #i#; e + \item o índice #j# do elemento dentro desse bloco. \end{enumerate} \javaimport{ods/SEList.Location} \cppimport{ods/SEList.Location} -To find the block that contains a particular element, we proceed the same -way as we do in a #DLList#. We either start at the front of the list and -traverse in the forward direction, or at the back of the list and traverse -backwards until we reach the node we want. The only difference is that, -each time we move from one node to the next, we skip over a whole block -of elements. +Para achar o bloco que contém um dado elemento, fazemos do mesmo jeito que +é feito em uma #DLList# +Iniciamos do início da lista e percorremos até o final, ou a partir do final da lista percorremos em direção ao início até encontrarmos o nodo que queremos. +A única diferença é que cada vez que movemos de um nodo ao seguinte, pulamos +um bloco inteiro de elementos. \pcodeimport{ods/SEList.get_location(i)} \javaimport{ods/SEList.getLocation(i)} \cppimport{ods/SEList.getLocation(i,ell)} -Remember that, with the exception of at most one block, each block -contains at least $#b#-1$ elements, so each step in our search gets -us $#b#-1$ elements closer to the element we are looking for. If we -are searching forward, this means that we reach the node we want after -$O(1+#i#/#b#)$ steps. If we search backwards, then we reach the node we -want after $O(1+(#n#-#i#)/#b#)$ steps. The algorithm takes the smaller -of these two quantities depending on the value of #i#, so the time to -locate the item with index #i# is $O(1+\min\{#i#,#n#-#i#\}/#b#)$. - -Once we know how to locate the item with index #i#, the #get(i)# and -#set(i,x)# operations translate into getting or setting a particular -index in the correct block: +Lembre-se que, com exceção de no máximo um bloco, cada bloco +contém pelo menos +$#b#-1$ elementos, então cada passo na nossa busca nos leva a +$#b#-1$ elementos mais próximos ao elemento que estamos procurando. +Se estamos procurando do começo ao fim, isso significa que alcançamos o nodo +que queremos após +$O(1+#i#/#b#)$ passos. +Se procuramos de trás para frente, então alcançamos o nodo que queremos +após +$O(1+(#n#-#i#)/#b#)$ passo. O algoritmo usa a menor dessas duas quantidades +dependendo do valor de #i#, então o tempo de localizar o item com índice #i# é + $O(1+\min\{#i#,#n#-#i#\}/#b#)$. + +Uma vez que sabemos como localizar o item com índice #i#, as operações #get(i)# +e #set(i,x)# são traduzidas em obter e atribuir em um índice particular no bloco correto: \codeimport{ods/SEList.get(i).set(i,x)} -The running times of these operations are dominated by the time it takes -to locate the item, so they also run in $O(1+\min\{#i#,#n#-#i#\}/#b#)$ -time. +Os tempos de execução dessas operações são dominados pelo tempo que +leva para achar o item, então eles também rodam em $O(1+\min\{#i#,#n#-#i#\}/#b#)$ de tempo. -\subsection{Adding an Element} +\subsection{Adicionando um Elemento} -Adding elements to an #SEList# is a little more complicated. Before -considering the general case, we consider the easier operation, #add(x)#, -in which #x# is added to the end of the list. If the last block is full -(or does not exist because there are no blocks yet), then we first -allocate a new block and append it to the list of blocks. Now that -we are sure that the last block exists and is not full, we append #x# -to the last block. +Adicionar elementos a uma + #SEList# é um pouco mais complicado. + Antes de considerar o caso geral, considerarmos a operação mais fácil, #add(x)#, + na qual #x# é adicinada ao final da lista. Se o último bloco está cheio + (ou não existe porque não há blocos ainda), então primeiro alocamos um + novo bloco e o acrescentamos na lista de blocos. + Agora que temos certeza que o último bloco existe e não está cheio, + acrescentamos #x# ao último bloco. \cppimport{ods/SEList.add(x)} \javaimport{ods/SEList.add(x)} \pcodeimport{ods/SEList.append(x)} -Things get more complicated when we add to the interior of the list -using #add(i,x)#. We first locate #i# to get the node #u# whose block -contains the #i#th list item. The problem is that we want to insert -#x# into #u#'s block, but we have to be prepared for the case where -#u#'s block already contains $#b#+1$ elements, so that it is full and -there is no room for #x#. +A situação complica quando adicionamos ao interior da lista usando #add(i,x)# +Primeiro alocamos #i# para obter o nodo #u# cujo bloco contém o #i#-ésimo item da lista. +O problema é que queremos inserir #x# no bloco de #u#, mas temos que estar +preparados para o caso em que o bloco de #u# já contém +$#b#+1$ elementos, ou seja, cheio e sem espaço para #x#. -Let $#u#_0,#u#_1,#u#_2,\ldots$ denote #u#, #u.next#, #u.next.next#, -and so on. We explore $#u#_0,#u#_1,#u#_2,\ldots$ looking for a node -that can provide space for #x#. Three cases can occur during our -space exploration (see \figref{selist-add}): +Considere que +$#u#_0,#u#_1,#u#_2,\ldots$ denotam #u#, #u.next#, #u.next.next# e assim por diante. +Exploramos $#u#_0,#u#_1,#u#_2,\ldots$ procurando por um nodo +que provê espaço para #x#. Três casos podem ocorrer durante +nossa exploração (ver \figref{selist-add}): \begin{figure} \noindent @@ -379,53 +379,60 @@ \subsection{Adding an Element} \includegraphics[width=\ScaleIfNeeded]{figs/selist-add-c}\\ \end{tabular} \end{center} - \caption[SEList add]{The three cases that occur during the addition of an item #x# in the interior of an #SEList#. (This #SEList# has block size $#b#=3$.)} + \caption[Adição no SEList]{Os três casos que ocorrem durante a adição de um item + #x# no interior de uma #SEList#. (Essa #SEList# tem tamano de bloco $#b#=3$.)} \figlabel{selist-add} \end{figure} \begin{enumerate} -\item We quickly (in $r+1\le #b#$ steps) find a node $#u#_r$ whose block -is not full. In this case, we perform $r$ shifts of an element from -one block into the next, so that the free space in $#u#_r$ becomes a -free space in $#u#_0$. We can then insert #x# into $#u#_0$'s block. - -\item We quickly (in $r+1\le #b#$ steps) run off the end of the list -of blocks. In this case, we add a new empty block to the end of the -list of blocks and proceed as in the first case. - -\item After #b# steps we do not find any block that is not full. -In this case, $#u#_0,\ldots,#u#_{#b#-1}$ is a sequence of #b# blocks -that each contain $#b#+1$ elements. We insert a new block $#u#_{#b#}$ -at the end of this sequence and \emph{spread} the original $#b#(#b#+1)$ -elements so that each block of $#u#_0,\ldots,#u#_{#b#}$ contains exactly -#b# elements. Now $#u#_0$'s block contains only #b# elements so it has -room for us to insert #x#. +\item Rapidamente (em $r+1\le #b#$ passo) achamos um nodo $#u#_r$ cujo + bloco não está cheio. Nesse caso, realizamos + $r$ deslocamentos de um elemento de um bloco ao próximo +, de forma que o espaço livro em $#u#_r$ torne-se um espaço livre em $#u#_0$. + Podemos então inserir #x# no bloco do $#u#_0$. + +\item Podemos rapidamente (em $r+1\le #b#$ passos) ir ao fim da lista de blocos. + Nesse caso, adicionamos um bloco vazio ao fim da lista de bloco e preceder como no primeiro caso. + +\item Após #b# passos não achamos quaisquer blocos que não está vazio. + Nesse caso, +$#u#_0,\ldots,#u#_{#b#-1}$ é uma sequência de #b# blocos +onde cada contém + $#b#+1$ elementos. Nós inserimos um novo bloco $#u#_{#b#}$ + no fim dessa sequência e \emph{espalhamos} os + $#b#(#b#+1)$ elementos originais para que cada bloco de + $#u#_0,\ldots,#u#_{#b#}$ contenho exatamente +#b# elementos. Agora o bloco de $#u#_0$'s contém somente #b# elementos então ele tem espaço para inserir #x#. \end{enumerate} \codeimport{ods/SEList.add(i,x)} -The running time of the #add(i,x)# operation depends on which of -the three cases above occurs. Cases~1 and 2 involve examining and -shifting elements through at most #b# blocks and take $O(#b#)$ time. -Case~3 involves calling the #spread(u)# method, which moves $#b#(#b#+1)$ -elements and takes $O(#b#^2)$ time. If we ignore the cost of Case~3 -(which we will account for later with amortization) this means that -the total running time to locate #i# and perform the insertion of #x# -is $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$. - -\subsection{Removing an Element} - -Removing an element from an #SEList# is similar to adding an element. -We first locate the node #u# that contains the element with index #i#. -Now, we have to be prepared for the case where we cannot remove an element -from #u# without causing #u#'s block to become smaller than $#b#-1$. - -Again, let $#u#_0,#u#_1,#u#_2,\ldots$ denote #u#, #u.next#, #u.next.next#, -and so on. We examine $#u#_0,#u#_1,#u#_2,\ldots$ in order to look -for a node from which we can borrow an element to make the size of -$#u#_0$'s block at least $#b#-1$. There are three cases to consider -(see \figref{selist-remove}): +O tempo de execução da operação + #add(i,x)# depende de quais dos três casos anteriores ocorrem. + Casos~1 e 2 envolvem examinar e deslocar elementos ao longo de até #b# blocos + e levam + $O(#b#)$ de tempo. +Caso~3 envolve chamar o método #spread(u)#, que move $#b#(#b#+1)$ +elementos e leva $O(#b#^2)$ de tempo. Se ignorarmos o custo do Caso~3 +(que iremos levar em consideração depois com amortização) isso significa +que o tempo total de execução para localizar #i# e realizar a inserção de #x# +é $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$. + +\subsection{Remoção de um Elemento} + +Remover um elemento de uma + #SEList# é similar a adicionar um elemento. + Nós primeiro localizamos o nodo #u# que contém o elemento com índice #i#. +Agora, temos estar preparamos para o caso no qual não podemos remover +um elemento de #u# sem fazer que o bloco de #u# se torne menor que $#b#-1$. + +Novamente, sejam + $#u#_0,#u#_1,#u#_2,\ldots$ os ponteiros #u#, #u.next#, #u.next.next# e assim por diante. +Nós examinamos $#u#_0,#u#_1,#u#_2,\ldots$ para procurar por um nodo +do qual podemos emprestar um elemento para fazer o tamanho do bloco +de $#u#_0$ pelo menos $#b#-1$. Há outros casos a considerar +(veja \figref{selist-remove}): \begin{figure} \noindent @@ -436,341 +443,353 @@ \subsection{Removing an Element} \includegraphics[scale=0.90909]{figs/selist-remove-c}\\ \end{tabular} \end{center} - \caption[SEList remove]{The three cases that occur during the removal of an item #x# in the interior of an #SEList#. (This #SEList# has block size $#b#=3$.)} + \caption[Remoção na SEList]{Os três casos que ocorrem durante a remoção de um item #x# no interior de uma #SEList#. (Essa #SEList# tem bloco de tamanho $#b#=3$.)} \figlabel{selist-remove} \end{figure} - \begin{enumerate} -\item We quickly (in $r+1\le #b#$ steps) find a node whose block contains -more than $#b#-1$ elements. In this case, we perform $r$ shifts of an -element from one block into the previous one, so that the extra element -in $#u#_r$ becomes an extra element in $#u#_0$. We can then remove the -appropriate element from $#u#_0$'s block. - -\item We quickly (in $r+1\le #b#$ steps) run off the end of the list of -blocks. In this case, $#u#_r$ is the last block, and there is no need -for $#u#_r$'s block to contain at least $#b#-1$ elements. Therefore, -we proceed as above, borrowing an element from $#u#_r$ to make an extra -element in $#u#_0$. If this causes $#u#_r$'s block to become empty, -then we remove it. - -\item After #b# steps, we do not find any block containing more than -$#b#-1$ elements. In this case, $#u#_0,\ldots,#u#_{#b#-1}$ is a sequence -of #b# blocks that each contain $#b#-1$ elements. We \emph{gather} -these $#b#(#b#-1)$ elements into $#u#_0,\ldots,#u#_{#b#-2}$ so that each -of these $#b#-1$ blocks contains exactly #b# elements and we remove -$#u#_{#b#-1}$, which is now empty. Now $#u#_0$'s block contains #b# -elements and we can then remove the appropriate element from it. +\item Rapidamente (em $r+1\le #b#$ passos) achamos um nodo cujo bloco contém + mais de $#b#-1$ elementos. Nesse caso, realizamos $r$ deslocamentos de um bloco no anterior, de forma tal que o elemento extra em +$#u#_r$ se torna um elemento extra em $#u#_0$. Podemos então remover o elemento apropriado do bloco de $#u#_0$. + +\item Podemos rapidamente (em $r+1\le #b#$ passos) pular ao fim da lista de blocos. +Nesse caso, $#u#_r$ é o último bloco e não há necessidade para o bloco de +$#u#_r$ conter pelo menos $#b#-1$ elementos. Portanto, da mesma maneira que + acima, emprestamos um elemento +de $#u#_r$ para fazer um elemento extra em +$#u#_0$. Se isso causar o bloco de $#u#_r$ se tornar vazio, então o removemos. + +\item Após #b# passos, não achamos nenhum bloco contendo mais de +$#b#-1$ elementos. Nesse caso, $#u#_0,\ldots,#u#_{#b#-1}$ é uma sequência de +#b# blocks que contém $#b#-1$ elementos. Nós \emph{acumulamos} +esses $#b#(#b#-1)$ elementos em $#u#_0,\ldots,#u#_{#b#-2}$ de forma tal que cada +um desses +$#b#-1$ blocos contém exatamente #b# elementos e removemos +$#u#_{#b#-1}$, que agora está vazio. Agora o bloco de $#u#_0$ contém #b# +elementos e então podemos remover o elemento apropriado dele. \end{enumerate} \codeimport{ods/SEList.remove(i)} -Like the #add(i,x)# operation, the running time of the #remove(i)# -operation is $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ if we ignore the cost of -the #gather(u)# method that occurs in Case~3. +Assim como a operação + #add(i,x)#, o tempo de execução da operação #remove(i)# +é $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ se ignorarmos o custo do método +#gather(u)# que ocorre no Caso~3. -\subsection{Amortized Analysis of Spreading and Gathering} +\subsection{Análise amortizada do Espalhamento e Acumulação} -Next, we consider the cost of the #gather(u)# and #spread(u)# methods -that may be executed by the #add(i,x)# and #remove(i)# methods. For the -sake of completeness, here they are: +A seguir, avaliaremos o csuto dos métodos + #gather(u)# e #spread(u)# que podem ser executados pelos métodos +#add(i,x)# e #remove(i)#. Por questão de completude, eles são mostrados a seguir: \codeimport{ods/SEList.spread(u)} \codeimport{ods/SEList.gather(u)} -The running time of each of these methods is dominated by the two -nested loops. Both the inner and outer loops execute at most -$#b#+1$ times, so the total running time of each of these methods -is $O((#b#+1)^2)=O(#b#^2)$. However, the following lemma shows that -these methods execute on at most one out of every #b# calls to #add(i,x)# -or #remove(i)#. +O tempo de execução de cada um desses métodos é dominado pelos dois laços aninhados. +Ambos laços interno e externo excutam no máximo +$#b#+1$ vezes, então o tempo total de execução de cada um desses métodos é +$O((#b#+1)^2)=O(#b#^2)$. Porém, o lema a seguir mostra que esses métodos executam em no máximo uma a cada #b# chamadas de #add(i,x)# ou #remove(i)#. \begin{lem}\lemlabel{selist-amortized} - If an empty #SEList# is created and any sequence of $m\ge 1$ calls - to #add(i,x)# and #remove(i)# is performed, then the total time - spent during all calls to #spread()# and #gather()# is $O(#b#m)$. + Se uma #SEList# é criada e qualquer sequência de + $m\ge 1$ chamadas a + #add(i,x)# e #remove(i)# é realizada então o tempo total gasto durante + todas as chamadas a + #spread()# e #gather()# é $O(#b#m)$. \end{lem} \begin{proof} - We will use the potential method of amortized analysis. + Usaremos o método do potencial da análise amortizada. \index{potential method}% - We say that - a node #u# is \emph{fragile} if #u#'s block does not contain #b# - elements (so that #u# is either the last node, or contains $#b#-1$ - or $#b#+1$ elements). Any node whose block contains #b# elements is - \emph{rugged}. Define the \emph{potential} of an #SEList# as the number - of fragile nodes it contains. We will consider only the #add(i,x)# - operation and its relation to the number of calls to #spread(u)#. - The analysis of #remove(i)# and #gather(u)# is identical. - - Notice that, if Case~1 occurs during the #add(i,x)# method, then - only one node, $#u#_r$ has the size of its block changed. Therefore, - at most one node, namely $#u#_r$, goes from being rugged to being - fragile. If Case~2 occurs, then a new node is created, and this node - is fragile, but no other node changes size, so the number of fragile - nodes increases by one. Thus, in either Case~1 or Case~2 the potential - of the SEList increases by at most one. - - Finally, if Case~3 occurs, it is because $#u#_0,\ldots,#u#_{#b#-1}$ - are all fragile nodes. Then $#spread(#u_0#)#$ is called and these #b# - fragile nodes are replaced with $#b#+1$ rugged nodes. Finally, #x# - is added to $#u#_0$'s block, making $#u#_0$ fragile. In total the - potential decreases by $#b#-1$. - - In summary, the potential starts at 0 (there are no nodes in the list). - Each time Case~1 or Case~2 occurs, the potential increases by at - most 1. Each time Case~3 occurs, the potential decreases by $#b#-1$. - The potential (which counts the number of fragile nodes) is never - less than 0. We conclude that, for every occurrence of Case~3, there - are at least $#b#-1$ occurrences of Case~1 or Case~2. Thus, for every - call to #spread(u)# there are at least #b# calls to #add(i,x)#. This - completes the proof. + \index{método do potencial}% + Dizemos que um nodo #u# é + \emph{frágil} se o bloco de #u# não contém #b# + elementos (então #u# pode ser o último nodo ou contém $#b#-1$ + ou $#b#+1$ elementos). Qualquer nodo cujo bloco contém #b# elementos é + \emph{durável}. Defina o \emph{potencial} de uma #SEList# como o número + de nodos frágeis que possui. + Iremos consideram somente a operação + #add(i,x)# + e sua relação com o número de chamadas a #spread(u)#. + As análises de + #remove(i)# e #gather(u)# são idênticas. + + Note que, se Caso~1 ocorra durante + o método #add(i,x)#, então somente um nodo + $#u#_r$ tem o tamanho de seu bloco alterado. Portanto, no máximo um nodo, + $#u#_r$, deixa de ser durável e torna-se frágil. Se o Caso~2 ocorrer, + então um novo nodo é criado, e esse nó é frágil, + mas nenhum outro nodo muda de tamanho, então o número de nodos frágeis aumenta em one. Então, nos Caso~1 ou 2 o potencial da SEList aumenta em até uma unidade. + + Finalmente, se Caso~3 ocorrer, é porque + $#u#_0,\ldots,#u#_{#b#-1}$ são todos nodos frágeis. + Então $#spread(#u_0#)#$ é chamado e esses #b# nodos frágeis são + substituídos por + $#b#+1$ nodos duráveis. Finalmente, #x# é adicionado ao bloco de + $#u#_0$ tornando $#u#_0$ frágil. No total, o potencial reduz em + $#b#-1$. + + Em resumo, o potencial inicia em 0 (não há nodos na lista). + Cada vez que um Caso~1 ou um Caso~2 ocorre o potencial aumenta em até 1 unidade. + Cada vez que um Caso~3 ocorre, o potencial decresce em + $#b#-1$. O potencial (que conta o número de nodos frágeis) nunca é menor que 0. + Concluímos que, para toda ocorrência do Caso~3, houveram pelo menos + $#b#-1$ ocorrências de um Caso~1 ou um Caso~2. Então, para toda chamada a + #spread(u)# existem pelo menos #b# chamadas a #add(i,x)#. Isso conclui a prova. \end{proof} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of the #SEList# data -structure: +O seguinte teorema resume o desempenho da estrutura de dados + #SEList#: \begin{thm}\thmlabel{selist} - An #SEList# implements the #List# interface. Ignoring the cost of - calls to #spread(u)# and #gather(u)#, an #SEList# with block size #b# - supports the operations +Uma #SEList# implementa a interface #List#. Ignorando o custo de chamadas +a #spread(u)# e #gather(u)#, uma #SEList# tamanho de bloco #b# + aceita as operações \begin{itemize} - \item #get(i)# and #set(i,x)# in $O(1+\min\{#i#,#n#-#i#\}/#b#)$ time per operation; and - \item #add(i,x)# and #remove(i)# in $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ time per operation. + \item #get(i)# e #set(i,x)# em $O(1+\min\{#i#,#n#-#i#\}/#b#)$ de tempo por operação; e + \item #add(i,x)# e #remove(i)# em $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ de tempo por operação. \end{itemize} - Furthermore, beginning with an empty #SEList#, any sequence of $m$ - #add(i,x)# and #remove(i)# operations results in a total of $O(#b#m)$ - time spent during all calls to #spread(u)# and #gather(u)#. - - The space (measured in words)\footnote{Recall \secref{model} for a - discussion of how memory is measured.} used by an #SEList# - that stores #n# elements is $#n# +O(#b# + #n#/#b#)$. + Além disso, iniciando com uma #SEList# vazia, qualquer sequência de + $m$ operações + #add(i,x)# e #remove(i)# resultam em um total de $O(#b#m)$ de tempo gasto + durante todas as chamadas a + #spread(u)# e #gather(u)#. + + O espaço (medido em palavras) + \footnote{Consulte \secref{model} para uma discussão de como a memória é medida. + } usada por uma #SEList# que guarda #n# elementos é + $#n# +O(#b# + #n#/#b#)$. \end{thm} -The #SEList# is a trade-off between an #ArrayList# and a #DLList# where -the relative mix of these two structures depends on the block size #b#. -At the extreme $#b#=2$, each #SEList# node stores at most three values, -which is not much different than a #DLList#. At the other extreme, -$#b#>#n#$, all the elements are stored in a single array, just like in -an #ArrayList#. In between these two extremes lies a trade-off between -the time it takes to add or remove a list item and the time it takes to -locate a particular list item. - -\section{Discussion and Exercises} - -Both singly-linked and doubly-linked lists are established techniques, -having been used in programs for over 40 years. They are discussed, -for example, by Knuth \cite[Sections~2.2.3--2.2.5]{k97v1}. Even the -#SEList# data structure seems to be a well-known data structures exercise. -The #SEList# is sometimes referred to as an \emph{unrolled linked list} +A #SEList# é uma busca por um balanceamento de custos visando um meio termo entre #ArrayList# e uma #DLList#, onde a mistura de custos entre essas duas estruturas depende do tamanho do bloco #b#. +Em um extremo + $#b#=2$, cada nodo da #SEList# guarda no máximo três valores, + o que não é muito diferente em uma #DLList#. No outro extremo, +$#b#>#n#$, todos os elementos são guardados em um único array, assim como em uma +#ArrayList#. Entre esses dois extremos está um balanceamento entre o tempo que leva para adicionar ou remover um item da lista e o tempo que leva para localizar um dado item na lista. + +\section{Discussão e Exercícios} + +Ambas listas simplesmente e duplamente encadeadas são técnicas bem estabelecidas e têm sido usadas em programas por mais de 40 anos. Elas são discutidas, por exemplo, por Knuth \cite[Sections~2.2.3--2.2.5]{k97v1}. Mesmo a estrutura de dados +#SEList# parece ser uma variante de estrutura de dados bem conhecida. +A +#SEList# é às vezes conhecida pelo nome de \emph{unrolled linked list} \cite{sra94}. \index{unrolled linked list|seealso{#SEList#}}% \index{linked list!unrolled|seealso{#SEList#}}% -Another way to save space in a doubly-linked list is to use -so-called XOR-lists. +Outra forma de economizar espaço em uma lista duplamente encadeada é usar uma XOR-list. \index{XOR-list}% -In an XOR-list, each node, #u#, contains only one -pointer, called #u.nextprev#, that holds the bitwise exclusive-or of #u.prev# -and #u.next#. The list itself needs to store two pointers, one to the #dummy# -node and one to #dummy.next# (the first node, or #dummy# if the list is -empty). This technique uses the fact that, if we have pointers to #u# -and #u.prev#, then we can extract #u.next# using the formula +Em uma XOR-list, cada nodo, #u# contém somente um ponteiro, chamado +#u.nextprev#, que guarda o resultado do ou-exclusivo bit-a-bit entre #u.prev# +e #u.next#. A lista em si precisa guardar dois ponteiros, +um para o nodo #dummy# e um para #dummy.next# +(o primeiro nodo, ou #dummy# se a lista está vazia). +Essa técnica usa o fato que, se temos os ponteiros para #u# e #u.prev#, +então podemos extrair #u.next# usando a fórmula \[ #u.next# = #u.prev# \verb+^+ #u.nextprev# \enspace . \] -(Here \verb+^+ computes the bitwise exclusive-or of its two arguments.) -This technique complicates the code a little and is not possible in -some languages, like Java and Python, that have garbage collection but gives a -doubly-linked list implementation that requires only one pointer per node. -See Sinha's magazine article \cite{s04} for a detailed discussion of -XOR-lists. +(Aqui \verb+^+ computa o ou-exclusivo bit-a-bit de seus dois argumentos.) +Essa técnica complica o código um pouco e não é possível em algumas linguagem como Java e Python, que tem coletores de lixo, porém fornece uma implementação de uma lista duplamente encadeada que requer somente um ponteiro por nodo. +Veja o artigo de Sinha \cite{s04} para uma discussão detalhada dessa XOR-list. \begin{exc} - Why is it not possible to use a dummy node in an #SLList# to avoid - all the special cases that occur in the operations #push(x)#, #pop()#, - #add(x)#, and #remove()#? + Porque não é possível usar um nodo dummy em uma #SLList# para evitar + todos os casos especiais que ocorrem nas operações +#push(x)#, #pop()#, + #add(x)#, e #remove()#? \end{exc} \begin{exc} - Design and implement an #SLList# method, #secondLast()#, that returns - the second-last element of an #SLList#. Do this without using the - member variable, #n#, that keeps track of the size of the list. + Projete e implemente um novo método para a + #SLList# chamado #secondLast()# que retorna + o penúltimo elemento de uma #SLList#. + Faça isso sem usar a variável membro, #n#, que registra o tamanho da lista. \end{exc} \begin{exc} - Implement the #List# operations #get(i)#, #set(i,x)#, - #add(i,x)# and #remove(i)# on an #SLList#. Each of these operations - should run in $O(1+#i#)$ time. + Implementa as operações de uma #List# + #get(i)#, #set(i,x)#, + #add(i,x)# e #remove(i)# em uma #SLList#. Cada uma dessas operações devem rodar + $O(1+#i#)$ de tempo. \end{exc} \begin{exc} - Design and implement an #SLList# method, #reverse()# that reverses the - order of elements in an #SLList#. This method should run in $O(#n#)$ - time, should not use recursion, should not use any secondary data - structures, and should not create any new nodes. + Projete e implemente um método para a + #SLList# chamado #reverse()# que inverte a ordem de elementos em uma + #SLList#. Esse método deve rodar em $O(#n#)$ de tempo, não deve usar recursão, não deve usar nenhuma estrutura de dados secundária, + , não deve criar nenhum nodo novo. \end{exc} \begin{exc} - Design and implement #SLList# and #DLList# methods called #checkSize()#. - These methods walk through the list and count the number of nodes to - see if this matches the value, #n#, stored in the list. These methods - return nothing, but throw an exception if the size they compute does - not match the value of #n#. + Projete e implemente o método #checkSize()# para as estruturas + #SLList# e #DLList#. + Esse método percorre a lista e conta o número de nodos para verificar se + ele é igual ao valor #n# guardados na lista. Esse método + não retorna nada, mas lança uma exceção se o tamanho que elas computam + não são idênticos ao valor de #n#. \end{exc} \begin{exc} - Try to recreate the code for the #addBefore(w)# operation that creates a - node, #u#, and adds it in a #DLList# just before the node #w#. Do not - refer to this chapter. Even if your code does not exactly match the - code given in this book it may still be correct. Test it and see if - it works. + Tente recriar o código para a operação + #addBefore(w)# que cria um nodo + , #u#, o adiciona em uma #DLList# logo antes do nodo #w#. + Não use o código deste capítulo. Mesmo se o seu código não seja exatamente igual ao código deste livro ele ainda pode estar correto. Teste e veja se funciona. \end{exc} -The next few exercises involve performing manipulations on #DLList#s. -You should complete them without allocating any new nodes or temporary -arrays. They can all be done only by changing the #prev# and #next# -values of existing nodes. +Os próximos exercícios envolvem a realização de manipulações em +#DLList#s. +Você deve realizá-los sem alocar novos nodos ou arrays temporários. +Todos eles podem ser feitos apenas com trocas de valores +de #prev# e #next# dos nodos existentes. \begin{exc} - Write a #DLList# method #isPalindrome()# that returns #true# if the - list is a \emph{palindrome}, - \index{palindrome}% - i.e., the element at position #i# is equal to - the element at position $#n#-i-1$ for all $i\in\{0,\ldots,#n#-1\}$. - Your code should run in $O(#n#)$ time. +Escreva um método para a #DLList# chamado de #isPalindrome()# que retornas #true# se a lista for um + \emph{palíndrome}, + \index{palíndrome}% + isto é, o elemento na posição #i# é igual ao elemento na posição + $#n#-i-1$ para todo $i\in\{0,\ldots,#n#-1\}$. O seu código deve rodar em + $O(#n#)$ de tempo. \end{exc} \begin{exc} - Implement a method #rotate(r)# that ``rotates'' a #DLList# so that list - item #i# becomes list item $(#i#+#r#)\bmod #n#$. This method should - run in $O(1+\min\{#r#,#n#-#r#\})$ time and should not modify any nodes in - the list. + Implemente um método #rotate(r)# que ``rotaciona'' uma #DLList# tal que o item #i# da lista se torna o item +$(#i#+#r#)\bmod #n#$. Esse método deve rodar em + $O(1+\min\{#r#,#n#-#r#\})$ de tempo não deve modificar quaisquer nodos na lista. \end{exc} \begin{exc}\exclabel{linkedlist-truncate} - Write a method, #truncate(i)#, that truncates a #DLList# at position - #i#. After executing this method, the size of the list will be #i# and - it should contain only the elements at indices $0,\ldots,#i#-1$. The - return value is another #DLList# that contains the elements at indices - $#i#,\ldots,#n#-1$. This method should run in $O(\min\{#i#,#n#-#i#\})$ - time. +Escreva um método, #truncate(i)#, que trunca uma #DLList# na posição #i#. +Após executar esse método, o tamanho da lista será #i# e deve conter somente os elementos nos índices + $0,\ldots,#i#-1$. O valor retornado é outra + #DLList# que contém os elementos nos índices + $#i#,\ldots,#n#-1$. Esse método deve rodar em $O(\min\{#i#,#n#-#i#\})$ de + tempo. \end{exc} \begin{exc} - Write a #DLList# method, #absorb(l2)#, that takes as an argument - a #DLList#, #l2#, empties it and appends its contents, in order, - to the receiver. For example, if #l1# contains $a,b,c$ and #l2# - contains $d,e,f$, then after calling #l1.absorb(l2)#, #l1# will contain - $a,b,c,d,e,f$ and #l2# will be empty. + Escreva uma método para a + #DLList# chamado #absorb(l2)#, que recebe como argumento + uma #DLList#, #l2#, a vazia e coloca seu conteúdo, em ordem, na lista receptora. + Por exemplo, se #l1# contém $a,b,c$ e #l2# + contém + $d,e,f$, então após chamar #l1.absorb(l2)#, #l1# terá + $a,b,c,d,e,f$ e #l2# estará vazia. \end{exc} \begin{exc} - Write a method #deal()# that removes all the elements with odd-numbered - indices from a #DLList# and return a #DLList# containing these elements. - For example, if #l1#, contains the elements $a,b,c,d,e,f$, then after - calling #l1.deal()#, #l1# should contain $a,c,e$ and a list containing - $b,d,f$ should be returned. + Escreva um método + #deal()# que remove todos os elementos com índices ímpares de uma + #DLList# e retorna uma #DLList# contendo esses elementos. + Por exemplo, se + #l1# contém os elementos $a,b,c,d,e,f$, então depois de chamar + #l1.deal()#, #l1# deve conter $a,c,e$ e uma lista contendo + $b,d,f$ deve ser retornada. \end{exc} \begin{exc} - Write a method, #reverse()#, that reverses the order of elements in - a #DLList#. + Escreva um método + #reverse()# que inverte a ordem de elementos em uma + #DLList#. \end{exc} \begin{exc}\exclabel{dllist-sort} \index{merge-sort}% - This exercise walks you through an implementation of the merge-sort - algorithm for sorting a #DLList#, as discussed in \secref{merge-sort}. - \javaonly{In your implementation, perform comparisons between elements - using the #compareTo(x)# method so that the resulting implementation can - sort any #DLList# containing elements that implement the #Comparable# - interface.} + Este exercício te guia para realizar uma implementação do algoritmo + merge-sort para a ordenação de uma + #DLList#, conforme discutido em \secref{merge-sort}. + \javaonly{Na sua implementação, realize comparações entre elementos usando o método + #compareTo(x)# de forma que a implementação resultante possa ordenar + + qualquer #DLList# contendo elementos que implementam da interface #Comparable#.} \begin{enumerate} - \item Write a #DLList# method called #takeFirst(l2)#. - This method takes the first node from #l2# and appends it to the the - receiving list. This is equivalent to #add(size(),l2.remove(0))#, - except that it should not create a new node. - \item Write a #DLList# static method, #merge(l1,l2)#, that takes two - sorted lists #l1# and #l2#, merges them, and returns a new sorted - list containing the result. This causes #l1# and #l2# to be emptied - in the proces. For example, if #l1# contains $a,c,d$ and #l2# contains - $b,e,f$, then this method returns a new list containing $a,b,c,d,e,f$. - \item Write a #DLList# method #sort()# that sorts the elements - contained in the list using the merge sort algorithm. - This recursive algorithm works in the following way: + \item Escreva um método para a #DLList# chamado #takeFirst(l2)#. + Esse método recebe o primeiro nodo de #l2# e o combina à lista que efetua + o método. Isso é equivalente a + #add(size(),l2.remove(0))#, + exceto que não deve criar um novo nodo. + \item Escreva um método estático para a #DLList# chamado #merge(l1,l2)#, que recebe sua listas ordenadas + #l1# e #l2#, faz a junção (em inglês, merge) delas, e retorna uma nova lista ordenada contendo o resultado. + Essa faz com que + #l1# e #l2# sejam esvaziadas no processo. + Por exemplo, se #l1# contém + $a,c,d$ e #l2# contém + $b,e,f$, então esse método retorna uma nova lista contendo $a,b,c,d,e,f$. + \item Escreva um método para a #DLList# que ordena os elementos contidos na lista usando o algoritmo merge sort. + Esse algoritmo recursivo funciona da seguinte maneira: \begin{enumerate} - \item If the list contains 0 or 1 elements then there is - nothing to do. Otherwise, - \item Using the #truncate(size()/2)# method, split the list - into two lists of approximately equal length, #l1# and #l2#; - \item Recursively sort #l1#; - \item Recursively sort #l2#; and, finally, - \item Merge #l1# and #l2# into a single sorted list. + \item Se a lista contém 0 ou 1 elemento então não há nada a fazer. Caso contrário, + \item Usando o método #truncate(size()/2)#, divida a lista em duas listas de tamanhos aproximadamente iguais +#l1# e #l2#; + \item Recursivamente ordene #l1#; + \item Recursivamente ordene #l2#; e finalmente + \item Faça a junção (em inglês, merge) da #l1# com #l2# em uma única lista ordenada. \end{enumerate} \end{enumerate} \end{exc} - -The next few exercises are more advanced and require a clear -understanding of what happens to the minimum value stored in a #Stack# -or #Queue# as items are added and removed. +Os próximos exercícios são mais avançados e requerem a compreensão +aprofundada do que acontence ao valor mínimo armazenado em uma +#Stack# ou #Queue# quando itens são adicionados ou removidos. \begin{exc} \index{MinStack@#MinStack#}% - Design and implement a #MinStack# data structure that can store - comparable elements and supports the stack operations #push(x)#, - #pop()#, and #size()#, as well as the #min()# operation, which - returns the minimum value currently stored in the data structure. - All operations should run in constant time. + Projeto e implemente uma + estrutura de dados #MinStack# que pode guardar elementos comparáveis + e aceita as operações de stack +#push(x)#, + #pop()#, e #size()#, assim como a operação #min()#, que retorna + o valor mínimo atualmente guardado na estrutura de dados. + Todas as operações devem rodar em tempo constante. \end{exc} \begin{exc} \index{MinQueue@#MinQueue#}% - Design and implement a #MinQueue# data structure that can store - comparable elements and supports the queue operations #add(x)#, - #remove()#, and #size()#, as well as the #min()# operation, which - returns the minimum value currently stored in the data structure. - All operations should run in constant amortized time. + Projete e implemente uma estrutura de dados + #MinQueue# que pode guardar elementos comparáveis + e aceita as operações de queue + #add(x)#, + #remove()# e #size()#, assim como a operação #min()#, que + retorna o valor mínimo atualmente armazenado na estrutura de dados. + Todas as operações devem rodar em tempo constante amortizado. \end{exc} \begin{exc} \index{MinDeque@#MinDeque#}% - Design and implement a #MinDeque# data structure that can store - comparable elements and supports all the deque operations #addFirst(x)#, - #addLast(x)# #removeFirst()#, #removeLast()# and #size()#, and the - #min()# operation, which returns the minimum value currently stored in - the data structure. All operations should run in constant amortized - time. + Projete e implemente uma estrutura de dados + #MinDeque# que pode guardar elementos comparáveis e aceita todas as operações + da deque + #addFirst(x)#, + #addLast(x)#, #removeFirst()#, #removeLast()# e #size()#, e a operação + #min()#, que retorna o valor mínimo atualmente guardado na estrutura de dados. + Todas as operações devem rodas em tempo amortizado constante. \end{exc} -The next exercises are designed to test the reader's understanding of -the implementation and analysis of the space-efficient #SEList#: +Os exercícios a seguir são planejados para testar a compreensão do leitor +a respeito da implementação e análise da lista eficiente em uso do espaço #SEList#: \begin{exc} - Prove that, if an #SEList# is used like a #Stack# (so that the - only modifications to the #SEList# are done using $#push(x)#\equiv - #add(size(),x)#$ and $#pop()#\equiv #remove(size()-1)#$), then these - operations run in constant amortized time, independent of the value - of #b#. + Prove que, se uma #SEList# é usada como uma #Stack# (tal que as + únicas modificações à #SEList# são feitas usando + $#push(x)#\equiv + #add(size(),x)#$ and $#pop()#\equiv #remove(size()-1)#$), então essas + operações rodam em tempo constante amortizado, independentemente + do valor de #b#. \end{exc} \begin{exc} - Design and implement of a version of an #SEList# that supports all - the #Deque# operations in constant amortized time per operation, - independent of the value of #b#. + Projete e implemente uma versão de uma #SEList# que aceita todas as operações + de uma #Deque# em tempo amortizado constante por operação, independentemente + do valor de #b#. \end{exc} \begin{exc} - Explain how to use the bitwise exclusive-or operator, \verb+^+, to - swap the values of two #int# variables without using a third variable. + Explique como usar o operador ou-exclusivo bit-a-bit, + \verb+^+ para trocar os valores de duas variáveis + #int# sem usar uma terceira variável. \end{exc} - - - - From 5de3f67adfccc30d6db8224605440bb72d497ac5 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 12 Aug 2020 18:19:49 -0300 Subject: [PATCH 16/66] beggining translation to portuguese. for now only first section --- latex/skiplists.tex | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/latex/skiplists.tex b/latex/skiplists.tex index 7ef8d908..de504304 100644 --- a/latex/skiplists.tex +++ b/latex/skiplists.tex @@ -1,24 +1,21 @@ \chapter{Skiplists} - \chaplabel{skiplists} -In this chapter, we discuss a beautiful data structure: the skiplist, -which has a variety of applications. Using a skiplist we can implement a -#List# that has $O(\log n)$ time implementations of #get(i)#, #set(i,x)#, -#add(i,x)#, and #remove(i)#. We can also implement an #SSet# in which -all operations run in $O(\log #n#)$ expected time. +Neste capítulo, discutimos uma elegante estrutura de dados: +a skiplist, que tem uma variedade de aplicações. +Usando uma skiplist podemos implementar uma +#List# que tem implementações $O(\log n)$ de time para #get(i)#, #set(i,x)#, +#add(i,x)# e #remove(i)#. Podemos também implementar um #SSet# no qual todas as operações rodam em tempo $O(\log #n#)$. %Finally, a skiplist can be used to implement a #Rope# in which all %operations run in $O(\log #n#)$ time. -The efficiency of skiplists relies on their use of randomization. -When a new element is added to a skiplist, the skiplist uses random coin -tosses to determine the height of the new element. The performance of -skiplists is expressed in terms of expected running times and path -lengths. This expectation is taken over the random coin tosses used by -the skiplist. In the implementation, the random coin tosses used by a -skiplist are simulated using a pseudo-random number (or bit) generator. +A eficiência de skiplists está o seu uso de +randomization. +Quando um novo elemento é adicionado a uma skiplist, a skiplists usa um lançamento de uma moeda aleatória para determinar a altura do novo elemento. +O desempenho das skiplists é expresso em termos da esperança do tempo de execução e do comprimento de caminhos percorridos. +Essa esperança (relativo ao valor esperado ou médio) é feita sobre os lançamentos da moeda aleatória usados pela skiplist. Na implementação, os lançamentos são simulados usando um gerador de números (ou bits) pseudo-aleatórios. -\section{The Basic Structure} +\section{A Estrutura Básica} \index{skiplist}% Conceptually, a skiplist is a sequence of singly-linked lists From 1484d6f0fd8fa5b5c612924f27d57e870082a001 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 12 Aug 2020 18:29:13 -0300 Subject: [PATCH 17/66] fixing minor issues related to language usage --- latex/why.tex | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/latex/why.tex b/latex/why.tex index 7aadbe39..aa9fdfe1 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -8,30 +8,31 @@ \chapter*{Por que este livro?} Vários livros gratuitos sobre estruturas de dados estão disponíveis online. Alguns muito bons, mas a maior parte está ficando desatualizada. Grande -parte deles se torna gratuita quando seus autores e/ou editores decidem +parte deles torna-se gratuita quando seus autores e/ou editores decidem parar de atualizá-los. Atualizar esses livros frequentemente não é possível, por duas razões: (1)~Os direitos autorais pertencem ao autor e/ou editor, os quais podem não autorizar tais atualizações. (2)~O \emph{código-fonte} desses livros muitas vezes não está disponível. Isto é os arquivos Word, WordPerfect, -FrameMaker, ou \LaTeX para o livro não está acessível e, ainda, a versão do -software que processa tais arquivo pode não estar mais disponível. +FrameMaker ou \LaTeX\ para o livro não está acessível e, ainda, a versão do +software que processa tais arquivos pode não estar mais disponível. A meta deste projeto é libertar estudantes de graduação de Ciência da Computação de ter que pagar por um livro introdutório a estruturas de dados. -Eu\footnote{Eu, Marcelo Keese Albertini, o tradutor para o Português, agradeço imensamente ao autor original Pat Morin por ter tomado tal decisão que permite a disponibilização de um livro de qualidade, gratuito e de livre distribuição na língua nativa de meus alunos brasileiros.}\footnote{I, Marcelo Keese Albertini, the translator to portuguese, am very grateful to the original author Pat Morin for this decision which allows the availability of a good quality and free book in the +Eu +\footnote{The translator to Portuguese language is deeply grateful to the original author of this book Pat Morin for his decision which allows the availability of a good quality and free book in the native language of my Brazilian students.} decidi implementar essa meta ao tratar esse livro como um projeto de Software Aberto \index{Open Source}% \index{Software Aberta}% . -O arquivos-fonte originais em \LaTeX\ , \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} +O arquivos-fonte originais em \LaTeX, \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} e também, de modo mais importante, em um site confiável de gerenciamento de códigos-fonte -.\footnote{\url{https://github.com/patmorin/ods} e, em português, \url{https://github.com/albertiniufu/ods}} +.\footnote{\url{https://github.com/patmorin/ods}}\footnote{Tradução em português em \url{https://github.com/albertiniufu/ods}} O código-fonte disponível é publicado sob uma licença Creative Commons Attribution, -o que quer dizer que qualquer um é livre para \emph{compartilhar} +o que quer dizer que qualquer um é livre para \emph{compartilhar}: \index{compartilhar} copiar, distribuir e transmitir essa obra; e \emph{modificar}: @@ -46,8 +47,7 @@ \chapter*{Por que este livro?} \index{git@\texttt{git}} . Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fontes livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). -Minha esperança é que, ao fazer dessee jeito, este livro irá continuar a -ser um livro didático bem depois que o meu interesse no projeto, ou meu pulso, (seja qual for que -aconteça primeiro) esvaneça. +Minha esperança é que, ao assim fazer, este livro irá continuar a +ser um livro didático bem depois que o meu interesse no projeto, ou meu batimento cardíaco, (seja qual for que aconteça primeiro) desapareça. From 011395714e29da76f014f2442a7d9b5204b26a47 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 13 Aug 2020 11:09:34 -0300 Subject: [PATCH 18/66] translated up to heap sort --- latex/sorting.tex | 394 +++++++++++++++++++++++++--------------------- 1 file changed, 211 insertions(+), 183 deletions(-) diff --git a/latex/sorting.tex b/latex/sorting.tex index 1dc4f9f4..50e2e9fd 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -1,110 +1,123 @@ -\chapter{Sorting Algorithms} +\chapter{Algoritmos de Ordenação} \chaplabel{sorting} -This chapter discusses algorithms for sorting a set of #n# items. -This might seem like a strange topic for a book on data structures, but -there are several good reasons for including it here. The most obvious -reason is that two of these sorting algorithms (quicksort and heap-sort) -are intimately related to two of the data structures we have already -studied (random binary search trees and heaps, respectively). - -The first part of this chapter discusses algorithms that sort using only -comparisons and presents three algorithms that run in $O(#n#\log #n#)$ -time. As it turns out, all three algorithms are asymptotically optimal; -no algorithm that uses only comparisons can avoid doing roughly $#n#\log -#n#$ comparisons in the worst case and even the average case. - -Before continuing, we should note that any of the #SSet# or priority -#Queue# implementations presented in previous chapters can also -be used to obtain an $O(#n#\log #n#)$ time sorting algorithm. -For example, we can sort #n# items by performing #n# #add(x)# -operations followed by #n# #remove()# operations on a #BinaryHeap# -or #MeldableHeap#. Alternatively, we can use #n# #add(x)# operations -on any of the binary search tree data structures and then perform an -in-order traversal (\excref{tree-traversal}) to extract the elements in -sorted order. However, in both cases we go through a lot of overhead to -build a structure that is never fully used. Sorting is such an important -problem that it is worthwhile developing direct methods that are as fast, -simple, and space-efficient as possible. - -The second part of this chapter shows that, if we allow other -operations besides comparisons, then all bets are off. Indeed, by using -array-indexing, it is possible to sort a set of #n# integers in the range -$\{0,\ldots,#n#^c-1\}$ in $O(c#n#)$ time. - - - -\section{Comparison-Based Sorting} - -\index{comparison-based sorting}% +Este capítulo discute algoritmos para ordenação de um conjunto de #n# itens. +Esse pode ser um tópico estranho para um livro em estruturas de dados, +mas existem várias boas razões para incluí-lo aqui. +A razão mais óbvia é que dois desses algoritmos de ordenação (quicksort e heap-sort) +são intimamente relacionados com duas das estruturas de dados que estudadas neste livro (árvores binárias de busca aleatórias e heaps, respectivamente). + +A primeira parte deste capítulo discute algoritmos que ordenam usando somente +comparações e apresenta três algoritmos que rodam em +$O(#n#\log #n#)$ de tempo. +Acontece que, esses três algoritmos são assintóticamente ótimos; +nenhum algoritmo que usa somente comparações pode evitar fazer +aproximadamente $#n#\log #n#$ comparações no pior caso e mesmo no caso médio. + +Antes de continuar, devemos notas que quaisquer implementações do #SSet# +ou da #Queue# de prioridades apresentadas nos capítulos anteriores +também podem ser usadas para obter um algoritmo de ordenação + $O(#n#\log #n#)$. + +Por exemplo, podemos ordenar #n# itens ao fazer #n# + operações #add(x)# seguidas de + #n# operações #remove()# em uma #BinaryHeap# ou #MeldableHeap#. + Alternativamente, podemos usar #n# operações #add(x)# + em qualquer estrutura de dados de árvore de busca binára e então + realizar uma travessia em ordem +(\excref{tree-traversal}) para extrair os elementos ordenados +Porém, nos dois casos teremos muitos gastos para construir uma estrutura +que nunca é completamente usada. Ordenação é um problema extremamente +importante e vale a pena desenvolver métodos diretos que são o mais rápidos, +simples e econômicos em uso da memória possíveis. + +A segunda parte deste capítulo mostra que, se for possível usar outras +operações além de comparações, então tudo é possível. Realmente, +ao usar indexação por arrays, é possível ordenar um conjunto de #n# inteiros +no intervalo $\{0,\ldots,#n#^c-1\}$ usando somente $O(c#n#)$ de time. + + +\section{Ordenação Baseada em Comparação} + +\index{ordenação baseada em comparação}% \index{sorting algorithm!comparison-based}% -In this section, we present three sorting algorithms: merge-sort, -quicksort, and heap-sort. Each of these algorithms takes an input array #a# -and sorts the elements of #a# into non-decreasing order in $O(#n#\log #n#)$ -(expected) time. These algorithms are all \emph{comparison-based}. -\javaonly{Their second argument, #c#, is a #Comparator# +\index{algoritmo de ordenação!baseado em comparação}% +Nesta seção, apresentamos três algoritmos: merge-sort, quicksort e heap-sort. +Cada um desses algoritmos recebe um array de entrada #a# +e ordena os elementos de #a# em ordem não-decrescente em +$O(#n#\log #n#)$ de tempo (esperado). +Esses algoritmos são todos \emph{baseados em comparação}. +\javaonly{O segundo argumento, #c#, é um #Comparator# \index{Comparator@#Comparator#}% -that implements -the #compare(a,b)# +que implementa o método +#compare(a,b)# \index{compare@#compare(a,b)#}% -method.} These algorithms don't care what type -of data is being sorted; the only operation they do on the data is -comparisons using the #compare(a,b)# method. Recall, from \secref{sset}, -that #compare(a,b)# returns a negative value if $#a#<#b#$, a positive -value if $#a#>#b#$, and zero if $#a#=#b#$. +.} Esses algoritmos dependem de qual tipo de dados está sendo ordenado; +a única operação que eles fazem nos dados é comparações +usando o método +#compare(a,b)#. Lembre-se, de \secref{sset}, +que #compare(a,b)# returna um valor negativo se $#a#<#b#$, um valor positivo +se $#a#>#b#$ e zero se $#a#=#b#$. \subsection{Merge-Sort} \seclabel{merge-sort} \index{merge-sort}% -The \emph{merge-sort} algorithm is a classic example of recursive divide -and conquer: +O algoritmo \emph{merge-sort} é um clássico exemplo do paradigma de divisão e conquista (recursiva): \index{divide-and-conquer}% -If the length of #a# is at most 1, then #a# is already -sorted, so we do nothing. Otherwise, we split #a# into two halves, -$#a0#=#a[0]#,\ldots,#a[n/2-1]#$ and $#a1#=#a[n/2]#,\ldots,#a[n-1]#$. -We recursively sort #a0# and #a1#, and then we merge (the now sorted) -#a0# and #a1# to get our fully sorted array #a#: +\index{divisão e conquista}% +Se o tamanho de +#a# for até 1, então #a# está ordenado então não fazemos nada. +Caso contrário, dividimos #a# em duas metades, +$#a0#=#a[0]#,\ldots,#a[n/2-1]#$ e $#a1#=#a[n/2]#,\ldots,#a[n-1]#$. +Nós recusivamente ordemos #a0# e #a1# e então fazermos o merge dos (agora +ordenados) #a0# e #a1# para ordenar nos array #a# completo: \javaimport{ods/Algorithms.mergeSort(a,c)} \cppimport{ods/Algorithms.mergeSort(a)} \pcodeimport{ods/Algorithms.mergeSort(a)} -An example is shown in \figref{merge-sort}. +Um exemplo é mostrado em \figref{merge-sort}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/mergesort} \end{center} - \caption[Merge sort]{The execution of #mergeSort(a,c)#} + \caption[Merge sort]{A execução de #mergeSort(a,c)#} \figlabel{merge-sort} \end{figure} -Compared to sorting, merging the two sorted arrays #a0# and #a1# is -fairly easy. We add elements to #a# one at a time. If #a0# or #a1# -is empty, then we add the next elements from the other (non-empty) -array. Otherwise, we take the minimum of the next element in #a0# and -the next element in #a1# and add it to #a#: +Comparada à ordaneção, fazer a junção (merge) de dois arrays ordenados #a0# +e #a1# é razoavelmente simples. Adicionamos elemento a #a# um por vez. +Se #a0# ou #a1# estão vazios, então adicionamos os próximos elementos do +outro array com elementos. +Por outro lado, pegamos o mínimo entre o próximo elemento em #a0# e +o próximo elemento em #a1# e adicioná-los a #a#: \javaimport{ods/Algorithms.merge(a0,a1,a,c)} \cppimport{ods/Algorithms.merge(a0,a1,a)} \pcodeimport{ods/Algorithms.merge(a0,a1,a)} -Notice that the #merge(a0,a1,a,c)# algorithm performs at most $#n#-1$ -comparisons before running out of elements in one of #a0# or #a1#. - -To understand the running-time of merge-sort, it is easiest to think -of it in terms of its recursion tree. Suppose for now that #n# is a -power of two, so that $#n#=2^{\log #n#}$, and $\log #n#$ is an integer. -Refer to \figref{mergesort-recursion}. Merge-sort turns the problem of -sorting #n# elements into two problems, each of sorting $#n#/2$ elements. -These two subproblem are then turned into two problems each, for a total -of four subproblems, each of size $#n#/4$. These four subproblems become eight -subproblems, each of size $#n#/8$, and so on. At the bottom of this process, -$#n#/2$ subproblems, each of size two, are converted into #n# problems, -each of size one. For each subproblem of size $#n#/2^{i}$, the time -spent merging and copying data is $O(#n#/2^i)$. Since there are $2^i$ -subproblems of size $#n#/2^i$, the total time spent working on problems -of size $2^i$, not counting recursive calls, is +Note que o algoritmo +#merge(a0,a1,a,c)# realiza em até $#n#-1$ +comparações antes de acabarem os elementos em #a0# ou #a1#. + +Para entender o tempo de execução de merge-sort, é mais fácil pensar +em termos de sua árvore de recursão. +Considere por agora que #n# seja uma potência de dois, tal que + $#n#=2^{\log #n#}$ e $\log #n#$ é um inteiro. +Veja \figref{mergesort-recursion}. Merge-sort torna-se o problema de ordenar +#n# elementos em dois problemas, cada qual ordenando $#n#/2$ elementos. +Esse dois subproblemas se tornam em dois problemas cada, a um total de +quatro subproblemas, cada um de tamanho + $#n#/4$. Esses quatro subproblemas se torna oito +subproblemas, cada um de tamanho $#n#/8$, assim por diante. +Na base nesse processo, +$#n#/2$ subproblemas, cada um de tamanho dois, são convertidos em #n# problemas, +cada de tamanho um. Para cada subproblema de tamanho + $#n#/2^{i}$, o tempo gasto fazendo merge e copiando dados é + $O(#n#/2^i)$. Como há $2^i$ +subproblemas de tamanho $#n#/2^i$, o tempo total gasto trabalhando em problemas de tamanho +$2^i$, sem contar as chamadas recursivas é \[ 2^i\times O(#n#/2^i) = O(#n#) \enspace . \] -Therefore, the total amount of time taken by merge-sort is +Portanto a tempo total usado pelo merge-sort é \[ \sum_{i=0}^{\log #n#} O(#n#) = O(#n#\log #n#) \enspace . \] @@ -112,28 +125,32 @@ \subsection{Merge-Sort} \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/mergesort-recursion} - \caption{The merge-sort recursion tree.} + \caption{A árvore de recusão do merge-sort.} \figlabel{mergesort-recursion} \end{center} \end{figure} -The proof of the following theorem is based on preceding analysis, -but has to be a little more careful to deal with the cases where #n# -is not a power of 2. +A prova do teorma a seguir é baseada na análise discutida anteriormente, +mas tem que ser um pouco cuidadosa para considerar os casos onde #n# +não é uma potência de 2. + \begin{thm} - The \javaonly{#mergeSort(a,c)#}\cpponly{mergeSort(a)}\pcodeonly{merge\_sort(a)} algorithm runs in $O(#n#\log #n#)$ time and - performs at most $#n#\log #n#$ comparisons. +O algoritmo \javaonly{#mergeSort(a,c)#}\cpponly{mergeSort(a)}\pcodeonly{merge\_sort(a)} roda em $O(#n#\log #n#)$ de tempo e faz até + $#n#\log #n#$ comparações. \end{thm} \begin{proof} -The proof is by induction on $#n#$. The base case, in which $#n#=1$, -is trivial; when presented with an array of length 0 or 1 the algorithm -simply returns without performing any comparisons. - -Merging two sorted lists of total length $#n#$ requires at most $#n#-1$ -comparisons. Let $C(#n#)$ denote the maximum number of comparisons performed by -#mergeSort(a,c)# on an array #a# of length #n#. If $#n#$ is even, then we apply the inductive hypothesis to -the two subproblems and obtain +Essa prova é por indução em + $#n#$. O caso base, em que $#n#=1$, é trivial; + ao ser apresentado com um array de tamano de 0 ou 1 o algoritmo + retorna sem fazer nenhuma comparação. + + Fazer o merge de duas listas ordenadas de tamanho total +$#n#$ requer no máximo $#n#-1$ comparações. +Seja +$C(#n#)$ o número máximo de comparações realizadas por +#mergeSort(a,c)# em um array #a# de tamanho #n#. Se $#n#$ é par, então +aplicamos a hipótese indutiva para os dois subproblemas e obter \begin{align*} C(#n#) &\le #n#-1 + 2C(#n#/2) \\ @@ -142,16 +159,17 @@ \subsection{Merge-Sort} &= #n#-1 + #n#\log #n#-#n# \\ &< #n#\log #n# \enspace . \end{align*} -The case where $#n#$ is odd is slightly more complicated. For this case, -we use two inequalities that are easy to verify: +O caso em que + $#n#$ é ímpar é ligeiramente mais complicado. Para esse caso, + usamos duas desigualdade que são de fácil verificação: \begin{equation} \log(x+1) \le \log(x) + 1 \enspace , \eqlabel{log-ineq-a} \end{equation} -for all $x\ge 1$ and +para todo $x\ge 1$ e \begin{equation} \log(x+1/2) + \log(x-1/2) \le 2\log(x) \enspace , \eqlabel{log-ineq-b} \end{equation} -for all $x\ge 1/2$. Inequality~\myeqref{log-ineq-a} comes from the fact that $\log(x)+1 = \log(2x)$ while \myeqref{log-ineq-b} follows from the fact that $\log$ is a concave function. With these tools in hand we have, for odd #n#, +para todo $x\ge 1/2$. A desigualdade~\myeqref{log-ineq-a} vem do fato que $\log(x)+1 = \log(2x)$ enquanto \myeqref{log-ineq-b} segue do fato que $\log$ é uma função côncava. Com essas ferramentas em mãos, para #n# ímpar, \begin{align*} C(#n#) &\le #n#-1 + C(\lceil #n#/2 \rceil) + C(\lfloor #n#/2 \rfloor) \\ @@ -171,89 +189,92 @@ \subsection{Merge-Sort} \subsection{Quicksort} \index{quicksort}% -The \emph{quicksort} algorithm is another classic divide and conquer -algorithm. Unlike merge-sort, which does merging after solving the two -subproblems, quicksort does all of its work upfront. - -Quicksort is simple to describe: Pick a random \emph{pivot} element, -\index{pivot element}% -#x#, from #a#; partition #a# into the set of elements less than #x#, the -set of elements equal to #x#, and the set of elements greater than #x#; -and, finally, recursively sort the first and third sets in this partition. -An example is shown in \figref{quicksort}. +O algoritmo \emph{quicksort} é outro exemplo clássico do paradigma de divisão e conquista. +Diferentemente do merge-sort, que faz merging após resolver os dois subproblemas, +o quicksort faz esse trabalho logo de uma vez. + +Quicksort é simples de descrever: escolher um elemento aleatório chamado de \emph{pivot}, +\index{elemento pivot}% +#x#, de #a#; particionar #a# em um conjunto de elementos menores que #x#, um conjunto de elementos igual a #x# e um conjunto de elementos maiores que #x#; +e, finalmente, ordenar recursivamente o primeiro e o terceiro conjunto dessa partição. +Um exemplo é mostrado no +\figref{quicksort}. \javaimport{ods/Algorithms.quickSort(a,c).quickSort(a,i,n,c)} \cppimport{ods/Algorithms.quickSort(a).quickSort(a,i,n)} \pcodeimport{ods/Algorithms.quickSort(a).quickSort(a,i,n)} \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/quicksort} - \caption[Quicksort]{An example execution of \javaonly{#quickSort(a,0,14,c)#}\cpponly{#quickSort(a,0,14)#}\pcodeonly{#quickSort(a,0,14)#}} + \caption[Quicksort]{Um exemplo de execução de \javaonly{#quickSort(a,0,14,c)#}\cpponly{#quickSort(a,0,14)#}\pcodeonly{#quickSort(a,0,14)#}} \figlabel{quicksort} \end{center} \end{figure} -All of this is done in place, so that instead of making copies of -subarrays being sorted, the #quickSort(a,i,n,c)# method only sorts the -subarray $#a[i]#,\ldots,#a[i+n-1]#$. Initially, this method is invoked -with the arguments +Isso tudo é feito \emph{in place} (ou seja, no próprio espaço de memória do array), +então em vez de fazer cópias de subarrays sendo ordenados, o método + #quickSort(a,i,n,c)# somente ordena o subarray +$#a[i]#,\ldots,#a[i+n-1]#$. Inicialmente, esse método é invocado com os argumentos #quickSort(a,0,a.length,c)#. -At the heart of the quicksort algorithm is the in-place partitioning -algorithm. This algorithm, without using any extra space, swaps elements -in #a# and computes indices #p# and #q# so that +No coração do algoritmo quicksort está o particionamento \emph{in place}. +Esse algoritmo, sem usar espaço extra, troca elementos em #a# +e computa índices #p# e #q# tal que \[ #a[i]# \begin{cases} - {}< #x# & \text{if $0\le #i#\le #p#$} \\ - {}= #x# & \text{if $#p#< #i# < #q#$} \\ - {}> #x# & \text{if $#q#\le #i# \le #n#-1$} + {}< #x# & \text{se $0\le #i#\le #p#$} \\ + {}= #x# & \text{se $#p#< #i# < #q#$} \\ + {}> #x# & \text{se $#q#\le #i# \le #n#-1$} \end{cases} \] -This partitioning, which is done by the #while# loop in the code, works -by iteratively increasing #p# and decreasing #q# while maintaining the -first and last of these conditions. At each step, the element at position -#j# is either moved to the front, left where it is, or moved to the back. -In the first two cases, #j# is incremented, while in the last case, #j# -is not incremented since the new element at position #j# has not yet been -processed. - -Quicksort is very closely related to the random binary search trees -studied in \secref{rbst}. In fact, if the input to quicksort consists -of #n# distinct elements, then the quicksort recursion tree is a random -binary search tree. To see this, recall that when constructing a random -binary search tree the first thing we do is pick a random element #x# and -make it the root of the tree. After this, every element will eventually -be compared to #x#, with smaller elements going into the left subtree -and larger elements into the right. - -In quicksort, we select a random element #x# and immediately compare -everything to #x#, putting the smaller elements at the beginning of -the array and larger elements at the end of the array. Quicksort then -recursively sorts the beginning of the array and the end of the array, -while the random binary search tree recursively inserts smaller elements -in the left subtree of the root and larger elements in the right subtree -of the root. - -The above correspondence between random binary search trees and quicksort -means that we can translate \lemref{rbs} to a statement about quicksort: +Esse particionamento, que é feito pelo laço +#while# no código, funciona iterativamente aumento #p# e decrementando #q# enquanto mantém a primeira e últma dessas condições. +A cada passo, o elemento na posição +#j# é ou movido para a frente, à esquerda do onde está, ou movido para trás. +Nos dois primeiros casos, + #j# é incrementado, enquanto no último caso +#j# não é incrementado pois o novo elemento na posição #j# ainda não foi processado. + +O quicksort é muito ligado às árvores binárias aleatórias de busca +estudadas em +\secref{rbst}. De fato, se a entrada ao quicksort +consiste de #n# elementos distintos, então a árvore de recursão do quicksort +é uma árvore binária de busca aleatória + +Para ver isso, lembre-se que ao construir uma árvore binária de busca aleatória +a primeira coisa a fazer é escolher um elemento aleatório #x# e +torná-lo a raiz da árvore. Após isso, todo elemento será eventualmente +comparado a #x#, com elementos menores indo para a subárvore à esquerda +e elementos maiores à direita. + +No quicksort, escolhemos um elemento aleatório #x# e imediatamente +comparamos todo o array a #x#, colocando os elementos menores no começo +do array e elementos maiores no final do array. +O quicksort então recursivamente ordena o começo do array e o final do array, +enquanto a árvore binária de busca aleatória recursivamente insere os +elementos menores na subáres à esquerda da raiz e os maiores elementos +na subárvore à direita da raiz. + +Ess correspondência entre as árvores binárias de busca aleatórias e o +quicksort significa que podemos traduzir +\lemref{rbs} para uma afirmação sobre o quicksort: \begin{lem}\lemlabel{quicksort} - When quicksort is called to sort an array containing the integers - $0,\ldots,#n#-1$, the expected number of times element #i# is compared - to a pivot element is at most $H_{#i#+1} + H_{#n#-#i#}$. + Quando o quicksort é chamado para ordenar um array contendo os inteiros + $0,\ldots,#n#-1$, o número esperado de vezes que o elemento #i# é comparado + ao elemento pivot é até $H_{#i#+1} + H_{#n#-#i#}$. \end{lem} -A little summing up of harmonic numbers gives us the following theorem -about the running time of quicksort: +A soma rápida dos números harmônicos nos dá o seguinte teorema +sobre o tempo de execução do quicksort: \begin{thm}\thmlabel{quicksort-i} - When quicksort is called to sort an array containing #n# distinct - elements, the expected number of comparisons performed is at most - $2#n#\ln #n# + O(#n#)$. + Quando o quicksort é chamado para ordenar um array contendo + #n# elementos distintos, o número esperado de comparações realizadas + é até $2#n#\ln #n# + O(#n#)$. \end{thm} \begin{proof} -Let $T$ be the number of comparisons performed by quicksort when sorting -#n# distinct elements. Using \lemref{quicksort} and linearity of -expectation, we have: +Seja $T$ o número de comparações realizadas pelo quicksort ao ordenar +#n# elementos distintos. Usando \lemref{quicksort} e a linearidade da esperança, temos: \begin{align*} \E[T] &= \sum_{i=0}^{#n#-1}(H_{#i#+1}+H_{#n#-#i#}) \\ &= 2\sum_{i=1}^{#n#}H_i \\ @@ -262,17 +283,18 @@ \subsection{Quicksort} \end{align*} \end{proof} -\thmref{quicksort} describes the case where the elements being sorted are -all distinct. When the input array, #a#, contains duplicate elements, -the expected running time of quicksort is no worse, and can be even -better; any time a duplicate element #x# is chosen as a pivot, all -occurrences of #x# get grouped together and do not take part in either -of the two subproblems. +\thmref{quicksort} descreve o caso em que os elementos sendo ordenados são todos distintos. +Guando o array de entrada, #a#, contém elementos duplicados +o tempo esperado do quicksort não é pior e pode ser ainda melhor; +toda vez que um elemento duplicado #x# é escolhido como um pivot, +todas as ocorrências de #x# são agrupadas e não participam +de nenhum dos dois subproblemas que se seguem. \begin{thm}\thmlabel{quicksort} - The \javaonly{#quickSort(a,c)#} \cpponly{#quickSort(a)#} - \pcodeonly{#quickSort(a,c)#} method runs in $O(#n#\log #n#)$ expected - time and the expected number of comparisons it performs is at most + O método \javaonly{#quickSort(a,c)#} \cpponly{#quickSort(a)#} + \pcodeonly{#quickSort(a,c)#} roda em tempo esperado de $O(#n#\log #n#)$ + e o número esperado de comparações que + realiza é em até $2#n#\ln #n# +O(#n#)$. \end{thm} @@ -280,23 +302,29 @@ \subsection{Heap-sort} \seclabel{heapsort} \index{heap-sort}% -The \emph{heap-sort} algorithm is another in-place sorting algorithm. -Heap-sort uses the binary heaps discussed in \secref{binaryheap}. -Recall that the #BinaryHeap# data structure represents a heap using -a single array. The heap-sort algorithm converts the input array #a# -into a heap and then repeatedly extracts the minimum value. - -More specifically, a heap stores #n# elements in an array, #a#, at array locations -$#a[0]#,\ldots,#a[n-1]#$ with the smallest value stored at the root, -#a[0]#. After transforming #a# into a #BinaryHeap#, the heap-sort -algorithm repeatedly swaps #a[0]# and #a[n-1]#, decrements #n#, and -calls #trickleDown(0)# so that $#a[0]#,\ldots,#a[n-2]#$ once again are -a valid heap representation. When this process ends (because $#n#=0$) -the elements of #a# are stored in decreasing order, so #a# is reversed -to obtain the final sorted order.\footnote{The algorithm -could alternatively redefine the #compare(x,y)# function so that the -heap sort algorithm stores the elements directly in ascending order.} -\figref{heapsort} shows an example of the execution of #heapSort(a,c)#. + O algoritmo \emph{heap-sort} é outro algoritmo de ordenação que funciona \emph{in place}. + +Heap-sort usa as heaps binárias discutidas em \secref{binaryheap}. +Lembre-se que a estrutura de dados +#BinaryHeap# representa uma heap usando um único array. +O algoritmo heap-sort converte o array de entrada #a# +em uma heap e então repetidamente extrai o valor mínimo. + +Mais especificamente, uma heap guarda #n# elementos em um array, #a#, em +em posições do arrau +$#a[0]#,\ldots,#a[n-1]#$ com o menor valor guardado na raiz, +#a[0]#. +Após transformar #a# em uma + #BinaryHeap#, o algoritmo heap-sort +algorithm repetidamente troca #a[0]# e #a[n-1]#, decrementa #n#, e +chama #trickleDown(0)# tal que $#a[0]#,\ldots,#a[n-2]#$ volte a ser +uma representação válida de uma heap. Quando esse processo termina +(porque $#n#=0$) os elementos de #a# são guardados em ordem decrescente, +então #a# é invertido para obter a forma ordenada final. +\footnote{O algoritmo poderia como opção redefinir a função +#compare(x,y)# para que o heap sort guarde os elementos +diretamente na ordem crescente.} +\figref{heapsort} mostra um exemplo da execução do #heapSort(a,c)#. \begin{figure} \begin{center} From 919609a2cf6323dd870f6f428bd0d0641e44a7e1 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 13 Aug 2020 17:07:54 -0300 Subject: [PATCH 19/66] translation of names of enviroments --- latex/ods.tex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/latex/ods.tex b/latex/ods.tex index 4ef6d69b..96ce7257 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -125,26 +125,26 @@ % Theorem-like environments \theoremstyle{plain} -\newtheorem{thm}{Theorem}[chapter] +\newtheorem{thm}{Teorema}[chapter] \newcommand{\thmlabel}[1]{\label{thm:#1}} \newcommand{\thmref}[1]{Teorema~\ref{thm:#1}} -\newtheorem{lem}{Lemma}[chapter] +\newtheorem{lem}{Lema}[chapter] \newcommand{\lemlabel}[1]{\label{lem:#1}} \newcommand{\lemref}[1]{Lema~\ref{lem:#1}} -\newtheorem{cor}{Corollary}[chapter] +\newtheorem{cor}{Corolário}[chapter] \newcommand{\corlabel}[1]{\label{cor:#1}} \newcommand{\corref}[1]{Corolário~\ref{cor:#1}} \theoremstyle{definition} -\newtheorem{exc}{Exercise}[chapter] +\newtheorem{exc}{Exercício}[chapter] \newcommand{\exclabel}[1]{\label{exc:#1}} \newcommand{\excref}[1]{Exercício~\ref{exc:#1}} -\newtheorem{prp}{Property}[chapter] +\newtheorem{prp}{Propriedade}[chapter] \newcommand{\prplabel}[1]{\label{prp:#1}} \newcommand{\prpref}[1]{Propriedade~\ref{prp:#1}} From fbe11152fc5bc4b6dd33f1143bfa199528e2d35e Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 13 Aug 2020 19:20:01 -0300 Subject: [PATCH 20/66] translation to portuguese --- latex/sorting.tex | 684 ++++++++++++++++++++++++---------------------- 1 file changed, 357 insertions(+), 327 deletions(-) diff --git a/latex/sorting.tex b/latex/sorting.tex index 50e2e9fd..3300142b 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -298,16 +298,16 @@ \subsection{Quicksort} $2#n#\ln #n# +O(#n#)$. \end{thm} -\subsection{Heap-sort} +\subsection{Heap sort} \seclabel{heapsort} \index{heap-sort}% - O algoritmo \emph{heap-sort} é outro algoritmo de ordenação que funciona \emph{in place}. + O algoritmo \emph{heap sort} é outro algoritmo de ordenação que funciona \emph{in place}. -Heap-sort usa as heaps binárias discutidas em \secref{binaryheap}. +O Heap sort usa as heaps binárias discutidas em \secref{binaryheap}. Lembre-se que a estrutura de dados #BinaryHeap# representa uma heap usando um único array. -O algoritmo heap-sort converte o array de entrada #a# +O algoritmo heap sort converte o array de entrada #a# em uma heap e então repetidamente extrai o valor mínimo. Mais especificamente, uma heap guarda #n# elementos em um array, #a#, em @@ -315,7 +315,7 @@ \subsection{Heap-sort} $#a[0]#,\ldots,#a[n-1]#$ com o menor valor guardado na raiz, #a[0]#. Após transformar #a# em uma - #BinaryHeap#, o algoritmo heap-sort + #BinaryHeap#, o algoritmo heap sort algorithm repetidamente troca #a[0]# e #a[n-1]#, decrementa #n#, e chama #trickleDown(0)# tal que $#a[0]#,\ldots,#a[n-2]#$ volte a ser uma representação válida de uma heap. Quando esse processo termina @@ -330,11 +330,10 @@ \subsection{Heap-sort} \begin{center} \includegraphics[scale=0.90909]{figs/heapsort} \end{center} - \caption[Heap sort]{A snapshot of the execution of #heapSort(a,c)#. - The shaded part of the - array is already sorted. The unshaded part is a #BinaryHeap#. - During the next iteration, element $5$ will be placed into array - location $8$.} + \caption[Heap sort]{Uma etapa da execução do #heapSort(a,c)#. + A parte sombreada do array já está ordenada. A parte não sombreada é uma + #BinaryHeap#. + Durante a próxima iteração, o elemento $5$ será colocado na posição $8$ do array.} \figlabel{heapsort} \end{figure} @@ -342,241 +341,273 @@ \subsection{Heap-sort} \cppimport{ods/BinaryHeap.sort(b)} \pcodeimport{ods/Algorithms.heapSort(a)} -A key subroutine in heap sort is the constructor for turning -an unsorted array #a# into a heap. It would be easy to do this -in $O(#n#\log#n#)$ time by repeatedly calling the #BinaryHeap# -#add(x)# method, but we can do better by using a bottom-up algorithm. -Recall that, in a binary heap, the children of #a[i]# are stored at -positions #a[2i+1]# and #a[2i+2]#. This implies that the elements -$#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ have no children. In other -words, each of $#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ is a sub-heap -of size 1. Now, working backwards, we can call #trickleDown(i)# for -each $#i#\in\{\lfloor #n#/2\rfloor-1,\ldots,0\}$. This works, because by -the time we call #trickleDown(i)#, each of the two children of #a[i]# -are the root of a sub-heap, so calling #trickleDown(i)# makes #a[i]# -into the root of its own subheap. +Uma subrotina chave no heap sort é construtor que transforma um +array desordenado #a# em uma heap. Seria fácil fazer isso usando +$O(#n#\log#n#)$ de tempo ao chamar repetidamente o método da #BinaryHeap# +#add(x)#, mas podemos faer melhor ao usar um algoritmo bottom-up, que atua de baixo para cima no processo, ou seja, dos subarrays menores até os maiores. + +Lembre-se que, em uma heap binária, os filhos de +#a[i]# são guardados nas posições +#a[2i+1]# e #a[2i+2]#. Isso implica que os elementos +$#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ não tem filhos. +Em outras palavras, cada +um dos elementos em $#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ é uma subheap de tamanho 1. +Agora, trabalhando de trás para frente, podemos chamar +#trickleDown(i)# para cada +$#i#\in\{\lfloor #n#/2\rfloor-1,\ldots,0\}$. Isso funciona porque +ao chamarmos + #trickleDown(i)#, cada um dos dois filhos de #a[i]# + são raizes de uma subheap, então chamar +#trickleDown(i)# torna #a[i]# a raiz de sua própria subheap. \javaimport{ods/BinaryHeap.BinaryHeap(a,c)} \cppimport{ods/BinaryHeap.BinaryHeap(b)} -The interesting thing about this bottom-up strategy is that it is more -efficient than calling #add(x)# #n# times. To see this, notice that, -for $#n#/2$ elements, we do no work at all, for $#n#/4$ elements, we call -#trickleDown(i)# on a subheap rooted at #a[i]# and whose height is one, for -$#n#/8$ elements, we call #trickleDown(i)# on a subheap whose height is two, -and so on. Since the work done by #trickleDown(i)# is proportional to -the height of the sub-heap rooted at #a[i]#, this means that the total -work done is at most +Um fato interessante sobre essa estratégia bottom-up é que ela é mais +eficiente que chamar + #add(x)# #n# vezes. Para ver isso, note que para +$#n#/2$ elementos, não é necessária nenhum operação, para $#n#/4$ elementos, chamamos +#trickleDown(i)# em uma subheap com raiz em #a[i]# e cuja altura é 1, para +$#n#/8$ elementos, chamamos #trickleDown(i)# em uma subheap cuja altura é dois, e assim segue. +Como o trabalho feito por + #trickleDown(i)# é proporcional à altura da subheap enraizada em +#a[i]#, isso significa que o trabalho total realizado é até \[ \sum_{i=1}^{\log#n#} O((i-1)#n#/2^{i}) \le \sum_{i=1}^{\infty} O(i#n#/2^{i}) = O(#n#)\sum_{i=1}^{\infty} i/2^{i} = O(2#n#) = O(#n#) \enspace . \] -The second-last equality follows by recognizing that the sum -$\sum_{i=1}^{\infty} i/2^{i}$ is equal, by definition of expected value, -to the expected number of times we toss a coin up to and including the -first time the coin comes up as heads and applying \lemref{coin-tosses}. -The following theorem describes the performance of #heapSort(a,c)#. +A penúltima igualdade pode ser obtida ao reconhecer que a soma +$\sum_{i=1}^{\infty} i/2^{i}$ é igual, pela definição de valos esperado, +ao número esperado de lançamentos de uma moeda, incluindo a primeira vez, cair +do lado cara e aplicar \lemref{coin-tosses}. + +O teorema a seguir descreve o desempenho de #heapSort(a,c)#. \begin{thm} - The #heapSort(a,c)# method runs in $O(#n#\log #n#)$ time and performs at - most $2#n#\log #n# + O(#n#)$ comparisons. + O método + #heapSort(a,c)# roda em $O(#n#\log #n#)$ de tempo e realiza no máximo + $2#n#\log #n# + O(#n#)$ comparações. \end{thm} \begin{proof} -The algorithm runs in three steps: (1)~transforming #a# into a heap, -(2)~repeatedly extracting the minimum element from #a#, and (3)~reversing -the elements in #a#. We have just argued that step~1 takes $O(#n#)$ -time and performs $O(#n#)$ comparisons. Step~3 takes $O(#n#)$ time and -performs no comparisons. Step~2 performs #n# calls to #trickleDown(0)#. -The $i$th such call operates on a heap of size $#n#-i$ and performs -at most $2\log(#n#-i)$ comparisons. Summing this over $i$ gives + O algoritmo roda em três passos: + (1)~transformar #a# em uma heap, + (2)~repetidamente extrair o elemento mínimo de #a# e + (3)~inverter os elementos em #a#. + +Previamente argumentamos que o passo~1 leva +$O(#n#)$ de tempo e realiza +$O(#n#)$ comparações. Passo~3 leva $O(#n#)$ de tempo e não faz comparações. +Passo~2 faz #n# chamadas a #trickleDown(0)#. +A #i#-ésima chamada opera em uma heap de tamanho +$#n#-i$ e faz até +$2\log(#n#-i)$ comparações. Somando os custos do Passo~2 sobre os possíveis valores de $i$ resulta em \[ \sum_{i=0}^{#n#-i} 2\log(#n#-i) \le \sum_{i=0}^{#n#-i} 2\log #n# = 2#n#\log #n# \] -Adding the number of comparisons performed in each of the three steps -completes the proof. +Somando o número de comparações realizadas em cada um dos três passos completa a prova. \end{proof} -\subsection{A Lower-Bound for Comparison-Based Sorting} - +\subsection{Um Limitante Inferior para Ordenação} +\index{limitante inferior} \index{lower-bound}% \index{sorting lower-bound}% -We have now seen three comparison-based sorting algorithms that each run -in $O(#n#\log #n#)$ time. By now, we should be wondering if faster -algorithms exist. The short answer to this question is no. If the -only operations allowed on the elements of #a# are comparisons, then no -algorithm can avoid doing roughly $#n#\log #n#$ comparisons. This is -not difficult to prove, but requires a little imagination. Ultimately, -it follows from the fact that +\index{limitante inferior para ordenação} +Estudamos até agora três algoritmos de ordenação baseados em comparação em funcionam em $O(#n#\log #n#)$ de tempo. Nesse momento, podemos nos perguntar se existem algoritmos mais rápidos. +A resposta a essa questão é não. Se as únicas operações permitidas nos elementos +de #a# são comparações, então nenhum algoritmo pode evitar fazer aproximadamente +$#n#\log #n#$ comparações. Isso não é difícil provar, mas requer um pouc +de imaginação. No final das contas, isso vêm do fato que \[ \log(#n#!) = \log #n# + \log (#n#-1) + \dots + \log(1) = #n#\log #n# - O(#n#) \enspace . \] -(Proving this fact is left as \excref{log-factorial}.) - -We will start by focusing our attention on deterministic algorithms like -merge-sort and heap-sort and on a particular fixed value of #n#. Imagine -such an algorithm is being used to sort #n# distinct elements. The key -to proving the lower-bound is to observe that, for a deterministic -algorithm with a fixed value of #n#, the first pair of elements that are -compared is always the same. For example, in #heapSort(a,c)#, when #n# -is even, the first call to #trickleDown(i)# is with #i=n/2-1# and the -first comparison is between elements #a[n/2-1]# and #a[n-1]#. - -Since all input elements are distinct, this first comparison has only -two possible outcomes. The second comparison done by the algorithm may -depend on the outcome of the first comparison. The third comparison -may depend on the results of the first two, and so on. In this way, -any deterministic comparison-based sorting algorithm can be viewed -as a rooted binary \emph{comparison tree}. -\index{comparison tree}% -Each internal node, #u#, -of this tree is labelled with a pair of indices #u.i# and #u.j#. -If $#a[u.i]#<#a[u.j]#$ the algorithm proceeds to the left subtree, -otherwise it proceeds to the right subtree. Each leaf #w# of this -tree is labelled with a permutation $#w.p[0]#,\ldots,#w.p[n-1]#$ of -$0,\ldots,#n#-1$. This permutation represents the one that is -required to sort #a# if the comparison tree reaches this leaf. That is, +(Fazer a prova desse fato é sugerida como \excref{log-factorial}.) + +Iniciaremos ao focar nossa atenção em algoritmos determinísticos como +merge sort e heap sort e em um valor fixo de #n#. Imagine +que tal algoritmo está sendo usado para ordenar #n# elementos distintos. +A chave para provar o limitante inferior é observar que, para um algoritmo +determinístico com valor fixo de #n#, o primeiro par de elementos que +são comparados é sempre o mesmo. +Por exemplo, no + #heapSort(a,c)#, quando #n# é par, a primeira chamada a +#trickleDown(i)# é com #i=n/2-1# e a primeira comparação está entre os +elementos #a[n/2-1]# e #a[n-1]#. + +Uma vez que todos os elementos de entrada são distintos, essa primeira +comparação tem somente duas possíveis saídas. A segunda comparação +feita pelo algoritmo depende na saída da primeira comparação. +A terceira comparação depende nos resultados das primeiras duas e assim segue. +Desse jeito, qualquer algoritmo determinístico de ordenação baseado em +comparações pode ser visto como uma \emph{árvore de comparações}. +\index{árvore de comparações}% +Cada nodo interno, #u#, dessa árvore é marcada com um par de índices #u.i# e +#u.j#. Se +$#a[u.i]#<#a[u.j]#$ o algoritmo segue para a subárvore à esquerda. +caso contrária ela vai para a subárvore à direita. +Cada folha #w# dessa árvore é marcada com uma permutação +$#w.p[0]#,\ldots,#w.p[n-1]#$ de +$0,\ldots,#n#-1$. +Essa permutação representa aquilo que é necessário para ordenar #a# +se a árvore de comparação atinge esse folha. +Isso é, \[ #a[w.p[0]]#<#a[w.p[1]]#<\cdots<#a[w.p[n-1]]# \enspace . \] -An example of a comparison tree for an array of size #n=3# is shown in +Um exemplo de uma árvore de comparações para um array de tamanho #n=3# +é mostrado em \figref{comparison-tree}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/comparison-tree} \end{center} - \caption[A comparison tree]{A comparison tree for sorting an array $#a[0]#,#a[1]#,#a[2]#$ of length #n=3#.} + \caption[Uma árvore de comparações]{Uma árvore de comparações para ordenar um array $#a[0]#,#a[1]#,#a[2]#$ de tamanho #n=3#.} \figlabel{comparison-tree} \end{figure} -The comparison tree for a sorting algorithm tells us everything about -the algorithm. It tells us exactly the sequence of comparisons that -will be performed for any input array, #a#, having #n# distinct elements -and it tells us how the algorithm will reorder #a# in order to sort it. -Consequently, the comparison tree must have at least $#n#!$ leaves; -if not, then there are two distinct permutations that lead to the same -leaf; therefore, the algorithm does not correctly sort at least one of -these permutations. - -For example, the comparison tree in \figref{comparison-tree-2} has only -$4< 3!=6$ leaves. Inspecting this tree, we see that the two input arrays -$3,1,2$ and $3,2,1$ both lead to the rightmost leaf. On the input $3,1,2$ -this leaf correctly outputs $#a[1]#=1,#a[2]#=2,#a[0]#=3$. However, on the -input $3,2,1$, this node incorrectly outputs $#a[1]#=2,#a[2]#=1,#a[0]#=3$. -This discussion leads to the primary lower-bound for comparison-based -algorithms. +A árvore de comparações para um algoritmo de ordenação nos diz tudo sobre +o algoritmo. Ela nos diz exatamente a sequência de comparações que +será realizada para qualquer array de entrada, #a#, tendo #n# elementos distintos +e nos diz como o algoritmo irá reordenar #a# para organizá-lo. + +Consequentemente, a árvore de comparação deve ter pelo menos +$#n#!$ folhas; +senão, então há duas permutações distintas que levam à mesma folha; portanto, +o algoritmo não ordena corretamente pelo uma dessas permutações. + +Por exemplo, a árvore de comparações em +\figref{comparison-tree-2} tem somente +$4< 3!=6$ folhas. Ao inspecionar essa árvore, vemos que os dois arrays de entrada +$3,1,2$ e $3,2,1$ ambos levam à folha mais a direita. +Na entrada +$3,1,2$ essa folha corretamente produz +$#a[1]#=1,#a[2]#=2,#a[0]#=3$. Entretanto, na entrada +$3,2,1$, esse nodo incorretamente produz $#a[1]#=2,#a[2]#=1,#a[0]#=3$. +Essa discussão nos conduz ao limitante inferior para algoritmos baseados em +comparação. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/comparison-tree-b} \end{center} - \caption{A comparison tree that does not correctly sort every input - permutation.} + \caption{Uma árvore de comparações que não ordena corretamente todas as permutações de entrada.} \figlabel{comparison-tree-2} \end{figure} \begin{thm}\thmlabel{deterministic-sorting-lower-bound} - For any deterministic comparison-based sorting algorithm $\mathcal{A}$ - and any integer $#n#\ge 1$, there exists an input array #a# of - length #n# such that $\mathcal{A}$ performs at least $\log(#n#!) = - #n#\log#n#-O(#n#)$ comparisons when sorting #a#. + Para qualquer algoritmo de ordenação determinístico baseado em comparação + $\mathcal{A}$ e qualquer inteiro + $#n#\ge 1$, existe um array de entrada #a# de tamanho #n# + tal que $\mathcal{A}$ usa pelo menos $\log(#n#!) = + #n#\log#n#-O(#n#)$ comparações ao ordenar #a#. \end{thm} \begin{proof} - By the preceding discussion, the comparison tree defined by $\mathcal{A}$ - must have at least $#n#!$ leaves. An easy inductive proof shows that - any binary tree with $k$ leaves has a height of at least $\log k$. - Therefore, the comparison tree for $\mathcal{A}$ has a leaf, #w#, - with a depth of at least $\log(#n#!)$ and there is an input array #a# - that leads to this leaf. The input array #a# is an input for which - $\mathcal{A}$ does at least $\log(#n#!)$ comparisons. + De acordo com a discussão anterior, a árvore de comparações definida por +$\mathcal{A}$ deve ter + pelo menos $#n#!$ folhas. Uma prova indutiva fácil mostra que + qualquer árvore binária com $k$ folhas tem altura de pelo menos + $\log k$. + Portante, a árvore de comparações para + $\mathcal{A}$ tem uma folha, #w#, com profundidade de pelo menos + $\log(#n#!)$ e existe um array de entradas #a# + que leva a essa folha. O array de entrada #a# é uma entrada para qual + $\mathcal{A}$ faz pelo menos $\log(#n#!)$ comparações. \end{proof} -\thmref{deterministic-sorting-lower-bound} deals with deterministic -algorithms like merge-sort and heap-sort, but doesn't tell us anything -about randomized algorithms like quicksort. Could a randomized algorithm -beat the $\log(#n#!)$ lower bound on the number of comparisons? -The answer, again, is no. Again, the way to prove it is to think -differently about what a randomized algorithm is. - -In the following discussion, we will assume that our decision -trees have been ``cleaned up'' in the following way: Any node that can not -be reached by some input array #a# is removed. This cleaning up implies -that the tree has exactly $#n#!$ leaves. It has at least $#n#!$ leaves -because, otherwise, it could not sort correctly. It has at most $#n#!$ -leaves since each of the possible $#n#!$ permutation of #n# distinct -elements follows exactly one root to leaf path in the decision tree. - -We can think of a randomized sorting algorithm, $\mathcal{R}$, as a -deterministic algorithm that takes two inputs: The input array #a# -that should be sorted and a long sequence $b=b_1,b_2,b_3,\ldots,b_m$ -of random real numbers in the range $[0,1]$. The random numbers provide -the randomization for the algorithm. When the algorithm wants to toss a -coin or make a random choice, it does so by using some element from $b$. -For example, to compute the index of the first pivot in quicksort, -the algorithm could use the formula $\lfloor n b_1\rfloor$. - -Now, notice that if we fix $b$ to some particular sequence $\hat{b}$ -then $\mathcal{R}$ becomes a deterministic sorting algorithm, -$\mathcal{R}(\hat{b})$, that has an associated comparison tree, -$\mathcal{T}(\hat{b})$. Next, notice that if we select #a# to be a random -permutation of $\{1,\ldots,#n#\}$, then this is equivalent to selecting -a random leaf, #w#, from the $#n#!$ leaves of $\mathcal{T}(\hat{b})$. - -\excref{randomized-lower-bound} asks you to prove that, if we select -a random leaf from any binary tree with $k$ leaves, then the expected -depth of that leaf is at least $\log k$. Therefore, the expected -number of comparisons performed by the (deterministic) algorithm -$\mathcal{R}(\hat{b})$ when given an input array containing a random -permutation of $\{1,\ldots,n\}$ is at least $\log(#n#!)$. Finally, -notice that this is true for every choice of $\hat{b}$, therefore it holds even for $\mathcal{R}$. This completes the proof of the lower-bound for randomized algorithms. +O \thmref{deterministic-sorting-lower-bound} trata de algoritmos +determinísticos como +merge-sort e heap-sort, mas não nos diz nada sobre algoritmos randomizados +como o quicksort. +Poderia um algoritmo randomizado usar um número de comparações menor que +o limitante inferior $\log(#n#!)$? +A resposta é, novamente, não. De novo, a forma de prová-lo é +pensar diferentemente sobre o que um algoritmo randomizado é. + +Na discussão a seguir, iremos assumir que nossas árvores de decisões +foram ``limpadas'' da seguinte forma: +qualquer nodo que não pode ser alcançado por algum array de entrada #a# é removido. +Essa limpeza implica que a árvore tem exatamente +$#n#!$ folhas. Ela tem pelo menos $#n#!$ folhas porque, caso contrário, +não ordenaria corretamente. Ela tem até $#p#!$ folhas pois +cada uma das possíveis permutações $#n#!$ de #n# elementos distintos segue +exatamente um caminho da raiz à folha na árvore de decisões. + +Podemos imaginar que um algoritmo de ordenação randomizado + $\mathcal{R}$ age como um algoritmo determinístico que recebe duas + entradas: o array de entradas #a# que devem ser ordenado e uma longa + sequência números reais aleatórios $b=b_1,b_2,b_3,\ldots,b_m$ no intervalo $[0,1]$. + Os números aleatórios provêem a randomização ao algoritmo. Quando o algoritmo + quer lançar uma moeda ou fazer uma escolha aleatória, ele o faz usando algum + elemento de $b$. Por exemplo, para computar o índice do primeiro pivot no + quicksort, o algoritmo poderia usar a fórmula $\lfloor n b_1\rfloor$. + + Agora, note que se fixarmos $B$ a uma sequência particular + $\hat{b}$ então +$\mathcal{R}$ torna-se um algoritmo de ordenação determinístico, +$\mathcal{R}(\hat{b})$, que tem uma árvore de comparações associada, +$\mathcal{T}(\hat{b})$. A seguir, note que se selecionarmos + #a# para ser uma permutação aleatória de +$\{1,\ldots,#n#\}$, então isso é equivalente a selecionar uma folha aleatória + #w# dentre as $#n#!$ folhas de $\mathcal{T}(\hat{b})$. + +\excref{randomized-lower-bound} pede que você prove que, se selecionarmos +uma folha aleatória de qualquer árvore binária com $k$ folhas, então +a profundidade esperada daquela folha é pelo menos +$\log k$. Portanto, o número esperado de comparações realizado pelo algoritmo (determinístico) +$\mathcal{R}(\hat{b})$ quando passado um array de entrada contendo uma permutação aleatória de +$\{1,\ldots,n\}$ é pelo menos $\log(#n#!)$. Finalmente, note que isso é verdadeiro para toda escolha de +$\hat{b}$, portanto isso vale mesmo para $\mathcal{R}$. Isso completa a prova do limitante inferior para algoritmos randomizados. \begin{thm}\thmlabel{randomized-sorting-lower-bound} - For any integer $n\ge 1$ and any (deterministic or randomized) - comparison-based sorting algorithm $\mathcal{A}$, the expected number - of comparisons done by $\mathcal{A}$ when sorting a random permutation - of $\{1,\ldots,n\}$ is at least $\log(#n#!) = #n#\log#n#-O(#n#)$. + Para qualquer inteiro + $n\ge 1$ e qualquer algoritmo de ordenação baseado em comparações (determinístico ou randomizado) + $\mathcal{A}$, o número esperado de comparações feito por + $\mathcal{A}$ ao ordenar uma permutação aleatória + de $\{1,\ldots,n\}$ é pelo menos $\log(#n#!) = #n#\log#n#-O(#n#)$. \end{thm} +\section{Counting Sort e Radix Sort} - -\section{Counting Sort and Radix Sort} - -In this section we study two sorting algorithms that are not -comparison-based. Specialized for sorting small integers, these algorithms -elude the lower-bounds of \thmref{deterministic-sorting-lower-bound} -by using (parts of) the elements in #a# as indices into an array. -Consider a statement of the form +Nesta seção estudamos dois algoritmos de ordeanção que não são +baseados em comparação. +Especializados para ordenar valores inteiros baixos, esses algoritmos +se esquivam dos limites +do \thmref{deterministic-sorting-lower-bound} ao usar (partes de) elementos em #a# +como índices para um array. +Considere um comando da forma \[ #c[a[i]]# = 1 \enspace . \] -This statement executes in constant time, but has #c.length# possible -different outcomes, depending on the value of #a[i]#. This means that the -execution of an algorithm that makes such a statement cannot be modelled -as a binary tree. Ultimately, this is the reason that the algorithms -in this section are able to sort faster than comparison-based algorithms. +Esse comando executa em tempo constante, mas tem +#c.length# diferentes saídas, dependendo do valor de #a[i]#. Isso +significa que a execução de um algoritmo que usa tal comando não pode ser +modelado como uma árvore binária. +É por essa razão que algoritmos desta seção são capazes de ordenar mais +rapidamente que algoritmos baseados em comparação. \subsection{Counting Sort} -Suppose we have an input array #a# consisting of #n# integers, each in -the range $0,\ldots,#k#-1$. The \emph{counting-sort} +Suponha que temos um array de entrada #a# consistindo de #n# inteiros, +cada qual no intervalo +$0,\ldots,#k#-1$. O algoritmo \emph{counting sort} \index{counting-sort}% -algorithm sorts #a# -using an auxiliary array #c# of counters. It outputs a sorted version -of #a# as an auxiliary array #b#. - -The idea behind counting-sort is simple: For each -$#i#\in\{0,\ldots,#k#-1\}$, count the number of occurrences of #i# in #a# -and store this in #c[i]#. Now, after sorting, the output will look like -#c[0]# occurrences of 0, followed by #c[1]# occurrences of 1, followed by -#c[2]# occurrences of 2,\ldots, followed by #c[k-1]# occurrences of #k-1#. -The code that does this is very slick, and its execution is illustrated in +ordena #a# usando um array auxiliar +#c# de contadores. Ele produz uma versão ordenada de #a# como um array auxiliar #b#. + +A ideia por trás do counting sort é simples: para cada +$#i#\in\{0,\ldots,#k#-1\}$, conte o número de ocorrências de #i# em #a# +e guarde essa contagem em #c[i]#. Agora, após a ordenação, a saída +vai ser do tipo +#c[0]# ocorrências de 0, seguida de #c[1]# ocorrências de 1, seguida de +#c[2]# ocorrências de 2,\ldots, seguida de #c[k-1]# ocorrências de #k-1#. +O código que faz isso é bem elegante e sua execução é ilustrada em \figref{countingsort}: \codeimport{ods/Algorithms.countingSort(a,k)} @@ -584,224 +615,224 @@ \subsection{Counting Sort} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/countingsort} \end{center} - \caption{The operation of counting sort on an array of length $#n#=20$ that stores integers $0,\ldots,#k#-1=9$.} + \caption{A execução do counting sort em um array de tamanho $#n#=20$ que guarda inteiros $0,\ldots,#k#-1=9$.} \figlabel{countingsort} \end{figure} -The first #for# loop in this code sets each counter #c[i]# so that it -counts the number of occurrences of #i# in #a#. By using the values -of #a# as indices, these counters can all be computed in $O(#n#)$ time -with a single for loop. At this point, we could use #c# to -fill in the output array #b# directly. However, this would not work if -the elements of #a# have associated data. Therefore we spend a little -extra effort to copy the elements of #a# into #b#. - -The next #for# loop, which takes $O(#k#)$ time, computes a running-sum -of the counters so that #c[i]# becomes the number of elements in -#a# that are less than or equal to #i#. In particular, for every -$#i#\in\{0,\ldots,#k#-1\}$, the output array, #b#, will have +O primeiro laço #for# nesse código usa cada contador #c[i]# tal que +ele conta o número de ocorrências de #i# em #a#. +Ao usar os valores de #a# como índices, esses contadores podem +ser computados em +$O(#n#)$ de tempo com um único #for#. +Nesse ponto, poderíamos usar #c# para preencher o array de saída diretamente. +Porém isso não funcionaria se os elementos de #a# tem dados associados. +Portanto, gastamos um pouco de esforço extra para copiar os elementos de #a# +em #b#. + +O próximo laço #for#, que leva $O(#k#)$ de tempo, computa uma soma acumulativa dos contadores tal que +#c[i]# se torna o número de elementos em #a# que são menores que ou iguais a #i#. +Em particular, para todo +$#i#\in\{0,\ldots,#k#-1\}$, o array de saída, #b#, terá \[ #b[c[i-1]]#=#b[c[i-1]+1]=#\cdots=#b[c[i]-1]#=#i# \enspace . \] -Finally, the algorithm scans #a# backwards to place its elements, in order, -into an output array #b#. When scanning, the element #a[i]=j# is placed -at location #b[c[j]-1]# and the value #c[j]# is decremented. +Finalmente, o algoritmo percorre #a# de trás para frente para posicionar seus +elementos em ordem no array de saída #b#. Ao percorrer, o elemento +#a[i]=j# é colocado na posição #b[c[j]-1]# e o valor #c[j]# é decrementado. \begin{thm} - The #countingSort(a,k)# method can sort an array #a# containing #n# - integers in the set $\{0,\ldots,#k#-1\}$ in $O(#n#+#k#)$ time. + O método + #countingSort(a,k)# pode ordenar um array #a# contendo #n# + inteiros no conjunto $\{0,\ldots,#k#-1\}$ em $O(#n#+#k#)$ de tempo. \end{thm} -The counting-sort algorithm has the nice property of being \emph{stable}; -\index{stable sorting algorithm}% -it preserves the relative order of equal elements. If two elements -#a[i]# and #a[j]# have the same value, and $#i#<#j#$ then #a[i]# will -appear before #a[j]# in #b#. This will be useful in the next section. - -\subsection{Radix-Sort} - -Counting-sort is very efficient for sorting an array of integers when the -length, #n#, of the array is not much smaller than the maximum value, -$#k#-1$, that appears in the array. The \emph{radix-sort} -\index{radix-sort}% -algorithm, -which we now describe, uses several passes of counting-sort to allow -for a much greater range of maximum values. - -Radix-sort sorts #w#-bit integers by using $#w#/#d#$ passes of counting-sort -to sort these integers #d# bits at a time.\footnote{We assume that -#d# divides #w#, otherwise we can always increase #w# to $#d#\lceil -#w#/#d#\rceil$.} More precisely, radix sort first sorts the integers by -their least significant #d# bits, then their next significant #d# bits, -and so on until, in the last pass, the integers are sorted by their most -significant #d# bits. +O algoritmo counting sort tem a interessante propriedade de ser \emph{estável}; +\index{algoritmo de ordenação estável}% +ele preserva a ordem relativa de elementos iguais. Se dois elementos +#a[i]# e #a[j]# tem o mesmo valor e $#i#<#j#$ então #a[i]# irá +aparecer antes de #a[j]# em #b#. Isso será útil na seção a seguir. + +\subsection{Radix sort} + +Counting sort é muito eficiente para ordenar um array de inteiros quando +o tamanho, #n#, do array não é muito menor que o maior valor +$#k#-1$ que aparece no array. O algoritmo \emph{radix sort} +\index{radix sort}% +, que agora descreveremos, usa várias passadas do counting sort para +permitir o uso seu uso em arrays cujo maior valor é bem maior que o tamanho do array. + +O radix sort ordena inteiros +de #w#-bits usando $#w#/#d#$ iterações do counting sort +para ordenar esses inteiros considerando somente #d# bits por iteração. +\footnote{Assumimos que #d# divide #w#, caso contrário podemos sempre aumentar +#w# para +$#d#\lceil +#w#/#d#\rceil$.} Mais precisamente, o radix sort primeiro ordena +os inteiros usando seus #d# bits menos significativos e então seus próximos #d# bits significativos e assim por diante até, na última iteração, os inteiros estão ordenados pelos seus #d# bits mais significativos. + \codeimport{ods/Algorithms.radixSort(a)} -(In this code, the expression #(a[i]>>d*p)&((1<>d*p)&((1<0$, the #radixSort(a,k)# method can sort an array - #a# containing #n# #w#-bit integers in $O((#w#/#d#)(#n#+2^{#d#}))$ time. + Para qualquer inteiro $#d#>0$, o método #radixSort(a,k)# pode ordenar um array + #a# contendo #n# inteiros de #w#-bits em $O((#w#/#d#)(#n#+2^{#d#}))$ de tempo. \end{thm} -If we think, instead, of the elements of the array being in the range -$\{0,\ldots,#n#^c-1\}$, and take $#d#=\lceil\log#n#\rceil$ we obtain -the following version of \thmref{radix-sort}. +Alternativamente, se os elementos do array estarem no intervalo +$\{0,\ldots,#n#^c-1\}$, e usarmos $#d#=\lceil\log#n#\rceil$ obtemos +a seguinte versão do + \thmref{radix-sort}. \begin{cor}\corlabel{radix-sort-poly} - The #radixSort(a,k)# method can sort an array #a# containing #n# - integer values in the range $\{0,\ldots,#n#^c-1\}$ in $O(c#n#)$ time. +O método #radixSort(a,k)# pode ordenar um array #a# contendo #n# valores inteiros no intervalo $\{0,\ldots,#n#^c-1\}$ em $O(c#n#)$ de tempo. \end{cor} -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -Sorting is \emph{the} fundamental algorithmic problem in computer science, -and it has a long history. Knuth \cite{k97v3} attributes the merge-sort -algorithm to von~Neumann (1945). Quicksort is due to Hoare \cite{h61}. -The original heap-sort algorithm is due to Williams \cite{w64}, but the -version presented here (in which the heap is constructed bottom-up -in $O(#n#)$ time) is due to Floyd \cite{f64}. Lower-bounds for -comparison-based sorting appear to be folklore. The following table -summarizes the performance of these comparison-based algorithms: +Ordenação é \emph{o} problema fundamental em Ciência da Computação e tem uma longa história. +Knuth \cite{k97v3} atribui o algoritmo merge sort a +von~Neumann (1945). Quicksort foi proposto por Hoare \cite{h61}. +O algoritmo heap sort original é de Williams \cite{w64}, mas a +versão apresentada aqui (na qual a heap é construída bottom-up em +$O(#n#)$ de tempo) foi proposta por Floyd \cite{f64}. Limitantes inferiores para ordenação baseada em comparações parecem ser folklore. +A tabela a seguir resume o desempenho desses algoritmos baseados em comparações: \begin{center} \begin{tabular}{|l|r@{}l@{ }l|l|} \hline - & \multicolumn{3}{c|}{comparisons} & in-place \\ \hline - Merge-sort & $#n#\log #n#$ & & worst-case & No \\ - Quicksort & $1.38#n#\log #n#$ & ${}+ O(#n#)$ & expected & Yes \\ - Heap-sort & $2#n#\log #n#$ & ${}+ O(#n#)$ & worst-case & Yes \\ \hline + & \multicolumn{3}{c|}{comparações} & in place \\ \hline + Merge sort & $#n#\log #n#$ & & pior caso& Não \\ + Quicksort & $1.38#n#\log #n#$ & ${}+ O(#n#)$ & esperado & Sim\\ + Heap sort & $2#n#\log #n#$ & ${}+ O(#n#)$ & pior caso & Sim \\ \hline \end{tabular} \end{center} -Each of these comparison-based algorithms has its advantages and -disadvantages. Merge-sort does the fewest comparisons and does not rely -on randomization. Unfortunately, it uses an auxilliary array during its -merge phase. Allocating this array can be expensive and is a potential -point of failure if memory is limited. Quicksort is an \emph{in-place} -\index{in-place algorithm}% -algorithm and is a close second in terms of the number of comparisons, -but is randomized, so this running time is not always guaranteed. -Heap-sort does the most comparisons, but it is in-place and deterministic. - -There is one setting in which merge-sort is a clear-winner; this -occurs when sorting a linked-list. In this case, the auxiliary -array is not needed; two sorted linked lists are very easily merged -into a single sorted linked-list by pointer manipulations (see +Cada um desses algoritmos baseados em comparação tem suas vantagens e desvantagens. +Merge sort faz o menor número de comparações e não precisa de randomização. +Infelizmente, ele usa um array auxiliar durante sua fase de merge. +Alocar esse array pode ser caro e é um ponto de falha se a memória estiver escassa. +Quicksort funciona \emph{in place} +\index{algoritmo in place}% +e é o segundo melhor em termos do número de comparações, mas é randomizado então esse tempo de execução nem sempre é garantido. +Heap sort faz mais comparações mas não usa memória extra e é determinístico. +Existe um cenário em que o merge sort é o vencedor: ao ordenar uma lista ligada. +Nesse caso, o array auxiliar extra não é necessário; duas listas ligadas ordenadas são fácilmente juntadas em uma única lista ligada ordenada usando manipulação de ponteiros (ver \excref{list-merge-sort}). -The counting-sort and radix-sort algorithms described here are due -to Seward \cite[Section~2.4.6]{s54}. However, variants of radix-sort -have been used since the 1920s to sort punch cards using punched card -sorting machines. These machines can sort a stack of cards into two -piles based on the existence (or not) of a hole in a specific location -on the card. Repeating this process for different hole locations gives -an implementation of radix-sort. - -Finally, we note that counting sort and radix-sort can be used to sort -other types of numbers besides non-negative integers. Straightforward -modifications of counting sort can sort integers, in any interval -$\{a,\ldots,b\}$, in $O(#n#+b-a)$ time. Similarly, radix sort can sort -integers in the same interval in $O(#n#(\log_{#n#}(b-a))$ time. Finally, both of these -algorithms can also be used to sort floating point numbers in the IEEE 754 -floating point format. This is because the IEEE format is designed to -allow the comparison of two floating point numbers by comparing their values -as if they were integers in a signed-magnitude binary representation +Os algoritmos counting sort e radix sort aqui foram apresentador por +Seward \cite[Section~2.4.6]{s54}. Porém, variantes do radix sort +têm sido usados desde a década de 1920 para ordenar cartões perfurados usando máquinas de ordenação. +Essas máquinas podem ordenar uma pilha de cartões em duas pilhas usando a existência (ou não) de um furo em uma posição específica no cartão. +Ao repetir esse processo para diferentes posições de furos resulta em +uma implementação do radix sort. + +Finalmente, notamos que counting sort e radix sort podem ser usados +para ordenar outros tipos de números além dos inteiros não negativos. +Modificações do counting sort podem ordenar inteiros em qualquer intervalo +$\{a,\ldots,b\}$, em $O(#n#+b-a)$ de tempo. De modo similar, radix sort pode ordenar inteiros no mesmo intervalo em + $O(#n#(\log_{#n#}(b-a))$ de tempo. Finalmente, esses dois algoritmos +também podem ser usados para ordenar números em ponto flutunte no formato IEEE 754. +Isso porque o formato IEEE 754 é projetado para permitir a comparação de dois números ponto flutuantes ao comparar seus valores como se estivessem em uma representação binária de inteiros com bit de sinal. \cite{ieee754}. \begin{exc} - Illustrate the execution of merge-sort and heap-sort on an input array - containing $1,7,4,6,2,8,3,5$. Give a sample illustration of one possible - execution of quicksort on the same array. + Ilustre a execução do merge sort e do heap sort em um array de entrada + contendo $1,7,4,6,2,8,3,5$. Dê umo exemplo simples de uma possível execução de quicksort no mesmo array. \end{exc} \begin{exc}\exclabel{list-merge-sort} - Implement a version of the merge-sort algorithm that sorts a #DLList# - without using an auxiliary array. (See \excref{dllist-sort}.) + Implementar uma versão do algoritmo merge sort que ordena uma + #DLList# sem usar um array auxiliar (Ver \excref{dllist-sort}). \end{exc} \begin{exc} - Some implementations of #quickSort(a,i,n,c)# always use #a[i]# - as a pivot. Give an example of an input array of length #n# in which - such an implementation would perform $\binom{#n#}{2}$ comparisons. + Algumas implementações do + #quickSort(a,i,n,c)# sempre usa #a[i]# como um pivot. + Dê um exemplo de um array de entrada de tamanho #n# no + qual tal implementação faria $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} - Some implementations of #quickSort(a,i,n,c)# always use #a[i+n/2]# - as a pivot. Given an example of an input array of length #n# in which - such an implementation would perform $\binom{#n#}{2}$ comparisons. + Algumas implementações de + #quickSort(a,i,n,c)# sempre usa #a[i+n/2]# como um pivot. + Dê um exemplo de um array de entrada de tamanho #n# no + qual tal implementação faria $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} - Show that, for any implementation of #quickSort(a,i,n,c)# - that chooses a pivot deterministically, without first looking at - any values in $#a[i]#,\ldots,#a[i+n-1]#$, there exists an input array of length #n# - that causes this implementation to perform $\binom{#n#}{2}$ comparisons. + Mostre que, para qualquer implementação de + #quickSort(a,i,n,c)# que escolha um pivot deterministicamente, sem primeiro + olhar os valores em + $#a[i]#,\ldots,#a[i+n-1]#$, existe um array de entrada de tamanho #n# + que faz que essa implementação realize + $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} - Design a #Comparator#, #c#, that you could pass as an argument - to #quickSort(a,i,n,c)# and that would cause quicksort to perform - $\binom{#n#}{2}$ comparisons. (Hint: Your comparator does not actually - need to look at the values being compared.) + Projete um + #Comparator#, #c#, que você poderia passar como um argumento ao + #quickSort(a,i,n,c)# e que faria o quicksort usar + $\binom{#n#}{2}$ comparações. (Dica: O seu comparador não precisa olhar nos valores sendo comparados.) \end{exc} \begin{exc} - Analyze the expected number of comparisons done by Quicksort a little - more carefully than the proof of \thmref{quicksort}. In particular, show - that the expected number of comparisons is $2#n#H_#n# -#n# + H_#n#$. + Analise o número esperado de comparações feitos pelo quicksort + um pouco mais cuidadosamente que a prova + \thmref{quicksort}. Em particular, mostre que o número esperado + de comparações é $2#n#H_#n# -#n# + H_#n#$. \end{exc} \begin{exc} - Describe an input array that causes heap sort to perform at least - $2#n#\log #n#-O(#n#)$ comparisons. Justify your answer. + Descreva um array de entrada que causa o heap sort realizar pelo menos + $2#n#\log #n#-O(#n#)$ comparações. Justifique sua resposta. \end{exc} \javaonly{ \begin{exc} - The heap sort implementation described here sorts the elements into - reverse sorted order and then reverses the array. This last step - could be avoided by defining a new #Comparator# that negates the - results of the input #Comparator#, #c#. Explain why this would not - be a good optimization. (Hint: Consider how many negations would need - to be done in relation to how long it takes to reverse the array.) + A implementação do heap sort descrita aqui ordena os elementos + em ordem reversa e então inverte o array. + Esse último passo poderia ser evitar ao definir + um novo #Comparator# que nega os resultados + da entrada #Comparator#, #c#. Explique porque isso não seria seria uma boa otimização. (Dica: considere quantas negações seriam necessárias para fazer + em relação a quanto tempo levar a inverter o array.) \end{exc} } \begin{exc} - Find another pair of permutations of $1,2,3$ that are not correctly - sorted by the comparison tree in \figref{comparison-tree-2}. + Achar outro par de permutações de + $1,2,3$ que não são corretamente ordenados pela árvore de comparações + em \figref{comparison-tree-2}. \end{exc} \begin{exc}\exclabel{log-factorial} - Prove that $\log #n#! = #n#\log #n#-O(#n#)$. + Prove que $\log #n#! = #n#\log #n#-O(#n#)$. \end{exc} \begin{exc} - Prove that a binary tree with $k$ leaves has height at least $\log k$. + Prove que uma árvore binária com $k$ folhas tem altura de pelo menos $\log k$. \end{exc} \begin{exc}\exclabel{randomized-lower-bound} - Prove that, if we pick a random leaf from a binary tree with $k$ - leaves, then the expected height of this leaf is at least $\log k$. + Prove que, se escolhermos uma folha aleatória de uma árvore binária com $k$ folhas, então a altura esperada dessa folha é pelo menos $\log k$. % (Hint: Use induction along with the inequality $(k_1/k)\log k_1 + % (k_2/k)\log k_2) \ge \log k-1$, when $k_1+k_2=k$.) \end{exc} @@ -821,11 +852,10 @@ \section{Discussion and Exercises} %\end{exc} \begin{exc} - The implementation of #radixSort(a,k)# given here works when the input - array, #a# contains only \cpponly{unsigned}\javaonly{non-negative} - integers. \javaonly{Extend this implementation so that it also - works correctly when #a# contains both negative and non-negative - integers.}\cpponly{Write a version that works correctly for signed - integers.} + A implementação do + #radixSort(a,k)# fornecida aqui funciona quando o array de entrada #a# + contém somente inteiros + \cpponly{sem sinal}\javaonly{não negativos} + . \javaonly{Adapte essa implementação para que também funcione corretamente quando #a# contém inteiros negativos e não negativos.}\cpponly{Escreva uma versão que funcione corretamente para inteiros com sinal.} \end{exc} From f5296b749cc5bfc07d5cdad97c8c0a99d439ade5 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 13 Aug 2020 19:20:44 -0300 Subject: [PATCH 21/66] minor typo --- latex/ods.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/ods.tex b/latex/ods.tex index 96ce7257..a78c78a0 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -185,7 +185,7 @@ \title{Open Data Structures (in \lang). Estruturas de Dados Abertas (em \lang)} \author{Autor: Pat Morin.\\Tradução e adaptação por: Marcelo Keese Albertini} \date{% -Edition 0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} +Edição PT-BR-0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} \htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} %Version 0.0 pre $\alpha$: \today} From 6b543c0070f8da03ff26800a4c891b5406d3e08d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 14 Aug 2020 21:15:37 -0300 Subject: [PATCH 22/66] translation to portuguese --- latex/skiplists.tex | 716 +++++++++++++++++++++++--------------------- 1 file changed, 368 insertions(+), 348 deletions(-) diff --git a/latex/skiplists.tex b/latex/skiplists.tex index de504304..50d54d6e 100644 --- a/latex/skiplists.tex +++ b/latex/skiplists.tex @@ -18,126 +18,140 @@ \chapter{Skiplists} \section{A Estrutura Básica} \index{skiplist}% -Conceptually, a skiplist is a sequence of singly-linked lists -$L_0,\ldots,L_h$. Each list $L_r$ contains a subset of the items -in $L_{r-1}$. We start with the input list $L_0$ that contains #n# -items and construct $L_1$ from $L_0$, $L_2$ from $L_1$, and so on. -The items in $L_r$ are obtained by tossing a coin for each element, #x#, -in $L_{r-1}$ and including #x# in $L_r$ if the coin turns up as heads. -This process ends when we create a list $L_r$ that is empty. An example -of a skiplist is shown in \figref{skiplist}. +Conceitualmente, uma skiplist é uma sequência de listas simplesmente ligadas +$L_0,\ldots,L_h$. Cada lista $L_r$ contém um subconjunto de itens +em $L_{r-1}$. +Iniciamos com a lista de entrada +$L_0$ que contém #n# itens e construímos + $L_1$ usando $L_0$, $L_2$ usando $L_1$ e assim por diante. + Os itens em $L_r$ são obtidos por lançamento de uma moeda para cada elemento #x# +na $L_{r-1}$ e incluindo #x# na $L_r$ se a moeda sai do lado cara. +Esse processo termina quando criamos uma lista $L_r$ que está vazia. +Um exemplo de uma skiplist é mostrado em \figref{skiplist}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist} \end{center} - \caption{A skiplist containing seven elements.} + \caption{Uma skiplist contendo sete elementos.} \figlabel{skiplist} \end{figure} -For an element, #x#, in a skiplist, we call the \emph{height} -\index{height!of a skiplist}% -of #x# the -largest value $r$ such that #x# appears in $L_r$. Thus, for example, -elements that only appear in $L_0$ have height $0$. If we spend a few -moments thinking about it, we notice that the height of #x# corresponds -to the following experiment: Toss a coin repeatedly until it comes -up as tails. How many times did it come up as heads? The answer, not -surprisingly, is that the expected height of a node is 1. (We expect to -toss the coin twice before getting tails, but we don't count the last -toss.) The \emph{height} of a skiplist is the height of its tallest node. - -At the head of every list is a special node, called the \emph{sentinel}, -\index{sentinel node}% -that acts as a dummy node for the list. The key property of skiplists -is that there is a short path, called the \emph{search path}, -\index{search path!in a skiplist}% -from the -sentinel in $L_h$ to every node in $L_0$. Remembering how to construct -a search path for a node, #u#, is easy (see \figref{skiplist-searchpath}) -: Start at the top left corner of your skiplist (the sentinel in $L_h$) -and always go right unless that would overshoot #u#, in which case you -should take a step down into the list below. - -More precisely, to construct the search path for the node #u# in $L_0$, -we start at the sentinel, #w#, in $L_h$. Next, we examine #w.next#. -If #w.next# contains an item that appears before #u# in $L_0$, then -we set $#w#=#w.next#$. Otherwise, we move down and continue the search -at the occurrence of #w# in the list $L_{h-1}$. We continue this way -until we reach the predecessor of #u# in $L_0$. +Para um elemento #x# em uma skiplist, chamamos de \emph{altura} +\index{altura!de uma skiplist}% +de #x# o +maior valor $r$ tal que #x# aparece em $L_r$. Então, por exemplo +elementos que somente aparecem em $L_0$ tem altura $0$. +Se pararmos alguns momentos para pensar sobre isso, notamos que a altura de #x# +corresponde ao seguinte experimento: +jogar uma moeda repetidamente até que saia coroa. Quantas vezes sai cara? +A resposta, pouco supreendentemente, é que a altura esperada de um nodo é 1. +(Esperamos jogar a moeda duas vezes antes de sair coroa, mas não contamos o último lançamento.) A \emph{altura} de uma skiplist é a altura do seu nodo mais alto. + +A cabeça de toda lista é um nodo especial, chamado de +\emph{sentinela}, +\index{nodo sentinela}% +que atua como um nodo dummy para a lista. A propriedade chama de skiplists +é existe um caminho curto, chamado de +\emph{caminho de busca}, +\index{caminho de busca!em uma skiplist}% +do sentinela em +$L_h$ para todo nodo em $L_0$. Como construir um caminho de busca para um nodo #u# é facil (veja \figref{skiplist-searchpath}) +: inicie no canto superior esquerdo da sua skiplist (o sentila em $L_h$) +e sempre vá à direita a menos que ultrapasse #u#, nesse caso você deve descer à lista logo abaixo. + +Mais precisamente, para construir o caminho de busca para o nodo #u# em $L_0$, +começamos no sentinela #w# em $L_h$. Depois examinamos #w.next#. +Se +#w.next# contém um item que aparece antes de #u# em $L_0$, então +fazemos + $#w#=#w.next#$. Caso contrário, seguimos a busca na lista abaixo e buscamos a ocorrência de #w# na lista \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-searchpath} \end{center} - \caption{The search path for the node containing $4$ in a skiplist.} + \caption{O caminho de busca para o nodo contendo $4$ em uma skiplist.} \figlabel{skiplist-searchpath} \end{figure} -The following result, which we will prove in \secref{skiplist-analysis}, -shows that the search path is quite short: +O resultado a seguir, que provaremos em +\secref{skiplist-analysis}, +mostra que o caminho de busca é bem curto: \begin{lem}\lemlabel{skiplist-searchpath} -The expected length of the search path for any node, #u#, in $L_0$ is at -most $2\log #n# + O(1) = O(\log #n#)$. + O comprimento esperado do caminho de busca para qualquer nodo #u# em +$L_0$ é no máximo $2\log #n# + O(1) = O(\log #n#)$. \end{lem} -A space-efficient way to implement a skiplist is to define a #Node#, -#u#, as consisting of a data value, #x#, and an array, #next#, of -pointers, where #u.next[i]# points to #u#'s successor in the list -$L_{#i#}$. In this way, the data, #x#, in a node is -\javaonly{referenced}\cpponly{stored}\pcodeonly{referenced} -only once, even though #x# may appear in several lists. +Um modo eficiente em relação ao espaço para implementar uma +skiplist é definir um #Node# #u# consistindo de um valor #x# +e um array #next# de ponteiros onde #u.next[i]# aponta para o sucessor de #u# +na lista +$L_{#i#}$. Desse jeito, o valor #x# em um nodo é +\javaonly{referenciado}\cpponly{guardado}\pcodeonly{referenciado} +somente uma vez #x# pode aparecer em várias listas. \javaimport{ods/SkiplistSSet.Node} \cppimport{ods/SkiplistSSet.Node} -The next two sections of this chapter discuss two different applications -of skiplists. In each of these applications, $L_0$ stores the main -structure (a list of elements or a sorted set of elements). -The primary difference between these structures is in how -a search path is navigated; in particular, they differ in how -they decide if a search path should go down into $L_{r-1}$ or go right -within $L_r$. +As próximas duas seções deste capítulo discutem duas diferentes aplicações de +skiplists. Em cada uma dessas aplicações, $L_0$ guarda a estrutura principal +(uma lista de elementos or um conjunto ordenado de elementos). +A principal diferença entre essas estruturas está em como um caminho +de busca é navegado; em particular, eles diferem em como eles +decidem se um caminho de busca ir para a lista abaixo $L_{r-1}$ ou à direita em $L_r$. -\section{#SkiplistSSet#: An Efficient #SSet#} +\section{#SkiplistSSet#: Um #SSet# Eficiente} \seclabel{skiplistset} \index{SkiplistSSet@#SkiplistSSet#}% -A #SkiplistSSet# uses a skiplist structure to implement the #SSet# -interface. When used in this way, the list $L_0$ stores the elements of -the #SSet# in sorted order. The #find(x)# method works by following -the search path for the smallest value #y# such that $#y#\ge#x#$: +Um #SkiplistSSet# usa uma estrutura de skiplist para implementar a interface #SSet#. +Quando usada dessa maneira, a lista +$L_0$ guarda os elementos de +#SSet# de modo ordenado. O método #find(x)# funciona seguindo o caminho de busa para o menor valor #y# tal que +$#y#\ge#x#$: \codeimport{ods/SkiplistSSet.find(x).findPredNode(x)} -Following the search path for #y# is easy: when situated at -some node, #u#, in $L_{#r#}$, we look right to #u.next[r].x#. -If $#x#>#u.next[r].x#$, then we take a step to the right in -$L_{#r#}$; otherwise, we move down into $L_{#r#-1}$. Each step -(right or down) in this search takes only constant time; thus, by -\lemref{skiplist-searchpath}, the expected running time of #find(x)# -is $O(\log #n#)$. - -Before we can add an element to a #SkipListSSet#, we need a method to -simulate tossing coins to determine the height, #k#, of a new node. -We do so by picking a random integer, #z#, and counting the number of -trailing $1$s in the binary representation of #z#:\footnote{This method -does not exactly replicate the coin-tossing experiment since the value of -#k# will always be less than the number of bits in an #int#. However, -this will have negligible impact unless the number of elements in the -structure is much greater than $2^{32}=4294967296$.} +Seguir o caminho de busca para #y# é fácil: quando situado em +algum nodo #u# em $L_{#r#}$ olhamos diretamente para + #u.next[r].x#. + Se + $#x#>#u.next[r].x#$, então damos um passo à direita em +$L_{#r#}$; caso contrário, descemos para a lista $L_{#r#-1}$. +Cada passo (à direita ou embaixo) nessa busca leva apenas um tempo constante, +então, pelo +\lemref{skiplist-searchpath}, o tempo esperado de execução de #find(x)# +é $O(\log #n#)$. + +Antes de podermos adicionar um elemento a uma + #SkipListSSet#, precisamos de um método para simular o lançamento + de moedas para determinar a altura, #k#, de um novo nodo. +Fazemos isso ao escolher um inteiro aleatório, #z#, e contar o número de +$1$s na representação binária de #z#: + +\footnote{Esse método não replica exatamente o experimento de lançamento de +moedas pois o valor de #k# será sempre menor que o número de bits em um #int#. +Porém, esse fato terá um impacto negligível a menos que o número de elementos em +uma estrutura seja muito maior que $2^{32}=4294967296$.} \codeimport{ods/SkiplistSSet.pickHeight()} -To implement the #add(x)# method in a #SkiplistSSet# we search for #x# -and then splice #x# into a few lists $L_0$,\ldots,$L_{#k#}$, where #k# -is selected using the #pickHeight()# method. The easiest way to do this -is to use an array, #stack#, that keeps track of the nodes at which -the search path goes down from some list $L_{#r#}$ into $L_{#r#-1}$. -More precisely, #stack[r]# is the node in $L_{#r#}$ where the search path -proceeded down into $L_{#r#-1}$. The nodes that we modify to insert #x# -are precisely the nodes $#stack[0]#,\ldots,#stack[k]#$. The following -code implements this algorithm for #add(x)#: +Para implementar o método + #add(x)# em uma #SkiplistSSet# procuramos por #x# + e então dividimos #x# em algumas listas + $L_0$,\ldots,$L_{#k#}$, onde #k# é selecionado usando o método +#pickHeight()#. O jeito mais fácil de fazer isso +é usar um array +#stack#, que registra os nodos em que o caminho de busca desce de alguma lista + $L_{#r#}$ em $L_{#r#-1}$. + Mais precisamente, +#stack[r]# é o nodo em $L_{#r#}$ onde o caminho de busca desceu em +$L_{#r#-1}$. Os nodos que modificamos para inserir #x# são +precisamente os nodos + $#stack[0]#,\ldots,#stack[k]#$. O código a seguir implementa esse + algoritmo para +#add(x)#: \label{pg:skiplist-add} \codeimport{ods/SkiplistSSet.add(x)} @@ -145,16 +159,17 @@ \section{#SkiplistSSet#: An Efficient #SSet#} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-add} \end{center} - \caption[Adding to a skiplist]{Adding the node containing $3.5$ to a skiplist. The nodes stored in #stack# - are highlighted.} + \caption[Adicionando a uma skiplist]{Adicionando o nodo contendo $3.5$ a uma skiplist. Os nodos guardados em #stack# estão em destaque. } \figlabel{skiplist-add} \end{figure} -Removing an element, #x#, is done in a similar way, except that there -is no need for #stack# to keep track of the search path. The removal -can be done as we are following the search path. We search for #x# -and each time the search moves downward from a node #u#, we check if -$#u.next.x#=#x#$ and if so, we splice #u# out of the list: +A remoção de um elemento #x# é feita de modo similar, exceto que +não há necessidade para a + #stack# registrar o caminho de busca. A remoção pode ser feita + conforme vamos seguindo o caminho de busca. +Buscamos por #x# e cada vez que a busca segue para baixo de um novo #u#, +verificamos se +$#u.next.x#$ é igual a $#x#$ e, caso positivo, tiramos #u# da lista: \codeimport{ods/SkiplistSSet.remove(x)} \begin{figure} @@ -165,160 +180,161 @@ \section{#SkiplistSSet#: An Efficient #SSet#} \figlabel{skiplist-remove} \end{figure} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of skiplists when used to -implement sorted sets: +O teorema a seguir resumo o desempenho de skiplists quando usadas para implementar conjuntos ordenados: \begin{thm}\thmlabel{skiplist} -#SkiplistSSet# implements the #SSet# interface. A #SkiplistSSet# supports -the operations #add(x)#, #remove(x)#, and #find(x)# in $O(\log #n#)$ -expected time per operation. +#SkiplistSSet# implementa a interface #SSet#. Uma #SkiplistSSet# aceita as +operações #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ de tempo esperado por operação. \end{thm} -\section{#SkiplistList#: An Efficient Random-Access #List#} +\section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} \seclabel{skiplistlist} \index{SkiplistList@#SkiplistList#}% -A #SkiplistList# implements the #List# interface using a skiplist -structure. In a #SkiplistList#, $L_0$ contains the elements of the -list in the order in which they appear in the list. As in a -#SkiplistSSet#, elements can be added, removed, and accessed in $O(\log -#n#)$ time. - -For this to be possible, we need a way to follow the search path for -the #i#th element in $L_0$. The easiest way to do this is to define -the notion of the \emph{length} of an edge in some list, $L_{#r#}$. -We define the length of every edge in $L_{0}$ as 1. The length of an edge, #e#, -in $L_{#r#}$, $#r#>0$, is defined as the sum of the lengths of the edges below #e# -in $L_{#r#-1}$. Equivalently, the length of #e# is -the number of edges in $L_0$ below #e#. See \figref{skiplist-lengths} for -an example of a skiplist with the lengths of its edges shown. Since the -edges of skiplists are stored in arrays, the lengths can be stored the same -way: +Uma #SkiplistList# implementa a interface #List# usando uma estrutura skiplist. Em uma #SkiplistList#, $L_0$ contém os elementos da lista na ordem que eles aparecem na lista. Como em uma +#SkiplistSSet#, elementos podem ser adicionados, removidos e acessados em $O(\log +#n#)$ de tempo. + +Para isso ser possível, precisamos de um jeito de seguir o caminho de busca para o +#i#-ésimo em $L_0$. +O jeito mais fácil de fazer isso é definir a noção de \emph{comprimento} de uma aresta em alguma lista, $#L_{#r#}#$. + +Definimos o comprimento de toda aresta em +$L_{0}$ como 1. O comprimento de uma aresta, #e#, +em $L_{#r#}$, $#r#>0$, é definido como a soma dos comprimentos das arestas abaixo de #e# em + $L_{#r#-1}$. De maneira equivalente, o comprimento de #e# é o número de arestas em +$L_0$ abaixo de #e#. Veja \figref{skiplist-lengths} para um exemplo +de uma skiplist com os comprimentos de suas arestas mostradas. +Como as arestas de skiplists são guardadas em arrays, os comprimentos podem ser guardados do mesmo jeito: \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-lengths} \end{center} - \caption{The lengths of the edges in a skiplist.} + \caption{Os comprimentos das arestas em uma skiplist.} \figlabel{skiplist-lengths} \end{figure} \javaimport{ods/SkiplistList.Node} \cppimport{ods/SkiplistList.Node} -The useful property of this definition of length is that, if we are -currently at a node that is at position #j# in $L_0$ and we follow an -edge of length $\ell$, then we move to a node whose position, in $L_0$, -is $#j#+\ell$. In this way, while following a search path, we can keep -track of the position, #j#, of the current node in $L_0$. When at a -node, #u#, in $L_{#r#}$, we go right if #j# plus the length of the edge -#u.next[r]# is less than #i#. Otherwise, we go down into $L_{#r#-1}$. +Essa definição de comprimento tem a útil propriedade de que se estivermos +atualmente em um nodo que está na posição #j# +em $L_0$ e seguimos uma aresta e de comprimento +$\ell$, então movemos para um nodo cuja posição, em $L_0$, +é $#j#+\ell$. Desse jeito, enquanto seguirmos um caminho de busca, podemos +rastrear a posição, #j#, do nodo atual em $L_0$. +Quando em um nodo +, #u#, em $L_{#r#}$, iremos à direita se #j# mais o comprimento da aresta +#u.next[r]# é menor que #i#. Caso contrário, descemos para $L_{#r#-1}$. \codeimport{ods/SkiplistList.findPred(i)} \codeimport{ods/SkiplistList.get(i).set(i,x)} -Since the hardest part of the operations #get(i)# and #set(i,x)# is -finding the #i#th node in $L_0$, these operations run in -$O(\log #n#)$ time. +Uma vez que a parte mais difícil das operações #get(i)# e #set(i,x)# é achar +o +#i#-ésimo nodo em $L_0$, essas operações rodam em +$O(\log #n#)$ de tempo. -Adding an element to a #SkiplistList# at a position, #i#, is fairly -simple. Unlike in a #SkiplistSSet#, we are sure that a new -node will actually be added, so we can do the addition at the same time -as we search for the new node's location. We first pick the height, #k#, -of the newly inserted node, #w#, and then follow the search path for #i#. -Any time the search path moves down from $L_{#r#}$ with $#r#\le #k#$, we -splice #w# into $L_{#r#}$. The only extra care needed is to ensure that -the lengths of edges are updated properly. See \figref{skiplist-addix}. +Adicionar um elemento na + #SkiplistList# em uma posição #i# razoavelmente simples. Diferentemente de uma #SkiplistSSet#, temos certeza que um novo nodo será realmente adicionado, então podemos fazer a adição ao mesmo tempo que procuramos pelo lugar de um novo nodo. +Primeiro pegamos a altura #k# do nodo recentemente inserido #w# +e então seguimos o caminho de busca para #i#. +Toda vez que o caminho de busca desce a partir de + +$L_{#r#}$ com $#r#\le #k#$, inserimos #w# +em $L_{#r#}$. O último cuidade extra necessário é assegurar que os comprimentos das arestas são atualizadas corretamente. Veja \figref{skiplist-addix}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-addix} \end{center} - \caption[Adding to a SkiplistList]{Adding an element to a #SkiplistList#.} + \caption[Adicionando a uma SkiplistList]{Adicionando um elemento a uma #SkiplistList#.} \figlabel{skiplist-addix} \end{figure} -Note that, each time the search path goes down at a node, #u#, in $L_{#r#}$, -the length of the edge #u.next[r]# increases by one, since we are adding -an element below that edge at position #i#. Splicing the node #w# between two nodes, -#u# and #z#, works as shown in \figref{skiplist-lengths-splice}. While -following the search path we are already keeping track of the position, -#j#, of #u# in $L_0$. Therefore, we know that the length of the edge from -#u# to #w# is $#i#-#j#$. We can also deduce the length of the edge -from #w# to #z# from the length, $\ell$, of the edge from #u# to #z#. -Therefore, we can splice in #w# and update the lengths of the edges in -constant time. +Note que, cada vez que o caminho de busca desce em um novo #u# em $L_{#r#}$, +o comprimento da aresta + #u.next[r]# aumenta em um, pois estamos adicionando um elemento abaixo daquela aresta na posição #i#. + +A divisão do nodo #w# entre dois nodos, #u# e #z# funciona como mostrado em \figref{skiplist-lengths-splice}. +Ao seguir o caminho de busca já estamos guardando a posição +#j#, de #u# em $L_0$. Portanto, sabemos que o comprimento da aresta +de #u# a #w# é $#i#-#j#$. Também podemos deduzir o comprimento da aresta de #w# a #z# a patir do comprimento $\ell$ de uma aresta de #u# a #z#. +Portanto, podemos dividir em #w# e atualizar os comprimentos das arestas em tempo constante. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/skiplist-lengths-splice} \end{center} - \caption[Adding to a SkiplistList]{Updating the lengths of edges while splicing a node - #w# into a skiplist.} + \caption[Adição à a SkiplistList]{Atualizar os comprimentos das arestas enquanto dividindo um nodo #w# em uma skiplist.} \figlabel{skiplist-lengths-splice} \end{figure} -This sounds more complicated than it is, for the code is actually -quite simple: +Isso parece mais complicado do que é, porque o código é bem simples: \codeimport{ods/SkiplistList.add(i,x)} \codeimport{ods/SkiplistList.add(i,w)} - -By now, the implementation of -the #remove(i)# operation in a #SkiplistList# should be obvious. We follow the search path for the node at position #i#. Each time the search path takes a step down from a node, #u#, at level #r# we decrement the length of the edge leaving #u# at that level. We also check if #u.next[r]# is the element of rank #i# and, if so, splice it out of the list at that level. An example is shown in \figref{skiplist-removei}. +Agora a implementação da operação #remove(i)# em uma #SkiplistList# deve ser óbvia. +Seguimos o caminho de pesquisa para o nodo na posição #i#. Cada vez que o caminho de busca desce a partir de um nodo #u# no nível #r# nós decrementamos o comprimento +da aresta que deixa #u# naquele nível. Também verificamos se + #u.next[r]# é o elemento do #i# e, se o for, o removemos da lista naquele nível. Um exemplo é mostrado em \figref{skiplist-removei}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-removei} \end{center} - \caption[Removing an element from a SkiplistList]{Removing an element from a #SkiplistList#.} + \caption[Remoção de um elemento de uma SkiplistList]{Remoção de um elemento de uma #SkiplistList#.} \figlabel{skiplist-removei} \end{figure} \codeimport{ods/SkiplistList.remove(i)} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of the #SkiplistList# -data structure: +O teorema a seguir resume o desempenho da estrutura de dados +#SkiplistList#: \begin{thm}\thmlabel{skiplistlist} - A #SkiplistList# implements the #List# interface. A #SkiplistList# - supports the operations #get(i)#, #set(i,x)#, #add(i,x)#, and - #remove(i)# in $O(\log #n#)$ expected time per operation. + Uma #SkiplistList# implementa a interface #List#. Uma #SkiplistList# + aceita a oeprações #get(i)#, #set(i,x)#, #add(i,x)#, e + #remove(i)# em $O(\log #n#)$ de tempo esperado por operação. \end{thm} %\section{Skiplists as Ropes} %TODO: A section on ropes -\section{Analysis of Skiplists} +\section{Análise de Skiplists} \seclabel{skiplist-analysis} -In this section, we analyze the expected height, size, and length of -the search path in a skiplist. This section requires a background in -basic probability. Several proofs are based on the following basic -observation about coin tosses. - +Nesta seção, analisaremos a altura esperada, tamanho, e comprimento do caminho +de busca em uma skiplist. Esta seção que conhecimentos básicos de +probabilidades. +Várias provas são baseadas na seguinte obervação básica sobre lançamentos de +moedas. \begin{lem}\lemlabel{coin-tosses} \index{coin toss}% - Let $T$ be the number of times a fair coin is tossed up to and including - the first time the coin comes up heads. Then $\E[T]=2$. +Seja $T$ o número de vezes que uma moeda honesta é lançada, incluindo +a primeira vez que a moeda saiu com a face cara. Então + $\E[T]=2$. \end{lem} \begin{proof} - Suppose we stop tossing the coin the first time it comes up - heads. Define the indicator variable + Suponha que paramos de lança a moeda na primeira vez que sai cara. + Defina a variável indicadora \[ I_{i} = \left\{\begin{array}{ll} - 0 & \mbox{if the coin is tossed less than $i$ times} \\ - 1 & \mbox{if the coin is tossed $i$ or more times} + 0 & \mbox{se a moeda é lançada menos de $i$ vezes} \\ + 1 & \mbox{se a moeda é lança $i$ vezes ou mais} \end{array}\right. \] - Note that $I_i=1$ if and only if the first $i-1$ coin tosses are tails, - so $\E[I_i]=\Pr\{I_i=1\}=1/2^{i-1}$. Observe that $T$, the total - number of coin tosses, can be written as $T=\sum_{i=1}^{\infty} I_i$. - Therefore, + Note que + $I_i=1$ se, e somente se, os primeiros $i-1$ lançamentos saem coroa, + então $\E[I_i]=\Pr\{I_i=1\}=1/2^{i-1}$. Observe que $T$, o número + total de lançamentos, pode ser escrito como + $T=\sum_{i=1}^{\infty} I_i$. + Portanto, \begin{align*} \E[T] & = \E\left[\sum_{i=1}^\infty I_i\right] \\ & = \sum_{i=1}^\infty \E\left[I_i\right] \\ @@ -328,44 +344,46 @@ \section{Analysis of Skiplists} \end{align*} \end{proof} -The next two lemmata tell us that skiplists have linear size: +Os próximos dois lemas afirmam que skiplists tem tamanho linear: \begin{lem}\lemlabel{skiplist-size1} - The expected number of nodes in a skiplist containing $#n#$ elements, - not including occurrences of the sentinel, is $2#n#$. + O número esperado de nodos em um skiplist contendo + $#n#$ elementos, + sem incluir as ocorrências da sentinela, é + $2#n#$. \end{lem} \begin{proof} - The probability that any particular element, #x#, is included in list - $L_{#r#}$ is $1/2^{#r#}$, so the expected number of nodes in $L_{#r#}$ - is $#n#/2^{#r#}$.\footnote{See \secref{randomization} to see how this - is derived using indicator variables and linearity of expectation.} - Therefore, the total expected number of nodes in all lists is + A probabilidade que qualquer elemento em particular, #x#, seja incluído + na lista + $L_{#r#}$ é $1/2^{#r#}$, então o número esperado de nodos em $L_{#r#}$ + é $#n#/2^{#r#}$.\footnote{Veja \secref{randomização} para ver como isso é obtido usando variáveis indicadores e linearidade de esperança.} + Portanto, o número esperado total de nodos em todas as listas é \[ \sum_{#r#=0}^\infty #n#/2^{#r#} = #n#(1+1/2+1/4+1/8+\cdots) = 2#n# \enspace . \qedhere \] \end{proof} \begin{lem}\lemlabel{skiplist-height} - The expected height of a skiplist containing #n# elements is at most + A altura esperada de uma skiplist contendo #n# elementos é até $\log #n# + 2$. \end{lem} \begin{proof} - For each $#r#\in\{1,2,3,\ldots,\infty\}$, - define the indicator random variable + Para cada $#r#\in\{1,2,3,\ldots,\infty\}$, + defina a variável indicadora aleatória \[ I_{#r#} = \left\{\begin{array}{ll} - 0 & \mbox{if $L_{#r#}$ is empty} \\ - 1 & \mbox{if $L_{#r#}$ is non-empty} + 0 & \mbox{se $L_{#r#}$ está vazia } \\ + 1 & \mbox{se $L_{#r#}$ não está vazia} \end{array}\right. \] - The height, #h#, of the skiplist is then given by + A altura, #h# da skiplist é então dada por \[ #h# = \sum_{r=1}^\infty I_{#r#} \enspace . \] - Note that $I_{#r#}$ is never more than the length, $|L_{#r#}|$, of $L_{#r#}$, so + Note que $I_{#r#}$ nunca é maior que o comprimento, $|L_{#r#}|$, de $L_{#r#}$, então \[ \E[I_{#r#}] \le \E[|L_{#r#}|] = #n#/2^{#r#} \enspace . \] - Therefore, we have + Portanto, temos \begin{align*} \E[#h#] &= \E\left[\sum_{r=1}^\infty I_{#r#}\right] \\ &= \sum_{#r#=1}^{\infty} E[I_{#r#}] \\ @@ -380,54 +398,58 @@ \section{Analysis of Skiplists} \end{proof} \begin{lem}\lemlabel{skiplist-size2} - The expected number of nodes in a skiplist containing $#n#$ elements, - including all occurrences of the sentinel, is $2#n#+O(\log #n#)$. + O número esperado de nodos em uma skiplist contendo + $#n#$ elementos, incluindo todas as ocorrências da sentinela é + $2#n#+O(\log #n#)$. \end{lem} \begin{proof} - By \lemref{skiplist-size1}, the expected number of nodes, not - including the sentinel, is $2#n#$. The number of occurrences of - the sentinel is equal to the height, $#h#$, of the skiplist so, by - \lemref{skiplist-height} the expected number of occurrences of the - sentinel is at most $\log #n#+2 = O(\log #n#)$. + Segundo \lemref{skiplist-size1}, o número esperado de nodos, sem incluir o sentinela, + é $2#n#$. O número de ocorrências do sentinela é igual à altura + $#h#$ da skiplist então, segundo + \lemref{skiplist-height}, o número esperado de ocorrências da sentinela é até + $\log #n#+2 = O(\log #n#)$. \end{proof} - - \begin{lem} -The expected length of a search path in a skiplist is at most $2\log #n# + O(1)$. +O comprimento esperado de um caminho de busca em uma skiplist é até +$2\log #n# + O(1)$. \end{lem} \begin{proof} - The easiest way to see this is to consider the \emph{reverse search - path} for a node, #x#. This path starts at the predecessor of #x# - in $L_0$. At any point in time, if the path can go up a level, then - it does. If it cannot go up a level then it goes left. Thinking about - this for a few moments will convince us that the reverse search path for - #x# is identical to the search path for #x#, except that it is reversed. - - The number of nodes that the reverse search path visits at a particular - level, #r#, is related to the following experiment: Toss a coin. - If the coin comes up as heads, then move up and stop. Otherwise, move - left and repeat the experiment. The number of coin tosses before - the heads represents the number of steps to the left that a reverse - search path takes at a particular level.\footnote{Note that this - might overcount the number of steps to the left, since the experiment - should end either at the first heads or when the search path reaches - the sentinel, whichever comes first. This is not a problem since the - lemma is only stating an upper bound.} \lemref{coin-tosses} tells us - that the expected number of coin tosses before the first heads is 1. - - Let $S_{#r#}$ denote the number of steps the forward search path takes at level - $#r#$ that go to the right. We have just argued that $\E[S_{#r#}]\le - 1$. Furthermore, $S_{#r#}\le |L_{#r#}|$, since we can't take more steps - in $L_{#r#}$ than the length of $L_{#r#}$, so + O jeito mais fácil de ver issso é considerar o + \emph{caminho de busca reverso}. Esse caminho inicia-se no predecessor de #x# + em + $L_0$. Em qualquer momento, se o caminho pode subir um nível, então ele o faz. + Se ele não puder subir um nível, então vai à esquerda. Considerando isso + por alguns momentos vemos que o caminho de busca reverso para #x# é idêntico + ao caminho de busca para #x#, exceto que é invertido. + + O número de nodos que o caminho de busca reverso visita em um dado + nível #r# é relacionado ao seguinte experimento: lance uma moeda. + Se a moeda cai cara, então suba um nível e pare. Caro contrário, + vá à esquerda e repita o experimento. O número de lançamentos + obtido dessa forma representa o número de passos à esquerda que um + caminho de busca invertido faz em um dado nível. + \footnote{Note que isso pode superestimar o número de passos à + esquerda pois o experimento deve terminar ou em um lançamento cara ou + quando o caminho de busca chega no sentinela, aquele que vier primeiro. + Isso não é um problema porque o lema está somente apresentando um limitante + superior.} + \lemref{coin-tosses} afirma que o número esperado de lançamento de moedas + antes da primeira cada é 1. +Seja + $S_{#r#}$ o número de passos que uma caminho de busca (direta) usa no + nível + $#r#$ para ir à direita. Acabamos de mostrar que $\E[S_{#r#}]\le + 1$. Além disso, $S_{#r#}\le |L_{#r#}|$, pois não podemos fazer mais passos em + $L_{#r#}$ que o próprio comprimento de $L_{#r#}$, então \[ \E[S_{#r#}] \le \E[|L_{#r#}|] = #n#/2^{#r#} \enspace . \] - We can now finish as in the proof of \lemref{skiplist-height}. - Let $S$ be the length of the search path for some node, #u#, in a - skiplist, and let $#h#$ be the height of the skiplist. Then + Podemos agora terminar como na prova de + \lemref{skiplist-height}. + Seja $S$ o comprimento do caminho de busca para algum nodo #u# em uma skiplist e seja $#h#$ a altura da skiplist. Então \begin{align*} \E[S] &= \E\left[ #h# + \sum_{#r#=0}^\infty S_{#r#} \right] \\ @@ -445,185 +467,183 @@ \section{Analysis of Skiplists} \end{align*} \end{proof} - -The following theorem summarizes the results in this section: +O teorema a seguir resume os resultados nessa seção: \begin{thm} -A skiplist containing $#n#$ elements has expected size $O(#n#)$ and the -expected length of the search path for any particular element is at most + Uma skiplist contendo +$#n#$ elementos tem tamanho esperado $O(#n#)$ e o comprimento esperado + do caminho de busca para qualquer elemento é até $2\log #n# + O(1)$. \end{thm} - - %\section{Iteration and Finger Search} %TODO: Write this section -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -Skiplists were introduced by Pugh \cite{p91} who also presented -a number of applications and extensions of skiplists \cite{p89}. Since then they -have been studied extensively. Several researchers have done very -precise analyses of the expected length and variance of the length of the -search path for the #i#th element in a skiplist \cite{kp94,kmp95,pmp92}. -Deterministic versions \cite{mps92}, biased versions \cite{bbg02,esss01}, -and self-adjusting versions \cite{bdl08} of skiplists have all been -developed. Skiplist implementations have been written for various -languages and frameworks and have been used in open-source database -systems \cite{skipdb,redis}. A variant of skiplists is used in the HP-UX -operating system kernel's process management structures \cite{hpux}. -\javaonly{Skiplists are even part of the Java 1.6 API \cite{oracle_jdk6}.} +Skiplists foram inicialmente propostas por \cite{p91} que também apresentou várias aplicações e extensões de skiplists +\cite{p89}. Desde então ela tem sido extensivamente estudada. +Vários pesquisadores tem realizado análises bem precisas do comprimento +esperado e da variância do comprimento do caminho de busca para o +#i#-ésimo elemento em uma skiplist \cite{kp94,kmp95,pmp92}. +Versões determinísticas + \cite{mps92}, versões tendenciosas \cite{bbg02,esss01}, +e versões auto-ajustáveis \cite{bdl08} de skiplists têm surgido. Implementações de skiplists têm sido escritas para várias linguagens e frameworks e usadas em sistemas de bancos de dados open-source \cite{skipdb,redis}. Uma variante de skiplists é usada nas estruturas do gerenciador de processos do kernel do sistema operacional HP-UDX A variant of skiplists is used in the HP-UX +\cite{hpux}. +\javaonly{Skiplists são até parte da Java 1.6 API \cite{oracle_jdk6}.} \begin{exc} - Illustrate the search paths for 2.5 and 5.5 on the skiplist in + Desenhe os caminhos de busca para 2.5 e 5.5 na skiplists em \figref{skiplist}. \end{exc} \begin{exc} - Illustrate the addition of the values 0.5 (with a height of 1) and - then 3.5 (with a height of 2) to the skiplist in \figref{skiplist}. + Desenhe a adição de valores 0.5 (com altura de 1) e então 3.5 (com + uma altura de 2) para a skiplist em + \figref{skiplist}. \end{exc} \begin{exc} - Illustrate the removal of the values 1 and then 3 from the skiplist - in \figref{skiplist}. + Desenhe a remoção de valores 1 e então 3 da skiplist em + \figref{skiplist}. \end{exc} \begin{exc} - Illustrate the execution of #remove(2)# on the #SkiplistList# - in \figref{skiplist-lengths}. + Desenhe a execução de + #remove(2)# na #SkiplistList# em + \figref{skiplist-lengths}. \end{exc} \begin{exc} - Illustrate the execution of #add(3,x)# on the #SkiplistList# - in \figref{skiplist-lengths}. Assume that #pickHeight()# selects a height - of 4 for the newly created node. + Desenhe a execução de + #add(3,x)# na #SkiplistList# + da \figref{skiplist-lengths}. Assuma que #pickHeight()# seleciona uma altura + de 4 para o nodo recentemente criado. \end{exc} \begin{exc}\exclabel{skiplist-changes} - Show that, during an #add(x)# or a #remove(x)# operation, the expected - number of pointers in a #SkiplistSet# that get changed is constant. + Mostre que, durante uma operação + #add(x)# ou #remove(x)#, o número esperado de ponteiros em + uma #SkiplistSet# que é alterado é constante. \end{exc} \begin{exc}\exclabel{skiplist-opt} - Suppose that, instead of promoting an element from $L_{i-1}$ into $L_i$ - based on a coin toss, we promote it with some probability $p$, $0 < + Suponha que, em vez de promover um elemento de + $L_{i-1}$ em $L_i$ usando um lançamento de moeda + , fizermos a promoção com uma probabilidade $p$, $0 < p < 1$. \begin{enumerate} - \item Show that, with this modification, the expected length of a - search path is at most $(1/p)\log_{1/p} #n# + O(1)$. - \item What is the value of $p$ that minimizes the preceding expression? - \item What is the expected height of the skiplist? - \item What is the expected number of nodes in the skiplist? + \item Mostre que, com essa modificação, o comprimento esperado de um caminho de busca é até $(1/p)\log_{1/p} #n# + O(1)$. + \item Qual é o valor esperado de $p$ que minimiza a expressão precedente? + \item Qual é altura esperada da skiplist? + \item Qual é o número esperado de nodos na skiplist? \end{enumerate} \end{exc} - \begin{exc}\exclabel{skiplist-opt-2} - The #find(x)# method in a #SkiplistSet# sometimes performs - \emph{redundant comparisons}; these occur when #x# is compared to the - same value more than once. They can occur when, for some node, #u#, - $#u.next[r]# = #u.next[r-1]#$. Show how these redundant comparisons - happen and modify #find(x)# so that they are avoided. Analyze the - expected number of comparisons done by your modified #find(x)# method. +O método #find(x)# em uma a #SkiplistSet# às vezes faz + \emph{comparações redundantes}; essas ocorrem quando #x# é comparado ao mesmo valor mais de uma vez. Eles podem ocorrer quando, para algum nodo #u# + $#u.next[r]# = #u.next[r-1]#$. Mostre quando essas comparações redundantes acontecem e modifique + #find(x)# para que sejam evitadas. Analise o número esperado de comparações feito pelo seu método modificado #find(x)#. \end{exc} \begin{exc} - Design and implement a version of a skiplist that implements the - #SSet# interface, but also allows fast access to elements by rank. - That is, it also supports the function #get(i)#, which returns the - element whose rank is #i# in $O(\log #n#)$ expected time. (The rank - of an element #x# in an #SSet# is the number of elements in the #SSet# - that are less than #x#.) + Projete e implemente uma versão de uma skiplist que implemente a interface + #SSet#, mas também permite acesso rápido a eles pelo rank. + Isto é, também aceita a função + #get(i)#, que retorna o elemento cujo rank é #i# + #i# em $O(\log #n#)$ de tempo esperado. (O rank + de um elemento #x# em uma #SSet# é o número de elementos em #SSet# + que são menores que #x#.) \end{exc} \begin{exc} \index{finger}% \index{finger search!in a skiplist}% - A \emph{finger} in a skiplist is an array that stores the - sequence of nodes on a search path at which the search path - goes down. (The variable #stack# in the #add(x)# code on - page~\pageref{pg:skiplist-add} is a finger; the shaded nodes in - \figref{skiplist-add} show the contents of the finger.) One can - think of a finger as pointing out the path to a node in the lowest - list, $L_0$. - - A \emph{finger search} implements the #find(x)# operation using a - finger, by walking up the list using the finger until reaching a node - #u# such that $#u.x# < #x#$ and $#u.next#=#null#$ or $#u.next.x# > - #x#$ and then performing a normal search for #x# starting from #u#. - It is possible to prove that the expected number of steps required - for a finger search is $O(1+\log r)$, where $r$ is the number values - in $L_0$ between #x# and the value pointed to by the finger. - - Implement a subclass of #Skiplist# called #SkiplistWithFinger# that - implements #find(x)# operations using an internal finger. This subclass - stores a finger, which is then used so that every #find(x)# operation - is implemented as a finger search. During each #find(x)# operation - the finger is updated so that each #find(x)# operation uses, as a - starting point, a finger that points to the result of the previous - #find(x)# operation. + Um \emph{finger} em uma skiplist é um array que guarda a sequência de nodos em um caminho de busca no qual o caminho de busca desce. + (A variável #stack# no código #add(x)# na página~\pageref{pg:skiplist-add} é um finger; os nodos com sombra em + \figref{skiplist-add} mostra o conteúdo do finger.) Pode-se pensar de um finger como estar apontando o caminho para um nodo na lista mais baixa, $L_0$. + + Uma \emph{busca finger} implementa a operação #find(x)# usando um + finger, ao percorrer a lista usando finger até chegar em um nodo + #u# tal que $#u.x# < #x#$ e $#u.next#=#null#$ ou $#u.next.x# > + #x#$ e então fazer uma busca normal por #x# començando de #u#. + É possível provar que o número esperado de passos necessários + para uma busca finger é + $O(1+\log r)$, onde $r$ é o número de valores em + $L_0$ entre #x# e o valor apontado pelo finger. + + Implemente uma subclasse de #Skiplist# chamada #SkiplistWithFinger# que + implementa operações #find(x)# usando um finger interno. Essa subclasse + guarda um finger, que é então usado para que toda operação + #find(x)# seja implementada como uma busca finger. + Durante cada operação + #find(x)# o finger é atualizado tal que cada operação + #find(x)# usa como ponto de partida + , um finger que aponta para o resultado da operação + #find(x)# prévia. \end{exc} \begin{exc}\exclabel{skiplist-truncate} - Write a method, #truncate(i)#, that truncates a #SkiplistList# - at position #i#. After the execution of this method, the size - of the list is #i# and it contains only the elements at indices - $0,\ldots,#i#-1$. The return value is another #SkiplistList# that - contains the elements at indices $#i#,\ldots,#n#-1$. This method - should run in $O(\log #n#)$ time. + Escreva um método + #truncate(i)# que trunca uma #SkiplistList# + na posição #i#. Após a execução desse método, o tamanho da lista é #i# + e contém somente os elementos nos índices + $0,\ldots,#i#-1$. O valor retornado é outra #SkiplistList# que + contém os elementos nos índices + $#i#,\ldots,#n#-1$. Esse método deve rodar em + $O(\log #n#)$ de tempo. \end{exc} \begin{exc} - Write a #SkiplistList# method, #absorb(l2)#, that takes as an - argument a #SkiplistList#, #l2#, empties it and appends its contents, - in order, to the receiver. For example, if #l1# contains $a,b,c$ - and #l2# contains $d,e,f$, then after calling #l1.absorb(l2)#, #l1# - will contain $a,b,c,d,e,f$ and #l2# will be empty. This method should - run in $O(\log #n#)$ time. + Escreva um método para a + #SkiplistList# chamado #absorb(l2)#, que recebe como argumento uma + #SkiplistList#, #l2#, a vazia e coloca seu conteúdo na estrutura receptora. +Por exemplo, se + #l1# contém $a,b,c$ + e #l2# contém $d,e,f$, então após chamar #l1.absorb(l2)#, #l1# + conterá $a,b,c,d,e,f$ e #l2# estará vazia. Esse método deve rodar em + $O(\log #n#)$ de tempo. \end{exc} \begin{exc} - Using the ideas from the space-efficient list, #SEList#, design - and implement a space-efficient #SSet#, #SESSet#. To do this, store the - data, in order, in an #SEList#, and store the blocks of this #SEList# - in an #SSet#. If the original #SSet# implementation uses $O(#n#)$ - space to store #n# elements, then the #SESSet# will use enough space - for #n# elements plus $O(#n#/#b#+#b#)$ wasted space. + Usando as ideias da lista eficiente em espaço, #SEList#, projete + e implemente uma #SSet# eficiente em espaço #SSet#s, #SESSet#. + Para isso, guarde os dados, em ordem, em uma #SEList# e armazene os blocos dessa + #SEList# em uma #SSet#. Se a implementação original #SSet# usa + $O(#n#)$ + de espaço para guardar #n# elementos, então a #SESSet# irá usar espaço suficiente + para #n# elementos mais + $O(#n#/#b#+#b#)$ de espaço desperdiçado. \end{exc} \begin{exc} - Using an #SSet# as your underlying structure, design and implement an - application that reads a (large) text file and allows you to search, - interactively, for any substring contained in the text. As the user - types their query, a matching part of the text (if any) should appear - as a result. - - \noindent Hint 1: Every substring is a prefix of some suffix, so it - suffices to store all suffixes of the text file. - - \noindent Hint 2: Any suffix can be represented compactly as a single - integer indicating where the suffix begins in the text. - - \noindent Test your application on some large texts, such as some of - the books available at Project Gutenberg \cite{gutenberg}. If done - correctly, your applications will be very responsive; there should be - no noticeable lag between typing keystrokes and seeing the results. + Usando uma #SSet# com estrutura básica, projete e implemente uma + aplicação que lê um arquivo de texto (potencialmente enorme) e permite + você buscar, iterativamente, por qualquer substring contida no texto. + Conforme o usuário digita a consulta, uma parte do texto que corresponde + à busca (se houver) deve aparecer como resultado. + + \noindent Dica 1: toda substring é um prefixo de algum sufixo, então basta guarda todos os sufixos do arquivo de texto. + + \noindent Dica 2: qualquer sufixo pode ser representado como um único inteiro indicando onde o sufixo começa no texto. + + \noindent Teste sua aplicação em textos maiores, tais como alguns livros disponibilizados pelo + Projeto Gutenberg \cite{gutenberg}. Se feito corretamente, + a sua aplicação será bem rápida; não deve have atraso considerável + entre digitar letras e ver os resultados. \end{exc} \begin{exc} - \index{skiplist!versus binary search tree}% - \index{binary search tree!versus skiplist}% - (This exercise should be done after reading about binary search trees, - in \secref{binarysearchtree}.) Compare skiplists with binary search - trees in the following ways: + \index{skiplist!versus árvore binária de busca}% + \index{árvore binária de busca!versus skiplist}% + (Este exercício deve ser feito após a leitura sobre árvores binárias de busca + em + \secref{binarysearchtree}.) Compare skiplists com árvores binárias de busca + das seguintes maneiras: \begin{enumerate} - \item Explain how removing some edges of a skiplist leads to - a structure that looks like a binary tree and is similar to a - binary search tree. - \item Skiplists and binary search trees each use about the same - number of pointers (2 per node). Skiplists make better use of - those pointers, though. Explain why. + \item Explique como algumas arestas de uma skiplist leva a uma estrutura que parece uma árvore binária e é similar a uma árvore de busca binária. + \item Skiplists e árvores de busca binária usam aproximadamente o mesmo número de ponteiros (2 por nodo). Contundo, skiplists fazer melhor uso desses ponteiros. Explique porque. \end{enumerate} \end{exc} From 6cd29d969bbbee09199fba1dbbdaae369799bcd0 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 17 Aug 2020 18:55:34 -0300 Subject: [PATCH 23/66] traducao para o portugues --- latex/hashing.tex | 1475 ++++++++++++++++++++++++--------------------- 1 file changed, 780 insertions(+), 695 deletions(-) diff --git a/latex/hashing.tex b/latex/hashing.tex index 83b612f6..34e843a7 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -1,127 +1,137 @@ -\chapter{Hash Tables} +\chapter{Tabelas Hash } \chaplabel{hashtables} \chaplabel{hashing} -Hash tables are an efficient method of storing a small number, -#n#, of integers from a large range $U=\{0,\ldots,2^{#w#}-1\}$. -The term \emph{hash table} +Tabelas hash são um eficiente método de guardar um pequeno número #n# +de inteiros provenientes de um grande intervalo +$U=\{0,\ldots,2^{#w#}-1\}$. +O termo +tabela hash, ou \emph{hash table}, \index{hash table}% -includes a broad range of data structures. The first part of this -chapter focuses on two of the most common implementations of hash tables: -hashing with chaining and linear probing. - -Very often hash tables store types of data that are not integers. -In this case, an integer \emph{hash code} -\index{hash code}% -is associated with each data -item and is used in the hash table. The second part of this chapter -discusses how such hash codes are generated. - -Some of the methods used in this chapter require random choices of -integers in some specific range. In the code samples, some of these -``random'' integers are hard-coded constants. These constants were -obtained using random bits generated from atmospheric noise. - - -\section{#ChainedHashTable#: Hashing with Chaining} +\index{tabela hash}% +inclui um grande número de tipos de estruturas de dados. A primeira parte +deste capítulo foca em duas das implementações mais comuns de tabelas hash: +hashing com encadeamento e sondagem linear. + +Muito frequentemente, tabelas hash guardam tipos de dados que não são inteiros. +Nesse caso, um inteiro chamado de código \emph{hash} +\index{código hash}% +é associado com cada item de dados e é usado na tabela hash. +A segunda parte deste capítulo discute como tais códigos hash +são gerados. + +Alguns desses métodos usados neste capítulo requerem escolhas +aleatórias de inteiros em algum intervalo. Nos trechos de código, +alguns desses inteiros ``aleatórios'' são constantes fixas no código. +Essas constantes foram obtidas usando bits aleatórios gerados a partir +de ruído atmosférico. + +\section{#ChainedHashTable#: Hashing com Encadeamento} \seclabel{hashtable} \index{ChainedHashTable@#ChainedHashTable#}% -\index{chaining}% -\index{hashing with chaining}% -A #ChainedHashTable# data structure uses \emph{hashing with chaining} to store -data as an array, #t#, of lists. An integer, #n#, keeps track of the -total number of items in all lists (see \figref{chainedhashtable}): +\index{encadeamento}% +\index{hashing com encadeamento}% +Uma estrutura de dados +#ChainedHashTable# use \emph{hashing com encadeamento} para +guardar dados como um array #t# de listas. Um inteiro #n# registra o +númer total de itens em todas as listas. +(ver \figref{chainedhashtable}): \codeimport{ods/ChainedHashTable.t.n} \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/chainedhashtable} \end{center} - \caption[A ChainedHashTable]{An example of a #ChainedHashTable# with $#n#=14$ and $#t.length#=16$. In this example $#hash(x)#=6$} + \caption[Uma ChainedHashTable]{Uma exemplo de #ChainedHashTable# com $#n#=14$ e $#t.length#=16$. Nesse exemplo $#hash(x)#=6$} \figlabel{chainedhashtable} \end{figure} -\index{hash value}% +\index{valor hash}% \index{hash(x)@#hash(x)#}% -The \emph{hash value} of a data item #x#, denoted #hash(x)# is a value -in the range $\{0,\ldots,#t.length#-1\}$. All items with hash value #i# -are stored in the list at #t[i]#. To ensure that lists don't get too -long, we maintain the invariant +O valor \emph{hash} de um item de dados #x#, denotado #hash(x)# é um valor +no intervalo +$\{0,\ldots,#t.length#-1\}$. Todos os itens com um valor hash #i# +são guardados na lista em +#t[i]#. Para garantir que listas não ficam muito longas, mantermos a invariante \[ #n# \le #t.length# \] -so that the average number of elements stored in one of these lists is +tal que o número médio de elementos guardados em uma dessas listas é $#n#/#t.length# \le 1$. -To add an element, #x#, to the hash table, we first check if -the length of #t# needs to be increased and, if so, we grow #t#. -With this out of the way we hash #x# to get an integer, #i#, in the -range $\{0,\ldots,#t.length#-1\}$, and we append #x# to the list +Para adicionar um elemento #x# à tabela primeiro verificamos se o tamanho de #t# precisar ser aumentado e, se precisar, expandimos #t#. +Com isso resolvido, obtemos a hash de #x# para obter um inteiro #i# no +intervalo +$\{0,\ldots,#t.length#-1\}$, e adicionamos #x# à lista #t[i]#: \codeimport{ods/ChainedHashTable.add(x)} -Growing the table, -if necessary, involves doubling the length of #t# and reinserting -all elements into the new table. This strategy is exactly the same -as the one used in the implementation of #ArrayStack# and the same -result applies: The cost of growing is only constant when amortized -over a sequence of insertions (see~\lemref{arraystack-amortized} on -page~\pageref{lem:arraystack-amortized}). - -Besides growing, the only other work done when adding a new value #x# to a -#ChainedHashTable# involves appending #x# to the list #t[hash(x)]#. For -any of the list implementations described in Chapters~\ref{chap:arrays} -or \ref{chap:linkedlists}, this takes only constant time. - -To remove an element, #x#, from the hash table, we iterate over the list -#t[hash(x)]# until we find #x# so that we can remove it: +Expandir a tabela, caso necessário, envolve dobrar o comprimento de #t# +e reinserir todos os elementos na nova tabela. Essa +estratégia é exatamente a mesmo que aquela usada na implementação do + +#ArrayStack# e o mesmo resultado se aplica +O custo de expandir somente é constante quando amortizado sobre uma sequência de +inserções (ver~\lemref{arraystack-amortized} na págia~\pageref{lem:arraystack-amortized}). + +Além de expandir, o único outro trabalho realizado ao adicionar um novo valor #x# +a uma +#ChainedHashTable# involve adicionar #x# à lista #t[hash(x)]#. +Para qualquer das implementações de lista descrita nos Capítulos~\ref{chap:arrays} +ou \ref{chap:linkedlists}, isso leva somente um tempo constante. + +Para remover um elemento #x# da tabela hash, iteramos sobre a lista +#t[hash(x)]# até acharmos #x# tal que podemos removê-lo: \codeimport{ods/ChainedHashTable.remove(x)} -This takes $O(#n#_{#hash(x)#})$ time, where $#n#_{#i#}$ denotes the length -of the list stored at #t[i]#. +Isso leva $O(#n#_{#hash(x)#})$ de tempo, onde $#n#_{#i#}$ denota o comprimento da lista guardada em #t[i]#. -Searching for the element #x# in a hash table is similar. We perform -a linear search on the list #t[hash(x)]#: +Buscar pelo elemento #x# +#x# em uma tabela hash é similar. Fazemos uma busca linear +na lista +#t[hash(x)]#: \codeimport{ods/ChainedHashTable.find(x)} -Again, this takes time proportional to the length of the list #t[hash(x)]#. +Novamente, isso leva tempo proporcional ao comprimento da lista + #t[hash(x)]#. -The performance of a hash table depends critically on the choice of the -hash function. A good hash function will spread the elements evenly -among the #t.length# lists, so that the expected size of the list -#t[hash(x)]# is $O(#n#/#t.length)# = O(1)$. On the other hand, a bad -hash function will hash all values (including #x#) to the same table -location, in which case the size of the list #t[hash(x)]# will be #n#. -In the next section we describe a good hash function. + O desempenho de um tabela hash depende criticamente na escolha da função + hash. Uma boa função hash irá espalhar os elementos de modo uniforme + entre as #t.length# listas, tal que o tamanho esperado da lista +#t[hash(x)]# é $O(#n#/#t.length)# = O(1)$. Por outro lado, +uma função hash ruim irá distribuir todos os valores (incluindo #x#) à mesma posição da tabela. Nesse caso, o tamanho da lista #t[hash(x)]# será #n#. +Na próxima seção descrevemos uma boa função hash. + +\subsection{Hashing Multiplicativo} -\subsection{Multiplicative Hashing} \seclabel{multihash} -\index{hashing!multiplicative}% -\index{multiplicative hashing}% -Multiplicative hashing is an efficient method of generating hash -values based on modular arithmetic (discussed in \secref{arrayqueue}) -and integer division. It uses the $\ddiv$ operator, which calculates -the integral part of a quotient, while discarding the remainder. -Formally, for any integers $a\ge 0$ and $b\ge 1$, $a\ddiv b = \lfloor +\index{multiplicativo!hashing}% +\index{hashing multiplicativo}% +Hashing multiplicativo é um método eficiente de gerar valores +hash baseado em aritmética modular + (discutido em \secref{arrayqueue}) + e divisão inteira. +Ele usa o operador $\ddiv$, que calcula a parte inteira de um quociente e +descarta o resto. +Formalmente, para quaisquer interios $a\ge 0$ e $b\ge 1$, $a\ddiv b = \lfloor a/b\rfloor$. -In multiplicative hashing, we use a hash table of size $2^{#d#}$ for some -integer #d# (called the \emph{dimension}). The formula for hashing an -integer $#x#\in\{0,\ldots,2^{#w#}-1\}$ is +Em hashing multiplicativo, usamos uma tabela hash de tamanho $2^{#d#}$ para um inteiro +#d# (chamado de \emph{dimensão}). A fórmula para aplicar hashing em um inteiro +$#x#\in\{0,\ldots,2^{#w#}-1\}$ é \[ #hash(x)# = ((#z#\cdot#x#) \bmod 2^{#w#}) \ddiv 2^{#w#-#d#} \enspace . \] -Here, #z# is a randomly chosen \emph{odd} integer in -$\{1,\ldots,2^{#w#}-1\}$. This hash function can be realized very -efficiently by observing that, by default, operations on integers -are already done modulo $2^{#w#}$ where $#w#$ is the number of bits -in an integer.\footnote{This is true for most programming languages -including C, C\#, C++, and Java. Notable exceptions are Python and -Ruby, in which the result of a fixed-length #w#-bit integer operation -that overflows is upgraded to a variable-length representation.} (See -\figref{multihashing}.) Furthermore, integer division by $2^{#w#-#d#}$ -is equivalent to dropping the rightmost $#w#-#d#$ bits in a binary -representation (which is implemented by shifting the bits right by -$#w#-#d#$ using the \javaonly{#>>>#}\cpponly{#>>#}\pcodeonly{#>>#} -operator). \notpcode{In this way, the code that implements the above -formula is simpler than the formula itself:} +Aqui, #z# é um inteiro ímpar escolhido aleatoriamente em +$\{1,\ldots,2^{#w#}-1\}$. Essa função hash pode ser computada muito eficientemente ao observar que, por padrão, operações em inteiros são +módulo $2^{#w#}$ onde $#w#$ é o número de bits em um inteiro.\footnote{Isso é verdade para a moior parte de linguagens de programação incluindo +C, C\#, C++, e Java. Exceções notáveis são Python e +Ruby, nos quais o resultado de uma operação inteira de tamanho fixo #w#-bit +que passa do intervalo permitido é convertido a uma variável com tamanho de representação variável.}a (Ver +\figref{multihashing}.) Além disso, divisão inteira por $2^{#w#-#d#}$ +é equivalente a descartar os +$#w#-#d#$ bits mais à direita em uma representação binária ( +que é implementado usando o deslocamento de bits $#w#-#d#$ à direita +usando o operador + \javaonly{#>>>#}\cpponly{#>>#}\pcodeonly{#>>#} +). \notpcode{Dessa forma, o código que implementa a fórmula acima é mais simples que a própria fórmula:} \codeimport{ods/ChainedHashTable.hash(x)} \begin{figure} @@ -139,40 +149,45 @@ \subsection{Multiplicative Hashing} \end{tabular}} \setlength{\arrayrulewidth}{.4pt} \end{center} - \caption{The operation of the multiplicative hash function with $#w#=32$ - and $#d#=8$.} + \caption{A operação da função de hashing multiplicativo com $#w#=32$ + e $#d#=8$.} \figlabel{multihashing} \end{figure} -The following lemma, whose proof is deferred until later in this section, -shows that multiplicative hashing does a good job of avoiding collisions: +O lema a seguir, cuja prova será feita posteriomente nesta seção, +mostra que o +hashing multiplicativo faz um bom trabalho em evitar colisões: \begin{lem}\lemlabel{universal-hashing} - Let #x# and #y# be any two values in $\{0,\ldots,2^{#w#}-1\}$ with - $#x#\neq #y#$. Then $\Pr\{#hash(x)#=#hash(y)#\} \le 2/2^{#d#}$. + Sejam #x# e #y# dois valores em $\{0,\ldots,2^{#w#}-1\}$ com + $#x#\neq #y#$. Então $\Pr\{#hash(x)#=#hash(y)#\} \le 2/2^{#d#}$. \end{lem} -With \lemref{universal-hashing}, the performance of #remove(x)#, and -#find(x)# are easy to analyze: +Com +\lemref{universal-hashing}, o desempenho de #remove(x)#, e +#find(x)# são fáceis de analisar: \begin{lem} - For any data value #x#, the expected length of the list #t[hash(x)]# - is at most $#n#_{#x#} + 2$, where $#n#_{#x#}$ is the number of - occurrences of #x# in the hash table. + Para qualquer valor + #x#, o comprimento esperado da lista #t[hash(x)]# + é no máximo + $#n#_{#x#} + 2$, onde $#n#_{#x#}$ é o número de ocorrências de + #x# na tabela hash. \end{lem} \begin{proof} - Let $S$ be the (multi-)set of elements stored in the hash table that - are not equal to #x#. For an element $#y#\in S$, define the indicator - variable + Seja + $S$ o conjunto de elementos guardado na tabela hash que não são iguais a #x#. Para um elemento + $#y#\in S$, defina a variável indicadora \[ I_{#y#} = \left\{\begin{array}{ll} - 1 & \mbox{if $#hash(x)#=#hash(y)#$} \\ - 0 & \mbox{otherwise} + 1 & \mbox{se $#hash(x)#=#hash(y)#$} \\ + 0 & \mbox{caso contrário} \end{array}\right. \] - and notice that, by \lemref{universal-hashing}, $\E[I_{#y#}] \le - 2/2^{#d#}=2/#t.length#$. The expected length of the list #t[hash(x)]# - is given by + e note que, segundo o + \lemref{universal-hashing}, $\E[I_{#y#}] \le + 2/2^{#d#}=2/#t.length#$. O comprimento esperado da lista #t[hash(x)]# + é dado por \begin{eqnarray*} \E\left[#t[hash(x)].size()#\right] &=& \E\left[#n#_{#x#} + \sum_{#y#\in S} I_{#y#}\right] \\ &=& #n#_{#x#} + \sum_{#y#\in S} \E [I_{#y#} ] \\ @@ -181,288 +196,323 @@ \subsection{Multiplicative Hashing} &\le& #n#_{#x#} + (#n#-#n#_{#x#})2/#n# \\ &\le& #n#_{#x#} + 2 \enspace , \end{eqnarray*} - as required. + conforme necessário. \end{proof} -Now, we want to prove \lemref{universal-hashing}, but first we need a -result from number theory. In the following proof, we use the notation -$(b_r,\ldots,b_0)_2$ to denote $\sum_{i=0}^r b_i2^i$, where each $b_i$ -is a bit, either 0 or 1. In other words, $(b_r,\ldots,b_0)_2$ is -the integer whose binary representation is given by $b_r,\ldots,b_0$. -We use $\star$ to denote a bit of unknown value. +Agora, queremos provar + \lemref{universal-hashing}, mas primeiro precisamos de um resultado + da teoria de números. Na prova a seguir, usamos a notação +$(b_r,\ldots,b_0)_2$ para denotar $\sum_{i=0}^r b_i2^i$, onde cada $b_i$ +é um bit, 0 ou 1. Em outras palavras, +$(b_r,\ldots,b_0)_2$ é o inteiro cuja representação binária é dada por +$b_r,\ldots,b_0$. +Usamos + $\star$ para denotar um valor bit desconhecido. \begin{lem}\lemlabel{hashing-mapping} - Let $S$ be the set of odd integers in $\{1,\ldots,2^{#w#}-1\}$; let $q$ - and $i$ be any two elements in $S$. Then there is exactly one value - $#z#\in S$ such that $#z#q\bmod 2^{#w#} = i$. + Sej $S$ o conjunto de inteiros ímpares em $\{1,\ldots,2^{#w#}-1\}$; seja $q$ + e $i$ quaisquer dois elementos em $S$. Então há exatamente um valor $#z#\in S$ tal que $#z#q\bmod 2^{#w#} = i$. \end{lem} \begin{proof} - Since the number of choices for $#z#$ and $i$ is the same, it is - sufficient to prove that there is \emph{at most} one value $#z#\in S$ that - satisfies $#z#q\bmod 2^{#w#} = i$. + Como o número de escolhas para + $#z#$ e $i$ é o mesmo, é suficiente provar que há \emph{no máximo} um valor + $#z#\in S$ que satisfaz + $#z#q\bmod 2^{#w#} = i$. - Suppose, for the sake of contradiction, that there are two such values - #z# and #z'#, with $#z#>#z#'$. Then + Suponha, para efeitos de uma contradição, que há dois tais valores + #z# e #z'#, com $#z#>#z#'$. Portanto \[ #z#q\bmod 2^{#w#} = #z#'q \bmod 2^{#w#} = i \] - So + Então \[ (#z#-#z#')q\bmod 2^{#w#} = 0 \] - But this means that + Mas isso significa que \begin{equation} (#z#-#z#')q = k 2^{#w#} \eqlabel{factors} \end{equation} - for some integer $k$. Thinking in terms of binary numbers, we have + para algum inteiro $k$. Pensando em termos de números binários, temos \[ (#z#-#z#')q = k\cdot(1,\underbrace{0,\ldots,0}_{#w#})_2 \enspace , \] - so that the #w# trailing bits in the binary representation of - $(#z#-#z#')q$ are all 0's. + tal que o últimos #w# bits na representação binária de + $(#z#-#z#')q$ são todos 0. - Furthermore $k\neq 0$, since $q\neq 0$ and $#z#-#z#'\neq 0$. Since $q$ - is odd, it has no trailing 0's in its binary representation: + Além disso, + $k\neq 0$, pois $q\neq 0$ e $#z#-#z#'\neq 0$. Uma vez que $q$ + é ímpar, não há 0's terminando sua representação binária: \[ q = (\star,\ldots,\star,1)_2 \enspace . \] - Since $|#z#-#z#'| < 2^{#w#}$, $#z#-#z#'$ has fewer than #w# trailing - 0's in its binary representation: + Como + $|#z#-#z#'| < 2^{#w#}$, $#z#-#z#'$ tem menos que #w# bits no final de sua representação binária: \[ #z#-#z#' = (\star,\ldots,\star,1,\underbrace{0,\ldots,0}_{<#w#})_2 \enspace . \] - Therefore, the product $(#z#-#z#')q$ has fewer than #w# trailing 0's in - its binary representation: + Portanto, o produto + $(#z#-#z#')q$ tem menos que #w# bits 0 no término da sua representação binária: \[ (#z#-#z#')q = (\star,\cdots,\star,1,\underbrace{0,\ldots,0}_{<#w#})_2 \enspace . \] - Therefore $(#z#-#z#')q$ cannot satisfy \myeqref{factors}, yielding a - contradiction and completing the proof. + Portanto + $(#z#-#z#')q$ não pode satisfazer \myeqref{factors}, + chegando a uma contradição e completando a prova. \end{proof} -The utility of \lemref{hashing-mapping} comes from the following -observation: If #z# is chosen uniformly at random from $S$, then #zt# -is uniformly distributed over $S$. In the following proof, it helps -to think of the binary representation of #z#, which consists of $#w#-1$ -random bits followed by a 1. - -\begin{proof}[Proof of \lemref{universal-hashing}] - First we note that the condition $#hash(x)#=#hash(y)#$ is equivalent to - the statement ``the highest-order #d# bits of $#z# #x#\bmod2^{#w#}$ - and the highest-order #d# bits of $#z# #y#\bmod 2^{#w#}$ are the same.'' - A necessary condition of that statement is that the highest-order - #d# bits in the binary representation of $#z#(#x#-#y#)\bmod 2^{#w#}$ - are either all 0's or all 1's. That is, +A utilidade do + \lemref{hashing-mapping} vem da seguine observação: + Se #z# é escolhido uniformemente aleatório de $S$, então #zt# + é uniformemente distribuído sobre $S$. Na prova a seguir, + ajuda a lembrar da representação binária de #z#, que consiste de +$#w#-1$ bits aleatórios seguindos por um 1. + +\begin{proof}[Prova da \lemref{universal-hashing}] + Primeiro notamos que a condição + $#hash(x)#=#hash(y)#$ equivale à + afirmação os #d# bits de ordem mais alta em + ``$#z# #x#\bmod2^{#w#}$ e os #d# bits de ordem mais alta de #z# + $#z# #y#\bmod 2^{#w#}$ são os mesmos.'' + Uma condição necessária dessa afirmação é que os #d# bits de ordem mais + alta na representação de + $#z#(#x#-#y#)\bmod 2^{#w#}$ + são todos 0 ou todos 1. Isto é, \begin{equation} #z#(#x#-#y#)\bmod 2^{#w#} = (\underbrace{0,\ldots,0}_{#d#},\underbrace{\star,\ldots,\star}_{#w#-#d#})_2 \eqlabel{all-zeros} \end{equation} - when $#zx#\bmod 2^{#w#} > #zy#\bmod 2^{#w#}$ or + quando $#zx#\bmod 2^{#w#} > #zy#\bmod 2^{#w#}$ ou \begin{equation} #z#(#x#-#y#)\bmod 2^{#w#} = (\underbrace{1,\ldots,1}_{#d#},\underbrace{\star,\ldots,\star}_{#w#-#d#})_2 \enspace . \eqlabel{all-ones} \end{equation} - when $#zx#\bmod 2^{#w#} < #zy#\bmod 2^{#w#}$. - Therefore, we only have to bound the probability that - $#z#(#x#-#y#)\bmod 2^{#w#}$ looks like \myeqref{all-zeros} or \myeqref{all-ones}. + quando $#zx#\bmod 2^{#w#} < #zy#\bmod 2^{#w#}$. + Portanto, só temos que delimitar a probabilidade de que + $#z#(#x#-#y#)\bmod 2^{#w#}$ pareça \myeqref{all-zeros} ou \myeqref{all-ones}. - Let $q$ be the unique odd integer such that $(#x#-#y#)\bmod - 2^{#w#}=q2^r$ for some integer $r\ge 0$. By - \lemref{hashing-mapping}, the binary representation of $#z#q\bmod - 2^{#w#}$ has $#w#-1$ random bits, followed by a 1: + Seja $q$ ser um inteiro único tal que $(#x#-#y#)\bmod + 2^{#w#}=q2^r$ para algum inteiro $r\ge 0$. Pelo + \lemref{hashing-mapping}, a representação binária de $#z#q\bmod + 2^{#w#}$ tem $#w#-1$ bits aleatórios, seguidos por um 1: \[ #z#q\bmod 2^{#w#} = (\underbrace{b_{#w#-1},\ldots,b_{1}}_{#w#-1},1)_2 \] - Therefore, the binary representation of $#z#(#x#-#y#)\bmod 2^{#w#}=#z#q2^r\bmod 2^{#w#}$ has - $#w#-r-1$ random bits, followed by a 1, followed by $r$ 0's: + Portanto, a representação binária de + $#z#(#x#-#y#)\bmod 2^{#w#}=#z#q2^r\bmod 2^{#w#}$ tem + $#w#-r-1$ bits aleatórios, seguidos por um 1, seguidos por $r$ dígitos 0: \[ #z#(#x#-#y#)\bmod 2^{#w#} = #z#q2^{r}\bmod 2^{#w#} = (\underbrace{b_{#w#-r-1},\ldots,b_{1}}_{#w#-r-1},1,\underbrace{0,0,\ldots,0}_{r})_2 \] - We can now finish the proof: If $r > #w#-#d#$, then the #d# - higher order bits of $#z#(#x#-#y#)\bmod 2^{#w#}$ contain both 0's - and 1's, so the probability that $#z#(#x#-#y#)\bmod 2^{#w#}$ looks - like \myeqref{all-zeros} or \myeqref{all-ones} is 0. If $#r#=#w#-#d#$, - then the probability of looking like \myeqref{all-zeros} is 0, but the - probability of looking like \myeqref{all-ones} is $1/2^{#d#-1}=2/2^{#d#}$ - (since we must have $b_1,\ldots,b_{d-1}=1,\ldots,1$). If $r < #w#-#d#$, - then we must have $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=0,\ldots,0$ or - $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=1,\ldots,1$. The probability of each - of these cases is $1/2^{#d#}$ and they are mutually exclusive, so the - probability of either of these cases is $2/2^{#d#}$. This completes - the proof. + Podemos agora terminar a prova: se + $r > #w#-#d#$, então os #d# bits de alta ordem de + $#z#(#x#-#y#)\bmod 2^{#w#}$ contém tanto 0 quanto + 1, tal que a probabilidade de que $#z#(#x#-#y#)\bmod 2^{#w#}$ pareça a + \myeqref{all-zeros} ou \myeqref{all-ones} é 0. + + + Se $#r#=#w#-#d#$, então a probabilidade de parecer a + \myeqref{all-zeros} é 0, mas a probabilidae de parecer a + \myeqref{all-ones} é $1/2^{#d#-1}=2/2^{#d#}$ + (como precisamos ter $b_1,\ldots,b_{d-1}=1,\ldots,1$). Se $r < #w#-#d#$, + então precisamos ter + $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=0,\ldots,0$ ou + $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=1,\ldots,1$. A probabilidade de cada um desses casos é + $1/2^{#d#}$ e eles são mutualmente exclusivos, então a probabilidade de cada deses casos é + $2/2^{#d#}$. Isso completa essa prova. \end{proof} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of a #ChainedHashTable# -data structure: +O teorema a seguir resume o desempenho de uma estrutura de dados +#ChainedHashTable#: -\begin{thm}\thmlabel{hashtable} - A #ChainedHashTable# implements the #USet# interface. Ignoring the cost of - calls to #grow()#, a #ChainedHashTable# supports the operations #add(x)#, - #remove(x)#, and #find(x)# in $O(1)$ expected time per operation. - Furthermore, beginning with an empty #ChainedHashTable#, any sequence of - $m$ #add(x)# and #remove(x)# operations results in a total of $O(m)$ - time spent during all calls to #grow()#. +\begin{thm}\thmlabel{hashtable} + Uma #ChainedHashTable# implementa a interface #USet#. Ignorando o custo das chamadas a + #grow()#, uma #ChainedHashTable# aceita as operações #add(x)#, + #remove(x)#, e #find(x)# em $O(1)$ de tempo esperado por operação. + + Além disso, comelando com uma + #ChainedHashTable# vazia, qualquer sequência de $m$ operações + #add(x)# e #remove(x)# resulta em um total de $O(m)$ de tempo gasto durante + todas a chamadas a #grow()#. \end{thm} -\section{#LinearHashTable#: Linear Probing} +\section{#LinearHashTable#: Sondagem Linear} \seclabel{linearhashtable} \index{LinearHashTable@#LinearHashTable#}% -The #ChainedHashTable# data structure uses an array of lists, where the #i#th -list stores all elements #x# such that $#hash(x)#=#i#$. An alternative, -called \emph{open addressing} -\index{open addressing}% -is to store the elements directly in an -array, #t#, with each array location in #t# storing at most one value. -This approach is taken by the #LinearHashTable# described in this -section. In some places, this data structure is described as \emph{open -addressing with linear probing}. -\index{linear probing}% - -The main idea behind a #LinearHashTable# is that we would, ideally, like -to store the element #x# with hash value #i=hash(x)# in the table location -#t[i]#. If we cannot do this (because some element is already stored -there) then we try to store it at location $#t#[(#i#+1)\bmod#t.length#]$; -if that's not possible, then we try $#t#[(#i#+2)\bmod#t.length#]$, -and so on, until we find a place for #x#. - -There are three types of entries stored in #t#: +A estrutura de dados #ChainedHashTable# usa um array de listas onde a #i#-ésima lista guarda todos os elementos #x# tais que +$#hash(x)#=#i#$. Uma outra opção de projeto, chamado de \emph{endereçamento aberto}, +\index{endereçamento aberto}% +é guardar elementos diretamento em um array #t# sendo cad posição #t# para armazenar no máximo um valor. +Essa abordagem é implementada pela +#LinearHashTable# descrita nesta seção. +Em alguns lugares, essa estrutura de dados é descrita como \emph{endereçamento aberta com sondagem linear}. +\index{sondagem linear}% + +A principal ideia por trás da + +#LinearHashTable# é que deveríamos, idealmente, +guardar o elemento #x# com valor hash +#i=hash(x)# na posição da tabela +#t[i]#. Se não podemos fazer isso (porque algum elemento já está armazenado lá) +então tentamos armazená-lo na posição + $#t#[(#i#+1)\bmod#t.length#]$; +se isso não for possível, então tentamos +$#t#[(#i#+2)\bmod#t.length#]$, +e assim por diante, até encontrarmos um lugar para #x#. + +Há três tipos de entradas guardadas em #t#: \begin{enumerate} - \item data values: actual values in the #USet# that we are representing; - \item #null# values: at array locations where no data has ever been - stored; and - \item #del# values: at array locations where data was once stored but that - has since been deleted. + \item valores de dados: valores reais no #USet# que estamos representando; + \item valores #null#: em posições do array onde nenhum valor foi guardado antes; e + \item valores #del#: em posições do array onde dados foram previamente guardados mas que desde então foram deletados. \end{enumerate} -In addition to the counter, #n#, that keeps track of the number of elements -in the #LinearHashTable#, a counter, #q#, keeps track of the number of -elements of Types~1 and 3. That is, #q# is equal to #n# plus the number -of #del# values in #t#. To make this work efficiently, we need -#t# to be considerably larger than #q#, so that there are lots of #null# -values in #t#. The operations on a #LinearHashTable# therefore maintain -the invariant that $#t.length#\ge 2#q#$. - -To summarize, a #LinearHashTable# contains an array, #t#, that stores -data elements, and integers #n# and #q# that keep track of the number of -data elements and non-#null# values of #t#, respectively. Because many -hash functions only work for table sizes that are a power of 2, we also -keep an integer #d# and maintain the invariant that $#t.length#=2^#d#$. +Em adição ao contador + #n# que registra o número de elementos na +#LinearHashTable#, um contador, #q#, registra o número de elementos dos Tipos~1 e 3. +Isto é #q# é igual a #n# mais o número de valores #del# em #t#. Para fazer isso eficientemente, precisamos que #t# seja consideravelmente maior que #q#, tal que haja muitos #null# em #t#. +As operações em + +#LinearHashTable# portanto mantém a invariante +$#t.length#\ge 2#q#$. + +Para resumir, uma +#LinearHashTable# contém um array #t# que guarda elementos de dados +e inteiros +#n# e #q# que registram o número de elementos de dados e de valores não-#null# de #t# respectivamente. Como muitas funções hash somente funcionam para tamanhos de tabela que são potência de 2, também mantemos um inteiro #d# e a invariante +$#t.length#=2^#d#$. \codeimport{ods/LinearHashTable.t.n.q.d} -The #find(x)# operation in a #LinearHashTable# is simple. We start -at array entry #t[i]# where $#i#=#hash(x)#$ and search entries #t[i]#, -$#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, and so on, -until we find an index #i'# such that, either, #t[i']=x#, or #t[i']=null#. -In the former case we return #t[i']#. In the latter case, we conclude -that #x# is not contained in the hash table and return #null#. +A operação +#find(x)# na #LinearHashTable# é simples. Iniciamos na posição do array #t[i]# +onde +$#i#=#hash(x)#$ e buscamos as posições #t[i]#, +$#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, e assim segue, +até encontrarmos um índice #i'# tal que ou + #t[i']=x#, ou #t[i']=null#. +No primero caso retornamos + #t[i']#. No outro, quando #t[i']=null# In the latter case, concluímos + que não está contido na tabela hash e retornamos +#null#. \codeimport{ods/LinearHashTable.find(x)} -The #add(x)# operation is also fairly easy to implement. After checking -that #x# is not already stored in the table (using #find(x)#), we search +A operação #add(x)# também é razoavelmente simples para implementar. Após verificar +que #x# não está na tabela (usando #find(x)#), procuramos em #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, -and so on, until we find a #null# or #del# and store #x# at that location, -increment #n#, and #q#, if appropriate. +e assim por diante até acharmos um +#null# ou #del# e guarde #x# naquela posição, +incremente #n#, e #q#, se apropriado. \codeimport{ods/LinearHashTable.add(x)} -By now, the implementation of the #remove(x)# operation should be obvious. -We search #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod -#t.length#]$, and so on until we find an index #i'# such that #t[i']=x# -or #t[i']=null#. In the former case, we set #t[i']=del# and return -#true#. In the latter case we conclude that #x# was not stored in the -table (and therefore cannot be deleted) and return #false#. +Até agora, a implementação da operação +#remove(x)# deve ser óbvia. +Buscamos + #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod +#t.length#]$, e assim por diante até acharmos um índice #i'# tal que #t[i']=x# +ou #t[i']=null#. No primeiro caso, atribuímos #t[i']=del# e retornamos +#true#. No segundo caso, concluímos que #x# não foi armazenado na tabela +(e portanto não pode ser deletado) e retorna #false#. \codeimport{ods/LinearHashTable.remove(x)} -The correctness of the #find(x)#, #add(x)#, and #remove(x)# methods is -easy to verify, though it relies on the use of #del# values. Notice -that none of these operations ever sets a non-#null# entry to #null#. -Therefore, when we reach an index #i'# such that #t[i']=null#, this is -a proof that the element, #x#, that we are searching for is not stored -in the table; #t[i']# has always been #null#, so there is no reason that -a previous #add(x)# operation would have proceeded beyond index #i'#. - -The #resize()# method is called by #add(x)# when the number of non-#null# -entries exceeds $#t.length#/2$ or by #remove(x)# when the number of -data entries is less than #t.length/8#. The #resize()# method works -like the #resize()# methods in other array-based data structures. -We find the smallest non-negative integer #d# such that $2^{#d#} -\ge 3#n#$. We reallocate the array #t# so that it has size $2^{#d#}$, -and then we insert all the elements in the old version of #t# into the -newly-resized copy of #t#. While doing this, we reset #q# equal to #n# -since the newly-allocated #t# contains no #del# values. +A corretude dos métodos +#find(x)#, #add(x)#, e #remove(x)# fácil de verificar embora dependa do uso dos valores #del#. Note que nenhuma dessas operações nunca atribui em posição não-#null# a #null#. +Portanto, se alcançarmos um índice #i'# tal que + #t[i']=null# isso comprova que o elemento + #x# pelo qual buscamos não está guardado na tabela +; #t[i']# sempre foi #null#, então não há porque uma operação prévia #add(x)# +continuaria além do índice #i'#. + +O método +#resize()# é chamado por #add(x)# quando o número de posições não-#null# +excede $#t.length#/2$ ou por #remove(x)# quando o número de entrada de dados é bem menor que +#t.length/8#. O método #resize()# funciona como os métodos +#resize()# em outras estruturas de dados baseadas em array. +Achamos o menor inteiro não-negativo #d# tal que +$2^{#d#} +\ge 3#n#$. Realocamos o array #t# tal que tenha tamanho $2^{#d#}$, +e então inserimos todos os elementos na versão antiga de #t# na nova +cópia redimensionada de #t#. Ao fazer isso, reiniciamos #q# igual a #n# +pois o novo #t# contém nenhum valor #del#. \codeimport{ods/LinearHashTable.resize()} -\subsection{Analysis of Linear Probing} - -Notice that each operation, #add(x)#, #remove(x)#, or #find(x)#, finishes -as soon as (or before) it discovers the first #null# entry in #t#. -The intuition behind the analysis of linear probing is that, since at -least half the elements in #t# are equal to #null#, an operation should -not take long to complete because it will very quickly come across a -#null# entry. We shouldn't rely too heavily on this intuition, though, -because it would lead us to (the incorrect) conclusion that the expected -number of locations in #t# examined by an operation is at most 2. - -For the rest of this section, we will assume that all hash values are -independently and uniformly distributed in $\{0,\ldots,#t.length#-1\}$. -This is not a realistic assumption, but it will make it possible for -us to analyze linear probing. Later in this section we will describe a -method, called tabulation hashing, that produces a hash function that is -``good enough'' for linear probing. We will also assume that all indices -into the positions of #t# are taken modulo #t.length#, so that #t[i]# -is really a shorthand for $#t#[#i#\bmod#t.length#]$. +\subsection{Análise da Sondagem Linear} +Note que cada operação +#add(x)#, #remove(x)#, ou #find(x)#, termina assim que (ou mesmo antes) +encontra a primeira posição #null# em #t#. +A intuição por trás da análise da sondagem linear é que, +como pelo menos metade dos elementos em #t# são iguais a #null#, uma +operação não deve demorar muito para completar pois irá rapidamente achar +uma posição com valor #null#. +Não devemos usar essa intuição ao pé da letra pois levaria à conclusão (incorreta) de que o número esperado de posições em #t# que foram examinadas por uma operação +é até 2. + +Para o resto desta seção, iremos assumir que todos os valores hash +são independentemente e uniformemente distribuídos em +$\{0,\ldots,#t.length#-1\}$. +Essa não é uma premissa realista, mas permitirá uma análise da sondagem linear. +Mais a seguir nesta seção, iremos descrever um método, chamado de hashing de tabulação, que produz uma função hash que é ``boa o suficiente'' para sondagem linear. Também isso assumir que todos os índices das posições de #t# +são obtidos módulo #t.length#, tal que #t[i]# é na verdade um atalho para +$#t#[#i#\bmod#t.length#]$. \index{run}% -We say that a \emph{run of length $k$ that starts at #i#} occurs when all -the table entries $#t[i]#, #t[i+1]#,\ldots,#t#[#i#+k-1]$ are non-#null# -and $#t#[#i#-1]=#t#[#i#+k]=#null#$. The number of non-#null# elements of -#t# is exactly #q# and the #add(x)# method ensures that, at all times, -$#q#\le#t.length#/2$. There are #q# elements $#x#_1,\ldots,#x#_{#q#}$ -that have been inserted into #t# since the last #resize()# operation. -By our assumption, each of these has a hash value, $#hash#(#x#_j)$, -that is uniform and independent of the rest. With this setup, we can -prove the main lemma required to analyze linear probing. +Dizemos que um \emph{agrupamento de comprimento $k$ iniciando em #i#} ocorre +quando as posições da tabela +$#t[i]#, #t[i+1]#,\ldots,#t#[#i#+k-1]$ are non-#null# +and $#t#[#i#-1]=#t#[#i#+k]=#null#$. O número de elementos não-#null# de +#t# é exatamente #q# e o método #add(x)# garante que, a qualquer momento, $#q#\le#t.length#/2$. Há #q# elementos $#x#_1,\ldots,#x#_{#q#}$ +que foram inseridos em +#t# desde a última operação #resize()#. +De acordo com nossa premissa, cada um desses elementos tem valor hash + $#hash#(#x#_j)$ que é de uma distribuição uniforme e cada qual é independente do resto. +Nesse cenário podemos prova que principal lema necessário para analisar +a sondagem linear. \begin{lem}\lemlabel{linear-probing} -Fix a value $#i#\in\{0,\ldots,#t.length#-1\}$. Then the probability that -a run of length $k$ starts at #i# is $O(c^k)$ for some constant $00$.) Since $\sqrt{e}/{2}< 0.824360636 < 1$, this completes -the proof. +(No último passo, usamos a desigualdade $(1+1/x)^x \le e$, que vale para + todo $x>0$.) Como $\sqrt{e}/{2}< 0.824360636 < 1$, isso completa a prova. \end{proof} -Using \lemref{linear-probing} to prove upper-bounds on the expected -running time of #find(x)#, #add(x)#, and #remove(x)# is now fairly -straightforward. Consider the simplest case, where we execute -#find(x)# for some value #x# that has never been stored in the -#LinearHashTable#. In this case, $#i#=#hash(x)#$ is a random value in -$\{0,\ldots,#t.length#-1\}$ independent of the contents of #t#. If #i# -is part of a run of length $k$, then the time it takes to execute the -#find(x)# operation is at most $O(1+k)$. Thus, the expected running time -can be upper-bounded by +Usar \lemref{linear-probing} para provar limitantes superiores no tempo de execução esperado de +#find(x)#, #add(x)# e #remove(x)# agora é razoavelmente direto. Considere o caso +mais simples, onde executamos +#find(x)# para algum valor #x# nunca foi guardado em #LinearHashTable#. Nesse caso, $#i#=#hash(x)#$ é uma variável aleatória em +$\{0,\ldots,#t.length#-1\}$ independente do conteúdo de #t#. Se #i# +é parte de um agrupamento de tamanho $k$, então o tempo que leva para executar +a operação +#find(x)# é até $O(1+k)$. Então, o tempo esperado de execução pode ser limitado superiormente por \[ - O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k\Pr\{\text{#i# is part of a run of length $k$}\}\right) \enspace . + O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k\Pr\{\text{#i# é parte de um agrupamento de tamanho $k$}\}\right) \enspace . \] -Note that each run of length $k$ contributes to the inner sum $k$ times for a total contribution of $k^2$, so the above sum can be rewritten as + +Note que cada agrupamento de tamanho $k$ contribui à soma interna $k$ vezes para uma contribuição total de $k^2$, então a soma anterior pode ser reescrita como \begin{align*} - & { } O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2\Pr\{\mbox{#i# starts a run of length $k$}\}\right) \\ + & { } O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2\Pr\{\mbox{#i# inicia um agrupamento de tamanho $k$}\}\right) \\ & \le O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2p_k\right) \\ & = O\left(1 + \sum_{k=0}^{\infty} k^2p_k\right) \\ & = O\left(1 + \sum_{k=0}^{\infty} k^2\cdot O(c^k)\right) \\ & = O(1) \enspace . \end{align*} -The last step in this derivation comes from the fact that -$\sum_{k=0}^{\infty} k^2\cdot O(c^k)$ is an exponentially decreasing -series.\footnote{In the terminology of many calculus texts, this sum -passes the ratio test: There exists a positive integer $k_0$ such -that, for all $k\ge k_0$, $\frac{(k+1)^2c^{k+1}}{k^2c^k} < 1$.} -Therefore, we conclude that the expected running time of the #find(x)# operation -for a value #x# that is not contained in a #LinearHashTable# is $O(1)$. - -If we ignore the cost of the #resize()# operation, then the above -analysis gives us all we need to analyze the cost of operations on a +O último passo dessa derivação vem do fato que +$\sum_{k=0}^{\infty} k^2\cdot O(c^k)$ é uma séries exponencial decrescente.\footnote{Em muitos livros de cálculo, essa soma passa no teste da taxa: +existe um inteiro positivo $k_0$ tal que, para todo + $k\ge k_0$, $\frac{(k+1)^2c^{k+1}}{k^2c^k} < 1$.} + + Portanto, concluímos que o tempo de execução esperado da operação #find(x)# + para um valor #x# que não é contido em uma + #LinearHashTable# é $O(1)$. + + Se ignorarmos o custo da operação +#resize()# , então a análise acima nos dá tudo que precisamos para analisar +o custo de operações em uma #LinearHashTable#. -First of all, the analysis of #find(x)# given above applies to the -#add(x)# operation when #x# is not contained in the table. To analyze -the #find(x)# operation when #x# is contained in the table, we need only -note that this is the same as the cost of the #add(x)# operation that -previously added #x# to the table. Finally, the cost of a #remove(x)# -operation is the same as the cost of a #find(x)# operation. +Primeiramente, a análise de +#find(x)# dada acima aplica à operação +#add(x)# quando#x# não está na tabela. Para analisar a operação + #find(x)# quando #x# está na tabela, precisamos somente notar que isso tem o mesmo custo da operação +#add(x)# que anteriormente adicionou +#x# à tabela. Finalmente, o custo de uma operação #remove(x)# +é o mesmo que o custo de uma operação +#find(x)#. + +Em resumo, se ignorarmo o custo de chamadas a + #resize()#, todas as operações em uma +#LinearHashTable# rodam em $O(1)$ de tempo esperado. Considerar o +custo de redimensionamento pode ser feito usando o mesmo tipo de análise amortizada +realizada para a estrutura de dados + #ArrayStack# feita em \secref{arraystack}. -In summary, if we ignore the cost of calls to #resize()#, all operations -on a #LinearHashTable# run in $O(1)$ expected time. Accounting for the -cost of resize can be done using the same type of amortized analysis -performed for the #ArrayStack# data structure in \secref{arraystack}. +\subsection{Resumo} -\subsection{Summary} +O seguinte teorema resume o desempenho da estrutura de dados +#LinearHashTable#: -The following theorem summarizes the performance of the #LinearHashTable# -data structure: \begin{thm}\thmlabel{linear-probing} - A #LinearHashTable# implements the #USet# interface. Ignoring the - cost of calls to #resize()#, a #LinearHashTable# supports the operations - #add(x)#, #remove(x)#, and #find(x)# in $O(1)$ expected time per - operation. - - Furthermore, beginning with an empty #LinearHashTable#, any - sequence of $m$ #add(x)# and #remove(x)# operations results in a total - of $O(m)$ time spent during all calls to #resize()#. + Uma #LinearHashTable# implementa a interface #USet#. Ignorando o custo + de chamadas a + #resize()#, uma #LinearHashTable# implementa as operações + #add(x)#, #remove(x)# e #find(x)# em $O(1)$ de tempo esperado por + operação. + + Além disso, ao começar com uma + #LinearHashTable#, qualquer sequência + de $m$ operações #add(x)# e #remove(x)# resulta em um total de + $O(m)$ de tempo gasto durante todas as chamadas a #resize()#. \end{thm} -\subsection{Tabulation Hashing} -\seclabel{tabulation} - -\index{tabulation hashing}% -While analyzing the #LinearHashTable# structure, we made a very strong -assumption: That for any set of elements, $\{#x#_1,\ldots,#x#_#n#\}$, -the hash values $#hash#($x$_1),\ldots,#hash#(#x#_#n#)$ are independently and -uniformly distributed over the set $\{0,\ldots,#t.length#-1\}$. One way to -achieve this is to store a giant array, #tab#, of length $2^{#w#}$, -where each entry is a random #w#-bit integer, independent of all the -other entries. In this way, we could implement #hash(x)# by extracting -a #d#-bit integer from #tab[x.hashCode()]#: +\subsection{Hashing por tabulação } +\seclabel{tabulação} + +\index{hashing por tabulação}% +Ao analisar a estrutura + #LinearHashTable#, fazemos uma suposição muito forte: + para qualquer conjunto de elementos + $\{#x#_1,\ldots,#x#_#n#\}$ os valores de hash +$#hash#($x$_1),\ldots,#hash#(#x#_#n#)$ são independentes e +uniformemente distribuídos sobre o conjunto + $\{0,\ldots,#t.length#-1\}$. Um jeito de alcançar isso é guardar em um array gigante #tab# de tamanho +$2^{#w#}$, onde cada entrada é um inteiro aleatório #w#-bit, independente de todas +as outras posições. Dessa maneira, poderíamos implementar #hash(x)# pela +extração de um inteiro com #d# bits da + #tab[x.hashCode()]#: \codeimport{ods/LinearHashTable.idealHash(x)} -\pcodeonly{Here, #>>#, is the \emph{bitwise right shift} operator, so -#x.hashCode() >> w-d# extracts the #d# most significant bits of #x#'s #w#-bit hash code.} - -Unfortunately, storing an array of size $2^{#w#}$ is prohibitive in terms -of memory usage. The approach used by \emph{tabulation hashing} is to, -instead, treat #w#-bit integers as being comprised of $#w#/#r#$ -integers, each having only $#r#$ bits. In this way, tabulation hashing -only needs $#w#/#r#$ arrays each of length $2^{#r#}$. All the entries -in these arrays are independent random #w#-bit integers. To obtain the value -of #hash(x)# we split #x.hashCode()# up into $#w#/#r#$ #r#-bit integers -and use these as indices into these arrays. We then combine all these -values with the bitwise exclusive-or operator to obtain #hash(x)#. -The following code shows how this works when $#w#=32$ and $#r#=4$: +\pcodeonly{Aqui, #>>#, é o operador de \emph{deslocamemto bit-a-bit à direita}, então +#x.hashCode() >> w-d# extrai os #d# bits mais significativos do código hash dos #w# bits do código hash de #x#} + +Infelizmente, guardar um array de tamanho + $2^{#w#}$ é proibitivo em termos de uso de memória. +A abordagem usada pela \emph{hashing por tabulação} é, em vez disso, tratar +inteiros de #w# bits como sendo compostos de +$#w#/#r#$ inteiros, cada qual tem somente $#r#$ bits. +Dessa maneira, hashing por tabulalão precisa somente de +$#w#/#r#$ arrays cada qual com tamanho $2^{#r#}$. Todas as entradas nesses arrays sao inteiros aleatórios independentes com #w# bits. + +Para oter o valor de +#hash(x)# dividimos #x.hashCode()# em $#w#/#r#$ inteiros com #r# bits +e os usamos como índices desses arrays. Então combinamos +todos esses valores com o operador bit-a-bit ou-exclusivo para obter #hash(x)#. +O código a seguir mostra como isso funciona quando +$#w#=32$ e $#r#=4$: \codeimport{ods/LinearHashTable.hash(x)} -In this case, #tab# is a two-dimensional array with four columns and -$2^{32/4}=256$ rows. +Nesse caso, + #tab# é um array bi-dimensional com quatro colunas e +$2^{32/4}=256$ linhas. \pcodeonly{ -Quantities like #0xff#, used above, are \emph{hexadecimal numbers} -\index{hexadecimal numbers}% -whose digits have 16 possible values 0--9, which have their usual -meaning and a--f, which denote 10--15. The number $#0xff#=15\cdot 16 + 15 = 255$. -The #&# symbol is the \emph{bitwise and} operator, so -code like #h >> 8 & 0xff# extracts bits with index 8 through 15 of #h#.} - -One can easily verify that, for any #x#, #hash(x)# is uniformly -distributed over $\{0,\ldots,2^{#d#}-1\}$. With a little work, one -can even verify that any pair of values have independent hash values. -This implies tabulation hashing could be used in place of multiplicative -hashing for the #ChainedHashTable# implementation. - -However, it is not true that any set of #n# distinct values gives a set -of #n# independent hash values. Nevertheless, when tabulation hashing is -used, the bound of \thmref{linear-probing} still holds. References for -this are provided at the end of this chapter. - -\section{Hash Codes} - -\index{hash code}% -The hash tables discussed in the previous section are used to associate -data with integer keys consisting of #w# bits. In many cases, we -have keys that are not integers. They may be strings, objects, arrays, -or other compound structures. To use hash tables for these types of data, -we must map these data types to #w#-bit hash codes. Hash code mappings should -have the following properties: + Quantidades como +#0xff#, usadas acima, são \emph{números hexadecimais} +\index{números hexadecimais}% +cujos dígitos tem 16 possíveis valores 0--9, com o significado usual e +a--f, que denotam valores de 10 a 15. O número +$#0xff#=15\cdot 16 + 15 = 255$. +O símbolo +#&# é o operador \emph{bit-a-bit and} então, o código +#h >> 8 & 0xff# extrai os bits com índices de 8 a 15 de #h#.} + +É fácil verificar que, para qualquer #x#, #hash(x)# é uniformemente +distribuído sobre $\{0,\ldots,2^{#d#}-1\}$. Com um pouco de trabalho, +é possível verificar que qualquer par de valores tem valores hash independentes. +Isso implica que hashing por tabulação poderia ser usada no lugar +do hashing multiplicativo para a implementação da + #ChainedHashTable#. + + Entretanto, não é verdade que qualquer conjunto de #n# valores distintos + resulte em #n# valores hash independentes. De qualquer forma, + quando hashing por tabulação é usado, o limitante + \thmref{linear-probing} ainda vale. Referências para isso + são providas no final deste capítulo. + +\section{Códigos Hash} +\index{código hash}% +Tabelas hash discutivas na seção anterior, são usadas para associar +dados com chaves inteiras consistindo de #w# bits. Em muitos casos, +temos chaves que não são inteiros. Elas podem ser strings, objetos, arrays, +ou outras estruturas compostas. Para usar tabelas hash para esses tipos de dados, +precisamos mapear esses tipos de dados para códigos hash de #w# bits. +Mapeamento de hash codes devem ter as seguintes propriedades: \begin{enumerate} - \item If #x# and #y# are equal, then #x.hashCode()# and #y.hashCode()# - are equal. + \item Se #x# e #y# são iguais, então #x.hashCode()# e #y.hashCode()# são iguais. - \item If #x# and #y# are not equal, then the probability that - $#x.hashCode()#=#y.hashCode()#$ should be small (close to + \item Se #x# e #y# não são iguais, então a probabilidade que + $#x.hashCode()#=#y.hashCode()#$ deve ser pequena (próxima a $1/2^{#w#}$). \end{enumerate} -The first property ensures that if we store #x# in a hash table and later -look up a value #y# equal to #x#, then we will find #x#---as we should. -The second property minimizes the loss from converting our objects -to integers. It ensures that unequal objects usually have different -hash codes and so are likely to be stored at different locations in -our hash table. - -\subsection{Hash Codes for Primitive Data Types} - -\index{hash code!for primitive data}% -Small primitive data types like #char#, #byte#, #int#, and #float# are -usually easy to find hash codes for. These data types always have a -binary representation and this binary representation usually consists of -#w# or fewer bits. \javaonly{(For example, in Java, #byte# is an 8-bit -type and #float# is a 32-bit type.)}\cpponly{(For example, in C++ #char# -is typically an 8-bit type and #float# is a 32-bit type.)} In these -cases, we just treat these bits as the representation of an integer in -the range $\{0,\ldots,2^#w#-1\}$. If two values are different, they get -different hash codes. If they are the same, they get the same hash code. - -A few primitive data types are made up of more than #w# bits, usually -$c#w#$ bits for some constant integer $c$. (Java's #long# and #double# -types are examples of this with $c=2$.) These data types can be treated -as compound objects made of $c$ parts, as described in the next section. - -\subsection{Hash Codes for Compound Objects} +A primeira propriedade assegura que se guardarmos #x# em um tabela hash +e depois olharmos um valor #y# igual a #x#, então iremos achar #x# --- conforme deveríamos. +A segunda propriedade minimiza a perda ao converter nossos objetos a inteiros. +Ela garante que objetos distintos usualmente tenham códigos hash diferentes +e então provavelmente são guardados em posições distintas na nossa +tabela hash. + +\subsection{Códigos hash para Tipos de Dados Primitivos} + +\index{código hash!para dados primitivos}% + +Tipos de dados primitivos pequenos como +#char#, #byte#, #int# e #float# são normal fáceis de obter códigos hash. +Esses tipos de dados sempre têm uma representação binária que usualmente consitem de #w# ou menos bits. +\javaonly{(Por exemplo, em Java, #byte# é um tipo de 8 bits +e #float# é um tipo de 32 bits.)}\cpponly{(Por exemplo, em C++ #char# +é tipicamente de 8 bits e #float# é um tipo de 32 bits.)} Nesses casos, +nós somente tratamos esses bits como a representação de um inteiro no +intervalo $\{0,\ldots,2^#w#-1\}$. Se dois valores são diferentes, eles recebem códigos hash distintos. Se são iguais, recebem o mesmo código hash. + +Alguns dados de tipos primitivos são compostos de mais de #w# bits, normalmente +$c#w#$ bits para alguma constante $c$. (No Java, os tipos #long# e #double# +são exemplos disso com $c=2$.) Esses tipos de dados podem ser tratados como objetos compostos de $c$ partes, conforme descrito na seção a seguir. + +\subsection{Códigos Hash para Objetos Compostos} \seclabel{stringhash} -\index{hash code!for compound objects}% -For a compound object, we want to create a hash code by combining the -individual hash codes of the object's constituent parts. This is not -as easy as it sounds. Although one can find many hacks for this (for -example, combining the hash codes with bitwise exclusive-or operations), -many of these hacks turn out to be easy to foil (see Exercises~\ref{exc:hash-hack-first}--\ref{exc:hash-hack-last}). -However, if one is willing to do arithmetic with $2#w#$ bits of -precision, then there are simple and robust methods available. -Suppose we have an object made up of several parts -$P_0,\ldots,P_{r-1}$ whose hash codes are $#x#_0,\ldots,#x#_{r-1}$. -Then we can choose mutually independent random #w#-bit integers -$#z#_0,\ldots,#z#_{r-1}$ and a random $2#w#$-bit odd integer #z# and -compute a hash code for our object with +\index{códigos hash!para objetos compostos}% +Para um objeto composto, queremos criar um código hash combinando os códigos +hash individuais das partes que constituem o objeto. +Isso não é tão fácil quanto parece. + +Embora seja possível achar muitos hacks para issso (por exemplo, combinar os códigos hash com operações bitwise ou-exclusivo), muitos desses hacks acabam sendo fáceis de enganar (veja os Exercícios~\ref{exc:hash-hack-first}--\ref{exc:hash-hack-last}). +Porém, se quisermos fazer aritmética com $2#w#$ bits de precisão, então +existem métodos simples e robustos a disposição. +Suponha que temos um objeto composto de várias partes +$P_0,\ldots,P_{r-1}$ cujos códigos hash são $#x#_0,\ldots,#x#_{r-1}$. +Então, podemos escolher inteiros de #w# bits mutuamente independentes +$#z#_0,\ldots,#z#_{r-1}$ e um inteiro ímpar aleatório com $2#w#$ bits #z# +e computar um código hash para nosso objeto com \[ h(#x#_0,\ldots,#x#_{r-1}) = \left(\left(#z#\sum_{i=0}^{r-1} #z#_i #x#_i\right)\bmod 2^{2#w#}\right) \ddiv 2^{#w#} \enspace . \] -Note that this hash code has a final step (multiplying by #z# and -dividing by $2^{#w#}$) that uses the multiplicative hash function -from \secref{multihash} to take the $2#w#$-bit intermediate result and -reduce it to a #w#-bit final result. Here is an example of this method applied to a simple compound object with three parts #x0#, #x1#, and #x2#: +Note que esse código hash tem um passo final (multiplicar por #z# e dividir +por $2^{#w#}$) que usa uma função hash multiplicativo de +\secref{multihash} para obter um resultado intermediária $2#w#$ bits +e reduzir a um resultado final de #w# bits. Aque segue um exemplo desse método +aplica a um único objeto composto de três partes + #x0#, #x1#, and #x2#: \javaimport{junk/Point3D.x0.hashCode()} \cppimport{ods/Point3D.hashCode()} \pcodeimport{ods/Point3d.hashCode()} -The following theorem shows that, in addition to being straightforward to implement, this method is provably good: +O teorema a seguir mostra que, em adição a ser de simples implementação, esse +método é comprovadamento bom: \begin{thm}\thmlabel{multihash} -Let $#x#_0,\ldots,#x#_{r-1}$ and $#y#_0,\ldots,#y#_{r-1}$ each be sequences of #w# bit integers in $\{0,\ldots,2^{#w#}-1\}$ and assume $#x#_i \neq #y#_i$ for at least one index $i\in\{0,\ldots,r-1\}$. Then +Sejam $#x#_0,\ldots,#x#_{r-1}$ e $#y#_0,\ldots,#y#_{r-1}$ sequências de inteiros de #w# bits em $\{0,\ldots,2^{#w#}-1\}$ e assuma $#x#_i \neq #y#_i$ para pelo menos um índice $i\in\{0,\ldots,r-1\}$. Então \[ \Pr\{ h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1}) \} \le 3/2^{#w#} \enspace . @@ -668,50 +739,57 @@ \subsection{Hash Codes for Compound Objects} \end{thm} \begin{proof} - We will first ignore the final multiplicative hashing step and see how - that step contributes later. Define: + Iremos a princípio ignorar o passo final de hashing multiplicativo + e ver como aquele passo contribui depois. Defina: \[ h'(#x#_0,\ldots,#x#_{r-1}) = \left(\sum_{j=0}^{r-1} #z#_j #x#_j\right)\bmod 2^{2#w#} \enspace . \] - Suppose that $h'(#x#_0,\ldots,#x#_{r-1}) = h'(#y#_0,\ldots,#y#_{r-1})$. - We can rewrite this as: + Assuma que $h'(#x#_0,\ldots,#x#_{r-1}) = h'(#y#_0,\ldots,#y#_{r-1})$. + Podemos rescrever isso como: \begin{equation} \eqlabel{bighash-x} #z#_i(#x#_i-#y#_i) \bmod 2^{2#w#} = t \end{equation} - where + onde \[ t = \left(\sum_{j=0}^{i-1} #z#_j(#y#_j-#x#_j) + \sum_{j=i+1}^{r-1} #z#_j(#y#_j-#x#_j)\right) \bmod 2^{2#w#} \] - If we assume, without loss of generality that $#x#_i> #y#_i$, then - \myeqref{bighash-x} becomes + Se assumirmos, sem perda de generalidade, que + $#x#_i> #y#_i$, então + \myeqref{bighash-x} torna-se \begin{equation} #z#_i(#x#_i-#y#_i) = t \eqlabel{bighash-xx} \enspace , \end{equation} - since each of $#z#_i$ and $(#x#_i-#y#_i)$ is at most $2^{#w#}-1$, so - their product is at most $2^{2#w#}-2^{#w#+1}+1 < 2^{2#w#}-1$. - By assumption, $#x#_i-#y#_i\neq 0$, so \myeqref{bighash-xx} has at most one - solution in $#z#_i$. Therefore, since $#z#_i$ and $t$ are - independent ($#z#_0,\ldots,#z#_{r-1}$ are mutually independent), the - probability that we select $#z#_i$ - so that $h'(#x#_0,\ldots,#x#_{r-1})=h'(#y#_0,\ldots,#y#_{r-1})$ is at most + pois cada + $#z#_i$ e $(#x#_i-#y#_i)$ são até $2^{#w#}-1$, então os seus produtos + são até + $2^{2#w#}-2^{#w#+1}+1 < 2^{2#w#}-1$. + + Por suposição, + $#x#_i-#y#_i\neq 0$, então \myeqref{bighash-xx} tem no máximo uma solução + em $#z#_i$. Portanto, como $#z#_i$ e $t$ são + independentes ($#z#_0,\ldots,#z#_{r-1}$ são mutuamente independentes), a probabilidade de selecionarmos + $#z#_i$ + tal que $h'(#x#_0,\ldots,#x#_{r-1})=h'(#y#_0,\ldots,#y#_{r-1})$ é no máximo $1/2^{#w#}$. - The final step of the hash function is to apply multiplicative hashing - to reduce our $2#w#$-bit intermediate result $h'(#x#_0,\ldots,#x#_{r-1})$ to - a #w#-bit final result $h(#x#_0,\ldots,#x#_{r-1})$. By \thmref{multihash}, - if $h'(#x#_0,\ldots,#x#_{r-1})\neq h'(#y#_0,\ldots,#y#_{r-1})$, then + O passo final da função hash é aplicar o + hashing multiplicativo + para reduzir nosso resultado intermediário de + $2#w#$ bits $h'(#x#_0,\ldots,#x#_{r-1})$ a um + resultado final com #w# bits $h(#x#_0,\ldots,#x#_{r-1})$. Usando \thmref{multihash}, + se $h'(#x#_0,\ldots,#x#_{r-1})\neq h'(#y#_0,\ldots,#y#_{r-1})$, então $\Pr\{h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1})\} \le 2/2^{#w#}$. - To summarize, + Para resumir, \begin{align*} & \Pr\left\{\begin{array}{l} h(#x#_0,\ldots,#x#_{r-1}) \\ \quad = h(#y#_0,\ldots,#y#_{r-1})\end{array}\right\} \\ &= \Pr\left\{\begin{array}{ll} - \mbox{$h'(#x#_0,\ldots,#x#_{r-1}) = h'(#y#_0,\ldots,#y#_{r-1})$ or} \\ + \mbox{$h'(#x#_0,\ldots,#x#_{r-1}) = h'(#y#_0,\ldots,#y#_{r-1})$ ou} \\ \mbox{$h'(#x#_0,\ldots,#x#_{r-1}) \neq h'(#y#_0,\ldots,#y#_{r-1})$} \\ - \quad \mbox{and + \quad \mbox{e $#z#h'(#x#_0,\ldots,#x#_{r-1})\ddiv2^{#w#} = #z# h'(#y#_0,\ldots,#y#_{r-1})\ddiv 2^{#w#}$} \end{array}\right\} \\ &\le 1/2^{#w#} + 2/2^{#w#} = 3/2^{#w#} \enspace . \qedhere @@ -719,58 +797,57 @@ \subsection{Hash Codes for Compound Objects} \end{proof} -\index{hash code!for strings}% -\index{hash code!for arrays}% -\subsection{Hash Codes for Arrays and Strings} +\index{código hash!para strings}% +\index{código hash!para arrays}% +\subsection{Códigos Hash para Arrays e Strings} \seclabel{polyhash} -The method from the previous section works well for objects that have a -fixed, constant, number of components. However, it breaks down when we -want to use it with objects that have a variable number of components, -since it requires a random #w#-bit integer $#z#_i$ for each component. -We could use a pseudorandom sequence to generate as many $#z#_i$'s as we -need, but then the $#z#_i$'s are not mutually independent, and it becomes -difficult to prove that the pseudorandom numbers don't interact badly -with the hash function we are using. In particular, the values of $t$ -and $#z#_i$ in the proof of \thmref{multihash} are no longer independent. +O método da seção anterior funciona bem para objetos que tem um número constante, fixo, de componentes. Entretanto, ele não funciona bem quando queremos usá-lo com +objetos que tem um número variável de componentes, +pois querer um inteiro aleatório de #w# bits +$#z#_i$ para cada componente. +Poderíamos usar uma sequência pseudoaleatória para gerar quantos +$#z#_i$ fossem necessários, então então os $#z#_i$ não seriam mutuamente independentes e se torna difícil provar que os números pseudoaleatórios não interagem mal com a função hash que estamos usando. +Em particular, os valores de $t$ e +$#z#_i$ na prova de \thmref{multihash} não seriam mais independentes. \index{prime field}% -A more rigorous approach is to base our hash codes on polynomials over -prime fields; these are just regular polynomials that are evaluated -modulo some prime number, #p#. This method is based on the following -theorem, which says that polynomials over prime fields behave pretty-much -like usual polynomials: +\index{corpo primal}% +Uma abordagem mais rigorosa é obter nossos códigos hash com base em polinômios sobre corpos primais; esse são basicamente polinômios comumns que são avaliados em módulo algum número primo, #p#. Esse método é baseado no seguinte teorema, que +diz que polinômios sobre corpos primais se comportam como polinômios comuns: \begin{thm}\thmlabel{prime-polynomial} - Let $#p#$ be a prime number, and let $f(#z#) = #x#_0#z#^0 + #x#_1#z#^1 + - \cdots + #x#_{r-1}#z#^{r-1}$ be a non-trivial polynomial with coefficients - $#x#_i\in\{0,\ldots,#p#-1\}$. Then the equation $f(#z#)\bmod #p# = 0$ - has at most $r-1$ solutions for $#z#\in\{0,\ldots,p-1\}$. + Seja $#p#$ um número primo, e seja $f(#z#) = #x#_0#z#^0 + #x#_1#z#^1 + + \cdots + #x#_{r-1}#z#^{r-1}$ um polinômio não trivial com coeficientes + $#x#_i\in\{0,\ldots,#p#-1\}$. Então a equação $f(#z#)\bmod #p# = 0$ + tem até $r-1$ soluções para $#z#\in\{0,\ldots,p-1\}$. \end{thm} -To use \thmref{prime-polynomial}, we hash a sequence of integers -$#x#_0,\ldots,#x#_{r-1}$ with each $#x#_i\in \{0,\ldots,#p#-2\}$ using -a random integer $#z#\in\{0,\ldots,#p#-1\}$ via the formula +Para usar \thmref{prime-polynomial}, aplicamos uma função hash em uma sequência de inteiros +$#x#_0,\ldots,#x#_{r-1}$ cada qual com $#x#_i\in \{0,\ldots,#p#-2\}$ usando um +inteiro aleatório +$#z#\in\{0,\ldots,#p#-1\}$ via a fórmula \[ h(#x#_0,\ldots,#x#_{r-1}) = \left(#x#_0#z#^0+\cdots+#x#_{r-1}#z#^{r-1}+(#p#-1)#z#^r \right)\bmod #p# \enspace . \] -Note the extra $(#p#-1)#z#^r$ term at the end of the formula. It helps -to think of $(#p#-1)$ as the last element, $#x#_r$, in the sequence -$#x#_0,\ldots,#x#_{r}$. Note that this element differs from every other -element in the sequence (each of which is in the set $\{0,\ldots,#p#-2\}$). -We can think of $#p#-1$ as an end-of-sequence marker. +Note o termo extra +$(#p#-1)#z#^r$ no fim da fórmula. Ajuda a pensar de +$(#p#-1)$ como o último elemento, $#x#_r$, na sequência +$#x#_0,\ldots,#x#_{r}$. Note que esse elemento difere de todos os outros elementos na sequência (cada qual está no conjunto +$\{0,\ldots,#p#-2\}$). +Podemos pensar que $#p#-1$ atua como um marcador de fim de sequência. -The following theorem, which considers the case of two sequences of -the same length, shows that this hash function gives a good return for -the small amount of randomization needed to choose #z#: +O teorema a seguir, que considera o caso de duas sequência de mesmo +tamanho, mostra que essa função hash produz bons resultados +para uma pequena quantidade de randomização necessária para escolher #z#: \begin{thm}\thmlabel{stringhash-eqlen} - Let $#p#>2^{#w#}+1$ be a prime, let $#x#_0,\ldots,#x#_{r-1}$ and - $#y#_0,\ldots,#y#_{r-1}$ each be sequences of #w#-bit integers in - $\{0,\ldots,2^{#w#}-1\}$, and assume $#x#_i \neq #y#_i$ for at least - one index $i\in\{0,\ldots,r-1\}$. Then + Seja $#p#>2^{#w#}+1$ um primo, com $#x#_0,\ldots,#x#_{r-1}$ e + $#y#_0,\ldots,#y#_{r-1}$ sendo sequências de inteiros com #w# bits em + $\{0,\ldots,2^{#w#}-1\}$, e assuma $#x#_i \neq #y#_i$ para pelo menos + um índice $i\in\{0,\ldots,r-1\}$. Então \[ \Pr\{ h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1}) \} \le (r-1)/#p# \enspace . @@ -778,287 +855,295 @@ \subsection{Hash Codes for Arrays and Strings} \end{thm} \begin{proof} - The equation $h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1})$ - can be rewritten as +A equação $h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1})$ +pode ser reescrita como \begin{equation} \eqlabel{strhash-eqlen} \left( (#x#_0-#y#_0)#z#^0+\cdots+(#x#_{r-1}-#y#_{r-1})#z#^{r-1} \right)\bmod #p# = 0. \end{equation} - Since $#x#_#i#\neq #y#_#i#$, this polynomial is non-trivial. Therefore, - by \thmref{prime-polynomial}, it has at most $r-1$ solutions in #z#. - The probability that we pick #z# to be one of these solutions is therefore - at most $(r-1)/#p#$. +Como $#x#_#i#\neq #y#_#i#$, esse polinômio é não-trivial. Portanto, usando , + \thmref{prime-polynomial}, ele tem no máximo $r-1$ soluções em #z#. + A probabilidade que escolhemos #z# para seja uma dessas soluções é portanto + no máximo + $(r-1)/#p#$. \end{proof} -Note that this hash function also deals with the case in which two -sequences have different lengths, even when one of the sequences is a -prefix of the other. This is because this function effectively hashes -the infinite sequence +Note que essa função hash também lida com o caso em que duas sequências tem comprimentos distintos, mesmo quando uma das sequências é um prefixo de outra. +Isso porque essa função efetivamente obtém o hash da sequência infinita \[ #x#_0,\ldots,#x#_{r-1}, #p#-1,0,0,\ldots \enspace . \] -This guarantees that if we have two sequences of length $r$ and $r'$ -with $r > r'$, then these two sequences differ at index $i=r$. In -this case, \myeqref{strhash-eqlen} becomes +Isso garante que se temos duas sequências de comprimento + $r$ e $r'$ +com $r > r'$, então essas duas sequências diferem no índice $i=r$. +Nesse caso, + \myeqref{strhash-eqlen} torna-se \[ \left( \sum_{i=0}^{i=r'-1}(#x#_i-#y#_i)#z#^i + (#x#_{r'} - #p# + 1)#z#^{r'} +\sum_{i=r'+1}^{i=r-1}#x#_i#z#^i + (#p#-1)#z#^{r} \right)\bmod #p# = 0 \enspace , \] -which, by \thmref{prime-polynomial}, has at most $r$ solutions in $#z#$. -This combined with \thmref{stringhash-eqlen} suffice to prove the -following more general theorem: +que, segundo \thmref{prime-polynomial}, tem até $r$ soluções em $#z#$. +Isso combinado com + \thmref{stringhash-eqlen} é suficiente para provar o seguinte teorema mais geral: \begin{thm}\thmlabel{stringhash} - Let $#p#>2^{#w#}+1$ be a prime, let $#x#_0,\ldots,#x#_{r-1}$ and - $#y#_0,\ldots,#y#_{r'-1}$ be distinct sequences of #w#-bit integers in - $\{0,\ldots,2^{#w#}-1\}$. Then +Seja $#p#>2^{#w#}+1$ um primo, usando duas sequências distintas de #w#bits $#x#_0,\ldots,#x#_{r-1}$ e + $#y#_0,\ldots,#y#_{r'-1}$ em + $\{0,\ldots,2^{#w#}-1\}$. Então \[ \Pr\{ h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1}) \} \le \max\{r,r'\}/#p# \enspace . \] \end{thm} -The following example code shows how this hash function is applied to -an object that contains an array, #x#, of values: +O trecho de código a seguir mostra como essa função hash é aplicada a +um objeto que contém um array, #x#, de valores: \javaimport{junk/GeomVector.hashCode()} \cppimport{ods/GeomVector.hashCode()} \pcodeimport{ods/GeomVector.hashCode()} -The preceding code sacrifices some collision probability for implementation -convenience. In particular, it applies the multiplicative hash function -from \secref{multihash}, with $#d#=31$ to reduce #x[i].hashCode()# to a -31-bit value. This is so that the additions and multiplications that are -done modulo the prime $#p#=2^{32}-5$ can be carried out using unsigned -63-bit arithmetic. Thus the probability of two different sequences, -the longer of which has length $r$, having the same hash code is at most +O código precedendo sacrifica alguma probabilidade de colisão por conveniência +na implementação. Em particular, ele aplica a função hash multiplicativa de +\secref{multihash}, com $#d#=31$ para reduzir #x[i].hashCode()# a um valor de 31 bits. Isso é feito dessa forma para que adições e multiplicações +feitas módulo o primo +$#p#=2^{32}-5$ possam ser realizadas usando aritmética 63 bits sem sinal. +Então a probabilidade de duas diferentes sequências, a mais longa com comprimento #r#, terem o mesmo código hash é até \[ 2/2^{31} + r/(2^{32}-5) \] -rather than the $r/(2^{32}-5)$ specified in \thmref{stringhash}. +em vez da +$r/(2^{32}-5)$ especificada em \thmref{stringhash}. -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -Hash tables and hash codes represent an enormous and active field -of research that is just touched upon in this chapter. The online -Bibliography on Hashing \cite{hashing} +Tabelas hash e códigos hash representam um campo de pesquisa muito ativo que é brevemente pincelado neste capítulo. +A online \emph{Bibliography on Hashing} \cite{hashing} \index{Bibliography on Hashing}% -contains nearly 2000 entries. +contém aproximadamente 2000 referencias. -A variety of different hash table implementations exist. The one -described in \secref{hashtable} is known as \emph{hashing with chaining} +Existe uma grande variedade de implementações de diferentes tabelas. +Aquela descrita em +\secref{hashtable} é conhecida como \emph{hashing with chaining} ou \emph{hashing com encadeamento} \index{hashing with chaining}% -(each array entry contains a chain (#List#) of elements). Hashing with -chaining dates back to an internal IBM memorandum authored by H. P. Luhn -and dated January 1953. This memorandum also seems to be one of the -earliest references to linked lists. +\index{hashing com encadeamento} +(cada posição do array contém uma lista, por vezes encadeada, (#List#) de elementos). Hashing com encadeamento tem suas origens em um +memorando interno da IBM escrito por + H. P. Luhn que data de Janeiro de 1953. Esse memorando também parece + ser uma das primeiras referências a listas ligadas. \index{open addressing}% -An alternative to hashing with chaining is that used by \emph{open -addressing} schemes, where all data is stored directly in an -array. These schemes include the #LinearHashTable# structure of -\secref{linearhashtable}. This idea was also proposed, independently, by -a group at IBM in the 1950s. Open addressing schemes must deal with the -problem of \emph{collision resolution}: -\index{collision resolution}% -the case where two values hash -to the same array location. Different strategies exist for collision -resolution; these provide different performance guarantees and often -require more sophisticated hash functions than the ones described here. - -Yet another category of hash table implementations are the so-called -\emph{perfect hashing} methods. -\index{perfect hashing}% -These are methods in which #find(x)# -operations take $O(1)$ time in the worst-case. For static data sets, -this can be accomplished by finding \emph{perfect hash functions} -\index{perfect hash function}% -\index{hash function!perfect}% -for -the data; these are functions that map each piece of data to a unique -array location. For data that changes over time, perfect hashing -methods include \emph{FKS two-level hash tables} -\index{two-level hash table}% -\index{hash table!two-level}% +\index{endereçamento aberto}% +Uma alternativa a hashing com encadeamento é aquela que usa esquemas +de + \emph{open +addressing}, ou , \emph{endereçamento aberto}, onde todos os dados são armazenados diretamente em um array. +Esses esquemas incluem a estrutura + #LinearHashTable# de +\secref{linearhashtable}. Essa ideia também foi proposta independentemente por um +grupo na IBM na década de 1950. Esquemas de endereçamento aberto devem tratar do problema de resolução de colisões: +\emph{resolução de colisões}: +\index{resolução de colisões}% +o caso em que dois valores hash mapeiam para a mesma posição do array. +Dintintas estratégias existem para resolução de colisões; essas provêem +diferentes garantias de desempenho e frequentemente +requerem funções hash mais sofisticadas que aquelas descritas aqui. + +Outra categoria de implementações de tabelas hash são os famosos +métodos de \emph{hashing perfeito}. +\index{hashing perfeito}% +Nesses métodos a operação + #find(x)# leva $O(1)$ de tempo no pior caso. + Para conjuntos de dados estáticos isso pode ser realizar ao achar + \emph{funções hash perfeitas} +\index{funções hash perfeitas}% +\index{função hash!perfeita}% +para os dadoss; existem funções que mapeiam cada dado da uma única +posição do array. Para dados que mudam com o tempo, métodos +de hashing perfeito incluem + \emph{tabelas hash de dois níveis FKS} +\index{tabela hash de dois níveis}% +\index{tabela hash!dois níveis}% \cite{fks84,dkkmrt94} -and \emph{cuckoo hashing} \cite{pr04}. -\index{cuckoo hashing}% -\index{hash table!cuckoo}% - -The hash functions presented in this chapter are probably among the most -practical methods currently known that can be proven to work well for any -set of data. Other provably good methods date back to the pioneering work -of Carter and Wegman who introduced the notion of \emph{universal hashing} -\index{universal hashing}% -\index{hashing!universal}% -and described several hash functions for different scenarios \cite{cw79}. -Tabulation hashing, described in \secref{tabulation}, is due to Carter -and Wegman \cite{cw79}, but its analysis, when applied to linear probing -(and several other hash table schemes) is due to P\v{a}tra\c{s}cu and +e \emph{hashing cuckoo} \cite{pr04}. +\index{hashing cuckoo }% +\index{tabela hash!cuckoo}% + +As funções has apresentadas neste capítulo estão provavelmente entre +os métodos práticos atualmente conhecidos que funcionam bem para qualquer +conjunto de dados. Outros métodos bons datam do trabalho pioneiro +de +Carter e Wegman que criaram o conceito de \emph{hashing universal} +\index{hashing universal }% +\index{universal!hashing}% +e descreveram várias funções hash para diferentes cenários \cite{cw79}. +Hashing por tabulação, descrita em + \secref{tabulation}, foi proposta por Carter +e Wegman \cite{cw79}, mas sua análise, quando aplicada a sondagem linear (e vários outros esquemas de tabela hash) +é creditada a P\v{a}tra\c{s}cu e Thorup \cite{pt12}. -The idea of \emph{multiplicative hashing} -\index{multiplicative hashing}% -\index{hashing!multiplicative}% -is very old and seems to be -part of the hashing folklore \cite[Section~6.4]{k97v3}. However, the -idea of choosing the multiplier #z# to be a random \emph{odd} number, -and the analysis in \secref{multihash} is due to Dietzfelbinger \etal\ -\cite{dhkp97}. This version of multiplicative hashing is one of the -simplest, but its collision probability of $2/2^{#d#}$ is a factor of two -larger than what one could expect with a random function from $2^{#w#}\to -2^{#d#}$. The \emph{multiply-add hashing} -\index{hashing!multiply-add}% -\index{multiply-add hashing}% -method uses the function +A ideia de + \emph{hashing multiplicativo} +\index{hashing multiplicativo}% +\index{hashing!multiplicativo}% +é muito antiga e parece ser parte do folklore de hashing + \cite[Section~6.4]{k97v3}. Porém, a ideia de escolher um +#z# sendo um número aleatório \emph{ímpar}, +e a análise em \secref{multihash} é de Dietzfelbinger \etal\ +\cite{dhkp97}. Essa versão de hashing multiplicativo é uma das mais +simples, mas sua probabilidade de colisão de +$2/2^{#d#}$ é um fator de 2 maior que se poderia esperar com uma função +aleatória de +$2^{#w#}\to +2^{#d#}$. O método de \emph{hashing multiplica e soma} +\index{hashing!multiplica e soma}% +\index{hashing multiplica e soma}% \[ h(#x#) = ((#z##x# + b) \bmod 2^{#2w#}) \ddiv 2^{#2w#-#d#} \] -where #z# and #b# are each randomly chosen from $\{0,\ldots,2^{#2w#}-1\}$. -Multiply-add hashing has a collision probability of only $1/2^{#d#}$ -\cite{d96}, but requires $2#w#$-bit precision arithmetic. - -There are a number of methods of obtaining hash codes from fixed-length -sequences of #w#-bit integers. One particularly fast method -\cite{bhkkr99} is the function +onde +#z# e #b# são escolhidos aleatoriamente de $\{0,\ldots,2^{#2w#}-1\}$. +Hashing multiplica e soma tem uma probabilidade de colisão de somente +$1/2^{#d#}$ +\cite{d96}, mas precisa de aritmética de $2#w#$ bits. + +Há vários métodos de obter códigos hash a partir de sequências de inteiros com +#w# bits. Um método rápido +\cite{bhkkr99} é a função \[\begin{array}{l} h(#x#_0,\ldots,#x#_{r-1}) \\ \quad = \left(\sum_{i=0}^{r/2-1} ((#x#_{2i}+#a#_{2i})\bmod 2^{#w#})((#x#_{2i+1}+#a#_{2i+1})\bmod 2^{#w#})\right) \bmod 2^{2#w#} \end{array} \] -where $r$ is even and $#a#_0,\ldots,#a#_{r-1}$ are randomly chosen from -$\{0,\ldots,2^{#w#}\}$. This yields a $2#w#$-bit hash code that has -collision probability $1/2^{#w#}$. This can be reduced to a #w#-bit hash -code using multiplicative (or multiply-add) hashing. This method is fast -because it requires only $r/2$ $2#w#$-bit multiplications whereas the -method described in \secref{stringhash} requires $r$ multiplications. -(The $\bmod$ operations occur implicitly by using #w# and $2#w#$-bit -arithmetic for the additions and multiplications, respectively.) - -The method from \secref{polyhash} of using polynomials over prime fields -to hash variable-length arrays and strings is due to Dietzfelbinger \etal\ -\cite{dgmp92}. Due to its use of the $\bmod$ operator which relies -on a costly machine instruction, it is, unfortunately, not very fast. -Some variants of this method choose the prime #p# to be one of the form -$2^{#w#}-1$, in which case the $\bmod$ operator can be replaced with -addition (#+#) and bitwise-and (#&#) operations \cite[Section~3.6]{k97v2}. -Another option is to apply one of the fast methods for fixed-length -strings to blocks of length $c$ for some constant $c>1$ and then apply -the prime field method to the resulting sequence of $\lceil r/c\rceil$ -hash codes. +onde +$r$ é um número par e $#a#_0,\ldots,#a#_{r-1}$ são escolhidos aleatoriamente de +$\{0,\ldots,2^{#w#}\}$. Isso resulta em um código hash de $2#w#$ bits com probabilidade de colisão +$1/2^{#w#}$. Isso pode ser reduzido a um código hash de #w# bits usando +hashing multiplicativo (ou multiplica e soma). Esse método é rápido porque +requer somente + $r/2$ multiplicações de $2#w#$ bits enquanto o método descrito em +\secref{stringhash} usa $r$ multiplicações. +(As operações $\bmod$ ocorrem implicitamente usando aritmética de #w# e $2#w#$ bits para as adições e multiplicações, respectivamente.) + +O método de + \secref{polyhash} de usar polinômios em corpos primais para fazer hash de arrays de tamanho variável e strings é atribuído a +Dietzfelbinger \etal\ +\cite{dgmp92}. Devido ao uso do operador $\bmod$ que usa uma instrução de máquina de alto custo, não é, infelizmente, muito rápida. +Algumas variantes desse método escolhem o primo +#p# para ser da forma +$2^{#w#}-1$, que nesse caso o operador $\bmod$ pode ser substituído pelas operações de +adição (#+#) e and bit a bit (#&#) \cite[Section~3.6]{k97v2}. +Outra opção é aplicar um dos métodos rápidos para strings de tamanho fixo para blocos de tamanho $c$ para alguma constante $c>1$ e então aplicar o +método de corpo primal à sequência resultante de + $\lceil r/c\rceil$ +códigos hash. \begin{exc} - A certain university assigns each of its students student numbers the - first time they register for any course. These numbers are sequential - integers that started at 0 many years ago and are now in the millions. - Suppose we have a class of one hundred first year students and we want - to assign them hash codes based on their student numbers. Does it - make more sense to use the first two digits or the last two digits of - their student number? Justify your answer. + Uma certa universidade atribui a cada um de seus estudantes números + na primeira vez que eles registram-se para qualquer disciplina. + Esses números são inteiros sequencias que iniciam-se em 0 muitos + anos atrás e agora estão na casa dos milhões. + Suponha que temos uma turma de cem alunos do primeiro ano + que queremos atribuí-los código hash baseados em seus números de estudantes. + Faz mais sentido usar os dois primeiros dois dígitos ou os últimos dois + dígitos do número de estudante deles? Justifique sua resposta. \end{exc} \begin{exc} - Consider the hashing scheme in \secref{multihash}, and suppose - $#n#=2^{#d#}$ and $#d#\le #w#/2$. + Considere o esquema de hashing em \secref{multihash}, e suponha + $#n#=2^{#d#}$ e $#d#\le #w#/2$. \begin{enumerate} - \item Show that, for any choice - of the muliplier, #z#, there exists #n# values that all have - the same hash code. (Hint: This is easy, and doesn't require any - number theory.) - \item Given the multiplier, #z#, describe #n# values that all - have the same hash code. (Hint: This is harder, and requires - some basic number theory.) + \item Mostre que, para qualquer escolha de multiplicador #z#, existe #n# valores que tem o mesmo código hash. + (Dica: isso é fácil e não precisa usar teoria dos números.) + \item Dado um multiplicador #z#, descreva #n# valores que tem o mesmo código hash. (Dica: isso é mais difícil e requer um pouco de teoria dos números.) \end{enumerate} \end{exc} \begin{exc} - Prove that the bound $2/2^{#d#}$ in \lemref{universal-hashing} is - the best possible bound by showing that, if $x=2^{#w#-#d#-2}$ and - $#y#=3#x#$, then $\Pr\{#hash(x)#=#hash(y)#\}=2/2^{#d#}$. (Hint look - at the binary representations of $#zx#$ and $#z#3#x#$ and use the fact - that $#z#3#x# = #z#x#+2#z#x#$.) + Prove que o limitante $2/2^{#d#}$ em \lemref{universal-hashing} é o + melhor limitante possível ao mostrar que, se + $x=2^{#w#-#d#-2}$ e + $#y#=3#x#$, então $\Pr\{#hash(x)#=#hash(y)#\}=2/2^{#d#}$. (Dica: + verifique as representações binárias de + $#zx#$ e $#z#3#x#$ e o uso o fato de que + $#z#3#x# = #z#x#+2#z#x#$.) \end{exc} \begin{exc}\exclabel{linear-probing} - Reprove \lemref{linear-probing} using the full version of Stirling's - Approximation given in \secref{factorials}. + Prove \lemref{linear-probing} usando a versão completa da aproximação de Stirling + dada em +\secref{factorials}. \end{exc} \begin{exc} - Consider the following simplified version of the code for adding - an element #x# to a #LinearHashTable#, which simply stores #x# in the - first #null# array entry it finds. Explain why this could be very slow - by giving an example of a sequence of $O(#n#)$ #add(x)#, #remove(x)#, - and #find(x)# operations that would take on the order of $#n#^2$ - time to execute. + Considere a seguinte versão simplificada do código para adicionar um elemento #x# a uma + #LinearHashTable#, que simplesmente guarda #x# na primeira posição + do array que esteja #null#. Explique porque isso poderia ficar muito lento fornecendo um exemplo de uma sequência de + operações $O(#n#)$ #add(x)#, #remove(x)#, + e #find(x)# que levaria $O(#n#^2)$ de tempo para executar. \codeimport{ods/LinearHashTable.addSlow(x)} \end{exc} \begin{exc} - Early versions of the Java #hashCode()# method for the #String# class - worked by not using all of the characters found in long strings. For - example, for a sixteen character string, the hash code was computed - using only the eight even-indexed characters. Explain why this was a - very bad idea by giving an example of large set of strings that all - have the same hash code. + Versões iniciais do método + #hashCode()# do Java para a classe #String# não usava todos os caracteres + disponíveis em strings longas. Por exemplo, para uma string com dezesseis caracteres, o código hash era computado usando somente oito caracteres com índices pares. Explique porque isso é uma ideia muito ruim fornecendo um exemplo de um grande conjunto de strings que possuem o mesmo código hash. \end{exc} \begin{exc}\exclabel{hash-hack-first} - Suppose you have an object made up of two #w#-bit integers, #x# and #y#. - Show why $#x#\oplus#y#$ does not make a good hash code for your object. - Give an example of a large set of objects that would all have hash - code 0. + Suponha que você tem um objeto composto de dois inteiros de #w# bits, #x# e #y#. + Mostre porque + $#x#\oplus#y#$ não é um bom código hash para o seu objeto. + Dê um exemplo de um grande conjunto de objetos que teriam código hash 0. \end{exc} \begin{exc} - Suppose you have an object made up of two #w#-bit integers, #x# and #y#. - Show why $#x#+#y#$ does not make a good hash code for your object. - Give an example of a large set of objects that would all have the same - hash code. -\end{exc} + Suponha que você tem um objeto feito de dois inteiros de #w# bits, #x# e #y#. + Mostre porque + $#x#+#y#$ não seria um bom código hash para o seu objeto. + Dê um exemplo de grande conjunto de objetos que teriam o mesmo código hash. + \end{exc} \begin{exc}\exclabel{hash-hack-last} - Suppose you have an object made up of two #w#-bit integers, #x# - and #y#. Suppose that the hash code for your object is defined - by some deterministic function $h(#x#,#y#)$ that produces a single - #w#-bit integer. Prove that there exists a large set of objects that - have the same hash code. + Suponha que você tem um objeto composto de dois inteiros de #w# bits, #x# e #y#. + Suponha que o código hash para o seu objeto é definido por alguma função hash determinística + $h(#x#,#y#)$ que produz um único inteiro de #w# bits. + Prove que existe um grande conjunto de objetos que tem o mesmo código hash. \end{exc} \begin{exc} - Let $p=2^{#w#}-1$ for some positive integer #w#. Explain why, for a - positive integer $x$ +Seja $p=2^{#w#}-1$ para algum inteiro positivo #w#. Explique porque, para um inteiro positivo $x$ \[ (x\bmod 2^{#w#}) + (x\ddiv 2^{#w#}) \equiv x \bmod (2^{#w#}-1) \enspace . \] \javaonly{ - (This gives an algorithm for computing $x \bmod (2^{#w#}-1)$ by - repeatedly setting + (Isso resulta em um algoritmo para computar $x \bmod (2^{#w#}-1)$ atribuindo repetidamente \javaonly{\[ #x = x&((1<>>w# \]} \cpponly{\[ #x = x&((1<>w# \]} - until $#x# \le 2^{#w#}-1$.)} + até $#x# \le 2^{#w#}-1$.)} \end{exc} \begin{exc} - Find some commonly used hash table implementation such as the \javaonly{Java + Ache alguma implementação comumente usada de tabela hash como, por exemplo, + \javaonly{Java Collection Framework #HashMap#,}\cpponly{C++ STL #unordered\_map#,} - the #HashTable# or #LinearHashTable# implementations in this book, - and design a program that stores integers in this data - structure so that there are integers, #x#, such that #find(x)# takes - linear time. That is, find a set of #n# integers for which there are - $c#n#$ elements that hash to the same table location. - - Depending on how good the implementation is, you may be able to do this - just by inspecting the code for the implementation, or you may have to - write some code that does trial insertions and searches, timing how long - it takes to add and find particular values. (This can be, and has been, - used to launch denial of service attacks on web servers \cite{cw03}.) - \index{algorithmic complexity attack}% + que são implementações da #HashTable# ou #LinearHashTable# deste livro e + projete um programa que guarda inteiros nessa estrutura de dados + de forma que haja inteiros #x# tais que #find(x)# gaste tempo linear. + Isso é, encontre um conjunto de #n# inteiros para os quais existam + $c#n#$ elementos cuja hash mapeia para a mesma posição da tabela. + + Dependendo da qualidade da implementação, você pode ser capaz de + fazê-los ao inspecionar o código da implementação, ou você pode ter que + escrever algum código que fazer tentativas de inserções e buscas, medindo + o tempo que leva para adicionar e achar valores. (Isso pode ser, tem sido, usado para efetuar ataques de negação de serviço em servidores web \cite{cw03}.) + \index{ataque de complexidade algoritmica}% \end{exc} From 199b31e0ffc90cbb7fce4f1b2752e48f265cd6a2 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 19 Aug 2020 10:12:55 -0300 Subject: [PATCH 24/66] translation to portuguese --- latex/binarytrees.tex | 709 ++++++++++++++++++++---------------------- latex/hashing.tex | 3 +- 2 files changed, 346 insertions(+), 366 deletions(-) diff --git a/latex/binarytrees.tex b/latex/binarytrees.tex index 5f4b0c37..4821f4c6 100644 --- a/latex/binarytrees.tex +++ b/latex/binarytrees.tex @@ -1,50 +1,47 @@ -\chapter{Binary Trees} +\chapter{Árvores Binárias} \chaplabel{binarytrees} -This chapter introduces one of the most fundamental structures in computer -science: binary trees. The use of the word \emph{tree} -\index{tree}% -\index{tree!binary}% -\index{binary tree}% -here comes from -the fact that, when we draw them, the resultant drawing often resembles -the trees found in a forest. There are many ways of ways of defining -binary trees. Mathematically, a \emph{binary tree} is a connected, -undirected, finite graph with no cycles, and no vertex of degree greater -than three. - -For most computer science applications, binary trees are \emph{rooted:} -\index{tree!rooted}% -\index{rooted tree}% -A special node, #r#, of degree at most two is called the \emph{root} -of the tree. For every node, $#u#\neq #r#$, the second node on the -path from #u# to #r# is called the \emph{parent} of #u#. -\index{parent}% -Each of the -other nodes adjacent to #u# is called a \emph{child} -\index{child} of #u#. Most of the -binary trees we are interested in are \emph{ordered}, -\index{ordered tree}% -\index{tree!ordered}% -so we distinguish -between the \emph{left child} and \emph{right child} of #u#. -\index{left child}% -\index{child!left}% -\index{right child}% -\index{child!right}% - -In illustrations, binary trees are usually drawn from the root -downward, with the root at the top of the drawing and the left and right -children respectively given by left and right positions in the drawing -(\figref{bintree-orientation}). For example, \figref{binary-tree}.a shows -a binary tree with nine nodes. +Este capítulo apresenta uma das estruturas mais fundamentais em ciência +da computação: árvores binárias. O uso da palavra +\emph{árvore} +\index{árvore}% +\index{árvore!binária}% +\index{árvore binária}% +vem do fato que, quando as desenhamos, ela frequentemente lembra árvores +encontradas em uma floresta. Existem muitos jeitos de definir +árvores binárias. Matematicamente, uma \emph{árvore binária} é um grafo finito, conectado, não direcionado, sem ciclos e sem nenhum vértice de grau maior que três. + +Para a maioria das aplicações em Ciência da Computação árvores binárias são \emph{enraizadas}: +\index{árvore!enraizada}% +\index{árvore enraizada}% +Um nodo especial #r# de grau até dois é chamado de \emph{raiz} da árvore. +Para todo nodo +$#u#\neq #r#$, o segundo nodo no caminho de +#u# a #r# é chamado de \emph{pai} de #u#. +\index{pai}% +Cada um dos outros nodos adjacentes a #u# é chamado de \emph{filho} +\index{filho} de #u#. A maior parte das árvores binárias que +estamos interessados são +\emph{ordenadas}, +\index{árvore ordenada}% +\index{árvore!ordenada}% +então diferenciamos entre o \emph{filho à esquerda} e \emph{filho à direita} de #u#. +\index{filho à esquerda}% +\index{filho!esquerda}% +\index{filho à direita}% +\index{filho!direita}% + +Em desenhos, árvores binárias são tipicamente desenhadas de ponta-cabeça, +com a raiz no topo do desenho e os filhos à esquerda e direita +respectivamente dados pelas posições à esquerda e direita no desenho +(\figref{bintree-orientation}). Por exemplo, \figref{binary-tree}.a mostra +uma árvore binária com nove nodos. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/bintree-traverse-1} \end{center} - \caption[Parent, left child, and right child]{The parent, left child, and right child of the node #u# - in a #BinaryTree#.} + \caption[Pai, filho à esquerda e filho à direita]{O pai, filho à esquerda e filho à direita do nodo #u# em uma #BinaryTree#.} \figlabel{bintree-orientation} \end{figure} @@ -57,99 +54,95 @@ \chapter{Binary Trees} (a) & (b) \end{tabular} \end{center} - \caption{A binary tree with (a)~nine real nodes and (b)~ten external nodes.} + \caption{Uma árvore binária com (a)~nove nodos reais e (b)~dez nodos externos.} \figlabel{binary-tree} \end{figure} -Because binary trees are so important, a certain terminology has developed -for them: The \emph{depth} -\index{depth}% -of a node, #u#, in a binary tree is the -length of the path from #u# to the root of the tree. If a node, #w#, -is on the path from #u# to #r#, then #w# is called an \emph{ancestor} -\index{ancestor}% -of #u# and #u# a \emph{descendant} -\index{descendant}% -of #w#. The \emph{subtree} of a -node, #u#, is the binary tree that is rooted at #u# and contains all -of #u#'s descendants. The \emph{height} -\index{height!in a tree} of a node, #u#, is the length -of the longest path from #u# to one of its descendants. The \emph{height} of -\index{height!of a tree}% -a tree is the height of its root. -A node, #u#, is a \emph{leaf} -\index{leaf}% -if it has no children. - -We sometimes think of the tree as being augmented with \emph{external -nodes}. Any node that does not have a left child has an external -node as its left child, and, correspondingly, any node that does -not have a right child has an external node as its right child (see -\figref{binary-tree}.b). It is easy to verify, by induction, that a -binary tree with $#n#\ge 1$ real nodes has $#n#+1$ external nodes. - - -\section{#BinaryTree#: A Basic Binary Tree} +Devido à importância de árvores binárias, uma certa terminologia se desenvolveu +para elas: a \emph{profundidade} +\index{profundidade}% +de um nodo #u#, em uma árvore binária é o comprimento do caminho de #u# à raiz da árvore. Se um nodo #w# está no caminho de #u# para #r#, então #w# é chamado de \emph{ancestral} +\index{ancestral}% +de #u# e #u# um \emph{descendente} +\index{descendente}% +de #w#. A \emph{subárvore} de um nodo +, #u#, é a árvore binária que é enraizada em #u# e contém todos os descendentes de #u#. A \emph{altura} +\index{altura!em uma árvore} de um nodo, #u#, é o comprimento do caminho mais +longo de #u# e um de seus descendentes. A \emph{altura} de +\index{altura!de uma árvore}% +uma árvore é a altura de sua raiz. +Um nodo #u# é uma \emph{folha} +se não tem filhos. + +Ás vezes pensamos de árvores sendo extendidas com \emph{nodos externos}. +Qualquer nodo que não tem filho à esquerda tem um nodo externo como filho à +esquerda e, de modo similar, um nodo que não tem filho à direita tem um nodo externo como filho à direita (ver +\figref{binary-tree}.b). É fácil verificar, por indução, que uma árvore binária +com $#n#\ge 1$ nodos reais tem $#n#+1$ nodos externos. + +\section{#BinaryTree#: Uma Árvore Binária Básica} \index{BinaryTree@#BinaryTree#}% -The simplest way to represent a node, #u#, in a binary tree is to -explicitly store the (at most three) neighbours of #u#\notpcode{:}\pcodeonly{.} +O jeito mais simples de representar um nodo #u# em uma árvore binária +é explicitamente guardar os +(no máximo três) vizinhos de #u#\notpcode{:}\pcodeonly{.} \javaimport{ods/BinaryTree.BTNode #u.x#$, then the search proceeds to #u.right#; -\item If $#x#= #u.x#$, then we have found the node #u# containing #x#. +\item Se $#x#< #u.x#$, então a busca segue para #u.left#; +\item Se $#x#> #u.x#$, então a busca segue para #u.right#; +\item Se $#x#= #u.x#$, então achamos o nodo #u# contendo #x#. \end{enumerate} -The search terminates when Case~3 occurs or when #u=nil#. In the -former case, we found #x#. In the latter case, we conclude that #x# -is not in the binary search tree. +A busca termina quando o Caso~3 ocorre ou quando #u=nil#. +No Caso~3, achamos #x#. Quando #u=nil#, concluímos que #x# não está +na árvore binária de busca. \codeimport{ods/BinarySearchTree.findEQ(x)} -Two examples of searches in a binary search tree are shown in -\figref{bst-search}. As the second example shows, even if we don't -find #x# in the tree, we still gain some valuable information. If we -look at the last node, #u#, at which Case~1 occurred, we see that #u.x# -is the smallest value in the tree that is greater than #x#. Similarly, -the last node at which Case~2 occurred contains the largest value in the -tree that is less than #x#. Therefore, by keeping track of the last -node, #z#, at which Case~1 occurs, a #BinarySearchTree# can implement -the #find(x)# operation that returns the smallest value stored in the -tree that is greater than or equal to #x#: +Dois exemplos de buscas em uma árvore binária de busca são mostrados +em +\figref{bst-search}. Conforme o segundo exemplo mostra, mesmo se não +acharmos $x$ no último nodo #u#, no qual ocorre o Caso~1, vemos que #u.x# é +o menor valor na árvore que é maior que #x#. +De modo similar, o último nodo no qual Caso~2 ocorreu contém o maior valor na +árvore que é menor que #x#. Portanto, ao registrar o último nodo #z# no qual Caso~1 ocorre, uma + #BinarySearchTree# pode implementar a operação +#find(x)# que retorna o menor valor guardado na árvore que é maior que ou igual a #x#: \codeimport{ods/BinarySearchTree.find(x)} \begin{figure} @@ -266,24 +249,26 @@ \subsection{Searching} (a) & (b) \end{tabular} \end{center} - \caption{An example of (a)~a successful search (for $6$) and (b)~an unsuccessful search (for $10$) in a binary search tree.} + \caption{Um exemplo de (a)~uma busca bem sucedida (por $6$) e (b)~uma busca mal-sucedida (por $10$) em uma árvore binária de busca.} \figlabel{bst-search} \end{figure} -\subsection{Addition} +\subsection{Adição} -To add a new value, #x#, to a #BinarySearchTree#, we first search for -#x#. If we find it, then there is no need to insert it. Otherwise, -we store #x# at a leaf child of the last node, #p#, encountered during the -search for #x#. Whether the new node is the left or right child of #p# depends on the result of comparing #x# and #p.x#. +Para adicionar um novo valor #x# a uma +#BinarySearchTree#, primeiro buscamos por #x#. +Se acharmos, então não há necessidade de o inserirmos. Por outro lado, +guardamos #x# na folha do último nodo #p# encontrado durante a busca +por #x#. Se um novo nodo está no filho à esquerda ou à direita de #p# depende +no resultado da comparação entre + #x# e #p.x#. \codeimport{ods/BinarySearchTree.add(x)} \codeimport{ods/BinarySearchTree.findLast(x)} \codeimport{ods/BinarySearchTree.addChild(p,u)} -An example is shown in \figref{bst-insert}. The most time-consuming -part of this process is the initial search for #x#, which takes an -amount of time proportional to the height of the newly added node #u#. -In the worst case, this is equal to the height of the #BinarySearchTree#. +Um exemplo é mostrado em +\figref{bst-insert}. A parte que mais gasta tempo desse processo é a busca inicial por #x#, que leva um tempo proporcional à altura do novo nodo #u#. +No pior caso, isso é igual à altura da #BinarySearchTree#. \begin{figure} @@ -293,17 +278,20 @@ \subsection{Addition} \includegraphics[width=\HalfScaleIfNeeded]{figs/bst-example-5} \end{tabular} \end{center} - \caption{Inserting the value $8.5$ into a binary search tree.} + \caption{Inserindo o valor $8.5$ em uma árvore binária de busca.} \figlabel{bst-insert} \end{figure} -\subsection{Removal} +\subsection{Remoção} -Deleting a value stored in a node, #u#, of a #BinarySearchTree# is a -little more difficult. If #u# is a leaf, then we can just detach #u# -from its parent. Even better: If #u# has only one child, then we can -splice #u# from the tree by having #u.parent# adopt #u#'s child (see +Remover um valor guardado em um nodo #u# de uma +#BinarySearchTree# é um pouco mais difícil. +Se #u# for uma folha, então podemos simplesmente desconectar #u# de seu pai. + +Melhor ainda, se #u# tiver um único filho, então podemos destacar #u# +da árvore fazendo +#u.parent# adotar o filho de #u# (veja \figref{bst-splice}): \codeimport{ods/BinarySearchTree.splice(u)} @@ -311,18 +299,18 @@ \subsection{Removal} \begin{center} \includegraphics[scale=0.90909]{figs/bst-splice} \end{center} - \caption{Removing a leaf ($6$) or a node with only one child ($9$) is easy.} + \caption{Removendo uma folha ($6$) ou um nodo com somente um filho ($9$) é fácil.} \figlabel{bst-splice} \end{figure} -Things get tricky, though, when #u# has two children. In this case, -the simplest thing to do is to find a node, #w#, that has less than -two children and such that #w.x# can replace #u.x#. To maintain -the binary search tree property, the value #w.x# should be close to the -value of #u.x#. For example, choosing #w# such that #w.x# is the smallest -value greater than #u.x# will work. Finding the node #w# is easy; it is -the smallest value in the subtree rooted at #u.right#. This node can -be easily removed because it has no left child (see \figref{bst-remove}). +A situação complicada quando #u# tem dois filhos. Nesse caso, +o mais simples a fazer é achar um nodo #w#, que seja menor que +os dois filho e tal que #w.x# possa substituir #u.x#. +Para manter a propriedade da árvore de busca binára, o valor +#w.x# deveria ser próximo ao valor de #u.x#. Por exemplo, escolher um #w# tal que #w.x# é o menor valor maior que #u.x# funcionaria. +Achar o nodo #w# é fácil; é o menor valor na subárvore enraizada em #u.right#. +Esse nodo pode ser facilmente removido porque não tem filho à esquerda +(veja \figref{bst-remove}). \javaimport{ods/BinarySearchTree.remove(u)} \cppimport{ods/BinarySearchTree.remove(u)} \pcodeimport{ods/BinarySearchTree.remove_node(u)} @@ -334,146 +322,146 @@ \subsection{Removal} \includegraphics[width=\HalfScaleIfNeeded]{figs/bst-delete-2} \end{tabular} \end{center} - \caption[Deleting from a BinarySearchTree]{Deleting a value ($11$) from a node, #u#, with two children is done by replacing #u#'s value with the smallest value in the right subtree of #u#.} + \caption[Removendo de uma BinarySearchTree]{A remoção de um valor ($11$) de um nodo, #u#, com dois filhos é feita pela substituição do valor de #u# com o menor valor na subárvore à direita de #u#.} \figlabel{bst-remove} \end{figure} -\subsection{Summary} +\subsection{Resumo} -The #find(x)#, #add(x)#, and #remove(x)# operations in a -#BinarySearchTree# each involve following a path from the root of the -tree to some node in the tree. Without knowing more about the shape of -the tree it is difficult to say much about the length of this path, -except that it is less than #n#, the number of nodes in the tree. -The following (unimpressive) theorem summarizes the performance of the -#BinarySearchTree# data structure: +A operações +#find(x)#, #add(x)# e #remove(x)# em uma +#BinarySearchTree# envolvem seguir um caminho da raiz da árvore a algum nodo. +Sem saber mais sobre o formato da árvore é difícil dizer algo sobre o comprimento desse caminho, exceto que é menor que #n#, o número de nodos na árvore. +A seguinte teorema (pouco impressionante) resume o desempenho da estrutura de dados +#BinarySearchTree#: \begin{thm}\thmlabel{bst} - #BinarySearchTree# implements the #SSet# interface and - supports the operations #add(x)#, #remove(x)#, - and #find(x)# in $O(#n#)$ time per operation. + #BinarySearchTree# implementa a interface #SSet# interface e aceita + as operações + #add(x)#, #remove(x)# + e #find(x)# em $O(#n#)$ de tempo por operação. \end{thm} -\thmref{bst} compares poorly with \thmref{skiplist}, which shows -that the #SkiplistSSet# structure can implement the #SSet# interface -with $O(\log #n#)$ expected time per operation. The problem with the -#BinarySearchTree# structure is that it can become \emph{unbalanced}. -Instead of looking like the tree in \figref{bst} it can look like a long -chain of #n# nodes, all but the last having exactly one child. - -There are a number of ways of avoiding unbalanced binary search -trees, all of which lead to data structures that have $O(\log -#n#)$ time operations. In \chapref{rbs} we show how $O(\log #n#)$ -\emph{expected} time operations can be achieved with randomization. -In \chapref{scapegoat} we show how $O(\log #n#)$ \emph{amortized} -time operations can be achieved with partial rebuilding operations. -In \chapref{redblack} we show how $O(\log #n#)$ \emph{worst-case} -time operations can be achieved by simulating a tree that is not binary: -one in which nodes can have up to four children. - -\section{Discussion and Exercises} - -Binary trees have been used to model relationships for thousands -of years. One reason for this is that binary trees naturally model -(pedigree) family trees. -\index{family tree}% -\index{pedigree family tree}% -These are the family trees in which the root is -a person, the left and right children are the person's parents, and so -on, recursively. In more recent centuries binary trees have also been -used to model species trees -\index{species tree} in biology, where the leaves of the tree -represent extant species and the internal nodes of the tree represent -\emph{speciation events} -\index{speciation event} in which two populations of a single species -evolve into two separate species. - -Binary search trees appear to have been discovered independently by -several groups in the 1950s \cite[Section~6.2.2]{k97v3}. Further -references to specific kinds of binary search trees are provided in -subsequent chapters. - -When implementing a binary tree from scratch, there are several design -decisions to be made. One of these is the question of whether or not -each node stores a pointer to its parent. If most of the operations -simply follow a root-to-leaf path, then parent pointers are unnecessary, -waste space, and are a potential source of coding errors. On the other -hand, the lack of parent pointers means that tree traversals must be done -recursively or with the use of an explicit stack. Some other methods -(like inserting or deleting into some kinds of balanced binary search -trees) are also complicated by the lack of parent pointers. - -Another design decision is concerned with how to store the parent, left -child, and right child pointers at a node. In the implementation given -here, these pointers are stored as separate variables. Another option -is to store them in an array, #p#, of length 3, so that #u.p[0]# is the -left child of #u#, #u.p[1]# is the right child of #u#, and #u.p[2]# is -the parent of #u#. Using an array this way means that some sequences -of #if# statements can be simplified into algebraic expressions. - -An example of such a simplification occurs during tree traversal. If -a traversal arrives at a node #u# from #u.p[i]#, then the next node in -the traversal is $#u.p#[(#i#+1)\bmod 3]$. Similar examples occur when -there is left-right symmetry. For example, the sibling of #u.p[i]# is -$#u.p#[(#i#+1)\bmod 2]$. This trick works whether #u.p[i]# is a left -child ($#i#=0$) or a right child ($#i#=1$) of #u#. In some cases this -means that some complicated code that would otherwise need to have both a -left version and right version can be written only once. See the methods -#rotateLeft(u)# and #rotateRight(u)# on page~\pageref{page:rotations} -for an example. +\thmref{bst} tem desempenho ruim em comparação a \thmref{skiplist}, que mostra que +uma estrutura #SkiplistSSet# implementa a interface #SSet# +com $O(\log #n#)$ de tempo esperado por operação. O problema com a estrutura +#BinarySearchTree# é que pode ser tornar \emph{desbalanceada}. +Em vez de parecer como uma árvore em + \figref{bst}, ela pode parecer como uma longa cadeias de +#n# nodos, todos exceto o último com exatamente um filho. + +Existe várias maneiras de evitar árvores binárias desbalanceadas, todas elas +levam a estruturas de dados que tem + operações que executam com $O(\log +#n#)$ de tempo. Em \chapref{rbs} mostramos como operações com $O(\log #n#)$ de tempo +\emph{esperado} podem ser obtidas com randomização. +Em \chapref{scapegoat} mostramos como operações que executam em $O(\log #n#)$ de tempo \emph{amortizado} podem ser obtidas com operações de reconstrução parcial. +Em \chapref{redblack} mostramos como operações que executam em $O(\log #n#)$ de tempo no \emph{pior caso} pode ser obtidas ao simular uma árvore que não é binária: uma árvore cujos nodos podem ter até quatro filhos. + +\section{Discussão e Exercícios} + +Árvores binárias têm sido usadas para modelar relações por milhares +de anos. Uma razão para isso é que árvores binárias naturalmente +modelam árvores genealógicas (e de pedigree). +\index{árvores genealógicas}% +\index{árvores de pedigree}% +Essas árvores genealógicas em que a raiz é uma pessoa, os filhos à esquerda e à direita são os pais de uma pessoa e assim por diante, recursivamente. +Em séculos mais recentes, árvores binárias também tem sido usadas para modelar +espécies de árvores +\index{espécies de árvores}% +em biologia, onde as folhas da árvore representam espécie existente e nodos internos +de uma árvore representam +\emph{eventos de especiação} +\index{evento de especiação} no qual duas populações de uma única espécie evoluem em duas espécies separadas. + +Árvores binárias de busca parecem terem sido descobertas independentemente +por vários grupos na década de 1950 +\cite[Section~6.2.2]{k97v3}. Referências adicionais a tipos específicos de árvores binárias de busca são fornecidas nos capítulos a seguir. + +Ao implementar uma árvore binária desde o início, várias decisões de projet devem ser feitas. Uma delas é se nodos devem guardar um ponteiro para o seu pai. + +Se a maior parte das operações simplesmente seguirem um caminho da raiz para a folha, então ponteiros para nodos-pai são desnecessários, desperdício de memória e uma potencial fonte de erros de codificação. Por outro lado, a falta de ponteiros para +nodos-pai significa que travessias em árvores devem serem feitas recursivamente ou com o uso de uma stack explícita. + +Alguns outros métodos (como inserir ou remover em alguns tipos de árvores binárias de busca balanceadas) também podem ficar mais complicados sem o uso de ponteiros para nodo-pai. + +Outra decisão de projeto refere-se a como guardar os ponteiros pai, filho à esquerda, filho à direita em um nodo. Na implementação dada aqui, esses +ponteiros são guardados como variáveis separadas. +Outra opção é guardá-los em um array #p# de tamanho 3, tal que +#u.p[0]# é o filho à esquerda de #u#, +#u.p[1]# é o filho à direita de #u# e +#u.p[2]# é o pai de #u#. +Usando um array dessa forma implica que algumas sequências de comandos #if# +podem ser simplificadas em expressões algébricas. + +Um exemplo dessa simplificação ocorre durante uma travessia de árvore. +Se uma travessia chega em um nodo +#u# a partir de #u.p[i]#, então o próximo nodo na travessia é +$#u.p#[(#i#+1)\bmod 3]$. Exemplos similares ocorrem quando há simetria esquerda-direita. +Por exemplo, o irmão de #u.p[i]# é +$#u.p#[(#i#+1)\bmod 2]$. Esse truque funciona se #u.p[i]# é um filho à esquerda +($#i#=0$) ou um filho à direita ($#i#=1$) de #u#. +Em alguns casos, isso significa que algum código complicado que de outra maneira +precisaria ter uma versão para atuar na esquerda e uma versão para atuar na direita +poderia ser escritos apenas uma vez. +Como exemplo, veja os métodos +#rotateLeft(u)# e #rotateRight(u)# na página~\pageref{page:rotations}. \begin{exc} - Prove that a binary tree having $#n#\ge 1$ nodes has $#n#-1$ edges. + Prove que uma árvore binária com $#n#\ge 1$ nodos tem $#n#-1$ arestars. \end{exc} \begin{exc} - Prove that a binary tree having $#n#\ge 1$ real nodes has $#n#+1$ - external nodes. + Prove que uma árvore binária com $#n#\ge 1$ nodos reais tem $#n#+1$ + nodos externos. \end{exc} \begin{exc} - Prove that, if a binary tree, $T$, has at least one leaf, then either - (a)~$T$'s root has at most one child or (b)~$T$ has more than - one leaf. + Prove que, se uma árvore binária, $T$, tem pelo menos uma folha, então + ou + (a)~a reaiz de $T$ tem no máximo um filho ou (b)~$T$ tem mais de uma folha. \end{exc} \begin{exc} - Implement a non-recursive method, #size2(u)#, that computes the size - of the subtree rooted at node #u#. + Implemente um método não-recursivo, #size2(u)#, que computa o tamanho da subárvore enraizada no nodo #u#. \end{exc} \begin{exc} - Write a non-recursive method, #height2(u)#, that computes the height - of node #u# in a #BinaryTree#. + Escreva um método não recursivo + #height2(u)# que computa a altura do nodo + #u# em uma #BinaryTree#. \end{exc} \begin{exc} - A binary tree is \emph{size-balanced} - \index{size-balanced}% - \index{binary search tree!size-balanced}% - if, for every node #u#, the size of - the subtrees rooted at #u.left# and #u.right# differ by at most one. - Write a recursive method, #isBalanced()#, that tests if a binary tree - is balanced. Your method should run in $O(#n#)$ time. (Be sure to - test your code on some large trees with different shapes; it is easy - to write a method that takes much longer than $O(#n#)$ time.) + Uma árvore binária é + \emph{balanceada no tamanho} + \index{balanceada no tamanho}% + \index{árvore binária de busca!balanceada no tamanho}% + se, para todo nodo #u#, o tamanho das subárvores enraziadas em + #u.left# e #u.right# diferem em no máximo uma unidade. + Escreva um método recursivo, #isBalanced()#, que testa se uma árvore + binária é balanceada. O seu método deve rodar em + $O(#n#)$ de tempo. (Teste o seu código em algumas árvores + maiores com diferentes formas; é fácil escrever um método que usa bem mais + de $O(#n#)$ de tempo.) \end{exc} -\index{traversal!pre-order}% -\index{traversal!post-order}% -\index{traversal!in-order}% -\index{pre-order traversal}% -\index{post-order traversal}% -\index{in-order traversal}% -A \emph{pre-order} traversal of a binary tree is a traversal that visits -each node, #u#, before any of its children. An \emph{in-order} traversal -visits #u# after visiting all the nodes in #u#'s left subtree but before -visiting any of the nodes in #u#'s right subtree. A \emph{post-order} -traversal visits #u# only after visiting all other nodes in #u#'s subtree. -The pre/in/post-order numbering of a tree labels the nodes of a tree with -the integers $0,\ldots,#n#-1$ in the order that they are encountered -by a pre/in/post-order traversal. See \figref{binarytree-numbering} -for an example. +\index{travessia!pré-ordem}% +\index{travessia!pós-ordem}% +\index{travessia!em ordem}% +\index{travessia pré-ordem}% +\index{travessia em ordem}% +\index{travessia pos ordem}% +Uma travessia em \emph{pré-ordem} (em inglês, \emph{pre-order} traversal) de uma árvore binária é uma travessia que visita cada nodo #u# antes de qualquer de seus filhos. +Uma travessia em ordem (em inglês, \emph{in-order} traversal) visita #u# +depois de visitar todos os nodos na subárvore à esquerda de #u# mas antes de visitar qualquer nodo na subárvore à direita de #u#. + +Um travessia em \emph{pós-ordem} (em inglês, \emph{post-order}) +visita #u# somente após visitar todos os outros nodos na subárvore de #u#. +A númeração pré/em/pós-ordem de uma árvore marca os nodos de uma árvore com os +inteiros +$0,\ldots,#n#-1$ na ordem em que são visitados por uma travessia +em pré/em/pós-ordem. Para um exemplo, veja \figref{binarytree-numbering}. \begin{figure} \begin{center} @@ -481,106 +469,99 @@ \section{Discussion and Exercises} \includegraphics[scale=0.90909]{figs/binarytree-numbering-2} \\[2ex] \includegraphics[scale=0.90909]{figs/binarytree-numbering-3} \end{center} - \caption{Pre-order, post-order, and in-order numberings of a binary tree.} + \caption{Numerações pré-ordem, pós-ordem e em ordem de uma árvore binária.} \figlabel{binarytree-numbering} \end{figure} \begin{exc} - \index{number!pre-order}% - \index{number!post-order}% - \index{number!in-order}% - \index{pre-order number}% - \index{post-order number}% - \index{in-order number}% - Create a subclass of #BinaryTree# whose nodes have fields for storing - pre-order, post-order, and in-order numbers. Write recursive methods - #preOrderNumber()#, #inOrderNumber()#, and #postOrderNumbers()# that + \index{numeração!pré-ordem}% + \index{numeração!pós-ordem}% + \index{numeração!em-ordem}% + \index{numeração pré-ordem}% + \index{numeração pós-ordem}% + \index{numeração em-ordem} % + Crie uma subclasse de + #BinaryTree# cujos nodos tem campos para guardar numerações pré/in/pós-ordem. +Escreva métodos recursivos + #preOrderNumber()#, #inOrderNumber()# e #postOrderNumbers()# que atribue + esses números corretamente. Cada um desses métodos deve rodar em $O(#n#)$ de tempo. assign these numbers correctly. These methods should each run in $O(#n#)$ time. \end{exc} \begin{exc}\exclabel{tree-traversal} - Implement the non-recursive functions #nextPreOrder(u)#, #nextInOrder(u)#, and - #nextPostOrder(u)# that return the node that follows #u# in a pre-order, - in-order, or post-order traversal, respectively. These functions - should take amortized constant time; if we start at any node - #u# and repeatedly call one of these functions and assign the return - value to #u# until $#u#=#null#$, then the cost of all these calls should - be $O(#n#)$. + Implemente as funções não recursivas #nextPreOrder(u)#, #nextInOrder(u)# e + #nextPostOrder(u)# que retornam o nodo que segue #u# em uma travessia pré/em/pós-ordem, respectivamente. Essas funções devem levar tempo constante amortizado; se iniciarmos em qualquer nodo #u# e chamarmos uma dessas funções e atribuirmos o valor a #u# até + $#u#=#null#$, então o custo de todas essas chamadas deve ser + $O(#n#)$. \end{exc} \begin{exc} - Suppose we are given a binary tree with pre-, post-, and in-order numbers - assigned to the nodes. Show how these numbers can be used to answer - each of the following questions in constant time: + Suponha que recebemos uma árvore binária com numerações pré/em/pós ordem atribuídas aos nodos. Mostre como esses números podem ser usados para responder cada uma das perguntas a seguir em tempo constante: \begin{enumerate} - \item Given a node #u#, determine the size of the subtree rooted at #u#. - \item Given a node #u#, determine the depth of #u#. - \item Given two nodes #u# and #w#, determine if #u# is an ancestor of #w# + \item Dado um nodo #u#, determine o tamanho da subárvore enraizada em #u#. + \item Dado um nodo #u#, determine a profundidade de #u#. + \item Dados dois nodos #u# e #w#, determine se #u# é um ancentral de #w#. \end{enumerate} \end{exc} \begin{exc} - Suppose you are given a list of nodes with pre-order and in-order - numbers assigned. Prove that there is at most one possible tree with - this pre-order/in-order numbering and show how to construct it. + Suponha que você recebeu uma lista de nodos com númerações pré/em-ordem atribuídas. Prove que existe no máximo uma árvore possível com essas numerações + e mostre como construí-la. \end{exc} \begin{exc} - Show that the shape of any binary tree on #n# nodes can be represented - using at most $2(#n#-1)$ bits. (Hint: think about recording what - happens during a traversal and then playing back that recording to - reconstruct the tree.) + Mostre que o tamanho de qualquer árvore binária em #n# nodos pode ser representado + usando no máximo + $2(#n#-1)$ bits. (Dicas: pense em gravar o que acontece durante uma travessia em então repetir essa gravação para reconstruir a árvore.) \end{exc} \begin{exc} - Illustrate what happens when we add the values $3.5$ and then 4.5 to - the binary search tree in \figref{bst}. + Ilustre o que acontece quando adicionamos os valores $3.5$ e então $4.5$ à árvore binária de busca em \figref{bst}. \end{exc} \begin{exc} - Illustrate what happens when we remove the values $3$ and then 5 from - the binary search tree in \figref{bst}. + Ilustre o que acontece ao removermos os valores $3$ and then $5$ da árvore binária de busca em \figref{bst}. \end{exc} \begin{exc} - Implement a #BinarySearchTree# method, #getLE(x)#, - that returns a list of all items in the tree that are less than or - equal to #x#. The running time of your method should be $O(#n#'+#h#)$ - where $#n#'$ is the number of items less than or equal to #x# and #h# - is the height of the tree. + Implemente um método na #BinarySearchTree# chamado de #getLE(x)# + que retorna uma lsita de todos os itens na árvore que são menores que + ou iguais a #x#. O tempo de execução do seu método deve ser + $O(#n#'+#h#)$ + onde $#n#'$ é o número de itens menores que ou iguais a #x# e #h# + é a altura da árvore. \end{exc} \begin{exc} - Describe how to add the elements $\{1,\ldots,#n#\}$ to an initially - empty #BinarySearchTree# in such a way that the resulting tree has - height $#n#-1$. How many ways are there to do this? + Descreva como adicionar elementos $\{1,\ldots,#n#\}$ a uma + #BinarySearchTree# inicialmente vazia de tal forma que a árvore resultante tem altura +$#n#-1$. Quantas formas existem para fazer isso? \end{exc} \begin{exc} - If we have some #BinarySearchTree# and perform the operations #add(x)# - followed by #remove(x)# (with the same value of #x#) do we necessarily - return to the original tree? + Se tivermos uma + #BinarySearchTree# e realizarmos as operações #add(x)# seguidas de #remove(x)# + (com o mesmo valor de #x#) retornamos à árvore original? \end{exc} \begin{exc} - Can a #remove(x)# operation increase the height of any node in a - #BinarySearchTree#? If so, by how much? + É possível um operação #remove(x)# aumentar a altura de um nodo em uma + #BinarySearchTree#? Se sim, em quanto? \end{exc} \begin{exc} - Can an #add(x)# operation increase the height of any node in a - #BinarySearchTree#? Can it increase the height of the tree? If so, - by how much? + Uma operação + #add(x)# consegue aumentar a altura de um nodo em uma + #BinarySearchTree#? Ela pode aumentar a altura da árvore? Se sim, em quanto? \end{exc} \begin{exc} - Design and implement a version of #BinarySearchTree# in which each node, - #u#, maintains values #u.size# (the size of the subtree rooted at #u#), - #u.depth# (the depth of #u#), and #u.height# (the height of the subtree - rooted at #u#). - - These values should be maintained, even during calls to the #add(x)# - and #remove(x)# operations, but this should not increase the cost of - these operations by more than a constant factor. + Projete e implemente uma versão da + #BinarySearchTree# em que cada nodo + #u#, mantém valores #u.size# (o tamanho da subárvore enraizada em #u#), + #u.depth# (a profundidade de #u#) e #u.height# (a altura da subárvore enraizada de #u#). + + Esses valores devem ser mantidos, mesmo durante chamadas às operações #add(x)# + e #remove(x)#, mas isso não devem aumentar o custo dessas operações em mais do que um fator constante. \end{exc} diff --git a/latex/hashing.tex b/latex/hashing.tex index 34e843a7..922ae922 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -979,8 +979,7 @@ \section{Discussão e Exercícios} \index{hashing universal }% \index{universal!hashing}% e descreveram várias funções hash para diferentes cenários \cite{cw79}. -Hashing por tabulação, descrita em - \secref{tabulation}, foi proposta por Carter +Hashing por tabulação, descrita em \secref{tabulation}, foi proposta por Carter e Wegman \cite{cw79}, mas sua análise, quando aplicada a sondagem linear (e vários outros esquemas de tabela hash) é creditada a P\v{a}tra\c{s}cu e Thorup \cite{pt12}. From 7159a33561ad80bd101bc8af9771eb557c3d7499 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 20 Aug 2020 19:44:47 -0300 Subject: [PATCH 25/66] translation to portuguese --- latex/rbs.tex | 890 +++++++++++++++++++++++++------------------------- 1 file changed, 438 insertions(+), 452 deletions(-) diff --git a/latex/rbs.tex b/latex/rbs.tex index 8a3f9e62..278edfef 100644 --- a/latex/rbs.tex +++ b/latex/rbs.tex @@ -1,16 +1,16 @@ -\chapter{Random Binary Search Trees} +\chapter{Árvores Binárias de Busca Aleatórias} \chaplabel{rbs} -In this chapter, we present a binary search tree structure that uses -randomization to achieve $O(\log #n#)$ expected time for all operations. +Neste capítulo, apresentamos uma estrutura de árvore binária de busca que +usa randomização para obter +$O(\log #n#)$ de tempo esperado para todas as operações. -\section{Random Binary Search Trees} +\section{Árvores Binárias de Busca Aleatórias} \seclabel{rbst} -Consider the two binary search trees shown in \figref{rbs-lvc}, each of -which has $#n#=15$ nodes. The one on the left is a list and the other -is a perfectly balanced binary search tree. The one on the left has a -height of $#n#-1=14$ and the one on the right has a height of three. +Considere as duas árvores binárias de busca mostradas em \figref{rbs-lvc}, cada qual com +$#n#=15$ nodos. A árvore na esquerda é uma lista e a outra é uma árvore binária de busca perfeitament balanceada. A altura da árvore na esquerda é +$#n#-1=14$ e da direita é três. \begin{figure} \begin{center} @@ -19,82 +19,77 @@ \section{Random Binary Search Trees} \includegraphics[scale=0.90909,scale=0.95]{figs/bst-balanced} \end{tabular} \end{center} - \caption{Two binary search trees containing the integers $0,\ldots,14$.} + \caption{Duas árvores binárias de busca contendo inteiros $0,\ldots,14$.} \figlabel{rbs-lvc} \end{figure} -Imagine how these two trees could have been constructed. The one on -the left occurs if we start with an empty #BinarySearchTree# and add -the sequence +Imagine como essas duas árvores podem ter sido construídas. Aquela na esquerda ocorre se iniciamos com uma +#BinarySearchTree# vazia e adicionando a +sequência \[ \langle 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14 \rangle \enspace . \] -No other sequence of additions will create this tree (as you can prove -by induction on #n#). On the other hand, the tree on the right can be -created by the sequence +Nenhuma outra sequência de adições irá criar essa árvore (como você pode provar +por indução em #n#). Por outro lado, a árvore na direita pode ser criada pela sequência \[ \langle 7,3,11,1,5,9,13,0,2,4,6,8,10,12,14 \rangle \enspace . \] -Other sequences work as well, including +Outras sequências também funcionariam, incluindo \[ \langle 7,3,1,5,0,2,4,6,11,9,13,8,10,12,14 \rangle \enspace , \] -and +e \[ \langle 7,3,1,11,5,0,2,4,6,9,13,8,10,12,14 \rangle \enspace . \] -In fact, there are $21,964,800$ addition sequences that generate the -tree on the right and only one that generates the tree on the left. - -The above example gives some anecdotal evidence that, if we choose a -random permutation of $0,\ldots,14$, and add it into a binary search -tree, then we are more likely to get a very balanced tree (the right -side of \figref{rbs-lvc}) than we are to get a very unbalanced tree -(the left side of \figref{rbs-lvc}). - -We can formalize this notion by studying random binary search trees. -A \emph{random binary search tree} -\index{random binary search tree}% -\index{binary search tree!random}% -of size #n# is obtained in the -following way: Take a random permutation, $#x#_0,\ldots,#x#_{#n#-1}$, -of the integers $0,\ldots,#n#-1$ and add its elements, one by one, -into a #BinarySearchTree#. By \emph{random permutation} -\index{permutation!random}% -\index{random permutation}% -we mean that -each of the possible $#n#!$ permutations (orderings) of $0,\ldots,#n#-1$ -is equally likely, so that the probability of obtaining any particular -permutation is $1/#n#!$. - -Note that the values $0,\ldots,#n#-1$ could be replaced by any ordered -set of #n# elements without changing any of the properties of the -random binary search tree. The element $#x#\in\{0,\ldots,#n#-1\}$ is -simply standing in for the element of rank #x# in an ordered set of -size #n#. - -Before we can present our main result about random binary search trees, -we must take some time for a short digression to discuss a type of number -that comes up frequently when studying randomized structures. For a -non-negative integer, $k$, the $k$-th \emph{harmonic number}, -\index{harmonic number}% -\index{H@$H_k$ (harmonic number)}% -denoted -$H_k$, is defined as +De fato, há + $21,964,800$ sequências que gerariam a árvore na direita e somente uma gera a àrvore na esquerda. + + O exemplo acima passa uma ideia de que, se escolhermos uma permutação + aleatória de +$0,\ldots,14$, e a adicionarmos em uma árvore binária de busca, então teremos uma chance maior de obter uma árvore muito balanceada (o lado direito de +\figref{rbs-lvc}) que temos de obter uma árvore muito desbalanceada +(o lado esquerdo de \figref{rbs-lvc}). + +Podemos formalizar essa noção ao estudar árvores binárias de busca aleatórias. +Uma \emph{árvore binária de busca aleatória} +\index{árvore binária de busca aleatória}% +\index{árvore binária de busca!aleatória}% +de tamanho #n# é obtida da seguinte forma: pegue uma permutação aleatória, + $#x#_0,\ldots,#x#_{#n#-1}$, +de inteiros $0,\ldots,#n#-1$ e adicione seus elementos, um por um em uma +#BinarySearchTree#. Queremos dizer com \emph{permutação aleatória} +\index{permutação!aleatória}% +\index{permutação aleatória}% +que cada uma das possíveis $#n#!$ permutações (reordenações) de $0,\ldots,#n#-1$ +é igualmente provável, tal que a probabilidade de obter qualqueer permutação em particular é +$1/#n#!$. + +Note que os valore $0,\ldots,#n#-1$ poderiam ser substituídos por qualquer conjunto ordenado de #n# elmento sem alterar a propriedade das árvores binárias de busca aleatória. +O elemento + $#x#\in\{0,\ldots,#n#-1\}$ está simplesmente representando o elemento de ranking (posição) #x# em um conjunto ordenado de tamanho #n#. + +Antes de apresentarmos nosso principal resultado sobre árvores binárias de busca aleatórias, precisamos fazer uma curta discussão sobre um tipo de número que +aparece frequentemente ao estudar estruturas randomizadas. Para um inteiro não-negativo $k$, o $k$-ésimo \emph{número harmônico}, +\index{número harmônico}% +\index{H@$H_k$ (númro harmônico)}% +denotado por +$H_k$, é definido como \[ H_k = 1 + 1/2 + 1/3 + \cdots + 1/k \enspace . \] -The harmonic number $H_k$ has no simple closed form, but it is very -closely related to the natural logarithm of $k$. In particular, +O número harmônico + $H_k$ na tem forma fechada simples, mas é muito relacionado ao + logaritmo natural de $k$. Em particular, \[ \ln k < H_k \le \ln k + 1 \enspace . \] \newcommand{\hint}{\int_1^k\! (1/x)\, \mathrm{d}x}% -Readers who have studied calculus might notice that this is because -the integral $\hint = \ln k$. Keeping in mind that an integral can be -interpreted as the area between a curve and the $x$-axis, the value of -$H_k$ can be lower-bounded by the integral $\hint$ and upper-bounded by -$1+ \hint$. (See \figref{harmonic-integral} for a graphical explanation.) +Leitores que estudaram cálculo podem perceber que isso é verdadeiro +pois a integral +$\hint = \ln k$. Tendo em mente que uma integral pode ser interpretada como a área entre uma curva e o eixo $x$, o valor de +$H_k$ pode ser limitado inferiormente pela integral $\hint$ e limitado superiormente por +$1+ \hint$. (Veja \figref{harmonic-integral} para uma explicação gráfica.) \begin{figure} \begin{center} @@ -103,101 +98,110 @@ \section{Random Binary Search Trees} & \includegraphics[width=\HalfScaleIfNeeded]{figs/harmonic-3} \end{tabular} \end{center} - \caption{The $k$th harmonic number $H_k=\sum_{i=1}^k 1/i$ is upper- and lower-bounded by two integrals. The value of these integrals is given by the - area of the shaded region, while the value of $H_k$ is given by the area of - the rectangles.} + \caption{O $k$-ésimo número harmônico $H_k=\sum_{i=1}^k 1/i$ é limitado superiormente e inferiormente por duas integrais. O valor dessas integrais é dado pela área da região sombreada, enquanto o valor de + $H_k$ é dado pela área dos retângulos.} \figlabel{harmonic-integral} \end{figure} \begin{lem}\lemlabel{rbs} - In a random binary search tree of size #n#, the following statements hold: + Em uma árvore binária de busca aleatória de tamanho #n#, as seguintes afirmações valem: \begin{enumerate} - \item For any $#x#\in\{0,\ldots,#n#-1\}$, the expected length of the - search path for #x# is $H_{#x#+1} + H_{#n#-#x#} - O(1)$.\footnote{The - expressions $#x#+1$ and $#n#-#x#$ can be interpreted respectively - as the number of elements in the tree less than or equal to #x# - and the number of elements in the tree greater than or equal to #x#.} - \item For any $#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$, the - expected length of the search path for #x# is $H_{\lceil#x#\rceil} + \item Para qualquer $#x#\in\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho de busca por + #x# é $H_{#x#+1} + H_{#n#-#x#} - O(1)$.\footnote{As expressões + $#x#+1$ e $#n#-#x#$ podem ser interpretadas respectivamente + como o número de elementos na árvore menos que ou igual a #x# + e o número de elementos na árvore maiores que ou iguais a #x#.} + \item Para qualquer $#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$, o + comprimento esperado do caminho de busca para #x# é + $H_{\lceil#x#\rceil} + H_{#n#-\lceil#x#\rceil}$. \end{enumerate} \end{lem} -We will prove \lemref{rbs} in the next section. For now, consider what -the two parts of \lemref{rbs} tell us. The first part tells us that if -we search for an element in a tree of size #n#, then the expected length -of the search path is at most $2\ln n + O(1)$. The second part tells -us the same thing about searching for a value not stored in the tree. -When we compare the two parts of the lemma, we see that it is only -slightly faster to search for something that is in the tree compared to -something that is not. - - -\subsection{Proof of \lemref{rbs}} - -The key observation needed to prove \lemref{rbs} is the following: -The search path for a value #x# in the open interval $(-1,#n#)$ in a -random binary search tree, $T$, contains the node with key $i < #x#$ -if, and only if, in the random permutation used to create $T$, $i$ -appears before any of $\{i+1,i+2,\ldots,\lfloor#x#\rfloor\}$. - -To see this, refer to \figref{rbst-records} and notice that until -some value in $\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ is added, the search -paths for each value in the open interval $(i-1,\lfloor#x#\rfloor+1)$ -are identical. (Remember that for two values to have -different search paths, there must be some element in the tree that -compares differently with them.) Let $j$ be the first element in -$\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ to appear in the random permutation. -Notice that $j$ is now and will always be on the search path for #x#. -If $j\neq i$ then the node $#u#_j$ containing $j$ is created before the -node $#u#_i$ that contains $i$. Later, when $i$ is added, it will be -added to the subtree rooted at $#u#_j#.left#$, since $i#x#$, $i$ appears in the search path for #x# -if and only if $i$ appears before any of $\{\lceil#x#\rceil, -\lceil#x#\rceil+1,\ldots,i-1\}$ in the random permutation used to -create $T$. - -Notice that, if we start with a random permutation of $\{0,\ldots,#n#\}$, -then the subsequences containing only $\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ -and $\{\lceil#x#\rceil, \lceil#x#\rceil+1,\ldots,i-1\}$ are also random -permutations of their respective elements. Each element, then, in the -subsets $\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ and $\{\lceil#x#\rceil, -\lceil#x#\rceil+1,\ldots,i-1\}$ is equally likely to appear before -any other in its subset in the random permutation used to create $T$. -So we have +De modo similar, para +$i>#x#$, $i$ aparece no caminho de busca para #x# +se e somente se + $i$ aparece antes de $\{\lceil#x#\rceil, +\lceil#x#\rceil+1,\ldots,i-1\}$ na permutação aleatória usada para criar $T$. + +Note que, se iniciarmos com uma permutação aleatória de + $\{0,\ldots,#n#\}$, + então as subsequências contendo somente + $\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ +e $\{\lceil#x#\rceil, \lceil#x#\rceil+1,\ldots,i-1\}$ também são +permutações aleatórias de seus respectivos elementos. +Cada elemento, então, nos subconjuntos +$\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ e $\{\lceil#x#\rceil, +\lceil#x#\rceil+1,\ldots,i-1\}$ é igualmente provável de aparecer antes +de que qualquer outro no seu subconjunto na permutação aleatória +usada para criar $T$. +Então temos \[ - \Pr\{\mbox{$i$ is on the search path for #x#}\} + \Pr\{\mbox{$i$ está no caminho de busca por #x#}\} = \left\{ \begin{array}{ll} - 1/(\lfloor#x#\rfloor-i+1) & \mbox{if $i < #x#$} \\ - 1/(i-\lceil#x#\rceil+1) & \mbox{if $i > #x#$} + 1/(\lfloor#x#\rfloor-i+1) & \mbox{se $i < #x#$} \\ + 1/(i-\lceil#x#\rceil+1) & \mbox{se $i > #x#$} \end{array}\right . \enspace . \] -With this observation, the proof of \lemref{rbs} -involves some simple calculations with harmonic numbers: +A partir dessa observação, a prova de + \lemref{rbs} +envolve alguns cálculos simples com números harmônicos: -\begin{proof}[Proof of \lemref{rbs}] -Let $I_i$ be the indicator random variable that is equal to one when $i$ -appears on the search path for #x# and zero otherwise. Then the length -of the search path is given by +\begin{proof}[Prova de \lemref{rbs}] +Seja +$I_i$ seja uma variável indicadora aleatória que é igual a um quando $i$ +aparece no caminho de busca por #x# e zero caso contrário. Então, o comprimento +do caminho de busca é dado por \[ \sum_{i\in\{0,\ldots,#n#-1\}\setminus\{#x#\}} I_i \] -so, if $#x#\in\{0,\ldots,#n#-1\}$, the expected length of the search -path is given by (see \figref{rbst-probs}.a) + então, se $#x#\in\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho de busca é dado por (veja \figref{rbst-probs}.a) \begin{align*} \E\left[\sum_{i=0}^{#x#-1} I_i + \sum_{i=#x#+1}^{#n#-1} I_i\right] & = \sum_{i=0}^{#x#-1} \E\left[I_i\right] @@ -210,8 +214,8 @@ \subsection{Proof of \lemref{rbs}} & \quad {} + \frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{#n#-#x#} \\ & = H_{#x#+1} + H_{#n#-#x#} - 2 \enspace . \end{align*} -The corresponding calculations for a search value -$#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$ are almost identical (see +Os cálculos correspondentes para um valor buscado +$#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$ são quase idênticos (veja \figref{rbst-probs}.b). \end{proof} @@ -222,155 +226,159 @@ \subsection{Proof of \lemref{rbs}} \includegraphics[width=\ScaleIfNeeded]{figs/rbst-probs-b} \\ (b) \\[2ex] \end{tabular} \end{center} - \caption[The probabilities of an element being on a search path]{The probabilities of an element being on the search path for #x# - when (a)~#x# is an integer and (b)~when #x# is not an integer.} + \caption[As probabilidade de um elemento estar em um caminho de busca]{As probabilidade de um elemento estar no caminho de busca por #x# quando + (a)~#x# é um inteiro e (b)~quando #x# não é um inteiro. } \figlabel{rbst-probs} \end{figure} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of a random binary -search tree: +O teorema a seguir resumo o desempenho de uma árvore binária de busca aleatória: \begin{thm}\thmlabel{rbs} -A random binary search tree can be constructed in $O(#n#\log #n#)$ time. -In a random binary search tree, the #find(x)# operation takes $O(\log -#n#)$ expected time. + Uma árvore binária de busca aleatória pode ser construída em +$O(#n#\log #n#)$ de tempo. +Em uma árvore binária de busca aleatória, a operação +#find(x)# leva $O(\log +#n#)$ de tempo esperado. \end{thm} -We should emphasize again that the expectation in \thmref{rbs} is with -respect to the random permutation used to create the random binary -search tree. In particular, it does not depend on a random choice of -#x#; it is true for every value of #x#. +Devemos enfatizar novamente que o valor esperado em + \thmref{rbs} é em respeito à permutação aleatória usada em respeito + à permutação aleatória usada para criar a árvore binária de busca aleatória. +Em particular, não depende em uma escolha aleatória de #x#; isso é verdade para +todo valor de #x#. - -\section{#Treap#: A Randomized Binary Search Tree} +\section{#Treap#: Uma Árvore Binária de Busca Aleatória} \seclabel{treap} \index{Treap@#Treap#}% -The problem with random binary search trees is, of course, that they are -not dynamic. They don't support the #add(x)# or #remove(x)# operations -needed to implement the #SSet# interface. In this section we describe -a data structure called a #Treap# that uses \lemref{rbs} to implement -the #SSet# interface.\footnote{The name #Treap# comes from the fact -that this data structure is simultaneously a binary search \textbf{tr}ee -(\secref{binarysearchtree}) and a h\textbf{eap} (\chapref{heaps}).} - -A node in a #Treap# is like a node in a #BinarySearchTree# in that it has -a data value, #x#, but it also contains a unique numerical \emph{priority}, -#p#, that is assigned at random: +O problema com uma árvore binária de busca aleatória é, claramente, que +elas não são dinâmica. Elas não aceitam as operações +#add(x)# ou #remove(x)# necessárias para implementar a interface #SSet#. +Nesta seção descrevemos uma estrutura de dados chamada de #Treap# que usa +o \lemref{rbs} para implementar +a interface #SSet#.\footnote{O nome #Treap# vem do fato que essa estrutura de dados é simultâneamente uma árvore binária de busca +(do inglês, binary search \textbf{tr}ee) (\secref{binarysearchtree}) e uma + h\textbf{eap} (\chapref{heaps}).} + +Um nodo em uma #Treap# é como um nodo em uma #BinarySearchTree# em que ele tem um valor de dado #x# mas também contém um valor único chamado \emph{priority} #p# +#p#, que é atribuído aleatoriamente: \javaimport{ods/Treap.Node} \cppimport{ods/Treap.TreapNode} -In addition to being a binary search tree, the nodes in a #Treap# -also obey the \emph{heap property}: +Além de ser uma árvore binária de busca, os nodos em uma #Treap# também obedecem a \emph{propriedade das heaps}: \begin{itemize} -\item (Heap Property) At every node #u#, except the root, +\item (Propriedade das Heaps) Em todo nodo #u#, exceto na raiz, $#u.parent.p# < #u.p#$. - \index{heap property}% + \index{propriedade raiz}% \end{itemize} -In other words, each node has a priority smaller than that of its two -children. An example is shown in \figref{treap}. +Em outras palavras, cada nodo tem uma prioridade menor que aquelas de seus filhos. +Um exemplo é mostrado em \figref{treap}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/treap} \end{center} - \caption[A Treap]{An example of a #Treap# containing the integers - $0,\ldots,9$. Each node, #u#, is illustrated as a box containing $#u.x#,#u.p#$.} + \caption[Uma Treap]{Um exemplo de uma #Treap# contendo os inteiros $0,\ldots,9$. Cada nodo, #u#, é ilustrado como uma caixa contendo $#u.x#,#u.p#$.} \figlabel{treap} \end{figure} -The heap and binary search tree conditions together ensure that, once -the key (#x#) and priority (#p#) for each node are defined, the -shape of the #Treap# is completely determined. The heap property tells us that -the node with minimum priority has to be the root, #r#, of the #Treap#. -The binary search tree property tells us that all nodes with keys smaller -than #r.x# are stored in the subtree rooted at #r.left# and all nodes -with keys larger than #r.x# are stored in the subtree rooted at #r.right#. - -The important point about the priority values in a #Treap# is that they -are unique and assigned at random. Because of this, there are -two equivalent ways we can think about a #Treap#. As defined above, a -#Treap# obeys the heap and binary search tree properties. Alternatively, -we can think of a #Treap# as a #BinarySearchTree# whose nodes -were added in increasing order of priority. For example, the #Treap# -in \figref{treap} can be obtained by adding the sequence of $(#x#,#p#)$ -values +As condições impostas pela heap e por uma árvore binária de busca juntas asseguram que, uma vez que a chave (#x#) e a prioridade (#p#) para cada nodo estejam definidos, a forma da #Treap# está completamente determinada. + +A propriedade das heaps nos diz que o nodo com prioridade mínima tem que ser a raiz #r# da #Treap#. A propriedade das árvores binárias de busca nos diz que todos os nodos com +chaves menores que #r.x# são guardados na subárvores enraizada na #r.left# e todos +os nodos com chaves maiores que #r.x# são guardados na subárvore enraizada em #r.right#. + +Um fato importante sobre os valores de prioridades em uma #Treap# é que +ele são únicos e atribuídos aleatoriamente. +Por causa disso, existem duas formas equivalentes que podem pensar sobre uma #Treap#. Conforme definido acima, uma #Treap# obedece as propriedades das árvores binárias de buca e das heaps. +Alternativamente, +podemos que uma #Treap# como uma +#BinarySearchTree# cujos nodos são adicionados em ordem crescente de prioridade. +Por exemplo, a #Treap# +em \figref{treap} pode ser obtida ao adicionar a sequência $(#x#,#p#)$ \[ \langle (3,1), (1,6), (0,9), (5,11), (4,14), (9,17), (7,22), (6,42), (8,49), (2,99) \rangle \] -into a #BinarySearchTree#. +em uma #BinarySearchTree#. -Since the priorities are chosen randomly, this is equivalent to taking a -random permutation of the keys---in this case the permutation is +Como as prioridades são escolhidas aleatoriamente, isso é equivalente a obter uma permutação aleatória das chaves --- nesse caso a permutação é \[ \langle 3, 1, 0, 5, 9, 4, 7, 6, 8, 2 \rangle \] ----and adding these to a #BinarySearchTree#. But this means that the -shape of a treap is identical to that of a random binary search tree. -In particular, if we replace each key #x# by its rank,\footnote{The -rank of an element #x# in a set $S$ of elements is the number of -elements in $S$ that are less than #x#.} then \lemref{rbs} applies. -Restating \lemref{rbs} in terms of #Treap#s, we have: +---e inserí-las à uma #BinarySearchTree#. + +Mas isso significa que a forma de uma treap é idêntica àquela de uma árvore binária de busca binária. +Em particular, se substituímos cad chave #x# por seu rank, \footnote{O +rank de um elemento #x# em um conjunto $S$ de elementos é o número de elementos +em $S$ que são menores que #x#.} então \lemref{rbs} aplica-se. + +Reafirmando +\lemref{rbs} em termos de #Treap#s, temos: \begin{lem}\lemlabel{rbs-treap} - In a #Treap# that stores a set $S$ of #n# keys, the following statements hold: + Em uma #Treap# que guarda um conjunto $S$ de #n# chaves, as seguintes afirmações valem: \begin{enumerate} - \item For any $#x#\in S$, the expected length of - the search path for #x# is $H_{r(#x#)+1} + H_{#n#-r(#x#)} - O(1)$. - \item For any $#x#\not\in S$, the expected length of the - search path for #x# is $H_{r(#x#)} + H_{#n#-r(#x#)}$. + \item Para qualquer $#x#\in S$, o comprimento esperado do caminho + de busca por #x# é $H_{r(#x#)+1} + H_{#n#-r(#x#)} - O(1)$. + \item Para qualquer $#x#\not\in S$, comprimento esperado do caminho de busca por #x# é $H_{r(#x#)} + H_{#n#-r(#x#)}$. \end{enumerate} - Here, $r(#x#)$ denotes the rank of #x# in the set $S\cup\{#x#\}$. + Aqui, $r(#x#)$ denota o rank #x# no conjunto $S\cup\{#x#\}$. \end{lem} -Again, we emphasize that the expectation in \lemref{rbs-treap} is taken -over the random choices of the priorities for each node. It does not -require any assumptions about the randomness in the keys. - -\lemref{rbs-treap} tells us that #Treap#s can implement the #find(x)# -operation efficiently. However, the real benefit of a #Treap# is that -it can support the #add(x)# and #delete(x)# operations. To -do this, it needs to perform rotations in order to maintain the heap property. Refer to \figref{rotations}. -A \emph{rotation} -\index{rotation}% -in a binary -search tree is a local modification that takes a parent #u# of a node #w# -and makes #w# the parent of #u#, while preserving the binary search tree -property. Rotations come in two flavours: \emph{left} or \emph{right} -depending on whether #w# is a right or left child of #u#, respectively. -\index{left rotation}% -\index{right rotation}% +Novamente, enfatizamos que o valor esperado em +\lemref{rbs-treap} é obtido sobre escolhas aleatórias das prioridade para cada nodo. Isso não requer quaisquer premissas sobre a aleatoriedade nas chaves. + +\lemref{rbs-treap} nos diz que #Treap#s podem implementar a operação #find(x)# +eficientemente. Contudo, o benefício real de uma #Treap# é que ela +pode implementar as operações + #add(x)# e #delete(x)#. Para fazer isso, ela precisa realizar rotações para manter a propriedade de heaps. +Veja \figref{rotations}. +Uma \emph{rotação} +\index{rotação}% +em uma árvore binária de busca é uma modificação local que pega um pai #u# +de um nodo #w# e torna #w# o pai de #u#, preservando a propriedade das árvores binárias de busca. Rotações vêm em dois sabores: à \emph{esquerda} e à \emph{direita} +dependendo em se #w# é um filho à direita ou esquerda de #u#, respectivamente. +\index{rotação à esquerda}% +\index{rotação à direita}% \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/rotation} \end{center} - \caption{Left and right rotations in a binary search tree.} + \caption{Rotações à esquerda e direita em uma árvore binária de busca.} \figlabel{rotations} \end{figure} -The code that implements this has to handle these two possibilities and -be careful of a boundary case (when #u# is the root), so the actual code -is a little longer than \figref{rotations} would lead a reader to believe: +O código que implementa isso tem que lidar com essas duas possibilidades +e ser cuidadoso com um caso especial (quando #u# for a raiz), então o +código real é um pouco mais longo que + \figref{rotations} daria a entender: \codeimport{ods/BinarySearchTree.rotateLeft(u).rotateRight(u)} \label{page:rotations} -In terms of the #Treap# data structure, the most important property of a -rotation is that the depth of #w# decreases by one while the depth of #u# -increases by one. - -Using rotations, we can implement the #add(x)# operation as follows: -We create a new node, #u#, assign #u.x=x#, and pick a random value -for #u.p#. Next we add #u# using the usual #add(x)# algorithm -for a #BinarySearchTree#, so that #u# is now a leaf of the #Treap#. -At this point, our #Treap# satisfies the binary search tree property, -but not necessarily the heap property. In particular, it may be the -case that #u.parent.p > u.p#. If this is the case, then we perform a -rotation at node #w#=#u.parent# so that #u# becomes the parent of #w#. -If #u# continues to violate the heap property, we will have to repeat this, decreasing #u#'s depth by one every time, until -#u# either becomes the root or $#u.parent.p# < #u.p#$. +Em termos da estrutura de dados #Treap#, a propriedade mais importante de uma rotação é que a profundidade de #w# é reduzido em 1 enquanto a profundidade de +#w# aumenta em 1. + +Usando rotações, podemos implementar a operação +#add(x)# da forma a seguir: +criamos um nodo #u#, atribuímos #u.x = x# e pegamos um valor aleatório +para #u.p#. Depois adicionamos #u# usando o algoritmo #add(x)# usual para +uma +#BinarySearchTree#, então #u# é agora uma folha da #Treap#. +Nesse ponto, nossa #Treap# satisfaz a propriedade das árvores binárias de busca, +mas não necessariamente a propriedade das heaps. + +Em particular, pode ser o caso que #u.parent.p > u.p#. +Se esse é o caso, então realizamos uma rotação no nodo +#w#=#u.parent# tal que #u# se torna o pai de #w#. + +Se #u# continua a violar a propriedade das heaps, teremos que repetir isso, +decrescendo a profundidade de #u# em um a cada vez, até que +#u# se torne raiz ou +$#u.parent.p# < #u.p#$. \codeimport{ods/Treap.add(x).bubbleUp(u)} -An example of an #add(x)# operation is shown in \figref{treap-add}. +Um exemplo de uma operação +#add(x)# é mostrado em \figref{treap-add}. \begin{figure} \begin{center} @@ -378,37 +386,32 @@ \section{#Treap#: A Randomized Binary Search Tree} \includegraphics[width=\ScaleIfNeeded]{figs/treap-insert-b} \\ \includegraphics[width=\ScaleIfNeeded]{figs/treap-insert-c} \\ \end{center} - \caption[Adding to a Treap]{Adding the value 1.5 into the #Treap# from \figref{treap}.} + \caption[Adiciionando a uma Treap]{Adicionando o valor 1.5 na #Treap# em \figref{treap}.} \figlabel{treap-add} \end{figure} -The running time of the #add(x)# operation is given by the time it -takes to follow the search path for #x# plus the number of rotations -performed to move the newly-added node, #u#, up to its correct location -in the #Treap#. By \lemref{rbs-treap}, the expected length of the -search path is at most $2\ln #n#+O(1)$. Furthermore, each rotation -decreases the depth of #u#. This stops if #u# becomes the root, so -the expected number of rotations cannot exceed the expected length of -the search path. Therefore, the expected running time of the #add(x)# -operation in a #Treap# is $O(\log #n#)$. (\excref{treap-rotates} -asks you to show that the expected number of rotations performed during -an addition is actually only $O(1)$.) - -The #remove(x)# operation in a #Treap# is the opposite of the #add(x)# -operation. We search for the node, #u#, containing #x#, then perform -rotations to move #u# downwards until it becomes a leaf, and then we -splice #u# from the #Treap#. Notice that, to move #u# downwards, we can -perform either a left or right rotation at #u#, which will replace #u# -with #u.right# or #u.left#, respectively. -The choice is made by the first of the following that apply: +O tempo de execução da operação +#add(x)# é dado pelo tempo que leva para seguir o caminho de busca para #x# mais o número de rotações realizadas para mover o novo nodo #u# subindo a árvore até sua posição correta na +#Treap#. +De acordo com \lemref{rbs-treap}, o comprimento esperado do caminho de busca é no máximo $2\ln #n#+O(1)$. Além disso, cada rotação reduz a profundidade de #u#. +Isso para se #u# se torna raiz, então o número esperado de rotações não pode exceder o comprimento esperado do caminho de busca. Portanto o tempo esperado de execução da operação #add(x)# em uma #Treap# é $O(\log #n#)$. (\excref{treap-rotates} pede que se mostre que o número esperado de rotações realizadas durante uma adição é na verdade somente $O(1)$.) + +A operação +#remove(x)# em uma #Treap# é o oposto da operação #add(x)#. +Buscamos pelo nodo #u#, contendo #x#, então realizamos rotações para mover +#u# abaixo na árvore até que se torne uma folha e então removemos +#u# da #Treap#. Note que, para mover #u# árvore abaixo, podemos fazer +uma rotação à esquerda ou direita em #u#, que subtituir #u# com #u.right# +ou #u.left#, respectivamente. +A escolha é feita de acordo com a primeira das seguintes regras que for adequada: \begin{enumerate} -\item If #u.left# and #u.right# are both #null#, then #u# is a leaf and no rotation is performed. -\item If #u.left# (or #u.right#) is #null#, then perform a right (or left, respectively) rotation at #u#. -\item If $#u.left.p# < #u.right.p#$ (or $#u.left.p# > #u.right.p#)$, then perform a right rotation (or left rotation, respectively) at #u#. +\item Se #u.left# e #u.right# forem ambos #null#, então #u# é uma folha e nenhuma rotação é realizada. +\item Se #u.left# (ou #u.right#) for #null#, então realizar uma rotação à direita (ou à esquerda, respectivamente) em #u#. +\item Se $#u.left.p# < #u.right.p#$ (ou $#u.left.p# > #u.right.p#)$, então realizar uma rotação à direita (ou rotação à esquerda, respectivamente) em #u#. \end{enumerate} -These three rules ensure that the #Treap# doesn't become disconnected and that the heap property is restored once #u# is removed. +Essas três regras asseguram que #Treap# não se torne desconectada e que a propriedade das heaps é restabelecida uma vez que #u# seja removida. \codeimport{ods/Treap.remove(x).trickleDown(u)} -An example of the #remove(x)# operation is shown in \figref{treap-remove}. +Um exemplo da operação #remove(x)# é mostrado em \figref{treap-remove}. \begin{figure} \begin{center} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-a} \\ @@ -416,275 +419,258 @@ \section{#Treap#: A Randomized Binary Search Tree} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-c} \\ \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-d} \end{center} - \caption[Removing from a treap]{Removing the value 9 from the #Treap# in \figref{treap}.} + \caption[Removendo de uma treap]{Removendo o valor 9 da #Treap# em \figref{treap}.} \figlabel{treap-remove} \end{figure} -The trick to analyze the running time of the #remove(x)# operation is -to notice that this operation reverses the #add(x)# operation. -In particular, if we were to reinsert #x#, using the same priority #u.p#, -then the #add(x)# operation would do exactly the same number of rotations -and would restore the #Treap# to exactly the same state it was in before -the #remove(x)# operation took place. (Reading from bottom-to-top, -\figref{treap-remove} illustrates the addition of the value 9 into a -#Treap#.) This means that the expected running time of the #remove(x)# -on a #Treap# of size #n# is proportional to the expected running time -of the #add(x)# operation on a #Treap# of size $#n#-1$. We conclude -that the expected running time of #remove(x)# is $O(\log #n#)$. +O truque para analisar o tempo de execução da operação +#remove(x)# é notar que essa operação inverte a operação #add(x)#. +Em particular, se fossemos reinserir #x# usando a mesmo prioridade #u.p#, +então a operação #add(x)# faria exatamente o mesmo número de rotações +e restabeceria a #Treap# para exatamente o mesmo estado que estava antes +que a operação #remove(x)# ocorreu. + +(Lendo de cima para baixo, \figref{treap-remove} ilustra a adição do valor $9$ em uma #Treap#.) +Isso significa que o tempo de execução esperado de #remove(x)# em uma #Treap# de tamanho #n# é proporcional ao tempo esperado de execução da operação #add(x)# em um #Treap# de tamanho $#n#-1$. Concluímos que o tempo esperado de execução de #remove(x)# é $O(\log #n#)$. -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of the #Treap# data -structure: +O teorema a seguir resume o desempenho da estrutura de dados #Treap#: \begin{thm} -A #Treap# implements the #SSet# interface. A #Treap# supports -the operations #add(x)#, #remove(x)#, and #find(x)# in $O(\log #n#)$ -expected time per operation. +Uma #Treap# implementa a interface #SSet#. Uma #Treap# executa +as operações #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ +de tempo esperado por operação. \end{thm} -It is worth comparing the #Treap# data structure to the #SkiplistSSet# -data structure. Both implement the #SSet# operations in $O(\log #n#)$ -expected time per operation. In both data structures, #add(x)# and -#remove(x)# involve a search and then a constant number of pointer changes -(see \excref{treap-rotates} below). Thus, for both these structures, -the expected length of the search path is the critical value in assessing -their performance. In a #SkiplistSSet#, the expected length of a search -path is +Vale a pena comparar a estrutura de dados +#Treap# à estrutura de dados #SkiplistSSet# +. Ambas implementam operações #SSet# em $O(\log #n#)$ de tempo esperado por operação. +Na duas estruturas de dados, #add(x)# e #remove(x)# envolvem uma busca e então um número constante de mudanças de ponteiros +(veja \excref{treap-rotates} a seguir). Então, para essas estruturas, o comprimento esperado do caminho de busca é um valor crítico na avaliação de seus desempenhos. +Em uma #SkiplistSSet#, o comprimento esperado de um caminho de busca é \[ 2\log #n# + O(1) \enspace , \] -In a #Treap#, the expected length of a search path is +Em uma #Treap#, o comprimento esperado de um caminho de busca é \[ 2\ln #n# +O(1) \approx 1.386\log #n# + O(1) \enspace . \] -Thus, the search paths in a #Treap# are considerably shorter and this -translates into noticeably faster operations on #Treap#s than #Skiplist#s. -\excref{skiplist-opt} in \chapref{skiplists} shows how the -expected length of the search path in a #Skiplist# can be reduced to +Então, os caminhos de busca em um +#Treap# são consideravelmente mais curtos e isso traduz em operações +mais rápidas em #Treap#s que #Skiplist#s. +\excref{skiplist-opt} em \chapref{skiplists} mostra como o comprimento +esperado do caminho de busca em uma + #Skiplist# pode ser reduzido a \[ e\ln #n# + O(1) \approx 1.884\log #n# + O(1) \] -by using biased coin tosses. Even with this optimization, the expected -length of search paths in a #SkiplistSSet# is noticeably longer than in -a #Treap#. - -\section{Discussion and Exercises} - -Random binary search trees have been studied extensively. Devroye -\cite{d88} gives a proof of \lemref{rbs} and related results. There are -much stronger results in the literature as well, the most impressive -of which is due to Reed \cite{r03}, who shows that the expected height -of a random binary search tree is +pelo uso de lançamentos de moedas tendenciosas. +Mesmo com essa otimização, o comprimento esperado de caminhos de busca +em uma +#SkiplistSSet# é notavelmente maior que em uma +#Treap#. + +\section{Discussão e Exercícios} + +Árvores binárias de busca aleatórias têm sido estudados extensivamente. +Devroye +\cite{d88} provou \lemref{rbs} e outros resultados relacionados. +Há resultados bem mais precisos na literatura também, o mais impressionant do qual é +de Reed +\cite{r03}, que mostra que a altura esperada de uma árvore binária de busca aleatória é \[ \alpha\ln n - \beta\ln\ln n + O(1) \] -where $\alpha\approx4.31107$ is the unique solution on the -interval $[2,\infty)$ of the equation $\alpha\ln((2e/\alpha))=1$ and -$\beta=\frac{3}{2\ln(\alpha/2)}$ . Furthermore, the variance of the -height is constant. - -The name #Treap# was coined by Seidel and Aragon \cite{as96} who discussed -#Treap#s and some of their variants. However, their basic structure was -studied much earlier by Vuillemin \cite{v80} who called them Cartesian -trees. - -One possible space-optimization of the #Treap# data structure -is the elimination of the explicit storage of the priority #p# -in each node. Instead, the priority of a node, #u#, is computed by -hashing #u#'s address in memory\javaonly{ (in 32-bit Java, this is equivalent -to hashing #u.hashCode()#)}. Although a number of hash functions will -probably work well for this in practice, for the important parts of the -proof of \lemref{rbs} to remain valid, the hash function should be randomized -and have the \emph{min-wise independent property}: -\index{min-wise independence}% -For any distinct -values $x_1,\ldots,x_k$, each of the hash values $h(x_1),\ldots,h(x_k)$ -should be distinct with high probability and, for each $i\in\{1,\ldots,k\}$, +onde $\alpha\approx4.31107$ é a única solução no intervalo +$[2,\infty)$ da equação $\alpha\ln((2e/\alpha))=1$ e +$\beta=\frac{3}{2\ln(\alpha/2)}$ . Além disso, a variância da altura é constante. + +O nome #Treap# foi cunhado por + Seidel e Aragon \cite{as96} que discutiram as propriedades das +#Treap#s e algumas de suas variantes. Entretanto, +a estrutura básica da #Treap# foi estudada muito antes por +Vuillemin \cite{v80} que chamou elas de árvores cartesianas. + +Uma possibilidade de otimização relacionada ao uso de memória da +estrutura de dados #Treap# é a eliminação do armazenamento explícito +da prioridade #p# em cada nodo. Em vez idsso, a prioridade de um nodo #u# +é computado usando o hashing do endereço de #u# em memória +\javaonly{ (no Java 32-bits, isso é equivalente a fazer o hashing de +#u.hashCode()#)}. Embora muitas funções has provavelmente funcionam bem para isso na prática, para as partes importantes da prova +de \lemref{rbs} continuarem a serem válidas, a função hash deve ser randomizada e ter +a \emph{propriedade de independência em relação ao mínimo}: +\index{independência em relação ao mínimo}% +Para quaisquer valores distintos +$x_1,\ldots,x_k$, cada um dos valores hash $h(x_1),\ldots,h(x_k)$ +devem ser distintos com alta probabilidade e para cada $i\in\{1,\ldots,k\}$, \[ \Pr\{h(x_i) = \min\{h(x_1),\ldots,h(x_k)\}\} \le c/k \] -for some constant $c$. -One such class of hash functions that is easy to implement and fairly -fast is \emph{tabulation hashing} (\secref{tabulation}). -\index{tabulation hashing}% -\index{hashing!tabulation}% - -Another #Treap# variant that doesn't store priorities at each node is -the randomized binary search tree -\index{randomized binary search tree}% -\index{binary search tree!randomized}% -of Mart\'\i nez and Roura \cite{mr98}. -In this variant, every node, #u#, stores the size, #u.size#, of the -subtree rooted at #u#. Both the #add(x)# and #remove(x)# algorithms are -randomized. The algorithm for adding #x# to the subtree rooted at #u# -does the following: +para alguma constante $c$. +Uma classe de funções hash desse tipo que é fácil de implementar e +razoavelmente rápida é \emph{hashing por tabulação} +(\secref{tabulation}). +\index{hashing por tabulação}% +\index{hashing!tabulaçãofilme star wars luke nasceu}% + +Outra variante +#Treap# que não guarda prioridade em cada nodo é a árvore binária de busca randomizada. +\index{árvore binária de busca randomizada}% +\index{árvore binária de busca!randomizada}% +de Mart\'\i nez e Roura \cite{mr98}. +Nessa variante, todo nodo #u# guarda o tamanho #u.size# da subárvore enraizada em #u#. Ambos algoritmos + #add(x)# e #remove(x)# algorithms são randomizados +. O algoritmo para adicionar #x# à subárvore enraizada em #u# +faz o seguinte: \begin{enumerate} - \item With probability $1/(#size(u)#+1)$, the value #x# is added - the usual way, as a leaf, and rotations are then done to bring #x# - up to the root of this subtree. - \item Otherwise (with probability $1-1/(#size(u)#+1)$), the value #x# - is recursively added into one of the two subtrees rooted at #u.left# - or #u.right#, as appropriate. + \item Com probabilidade $1/(#size(u)#+1)$, %TODO + o valor #x# é adicionado normalmente, como uma folha, e rotações + são então feitas para trazer #x# à raiz dessa subárvore. + \item Caso contrário, (com probabilidade $1-1/(#size(u)#+1)$), o valor #x# + é recursivamente adicionado em uma das duas subárvores enraizadas em #u.left# + ou #u.right#, conforme apropriado. \end{enumerate} -The first case corresponds to an #add(x)# operation in a #Treap# where -#x#'s node receives a random priority that is smaller than any of the -#size(u)# priorities in #u#'s subtree, and this case occurs with exactly -the same probability. - -Removing a value #x# from a randomized binary search tree is similar -to the process of removing from a #Treap#. We find the node, #u#, -that contains #x# and then perform rotations that repeatedly increase -the depth of #u# until it becomes a leaf, at which point we can splice -it from the tree. The choice of whether to perform a left or right -rotation at each step is randomized. +O primeiro caso corresponde a uma operação #add(x)# em uma #Treap# onde o nodo +de #x# recebe uma prioridade aleatória que é menor que qualquer uma das prioridades na subárvore de #u# e esse caso ocorre com exatamente a mesma probabilidade. + +A remoção de um valor #x# de uma árvore binária de busca randomizada é similar +ao processo de remoção de uma #Treap#. Achamos o nodo #u# que contém #x# e então +realizamos rotações que repetidamente aumentar a profundidade de #u# até tornar-se uma folha, onde podemos remover da árvore. A decisão de fazer rotações à esquerda +ou direita é randomizada. \begin{enumerate} - \item With probability #u.left.size/(u.size-1)#, we perform a right - rotation at #u#, making #u.left# the root of the subtree that was - formerly rooted at #u#. - \item With probability #u.right.size/(u.size-1)#, we perform a left - rotation at #u#, making #u.right# the root of the subtree that was - formerly rooted at #u#. + \item Com probabilidade #u.left.size/(u.size-1)#, fazemos uma rotação à direita em #u#, fazendo #u.left# a raiz da subárvore que anteriormente estava enraizada em #u#. + \item Com probabilidade #u.right.size/(u.size-1)#, realizamos uma rotação à esquerda em #u#, fazendo #u.right# a raiz da subárvore que estava anteriormente + enraizada em #u#. \end{enumerate} -Again, we can easily verify that these are exactly the same probabilities -that the removal algorithm in a #Treap# will perform a left or right -rotation of #u#. - -Randomized binary search trees have the disadvantage, compared to treaps, -that when adding and removing elements they make many random choices, and -they must maintain the sizes of subtrees. One advantage of randomized -binary search trees over treaps is that subtree sizes can serve another -useful purpose, namely to provide access by rank in $O(\log #n#)$ expected -time (see \excref{treap-get}). In comparison, the random priorities -stored in treap nodes have no use other than keeping the treap balanced. +Novamente, podemos facilmente verificar que essas são exatamente as mesmas +probabilidades que o algoritmo de remoção em uma #Treap# terá para fazer rotação à esquerda ou direita de #u#. + +Árvores binárias de busca randomizada tem a desvantagem, em relação às treaps, +de que ao adicionar ou remover elementos elas farão muitas escolhas aleatórias +e precisam manter os tamanhos das subárvores. +Uma vantagem de árvores binárias de busca randomizada em relação às treaps é que +os tamanhos de subárvores pode servir para outra utilidade: prover acesso por rank +em tempo esperado $O(\log #n#)$ (veja \excref{treap-get}). +Em comparação, as prioridades aleatórias guardadas em nodos de uma treap somente são úteis para manter a treap balanceada. \begin{exc} - Illustrate the addition of 4.5 (with priority 7) and then 7.5 (with - priority 20) on the #Treap# in \figref{treap}. + Simule a adição de 4.5 (com prioridade 7) e então 7.5 (com prioridade 20) na #Treap# em \figref{treap}. \end{exc} \begin{exc} - Illustrate the removal of 5 and then 7 on the #Treap# in \figref{treap}. + Simule a remoção do elemento 5 e então do elemento 7 na #Treap# em + \figref{treap}. \end{exc} \begin{exc} - Prove the assertion that there are $21,964,800$ sequences that generate - the tree on the right hand side of \figref{rbs-lvc}. (Hint: Give a - recursive formula for the number of sequences that generate a complete - binary tree of height $h$ and evaluate this formula for $h=3$.) + Prove que existem $21,964,800$ sequências que geram a árvore + do lado direito de + \figref{rbs-lvc}. (Dica: obtenha uma fórmula recursiva para o número + de sequências que geram uma árvore binária de altura $h$ e use essa + fórmula para $h=3$.) \end{exc} \begin{exc} - Design and implement the #permute(a)# method that takes as input an - array, #a#, that contains #n# distinct values and randomly permutes #a#. - The method should run in $O(#n#)$ time and you should prove that each - of the $#n#!$ possible permutations of #a# is equally probable. + Projete e implemente o método + #permute(a)# que contém #n# valores distintos e aleatoriamente permuta #a#. + O método deve rodar em + $O(#n#)$ de tempo e você deve provar que cada uma das + $#n#!$ possíveis permutações de #a# é igualmente provável. \end{exc} \begin{exc}\exclabel{treap-rotates} - Use both parts of \lemref{rbs-treap} to prove that the expected number - of rotations performed by an #add(x)# operation (and hence also a - #remove(x)# operation) is $O(1)$. + Use ambas partes de + \lemref{rbs-treap} para provar que o número esperado de rotações + realizadas por uma operação #add(x)# (e portanto uma operação + #remove(x)#) é $O(1)$. \end{exc} \begin{exc} - Modify the #Treap# implementation given here so that it does not - explicitly store priorities. Instead, it should simulate them by - hashing the #hashCode()# of each node. + Modifique a implementação + #Treap# dada aqui para que não guarde explicitamente os valores de prioridades. + Em vez disso, você deve simular esses valores com o hashing de cada nodo usando #hashCode()#. \end{exc} \begin{exc} - Suppose that a binary search tree stores, at each node, #u#, the height, - #u.height#, of the subtree rooted at #u#, and the size, #u.size# of - the subtree rooted at #u#. + Suponha que uma árvore binária de busca guarda em cada nodo #u# a altura + #u.height# da subárvore enraizada em #u# e o tamanho #u.size# da subárvore + enraizada em #u#. \begin{enumerate} - \item Show how, if we perform a left or right - rotation at #u#, then these two quantities can be updated, in - constant time, for all nodes affected by the rotation. - \item Explain why the same result is not possible if we try to - also store the depth, #u.depth#, of each node #u#. + \item Mostre que, se fizermos uma rotação à esquerda ou direita em #u#, então + essas duas quantidade podem ser atualizadas em tempo constante para todos os nodos afetados pela rotação. + \item Explique porque o mesmo resultado não é possível se tentarmos também guardar a profundidade #u.depth# de cada nodo #u#. \end{enumerate} \end{exc} \begin{exc} - Design and implement an algorithm that constructs a #Treap# from a - sorted array, #a#, of #n# elements. This method should run in $O(#n#)$ - worst-case time and should construct a #Treap# that is indistinguishable - from one in which the elements of #a# were added one at a time using - the #add(x)# method. + Projete e implemente um algoritmo que constrói uma #Treap# a partir de um + array ordenado #a# de #n# elementos. Esse método deve rodar em + $O(#n#)$ de tempo no pios caso e deve construir uma #Treap# que é + indistinguível de uma em que os elementos de #a# foram adicionados um + por vez usando o método #add(x)#. \end{exc} - \begin{exc} \index{finger}% - \index{finger search!in a treap}% - This exercise works out the details of how one can efficiently search - a #Treap# given a pointer that is close to the node we are searching for. + \index{busca finger!em uma treap}% + Este exercício trabalha os detalhes de como é possível eficientemente + fazer buscas em uma #Treap# dado um ponteiro que está próximo ao nodo + que estamos procurando. \begin{enumerate} - \item Design and implement a #Treap# implementation in which each - node keeps track of the minimum and maximum values in its subtree. - \item Using this extra information, add a #fingerFind(x,u)# method - that executes the #find(x)# operation with the help of a pointer - to the node #u# (which is hopefully not far from the node that - contains #x#). This operation should start at #u# and walk upwards - until it reaches a node #w# such that $#w.min#\le #x#\le #w.max#$. - From that point onwards, it should perform a standard search - for #x# starting from #w#. (One can show that #fingerFind(x,u)# - takes $O(1+\log r)$ time, where $r$ is the number of elements in - the treap whose value is between #x# and #u.x#.) - \item Extend your implementation into a version of a treap that - starts all its #find(x)# operations from the node most recently - found by #find(x)#. + \item Projete e implemente uma #Treap# em que cada nodo registra os valores mínimos e máximos em sua subárvore. + \item Usando essa informação extrea, adicione um método #fingerFind(x,u)# + que executa a operação + #find(x)# com a ajudar de um ponteiro para o nodo #u# (que + espera-se que não esteja distante do nodo que contém #x#). + Essa operação deve iniciar em #u# subir na árvore até que alcance um + nodo #w# tal que + $#w.min#\le #x#\le #w.max#$. + A partir desse ponto, deve-se realizar uma busca padrão por #x# + partindo de #w#. + (É possível mostrar que + #fingerFind(x,u)# leva + $O(1+\log r)$ de tempo, onde $r$ é o número de elementos em uma treap cujo valor está entre #x# e #u.x#.) + \item Estenda sua implementação em uma versão de treap que inicia todas as operações #find(x)# a partir do nodo mais recentemente encontrado por #find(x)#. \end{enumerate} \end{exc} \begin{exc}\exclabel{treap-get} - Design and implement a version of a #Treap# that includes a #get(i)# - operation that returns the key with rank #i# in the #Treap#. (Hint: - Have each node, #u#, keep track of the size of the subtree rooted - at #u#.) + Projete e implemente uma versão de #Treap# que inclui uma operação + #get(i)# + que retorna a chave com rank #i# na #Treap#. (Dica: faça que cada nodo #u# registre o tamanho da subárvore enraizada em #u#.) Hint: \end{exc} \begin{exc} \index{TreapList@#TreapList#}% - Implement a #TreapList#, an implementation of the #List# interface - as a treap. Each node in the treap should store a list item, and an - in-order traversal of the treap finds the items in the same order that - they occur in the list. All the #List# operations #get(i)#, #set(i,x)#, - #add(i,x)# and #remove(i)# should run in $O(\log #n#)$ expected time. + Codifique uma #TreapList#, uma implementação de uma interface #List# na forma de uma treap. Cada nodo na treap deve guardar um item da lista e uma + travessia em-ordem na treap encontra os itens na mesma ordem que ocorrem na lista. + Todas as operações da #List#, + #get(i)#, #set(i,x)#, + #add(i,x)# e #remove(i)# rodar em tempo esperado $O(\log #n#)$. \end{exc} +\begin{exc}\exclabel{treap-split} + Projete e implemente uma versão de uma #Treap# que aceita a operação #split(x)#. + Essa operação remove todos os valores da #Treap# que são maiores que + #x# e retorna uma segunda #Treap# que contém todos os valores removidos. + \noindent Exemplo: o código #t2 = t.split(x)# remove de #t# todos os + valores maiores que + #x# e retorna uma nova #Treap# #t2# contendo todos esses valores. + A operação #split(x)# devem rodar em tempo esperado $O(\log #n#)$. -\begin{exc}\exclabel{treap-split} - Design and implement a version of a #Treap# that supports the #split(x)# - operation. This operation removes all values from the #Treap# that - are greater than #x# and returns a second #Treap# that contains all - the removed values. - - \noindent Example: the code #t2 = t.split(x)# removes from #t# all values - greater than #x# and returns a new #Treap# #t2# containing all - these values. The #split(x)# operation should run in $O(\log #n#)$ - expected time. - - \noindent Warning: For this modification to work properly and still allow the - #size()# method to run in constant time, it is necessary to implement - the modifications in \excref{treap-get}. + \noindent Aviso: Para essa modificação funcionar adequadamente e ainda possibilitar o método #size()# rodar em tempo constante, é necessário implementar as modificações em \excref{treap-get}. \end{exc} \begin{exc}\exclabel{treap-join} - Design and implement a version of a #Treap# that supports the - #absorb(t2)# operation, which can be thought of as the inverse of - the #split(x)# operation. This operation removes all values from the - #Treap# #t2# and adds them to the receiver. This operation presupposes - that the smallest value in #t2# is greater than the largest value in - the receiver. The #absorb(t2)# operation should run in $O(\log #n#)$ - expected time. + Projete e implemente uma versão de uma #Treap# que aceita a operação + #absorb(t2)#, que pode ser pensada como o inverso da operação #split(x)#. + Essa operação remove todos os valores da #Treap# #t2# e os adiciona ao receptor. + Essa operação pressupõe que o menor valor em #t2# é maior que o maior valor no receptor. A operação #absorb(t2)# deve rodar em tempo esperado + $O(\log #n#)$. \end{exc} \begin{exc} - Implement Martinez's randomized binary search trees, as discussed in - this section. Compare the performance of your implementation with - that of the #Treap# implementation. + Implemente a árvore binária de busca randomizada de Martinez conforme discutido nesta seção. Compare o desempenho da sua implementação com a implementação da #Treap#. \end{exc} - From 30b1c3ae577a974128f4f94410b290d9f8bba89b Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sat, 22 Aug 2020 17:43:17 -0300 Subject: [PATCH 26/66] translation to portuguese --- latex/scapegoat.tex | 562 +++++++++++++++++++++++--------------------- 1 file changed, 300 insertions(+), 262 deletions(-) diff --git a/latex/scapegoat.tex b/latex/scapegoat.tex index 029220d4..279ac882 100644 --- a/latex/scapegoat.tex +++ b/latex/scapegoat.tex @@ -1,91 +1,98 @@ -\chapter{Scapegoat Trees} +\chapter{Árvores Scapegoat} \chaplabel{scapegoat} -In this chapter, we study a binary search tree data structure, the -#ScapegoatTree#. This structure is based on the common wisdom that, -when something goes wrong, the first thing people tend to do is find -someone to blame (the \emph{scapegoat}). +Neste capítulo, estudamos uma estrutura de dados de árvore binária de busca, a +#ScapegoatTree#. Essa estrutura é baseada na ideia popular que +quando algo sai errado, a primeira coisa que pessoas tendem a fazer +é achar alguém para por a culpa (o bode expiatório, em inglês \emph{scapegoat}). \index{scapegoat}% -Once blame is firmly -established, we can leave the scapegoat to fix the problem. - -A #ScapegoatTree# keeps itself balanced by \emph{partial rebuilding -operations}. -\index{partial rebuilding}% -\index{binary search tree!partial rebuilding}% -During a partial rebuilding operation, an entire subtree is -deconstructed and rebuilt into a perfectly balanced subtree. There are -many ways of rebuilding a subtree rooted at node #u# into a perfectly -balanced tree. One of the simplest is to traverse #u#'s subtree, -gathering all its nodes into an array, #a#, and then to recursively -build a balanced subtree using #a#. If we let $#m#=#a.length#/2$, -then the element #a[m]# becomes the root of the new subtree, -$#a#[0],\ldots,#a#[#m#-1]$ get stored recursively in the left subtree -and $#a#[#m#+1],\ldots,#a#[#a.length#-1]$ get stored recursively in the -right subtree. -\codeimport{ods/ScapegoatTree.rebuild(u).packIntoArray(u,a,i).buildBalanced(a,i,ns)} -A call to #rebuild(u)# takes $O(#size(u)#)$ time. The resulting subtree -has minimum height; there is no tree of smaller height that -has #size(u)# nodes. +\index{bode expiatório}% +Uma vez que culpa está estabelecida, podemos deixar que o bode expiatório resolva o problema. + +Uma #ScapegoatTree# se mantém balanceada usando \emph{operações de reconstrução parcial} +\index{reconstrução parcial}% +\index{árvore de busca binária!reconstrução parcial}% +Durante uma operação de reconstrução parcial, uma subárvore inteira +é desconstruída e reconstruída em uma subárvore perfeitamente balanceada. +Existem muitas forma de reconstrução de uma subárvore enraizada no nodo #u# +em uma árvore perfeitamente balanceada. Uma das mais simples é percorrer a subárvore de #u#, coletando todos seu nodos em um array, #a# e então recursivamente construir uma subárvore balanceada usando #a#. + +Se fazemos +$#m#=#a.length#/2$, +então o elemento #a[m]# torna-se raiz da nova subárvore, +$#a#[0],\ldots,#a#[#m#-1]$ é armazenada recursivamente na subárvore à esquerda +e $#a#[#m#+1],\ldots,#a#[#a.length#-1]$ é armazenada na subárvore à direita. +\codeimport{ods/ScapegoatTree.rebuild(u).packIntoArray(u,a,i).buildBalanced(a,i,ns)} +Uma chamada a +#rebuild(u)# leva $O(#size(u)#)$ de tempo. A subárvore resultante altura mínima; +não há árvore de menor altura com #size(u)# nodos. -\section{#ScapegoatTree#: A Binary Search Tree with Partial Rebuilding} +\section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parcial} \seclabel{scapegoattree} - \index{ScapegoatTree@#ScapegoatTree#}% -A #ScapegoatTree# is a #BinarySearchTree# that, in addition to keeping -track of the number, #n#, of nodes in the tree also keeps a counter, #q#, -that maintains an upper-bound on the number of nodes. +Uma #ScapegoatTree# é uma #BinarySearchTree# que além de registrar o númer #n# de nodos na árvore também mantém um contador #q# que mantém um limitante superior no número de nodos. \codeimport{ods/ScapegoatTree.q} -At all times, #n# and #q# obey the following inequalities: +Durante todo seu uso, #n# e #q# seguem as seguintes desigualdades: \[ #q#/2 \le #n# \le #q# \enspace . \] -In addition, a #ScapegoatTree# has logarithmic height; at all times, the height of the scapegoat tree does not exceed +Além disso, uma +#ScapegoatTree# tem altura logaritmica; e a altura da árvore scapegoat não excede: \begin{equation} \log_{3/2} #q# \le \log_{3/2} 2#n# < \log_{3/2} #n# + 2\enspace . \eqlabel{scapegoat-height} \end{equation} -Even with this constraint, a #ScapegoatTree# can look surprisingly unbalanced. The tree in \figref{scapegoat-example} has $#q#=#n#=10$ and height $5<\log_{3/2}10 \approx 5.679$. +Mesmo com essa restrição, uma + #ScapegoatTree# pode parecer surpreendetemente desbalanceada. A árvore em \figref{scapegoat-example} tem $#q#=#n#=10$ e altura $5<\log_{3/2}10 \approx 5.679$. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/scapegoat-insert-1} \end{center} - \caption[A ScapegoatTree]{A #ScapegoatTree# with 10 nodes and height 5.} + \caption[Uma ScapegoatTree]{Uma #ScapegoatTree# com 10 nodos e altura 5.} \figlabel{scapegoat-example} \end{figure} -Implementing the #find(x)# operation in a #ScapegoatTree# is done -using the standard algorithm for searching in a #BinarySearchTree# -(see \secref{binarysearchtree}). This takes time proportional to the -height of the tree which, by \myeqref{scapegoat-height} is $O(\log #n#)$. - -To implement the #add(x)# operation, we first increment #n# and #q# -and then use the usual algorithm for adding #x# to a binary search -tree; we search for #x# and then add a new leaf #u# with $#u.x#=#x#$. -At this point, we may get lucky and the depth of #u# might not exceed -$\log_{3/2}#q#$. If so, then we leave well enough alone and don't do -anything else. - -Unfortunately, it will sometimes happen that $#depth(u)# > \log_{3/2} -#q#$. In this case, we need to reduce the height. This isn't a big -job; there is only one node, namely #u#, whose depth exceeds $\log_{3/2} -#q#$. To fix #u#, we walk from #u# back up to the root looking for a -\emph{scapegoat}, #w#. The scapegoat, #w#, is a very unbalanced node. -It has the property that +A implementação da operação #find(x)# em uma + #ScapegoatTree# é feita usando o algoritmo padrão para buscas em uma +#BinarySearchTree# +(veja \secref{binarysearchtree}). Isso leva tempo proporcional à altura da árvore que, de acordo com + \myeqref{scapegoat-height} é $O(\log #n#)$. + + Para implementar a operação +#add(x)#, primeiramente incrementamos #n# e #q# e então usamos o algoritmo usual +para adicionar #x# a uma árvore binária de busca; buscamos por #x# e então +adicionamos uma nova filha #u# com +$#u.x#=#x#$. +A esse ponto, podemos ter sorte e a profundidade de #u# pode não exceder +$\log_{3/2}#q#$. Se assim for, então deixamos a árvore como está e não fazemos mais nada. + +Infelizmente, em algumas vezes pode acontecer que +$#depth(u)# > \log_{3/2} +#q#$. Nesse caso, precisamos reduzir a altura. +Isso não é nada demais; há somente um nodo, #u#, cuja profundidade excede +$\log_{3/2} +#q#$. Para arrumar #u#, saimos de #u# para subir na árvore em busca de um \emph{bode expiatório} #w#. Esse bode expiatório é um nodo muito desbalanceado. +Ele tem a seguinte propriedade \begin{equation} \frac{#size(w.child)#}{#size(w)#} > \frac{2}{3} \enspace , \eqlabel{scapegoat} \end{equation} -where #w.child# is the child of #w# on the path from the root to #u#. -We'll very shortly prove that a scapegoat exists. For now, we can -take it for granted. Once we've found the scapegoat #w#, we completely -destroy the subtree rooted at #w# and rebuild it into a perfectly balanced -binary search tree. We know, from \myeqref{scapegoat}, that, even before -the addition of #u#, #w#'s subtree was not a complete binary tree. -Therefore, when we rebuild #w#, the height decreases by at least 1 so that the height of the #ScapegoatTree# is once again at most $\log_{3/2}#q#$. +onde + #w.child# é o filho de #w# no caminho da raiz a #u#. +Iremos rapidamente provar que um bode expiatório existe. +Por agora, podemos assumir que ele existe. +Uma vez que encontramos o bode expiatório #w#, podemos completamente +destruir a subárvore enraizada em #w# e reconstruí-la em uma árvore binária +de busca perfeitamente balanceada. +Sabemos +de \myeqref{scapegoat} que mesmo antes da adição de #u#, a subárvore +de #w# não era uma árvore binária completa. +Portanto, quando reconstruirmos #w#, a altura decresce por pelo menos 1 tal que +a altura de + #ScapegoatTree# seja novamente até $\log_{3/2}#q#$. \codeimport{ods/ScapegoatTree.add(x)} @@ -96,55 +103,62 @@ \section{#ScapegoatTree#: A Binary Search Tree with Partial Rebuilding} \includegraphics[scale=0.90909]{figs/scapegoat-insert-4} \end{tabular} \end{center} - \caption[Adding to a scapegoat tree]{Inserting 3.5 into a #ScapegoatTree# increases its height to 6, which violates \myeqref{scapegoat-height} since $6 > \log_{3/2} 11 \approx 5.914$. A scapegoat is found at the node containing 5.} + \caption[Adicionando à árvore scapegoat]{Inserindo 3.5 em uma #ScapegoatTree# aumenta sua altura para 6, o que viola \myeqref{scapegoat-height} pois $6 > \log_{3/2} 11 \approx 5.914$. Um bode expiatório é achado no nodo contendo 5.} \end{figure} -If we ignore the cost of finding the scapegoat #w# and rebuilding the -subtree rooted at #w#, then the running time of #add(x)# is dominated -by the initial search, which takes $O(\log #q#) = O(\log #n#)$ time. -We will account for the cost of finding the scapegoat and rebuilding -using amortized analysis in the next section. - -The implementation of #remove(x)# in a #ScapegoatTree# is very simple. -We search for #x# and remove it using the usual algorithm for removing a -node from a #BinarySearchTree#. (Note that this can never increase the -height of the tree.) Next, we decrement #n#, but leave #q# unchanged. -Finally, we check if $#q# > 2#n#$ and, if so, then we \emph{rebuild the entire -tree} into a perfectly balanced binary search tree and set $#q#=#n#$. +Se ignorarmos o custo de achar o bode expiatório #w# e reconstruirmos +a subárvore enraizada em #w#, então o tempo de execução de #add(x)# é +dominado pela busca inicial, o que leva +$O(\log #q#) = O(\log #n#)$ de tempo. +Iremos considerar o custo de encontrar o bode expiatório e de reconstrução +usando análise amortizada na seção a seguir. + +A implementação de #remove(x)# em uma +#ScapegoatTree# é muito simples. +Buscamos por #x# e o removemos usando o algoritmo usual para remover um +nodo de uma +#BinarySearchTree#. (Note que isso nunca aumenta a altura da árvore.) +A seguir, decrementamos #n#, mas não alteramos #q#. +Finalmente, verificamos se +$#q# > 2#n#$ e, caso positivo, então \emph{reconstruímos a árvore inteira} +em uma árvore binária perfeitamente balanceada em atribuímos + $#q#=#n#$. \codeimport{ods/ScapegoatTree.remove(x)} -Again, if we ignore the cost of rebuilding, the running time of the -#remove(x)# operation is proportional to the height of the tree, and is -therefore $O(\log #n#)$. +Novamente, se ignorarmos o custo de recontrução, o tempo de execução da +operação +#remove(x)# é proporcional à altura da árvore que é +$O(\log #n#)$. -\subsection{Analysis of Correctness and Running-Time} +\subsection{Análise de Corretude e Tempo de Execução} -In this section, we analyze the correctness and amortized running time -of operations on a #ScapegoatTree#. We first prove the correctness by -showing that, when the #add(x)# operation results in a node that violates -Condition \myeqref{scapegoat-height}, then we can always find a scapegoat: +Nesta seção, analizaremos a corretude e o tempo amortizado de operações +em uma + #ScapegoatTree#. Primeiro provamos a corretude ao mostrar que, quando a operação #add(x)# resulta em um nodo que viola a +Condition \myeqref{scapegoat-height}, então sempre podemos achar um bode expiatório: \begin{lem} - Let #u# be a node of depth $h>\log_{3/2} #q#$ in a #ScapegoatTree#. - Then there exists a node $#w#$ on the path from #u# to the root - such that + Seja #u# um nodo de profundidade $h>\log_{3/2} #q#$ em uma #ScapegoatTree#. + Então existe um nodo + $#w#$ no caminho de #u# à raiz tal que \[ \frac{#size(w)#}{#size(parent(w))#} > 2/3 \enspace . \] \end{lem} \begin{proof} - Suppose, for the sake of contradiction, that this is not the case, and + Suponha, para efeitos de contradição, que esse não é o caso, e \[ \frac{#size(w)#}{#size(parent(w))#} \le 2/3 \enspace . \] - for all nodes #w# on the path from #u# to the root. Denote the path - from the root to #u# as $#r#=#u#_0,\ldots,#u#_h=#u#$. Then, we have + para todos os nodos #w# no caminho de #u# a raiz. Denote o caminho + da raiz a #u# como $#r#=#u#_0,\ldots,#u#_h=#u#$. Então, temos $#size(u#_0#)#=#n#$, $#size(u#_1#)#\le\frac{2}{3}#n#$, - $#size(u#_2#)#\le\frac{4}{9}#n#$ and, more generally, + $#size(u#_2#)#\le\frac{4}{9}#n#$ e, de modo mais geral, \[ #size(u#_i#)#\le\left(\frac{2}{3}\right)^i#n# \enspace . \] - But this gives a contradiction, since $#size(u)#\ge 1$, hence + Mas isso resulta em uma contradição, pois + $#size(u)#\ge 1$, portanto \[ 1 \le #size(u)# \le \left(\frac{2}{3}\right)^h#n# < \left(\frac{2}{3}\right)^{\log_{3/2} #q#}#n# @@ -154,25 +168,28 @@ \subsection{Analysis of Correctness and Running-Time} \] \end{proof} -Next, we analyze the parts of the running time that are not yet -accounted for. There are two parts: The cost of calls to #size(u)# -when searching for scapegoat nodes, and the cost of calls to #rebuild(w)# -when we find a scapegoat #w#. The cost of calls to #size(u)# can be -related to the cost of calls to #rebuild(w)#, as follows: +A seguirm analizamos as partes do tempo de execuão que ainda não foram +levadas em conta. +Existem duas partes: o custo das chamadas a #size(u)# ao buscar por nodos +bodes expiatórios e o custo de chamadas a +#rebuild(w)# quando encontramos um bode expiatório #w#. +O custo das chamadas a #size(u)# pode ser relacionado ao custo de chamadas +a #rebuild(w)# da seguinte forma: \begin{lem} -During a call to #add(x)# in a #ScapegoatTree#, the cost of finding the scapegoat #w# and rebuilding the subtree rooted at #w# is $O(#size(w)#)$. + Durante uma chamada a +#add(x)# em uma #ScapegoatTree#, o custo de encontrar o bode expiatório #w# e reconstruir a subárvore enraizada em #w# é $O(#size(w)#)$. \end{lem} \begin{proof} -The cost of rebuilding the scapegoat node #w#, once we find it, is -$O(#size(w)#)$. When searching for the scapegoat node, we call #size(u)# -on a sequence of nodes $#u#_0,\ldots,#u#_k$ until we find the scapegoat -$#u#_k=#w#$. However, since $#u#_k$ is the first node in this sequence -that is a scapegoat, we know that + O custo de reconstruir o nodo bode expiatório #w# uma vez que o achamos é +$O(#size(w)#)$. Ao busca o nodo bode expiatório, chamamos #size(u)# em uma +sequência de nodos +$#u#_0,\ldots,#u#_k$ até que encontramos um bode expiatório +$#u#_k=#w#$. Porém, como $#u#_k$ é o primeiro nodo nessa sequência que é um bode expiatório, sabemos que \[ #size(u#_{i}#)# < \frac{2}{3}#size(u#_{i+1}#)# \] -for all $i\in\{0,\ldots,k-2\}$. Therefore, the cost of all calls to #size(u)# is +para todo $i\in\{0,\ldots,k-2\}$. Portanto, o custo de todas as chamadas a #size(u)# é \begin{eqnarray*} O\left( \sum_{i=0}^k #size(u#_{k-i}#)# \right) &=& O\left( @@ -189,248 +206,269 @@ \subsection{Analysis of Correctness and Running-Time} \right)\right) \\ &=& O(#size(u#_k#)#) = O(#size(w)#) \enspace , \end{eqnarray*} -where the last line follows from the fact that the sum is a geometrically decreasing series. + onde a última linha segue do fato que a soma é uma séries geométrica decrescente. \end{proof} -All that remains is to prove an upper-bound on the cost of all calls to -#rebuild(u)# during a sequence of $m$ operations: +Somente falta provar um limitante superior no custo de todas as chamadas a +#rebuild(u)# durante uma sequência de $m$ operações: \begin{lem}\lemlabel{scapegoat-amortized} - Starting with an empty #ScapegoatTree# any sequence of $m$ #add(x)# - and #remove(x)# operations causes at most $O(m\log m)$ time to be used - by #rebuild(u)# operations. + Iniciando com uma + #ScapegoatTree# vazia com qualquer sequência de $m$ operações #add(x)# + e #remove(x)# causam até $O(m\log m)$ de tempo para ser usado por operações + #rebuild(u)#. \end{lem} \begin{proof} - To prove this, we will use a \emph{credit scheme}. - \index{credit scheme}% - We imagine that each node - stores a number of credits. Each credit can pay for some constant, - $c$, units of time spent rebuilding. The scheme gives out a total of - $O(m\log m)$ credits and every call to #rebuild(u)# is paid for with - credits stored at #u#. - - During an insertion or deletion, we give one credit to each node on the - path to the inserted node, or deleted node, #u#. In this way we hand - out at most $\log_{3/2}#q#\le \log_{3/2}m$ credits per operation. - During a deletion we also store an additional credit ``on the side.'' - Thus, in total we give out at most $O(m\log m)$ credits. All that - remains is to show that these credits are sufficient to pay for all - calls to #rebuild(u)#. - - If we call #rebuild(u)# during an insertion, it is because #u# is - a scapegoat. Suppose, without loss of generality, that + Para provar isso, iremos usar um \emph{esquema de créditos}. + \index{esquema de créditos}% + Imaginamos que cada nodo guarda um número de créditos. Cada crédito + pode pagar por alguma constante, $c$, unidades de tempo gastos na reconstrução. + + O esquema resulta provê um total de + $O(m\log m)$ créditos e toda chamada de #rebuild(u)# é paga com esses créditos guardados em #u#. +Durante uma inserção ou deleção, cedemos um crédito a cada nodo no +caminho ao nodo inserido, ou removido, #u#. +Dessa maneira gastamos até + $\log_{3/2}#q#\le \log_{3/2}m$ créditos por operação. + Duração a remoção também guardamos um crédito adicional de reserva. + Então, no total cedemos até + $O(m\log m)$ créditos. Tudo o que resta é mostrar que esses créditos são suficientes para pagar por todas as chamadas a #rebuild(u)#. +Se chamarmos + #rebuild(u)# durante uma inserção, é porque #u# é um bode expiatório. + Suponha, sem perda de generalidade, que \[ \frac{#size(u.left)#}{#size(u)#} > \frac{2}{3} \enspace . \] - Using the fact that + Usando o fato que \[ #size(u)# = 1 + #size(u.left)# + #size(u.right)# \] - we deduce that + deduzimos que \[ \frac{1}{2}#size(u.left)# > #size(u.right)# \enspace \] - and therefore + e portanto \[ #size(u.left)# - #size(u.right)# > \frac{1}{2}#size(u.left)# > \frac{1}{3}#size(u)# \enspace . \] - Now, the last time a subtree containing #u# was rebuilt (or when #u# - was inserted, if a subtree containing #u# was never rebuilt), we had + Agor, a última vez que uma subárvore contendo #u# foi reconstruída (ou quando #u# + foi inserido, se uma subárvore contendo #u# nunca foi reconstruída), temos \[ #size(u.left)# - #size(u.right)# \le 1 \enspace . \] - Therefore, the number of #add(x)# or #remove(x)# operations that have - affected #u.left# or #u.right# since then is at least + Portanto, o número de operações + #add(x)# ou #remove(x)# que afetaram + #u.left# ou #u.right# desde então é pelo menos \[ \frac{1}{3}#size(u)# - 1 \enspace . \] - and there are therefore at least this many credits stored at #u# - that are available to pay for the $O(#size(u)#)$ time it takes to - call #rebuild(u)#. - - If we call #rebuild(u)# during a deletion, it is because $#q# > 2#n#$. - In this case, we have $#q#-#n#> #n#$ credits stored ``on the side,'' and - we use these to pay for the $O(#n#)$ time it takes to rebuild the root. - This completes the proof. + e há portanto pelo menos essa quantidade de créditos guardados em #u# + que estão disponíveis para pagar pelo + $O(#size(u)#)$ de time que leva para chamar + #rebuild(u)#. + + Se chamarmos + #rebuild(u)# durante uma remoção, é porque $#q# > 2#n#$. + Nesse caso, temos + $#q#-#n#> #n#$ créditos guardados em uma reserva e os usamos + para pagar pelo + $O(#n#)$ de tempo que leva para reconstrução a raiz. Isso completa a prova. \end{proof} -\subsection{Summary} -The following theorem summarizes the performance of the #ScapegoatTree# data structure: +\subsection{Resumo} +O teorema a seguir resume o desempenho da estrutura de dados + #ScapegoatTree#: \begin{thm}\thmlabel{scapegoat} - A #ScapegoatTree# implements the #SSet# interface. Ignoring the cost - of #rebuild(u)# operations, a #ScapegoatTree# supports the operations - #add(x)#, #remove(x)#, and #find(x)# in $O(\log #n#)$ time per operation. + Uma #ScapegoatTree# implementa a interface #SSet#. Ignorando o custo + de operações + #rebuild(u)#, uma #ScapegoatTree# aceita as operações + #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ de tempo por operação. - Furthermore, beginning with an empty #ScapegoatTree#, any sequence of $m$ - #add(x)# and #remove(x)# operations results in a total of $O(m\log m)$ - time spent during all calls to #rebuild(u)#. + Além disso, iniciando com uma + #ScapegoatTree# vazia, qualquer sequência de $m$ operações + #add(x)# e #remove(x)# resulta em um tempo total $O(m\log m)$ + gasto em todas as chamadas a #rebuild(u)#. \end{thm} -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -The term \emph{scapegoat tree} is due to Galperin and Rivest \cite{gr93}, -who define and analyze these trees. However, the same structure -was discovered earlier by Andersson \cite{a89,a99}, who called them -\emph{general balanced trees} +O termo + \emph{scapegoat tree} é atribuído a Galperin e Rivest \cite{gr93}, + que defime e analizam essas árvores. Porém, a mesma estrutura foi + descuberta anteriormente por + \cite{a89,a99}, que as chamou de +\emph{árvores balanceadas gerais (originalmente, em inglês, general balanced trees)} \index{general balanced tree}% -since they can have any shape as long as -their height is small. +\index{árvores balanceadas gerais}% +pois elas podem ter qualquer forma desde que a altura seja pequena. -Experimenting with the #ScapegoatTree# implementation will reveal that -it is often considerably slower than the other #SSet# implementations -in this book. This may be somewhat surprising, since height bound of +Experimentos com a implementação + #ScapegoatTree# irá revelar que + ela costuma ser consideravelmente mais lenta que as outras implementações de #SSet# neste livro. + Isso pode ser surpreendente pois a altura é limitada por \[ \log_{3/2}#q# \approx 1.709\log #n# + O(1) \] -is better than the expected length of a search path in a #Skiplist# and -not too far from that of a #Treap#. The implementation could be optimized -by storing the sizes of subtrees explicitly at each node or by reusing -already computed subtree sizes (Exercises~\ref{exc:scapegoat-quicksize} -and \ref{exc:scapegoat-explicitsize}). Even with these optimizations, -there will always be sequences of #add(x)# and #delete(x)# operation for -which a #ScapegoatTree# takes longer than other #SSet# implementations. - -This gap in performance is due to the fact that, unlike the other #SSet# -implementations discussed in this book, a #ScapegoatTree# can spend a lot -of time restructuring itself. \excref{scapegoat-nlogn} asks you to prove -that there are sequences of #n# operations in which a #ScapegoatTree# -will spend on the order of $#n#\log #n#$ time in calls to #rebuild(u)#. -This is in contrast to other #SSet# implementations discussed in this -book, which only make $O(#n#)$ structural changes during a sequence -of #n# operations. This is, unfortunately, a necessary consequence of -the fact that a #ScapegoatTree# does all its restructuring by calls to +é melhor que o comprimento esperado de um caminho de busca em uma +#Skiplist# e não muito longe de um em uma #Treap#. + +A implementação pode ser otimizada ao armazenar os tamanhos das subárvores +explicitamente em cada nodo ou reutilizando tamanhos de subárvores previamente +comptuados (Exercícios~\ref{exc:scapegoat-quicksize} +e \ref{exc:scapegoat-explicitsize}). Mesmo com essas otimizações, +sempre haverão sequências de operações +#add(x)# e #delete(x)# para as quais uma + #ScapegoatTree# leva mais tempo que outras implementações da #SSet#. + +Essa diferença em despempenho é devido ao fato que, diferentemente de +outras implementações #SSet# discutidas neste livro, uma + #ScapegoatTree# pode gastar muito tempo se reestruturando. + \excref{scapegoat-nlogn} pede que você prova que existem sequências de #n# operações em que uma +#ScapegoatTree# irá gastar + $#n#\log #n#$ de tempo em chamadas a #rebuild(u)#. +Isso contrasta a outras implementações #SSet# discutidas neste livro, que fazem somente +$O(#n#)$ mudanças estruturais durante uma sequência de +#n# operações. Isto é, infelizmente, uma consequência necessária do fato que +uma #ScapegoatTree# faz sua restruturação usando chamadas a #rebuild(u)# \cite{d90}. -Despite their lack of performance, there are applications in which a -#ScapegoatTree# could be the right choice. This would occur any time -there is additional data associated with nodes that cannot be updated -in constant time when a rotation is performed, but that can be updated -during a #rebuild(u)# operation. In such cases, the #ScapegoatTree# -and related structures based on partial rebuilding may work. An example of such an application is outlined in \excref{list-order-maintenance}. +Embora seu desempenho relativamente ruim, há aplicações em que uma +#ScapegoatTree# pode ser a escolha certa. +Isso ocorreria quando há dados adicionais associados a nodos que não +podem ser atualizados em tempo constante quando uma rotação é realizada +mas que podem ser atualizados em uma operação #rebuild(u)#. +Nesses casos, a +#ScapegoatTree# e outras estruturas similares baseadas em reconstrução parcial podem +funcionar bem. Um exemplo de tal aplicação é esboçado em +\excref{list-order-maintenance}. \begin{exc} - Illustrate the addition of the values 1.5 and then 1.6 on the - #ScapegoatTree# in \figref{scapegoat-example}. +Simule a adição dos valores 1.5 e depois 1.6 na + #ScapegoatTree# em \figref{scapegoat-example}. \end{exc} \begin{exc} - Illustrate what happens when the sequence $1,5,2,4,3$ is added to an - empty #ScapegoatTree#, and show where the credits described in the - proof of \lemref{scapegoat-amortized} go, and how they are used during - this sequence of additions. + Ilustre o que acontece quando a sequência + $1,5,2,4,3$ é adicionada a uma + #ScapegoatTree# vazia, e mostre onde os créditos descritos na prova + de + \lemref{scapegoat-amortized} vão, e como eles são usados durante + essa sequência de adições. \end{exc} \begin{exc}\exclabel{scapegoat-nlogn} - Show that, if we start with an empty #ScapegoatTree# and call #add(x)# - for $#x#=1,2,3,\ldots,#n#$, then the total time spent during calls to - #rebuild(u)# is at least $c#n#\log #n#$ for some constant $c>0$. + Mostre que, se começarmos com uma + #ScapegoatTree# vazia e chamarmos #add(x)# para + $#x#=1,2,3,\ldots,#n#$, então o tempo total gasto em chamadas a + #rebuild(u)# é pelo menos $c#n#\log #n#$ para alguma constante $c>0$. \end{exc} \begin{exc} - The #ScapegoatTree#, as described in this chapter, guarantees that the - length of the search path does not exceed $\log_{3/2}#q#$. + A + #ScapegoatTree#, conforme descrita neste capítulo, garante que o + comprimento do caminho de busca não excede + $\log_{3/2}#q#$. \begin{enumerate} - \item Design, analyze, and implement a modified version of - #ScapegoatTree# where the length of the search path does not exceed - $\log_{#b#} #q#$, where #b# is a parameter with $1<#b#<2$. - \item What does your analysis and/or your experiments say about the - amortized cost of #find(x)#, #add(x)# and #remove(x)# as a function - of #n# and #b#? + \item Projete, analise e implemente uma versão modificada de + #ScapegoatTree# onde o comprimento do caminho de busca que não excede + $\log_{#b#} #q#$, onde #b# é um parâmetro com $1<#b#<2$. + \item O que sua análise e experimentos dizem sobre o custo amortizado de + #find(x)#, #add(x)# e #remove(x)# como uma função de + #n# e #b#? \end{enumerate} \end{exc} \begin{exc}\exclabel{scapegoat-quicksize} - Modify the #add(x)# method of the #ScapegoatTree# so that it does not - waste any time recomputing the sizes of subtrees that have already - been computed. This is possible because, by the time the method - wants to compute #size(w)#, it has already computed one of #size(w.left)# - or #size(w.right)#. Compare the performance of your modified - implementation with the implementation given here. +Modifique o método #add(x)# da #ScapegoatTree# tal que não gaste +qualquer tempo recomputando os tamanhos das subárvores que foram +anteriormente computados. Isso é possível porque quando o método + quer computar #size(w)#, ele já computou #size(w.left)# + ou #size(w.right)#. Compare o desempenho de sua + implementação modificada com a implementação dada aqui. \end{exc} \begin{exc}\exclabel{scapegoat-explicitsize} - Implement a second version of the #ScapegoatTree# data structure that - explicitly stores and maintains the sizes of the subtree rooted at - each node. Compare the performance of the resulting implementation - with that of the original #ScapegoatTree# implementation as well as - the implementation from \excref{scapegoat-quicksize}. + Implemente uma segunda versão da estrutura de dados #ScapegoatTree# + que explicitamente guarda e mantém os tamanhos da subárvore enraizada + em cada nodo. Compare o desempenho da implementação resultante + com a #ScapegoatTree# original assim como a implementação feita para o + \excref{scapegoat-quicksize}. \end{exc} \begin{exc} - Reimplement the #rebuild(u)# method discussed at the beginning of this - chapter so that it does not require the use of an array to store the - nodes of the subtree being rebuilt. Instead, it should use recursion - to first connect the nodes into a linked list and then convert this - linked list into a perfectly balanced binary tree. (There are - very elegant recursive implementations of both steps.) + Reimplemente o método #rebuild(u)# discutido no começo desse capítulo tal que não requer o uso de um array para guardar os nodos da subárve sendo reconstruída. + Em vez disso, deve-se usar recursão primeiro para conectar os nodos em uma lista ligada e então converter essa lista ligada em uma árvore binária perfeitamente balanceada. (Existem implementações recursivas muito elegantes de ambos os passos.) \end{exc} \begin{exc} \index{WeightBalancedTree@#WeightBalancedTree#}% - Analyze and implement a #WeightBalancedTree#. This is a tree in - which each node #u#, except the root, maintains the \emph{balance - invariant} that $#size(u)# \le (2/3)#size(u.parent)#$. The #add(x)# and - #remove(x)# operations are identical to the standard #BinarySearchTree# - operations, except that any time the balance invariant is violated at - a node #u#, the subtree rooted at #u.parent# is rebuilt. - Your analysis should show that operations on a #WeightBalancedTree# - run in $O(\log#n#)$ amortized time. + Analise e implemente uma #WeightBalancedTree#. Essa é uma árvore em que cada nodo #u#, exceto a raiz, mantém a \emph{invariante de balanceamento} + $#size(u)# \le (2/3)#size(u.parent)#$. As operações #add(x)# e + #remove(x)# são idênticas às operações padrões da #BinarySearchTree#, + exceto que toda vez que a invariante de balanceamento é violada + em um nodo #u#, a subárvore enraizada em #u.parent# é reconstruída. +A sua análise deve mostrar que operações em uma + #WeightBalancedTree# rodam em tempo amortizado +$O(\log#n#)$. \end{exc} \begin{exc} \index{CountdownTree@#CountdownTree#}% - Analyze and implement a #CountdownTree#. In a #CountdownTree# each - node #u# keeps a \emph{timer} #u.t#. The #add(x)# and #remove(x)# - operations are exactly the same as in a standard #BinarySearchTree# - except that, whenever one of these operations affects #u#'s subtree, - #u.t# is decremented. When $#u.t#=0$ the entire subtree rooted at #u# - is rebuilt into a perfectly balanced binary search tree. When a node - #u# is involved in a rebuilding operation (either because #u# is rebuilt - or one of #u#'s ancestors is rebuilt) #u.t# is reset to $#size(u)#/3$. - - Your analysis should show that operations on a #CountdownTree# run - in $O(\log #n#)$ amortized time. (Hint: First show that each node #u# - satisfies some version of a balance invariant.) + Analise e implemente uma + #CountdownTree#. Em uma #CountdownTree# cada nodo #u# mantém um + \emph{timer} #u.t#. As operações #add(x)# e #remove(x)# + são exatamente as mesmas que em uma #BinarySearchTree# padrão + exceto que, sempre que uma dessas operações afetam a subárvore #u# + a variável #u.t# é decrementada. Quando + $#u.t#=0$ a subárvore inteira enraizada em #u# + é reconstruída em uma árvore binária de busca perfeitamente balanceada. + Quando um nodo #u# estiver envolvido em uma operação de reconstrução (porque #u# foi reconstruída ou um dos ancestrais de #u# foi reconstruído) #u.t# é reiniciado a + $#size(u)#/3$. + + Sua análise deve mostrar que as operações em uma + #CountdownTree# roda em tempo amortizado + $O(\log #n#)$. (Dica: primeiro mostre que cada nodo #u# satisfaz alguma versão de uma invariante de balanceamento.) \end{exc} \begin{exc} \index{DynamiteTree@#DynamiteTree#}% - Analyze and implement a #DynamiteTree#. In a #DynamiteTree# each - node #u# keeps tracks of the size of the subtree rooted at #u# in a - variable #u.size#. The #add(x)# and #remove(x)# operations are exactly - the same as in a standard #BinarySearchTree# except that, whenever one - of these operations affects a node #u#'s subtree, #u# \emph{explodes} - with probability $1/#u.size#$. When #u# explodes, its entire subtree - is rebuilt into a perfectly balanced binary search tree. - - Your analysis should show that operations on a #DynamiteTree# run - in $O(\log #n#)$ expected time. + Analise e implemente uma + #DynamiteTree#. Em uma #DynamiteTree# cada nodo + #u# registra o tamanho da subárvore enraizada em #u# em uma variável + #u.size#. As operações #add(x)# e #remove(x)# são exatamente as mesmas que em uma + #BinarySearchTree# padrão exceto que, sempre que uma dessas operações afetam uma subárvore de um nodo #u#, esse nodo \emph{explode} com probabilidade + $1/#u.size#$. Quando #u# explode, sua subárvore inteira é reconstruída + em uma árvore binária de busca perfeitamente balanceada. + + A sua análise deve mostrar que operações em uma + #DynamiteTree# roda em tempo esperado + $O(\log #n#)$. \end{exc} \begin{exc}\exclabel{list-order-maintenance} \index{Sequence@#Sequence#}% - Design and implement a #Sequence# data structure that maintains a - sequence (list) of elements. It supports these operations: + Projete e implemente uma estrutura de dados + #Sequence# que mantém uma sequência (lista) de elementos. + Ela suporta as seguintes operações: \begin{itemize} - \item #addAfter(e)#: Add a new element after the element #e# in the - sequence. Return the newly added element. (If #e# is null, - the new element is added at the beginning of the sequence.) - \item #remove(e)#: Remove #e# from the sequence. - \item #testBefore(e1,e2)#: return #true# if and only if #e1# comes - before #e2# in the sequence. + \item #addAfter(e)#: adicionamos um novo elemento após o elemento #e# na sequência. Retorna o elemento adicionado. (Se #e# é null, + o novo elemento é adicionado no início da sequência.) + \item #remove(e)#: Remove #e# da sequência. + \item #testBefore(e1,e2)#: retorna #true# se e somente se #e1# vem antes de + #e2# na sequência. \end{itemize} - The first two operations should run in $O(\log #n#)$ amortized time. - The third operation should run in constant time. - - The #Sequence# data structure can be implemented by storing the elements - in something like a #ScapegoatTree#, in the same order that they occur - in the sequence. To implement #testBefore(e1,e2)# in constant time, - each element #e# is labelled with an integer that encodes the path from - the root to #e#. In this way, #testBefore(e1,e2)# can be implemented - by comparing the labels of #e1# and #e2#. + As primeiras duas operações devem rodas em tempo amortizado + $O(\log #n#)$. A terceira operação deve rodar em tempo constante. + + A estrutura de dados + #Sequence# pode ser implementada na mesma ordem que ocorrem na sequência. + Para implementar #testBefore(e1,e2)# em tempo constante, + cada elemento #e# é marcado com um inteiro que codifica o caminho da raiz até #e#. + Dessa forma, + #testBefore(e1,e2)# pode ser implementado pela comparação das marcações de #e1# e #e2#. \end{exc} From 1decc32580c6baf4048459142b4df6f14ab2cf8e Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 23 Aug 2020 23:12:47 -0300 Subject: [PATCH 27/66] translation to portuguese --- latex/redblack.tex | 1050 ++++++++++++++++++++++---------------------- 1 file changed, 533 insertions(+), 517 deletions(-) diff --git a/latex/redblack.tex b/latex/redblack.tex index e04f63eb..833842cf 100644 --- a/latex/redblack.tex +++ b/latex/redblack.tex @@ -1,98 +1,105 @@ -\chapter{Red-Black Trees} +%%merge = união +%%split = repartição +\r{ck.tex" 776L, 36435C written + Árvores Rubro-Negras} \chaplabel{redblack} \index{binary search tree!red-black}% +\index{árvore binária de busca!rubro-negra}% \index{red-black tree}% -In this chapter, we present red-black trees, a version of binary search -trees with logarithmic height. Red-black trees are one of the most -widely used data structures. They appear as the primary search structure -in many library implementations, including the Java Collections Framework -and several implementations of the C++ Standard Template Library. They -are also used within the Linux operating system kernel. There are -several reasons for the popularity of red-black trees: +\index{árvore rubro-negra}% +Neste capítulo, apresentamos as árvores rubro-negras (em inglês, red-black trees), +uma versão de árvores binárias de busca com altura logarítmica. +Árvores rubros-negras são as estruturas de dados mais amplamente usadas. +Elas aparecerem como estrutura de busca primária em muitas implementações, incluindo a +Java Collections Framework +e várias implementações da +C++ Standard Template Library. Elas também são usadas dentro do kernel Linux. Existem várias razões para a popularidade das árvores rubro-negras: \begin{enumerate} -\item A red-black tree storing #n# values has height at most $2\log #n#$. -\item The #add(x)# and #remove(x)# operations on a red-black tree run - in $O(\log #n#)$ \emph{worst-case} time. -\item The amortized number of rotations performed during an #add(x)# - or #remove(x)# operation is constant. +\item Uma árvore rubro-negra com #n# valores tem altura no máximo $2\log #n#$. +\item As operações #add(x)# e #remove(x)# em uma árvore rubro-negra rodam em tempo + $O(\log #n#)$ no \emph{pior caso}. +\item O número amortizado de rotações realizado durante uma operação #add(x)# + ou #remove(x)# é constante. \end{enumerate} -The first two of these properties already put red-black trees -ahead of skiplists, treaps, and scapegoat trees. -Skiplists and treaps rely on randomization and their $O(\log #n#)$ -running times are only expected. Scapegoat trees have a guaranteed -bound on their height, but #add(x)# and #remove(x)# only run in $O(\log -#n#)$ amortized time. The third property is just icing on the cake. It -tells us that that the time needed to add or remove an element #x# is -dwarfed by the time it takes to find #x#.\footnote{Note that skiplists and -treaps also have this property in the expected sense. See -Exercises~\ref{exc:skiplist-changes} and \ref{exc:treap-rotates}.} - -However, the nice properties of red-black trees come with a price: -implementation complexity. Maintaining a bound of $2\log #n#$ on the -height is not easy. It requires a careful analysis of a number of cases. -We must ensure that the implementation does exactly the right -thing in each case. One misplaced rotation or change of colour produces -a bug that can be very difficult to understand and track down. - -Rather than jumping directly into the implementation of red-black trees, -we will first provide some background on a related data structure: -2-4 trees. This will give some insight into how red-black trees were -discovered and why efficiently maintaining them is even possible. - -\section{2-4 Trees} +As duas primeiras propriedades põem árvores rubro-negras a frente de skiplists, +treaps e árvores scapegoat. +Skiplists e treaps dependem de randomization e seus tempos de execução $O(\log #n#)$ +somente são esperados. Árvores scapegoat tem limitante garantido na altura, mas #add(x)# e #remove(x)# somente rodam em +tempo amortizado $O(\log +#n#)$. A terceira propriedade é apenas a cereja do bolo. Ela nos diz que o tempo necessário para adicionar ou remover um elemento #x# é minúsculo em relação ao tempo que leva para achar #x#. +\footnote{Note que skiplists e +treaps também tem essas propriedade de forma esperada. Veja +Exercícios~\ref{exc:skiplist-changes} e \ref{exc:treap-rotates}.} + +Entretanto, as boas propriedades de árvores rubro-negras têm um preço: complexidade de implementação. Manter um limitante de +$2\log #n#$ na altura não é fácil. +Isso exige uma análise cuidadosa de vários casos. +Precisamos assegurar que a implementação siga exatamente os passos corretos em cada caso. +Uma rotação mal posicionada ou um alteração de cor errada causa um bug que pode ser muito difícil de entender e resolver. + +Em vez de pular diretamente à implementação de árvores rubro-negras, +primeiro construímos uma base por meio de uma estrutura de dados diretamente relacionada: árvore 2-4. Isso fornecerá entendimento de como árvores rubro-negras +foram descobertas e porque é possível manter essa estrutura eficientemente. + +\section{Árvore 2-4} \seclabel{twofour} -A 2-4 tree is a rooted tree with the following properties: +Uma árvore 2-4 é uma árvore enraizada com as seguintes propriedades: \begin{prp}[height] - All leaves have the same depth. + Todas as folhas tem a mesma profundidade. \end{prp} \begin{prp}[degree] - Every internal node has 2, 3, or 4 children. + Todo nodo interno tem 2,3 ou 4 filhos. \end{prp} -An example of a 2-4 tree is shown in \figref{twofour-example}. +Um exemplo de uma árvore 2-4 é mostrado na \figref{twofour-example}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/24rb-2} \end{center} - \caption{A 2-4 tree of height 3.} + \caption{Uma árvore 2-4 de altura 3.} \figlabel{twofour-example} \end{figure} -The properties of 2-4 trees imply that their height is logarithmic in -the number of leaves: +As propriedades de uma árvore 2-4 implicam que sua altura é logarítmica no número de folhas: \begin{lem}\lemlabel{twofour-height} - A 2-4 tree with #n# leaves has height at most $\log #n#$. + A árvore 2-4 com #n# folhas tem altura até $\log #n#$. \end{lem} \begin{proof} - The lower-bound of 2 on the number of children of an internal node - implies that, if the height of a 2-4 tree is $h$, then it has at least - $2^h$ leaves. In other words, + O limitante inferior de 2 no número de filhos de um nodo interno + implica que, se + altura de uma árvore 2-4 é $h$, então ela tem pelo menos + $2^h$ folhas. Em outras palavras, \[ #n# \ge 2^h \enspace . \] - Taking logarithms on both sides of this inequality gives $h \le \log #n#$. + Aplicando logaritmos em ambos lados dessa desigualdade resulta em + $h \le \log #n#$. \end{proof} -\subsection{Adding a Leaf} - -Adding a leaf to a 2-4 tree is easy (see \figref{twofour-add}). If we -want to add a leaf #u# as the child of some node #w# on the second-last -level, then we simply make #u# a child of #w#. This certainly maintains -the height property, but could violate the degree property; if #w# -had four children prior to adding #u#, then #w# now has five children. -In this case, we \emph{split} -\index{split}% -#w# into two nodes, #w# and #w#', having -two and three children, respectively. -But now #w#' has no parent, -so we recursively make #w#' a child of #w#'s parent. Again, this may -cause #w#'s parent to have too many children in which case we split it. -This process goes on until we reach a node that has fewer than four children, -or until we split the root, #r#, into two nodes #r# and #r'#. In the -latter case, we make a new root that has #r# and #r'# as children. -This simultaneously increases the depth of all leaves and so maintains -the height property. +\subsection{Adição de uma Folha} + +Adicionar uma folha a uma +árvore 2-4 é fácil (veja a \figref{twofour-add}). Se +queremos adicionar uma folha +#u# com filha de um nodo #w# no penúltimo nível, +então simplesmente fazemos #u# um filho de #w#. Isso certamente +mantém a +propriedade da altura, mas pode violar a propriedade do grau; se #w# +tinha quatro filhos antes de adicionar #u#, então #w# agora tem cinco filhos. +Neste caso, \emph{repartimos} +\index{repartição}% +#w# em dois nodos, #w# e #w#', com dois e três filhos, respectivamente. +Mas agora +#w#' não tem pai, +então recursivamente tornamos +#w#' um filho do pai de #w#. Novamente, isso pode fazer com que o pai de #w# +tenha muitos filhos e, dessa forma, teremos que repartí-los. +Esse processo segue repetidamente até alcançar um nodo que tem menos que quatro filhos, ou até repartimos a raiz #w# em dois nodos #r# e #r'#. No último caso, +fazemos uma nova raiz que tem #r# e #r'# como filhos. +Isso simultaneamente aumenta a + profundidade de todas as folhas e assim mantém propridade da +altura. \begin{figure} \begin{center} @@ -102,21 +109,22 @@ \subsection{Adding a Leaf} \includegraphics[scale=0.90909]{figs/24tree-add-3} \end{tabular} \end{center} - \caption[Adding a leaf to a 2-4 Tree]{Adding a leaf to a 2-4 Tree. - This process stops after one split because #w.parent# has a degree of less - than 4 before the addition.} + \caption[Adição de uma folha em uma árvore 2-4]{Adição de uma folha a uma árvore 2-4. Esse processo para após um repartição porque #w.parent# tem um grau menor que 4 antes da adição.} \figlabel{twofour-add} \end{figure} -Since the height of the 2-4 tree is never more than $\log #n#$, the -process of adding a leaf finishes after at most $\log #n#$ steps. +Como a altura da árvore 2-4 nunca é maior que $\log #n#$, o +processo de adicionar uma folha termina após no máximo +$\log #n#$ passos. -\subsection{Removing a Leaf} +\subsection{Remoção de um Folha} -Removing a leaf from a 2-4 tree is a little more tricky (see -\figref{twofour-remove}). To remove a leaf #u# from its parent #w#, we -just remove it. If #w# had only two children prior to the removal of #u#, -then #w# is left with only one child and violates the degree property. +Remoção de uma folha de uma +árvore 2-4 é um pouco mais complicada (veja +a \figref{twofour-remove}). Para remover uma folha #u# de seu pai #w#, +então simplesmente a removemos. +Se #w# tivesse somente dois filhos antes à remoção de #u#, +então #w# é deixamos com somente um filho o que viola a propriedade de grau. \begin{figure} \begin{center} @@ -128,89 +136,92 @@ \subsection{Removing a Leaf} \includegraphics[height=\FifthHeightScaleIfNeeded]{figs/24tree-remove-5} \\ \end{tabular} \end{center} - \caption[Removing a leaf from a 2-4 Tree]{Removing a leaf from a - 2-4 Tree. This process goes all the way to the root because each of - #u#'s ancestors and their siblings have only two children.} + \caption[Remoção de uma folha de uma árvore 2-4]{Remoção de uma folha de uma + árvore 2-4. Esse processo segue até a raiz pois cada + ancestral de #u# e seus irmãos tem somente dois filhos.} \figlabel{twofour-remove} \end{figure} -To correct this, we look at #w#'s sibling, #w'#. The node #w'# is -sure to exist since #w#'s parent had at least two children. If #w'# -has three or four children, then we take one of these children from #w'# -and give it to #w#. Now #w# has two children and #w'# has two or three -children and we are done. - -On the other hand, if #w'# has only two children, then we \emph{merge} -\index{merge}% -#w# and #w'# into a single node, #w#, that has three children. Next we -recursively remove #w'# from the parent of #w'#. This process ends -when we reach a node, #u#, where #u# or its sibling has more than two -children, or when we reach the root. In the latter case, if the root -is left with only one child, then we delete the root and make its child -the new root. Again, this simultaneously decreases the height of every -leaf and therefore maintains the height property. - -Again, since the height of the tree is never more than $\log #n#$, -the process of removing a leaf finishes after at most $\log #n#$ steps. - -\section{#RedBlackTree#: A Simulated 2-4 Tree} +Para corrigir isso, olhamos em um irmão de #w#, o #w'#. O nodo #w'# +com certeza existe pois o pai de #w# tem pelo menos dois filhos. Se #w'# +tem três ou quatro filhos, então pegamos um desses filhos de #w'# +e o transferimos para #w#. Agora #w# tem dois filhos e #w'# tem dois ou +três filhos e terminamos o processo. + +Por outro lado, se #w'# tem somente dois filhos, então fazemos uma fusão +\index{fusão}% +de #w# e #w'# em um único nodo, #w#, que tem três filhos. A seguir, +recursivamente removemos #w'# do pai de #w'#. Esse processo +termina guando alcançamos um nodo #u#, onde #u# ou um irmão seu tem mais de +dois filhos, ou quando chegamos à raiz. No último caso, se a raiz +é deixada com somente um filho, então removemos a raiz e fazemos seu filho a +nova raiz. + +Novamente, isso simultaneamente reduz a altua de toda folha e portanto +mantém a propriedade de altura. + +Como a altura da árvore não passa de $\log #n#$, o processo de remover uma folha acaba após no máximo $\log #n#$ passos. + +\section{#RedBlackTree#: Uma Árvore 2-4 Simulada} \seclabel{redblacktree} -A red-black tree is a binary search tree in which each node, #u#, -has a \emph{colour} -\index{colour}% -which is either \emph{red} or \emph{black}. Red is -represented by the value $0$ and black by the value $1$. -\index{red node}% -\index{black node}% +Uma árvore rubro-negra é uma árvore binária de busca em que cada nodo #u# +tem uma \emph{cor} +\index{cor}% +que é +\emph{vermelha} ou \emph{preta}. Vermelho é representado pelo valor $0$ +e preto pelo valor $1$. +\index{nodo vermelho}% +\index{nodo preto}% \javaimport{ods/RedBlackTree.red.black.Node} \cppimport{ods/RedBlackTree.RedBlackNode.red.black} -Before and after any operation on a red-black tree, the following two -properties are satisfied. Each property is defined both in terms of the -colours red and black, and in terms of the numeric values 0 and 1. +Antes e depois de qualquer operação em uma árvore rubro-negra, as +seguinter duas propriedades satisfeitas. Cada propriedade é definida em +termos das cores vermelha e preta e em termos dos valores numéricos 0 e 1. + \begin{prp}[black-height] - \index{black-height property}% - There are the same number of black nodes on every root to leaf - path. (The sum of the colours on any root to leaf path is the same.) + \index{propriedade da altura preta}% + Há o mesmo número de nodos pretos em todo caminho da raiz para a folha. + (A soma das cores em qualquer caminho da raiz a uma folha é a mesma.) \end{prp} \begin{prp}[no-red-edge] - \index{no-red-edge property}% - No two red nodes are adjacent. (For any node #u#, except the root, + \index{propriedade de nenhuma aresta vermelha}% + Não há nodos vermelhos adjacentes. (Para qualquer nodo #u#, exceto a raiz, $#u.colour# + #u.parent.colour# \ge 1$.) \end{prp} -Notice that we can always colour the root, #r#, of a red-black tree black -without violating either of these two properties, so we will assume -that the root is black, and the algorithms for updating a red-black -tree will maintain this. Another trick that simplifies red-black trees -is to treat the external nodes (represented by #nil#) as black nodes. -This way, every real node, #u#, of a red-black tree has exactly two -children, each with a well-defined colour. An example of a red-black -tree is shown in \figref{redblack-example}. +Note que sempre podemos colorir a raiz #r# de uma árvore rubro-negra de preto +sem violar nenhuma dessas duas propriedades, então iremos +assumir que a raiz é preta e os algoritmos para atualizar uma árvore rubro-negra +irão manter isso. +Outro truque que simplifica +rubro-negras +é tratar os nodos externos (representados por #nil#) como nodos pretos. +Dessa forma, todo nodo real, #u#, de uma árvore rubro-negra tem exatamente +dois filhos, cada qual com uma cor bem definida. Um exemplo de uma +árvore rubro-negra é mostrado na +\figref{redblack-example}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/24rb-1} \end{center} - \caption[A red-black tree]{An example of a red-black tree with black-height 3. External (#nil#) nodes are drawn as squares.} + \caption[Uma árvore rubro-negra]{Um exemplo de uma árvore rubro-negra com uma altura preta de 3. Nodos externos (#nil#) são desenhados como quadrados.} \figlabel{redblack-example} \end{figure} -\subsection{Red-Black Trees and 2-4 Trees} +\subsection{Árvores Rubro-Negras e Árvores 2-4} -At first it might seem surprising that a red-black tree can be efficiently -updated to maintain the black-height and no-red-edge properties, and -it seems unusual to even consider these as useful properties. However, -red-black trees were designed to be an efficient simulation of 2-4 trees -as binary trees. +À primeira vista, pode parecer supreendente que uma árvore rubro-negra possa +ser atualizada eficientemente para manter as propriedades de altura preta e de +nenhuma aresta vermelha e parece estranho considerar essas propriedades como úteis. +Entretanto, árvores rubro-negras foram projetada para ser uma simulação eficiente das árvores 2-4 na forma de árvores binárias. -Refer to \figref{twofour-redblack}. -Consider any red-black tree, $T$, having #n# nodes and perform the -following transformation: Remove each red node #u# and connect #u#'s two -children directly to the (black) parent of #u#. After this transformation -we are left with a tree $T'$ having only black nodes. +Veja a \figref{twofour-redblack}. +Considere qualquer árvore rubro-negra, $T$, com #n# nodos e realize a seguinte transformação: remova cada nodo vermelho #u# e conecte os dois filhos de #u# diretamente ao pai (preto) de #u#. +Após essa transformação, a árvore resultante $T'$ possui somente nodos pretos. \begin{figure} \begin{center} \begin{tabular}{cc} @@ -218,46 +229,55 @@ \subsection{Red-Black Trees and 2-4 Trees} \includegraphics[scale=0.90909]{figs/24rb-2} \end{tabular} \end{center} - \caption{Every red-black tree has a corresponding 2-4 tree.} + \caption{Toda árvore rubro-negra tem uma árvore 2-4 correspondente.} \figlabel{twofour-redblack} \end{figure} -Every internal node in $T'$ has two, three, or four children: A black -node that started out with two black children will still have two -black children after this transformation. A black node that started -out with one red and one black child will have three children after this -transformation. A black node that started out with two red children will -have four children after this transformation. Furthermore, the black-height -property now guarantees that every root-to-leaf path in $T'$ has the -same length. In other words, $T'$ is a 2-4 tree! - -The 2-4 tree $T'$ has $#n#+1$ leaves that correspond to the $#n#+1$ -external nodes of the red-black tree. Therefore, this tree has height -at most $\log (#n#+1)$. Now, every root to leaf path in the 2-4 tree corresponds -to a path from the root of the red-black tree $T$ to an external node. -The first and last node in this path are black and at most one out of -every two internal nodes is red, so this path has at most $\log(#n#+1)$ -black nodes and at most $\log(#n#+1)-1$ red nodes. Therefore, the longest path from the root to any \emph{internal} node in $T$ is at most +Todo nodo interno em $T'$ tem dois, três ou quatro filhos: um nodo preto +que tinha dois filhos pretos continuará com +dois filhos pretos após essa transformação. +Um nodo preto que tinha com um nodo preto e um vermelho terá três +filhos após essa transformação. +Um nodo preto que começou com dois filhos vermelhos terá quatro filhos após +essa transformação. Além disso, a propriedade de altura preta garante +que todo caminho da raiz até a folha em $T'$ tem o mesmo comprimento. +Em outras palavras, $T'$ é uma +árvore 2-4! + +A árvore 2-4 $T'$ tem $#n#+1$ folhas que correspondem +a $#n#+1$ nodos externos da árvore rubro-negra. Portanto, essa árvore +tem altura de até +$\log (#n#+1)$. Agora, todo caminho da raiz para uma folha na árvore 2-4 corresponde +a um caminho da raiz da árvore rubro-negra $T$ a um nodo externo. + +O primeiro e último nodo nesse caminho são pretos e no máximo de cada dois +nodos internos é vermelho, então esse caminho tem no máximo +$\log(#n#+1)$ nodos pretos e no máximo +$\log(#n#+1)-1$ nodos vermelhos. Portanto, o caminho mais longo da raiz para qualquer nodo \emph{interno} em $T$ é no máximo \[ 2\log(#n#+1) -2 \le 2\log #n# \enspace , \] -for any $#n#\ge 1$. This proves the most important property of -red-black trees: +para todo +$#n#\ge 1$. Isso a propriedade mais importante das +árvores rubro-negras: \begin{lem} -The height of red-black tree with #n# nodes is at most $2\log #n#$. +The altura de uma árvore rubro-negra com #n# nodos é no máximo $2\log #n#$. \end{lem} -Now that we have seen the relationship between 2-4 trees and -red-black trees, it is not hard to believe that we can efficiently -maintain a red-black tree while adding and removing elements. - -We have already seen that adding an element in a #BinarySearchTree# -can be done by adding a new leaf. Therefore, to implement #add(x)# in a -red-black tree we need a method of simulating splitting a node with five -children in a 2-4 tree. A 2-4 tree node with five children is represented -by a black node that has two red children, one of which also has a red -child. We can ``split'' this node by colouring it red and colouring its -two children black. An example of this is shown in \figref{rb-split}. +Agora que vimos a relação entre as árvores 2-4 e as +árvores rubro-negras, não é difícil de acreditar que podemos manter eficientemente uma árvore rubro-negra ao adicionar e remover elementos. + +Vimos que a adição de elementos em uma + #BinarySearchTree# + pode ser feita pela adição de uma nova folha. Portanto, para implementar +#add(x)# em uma árvore rubro-negra precisamos de um método de simular +a repartição de um nodo com cinco filhos em uma +árvore 2-4. O nodo da árvore 2-4 com cinco filhos é representado por +um nodo preto que tem dois filhos vermelhos, um dos quais também tem um filho +vermelho. Podemos ``reparticionar'' esse nodo pintando ele de vermelho +e pintando seus dois filhos de preto. +Um exemplo disso é mostrado na + \figref{rb-split}. \begin{figure} \begin{center} @@ -267,489 +287,485 @@ \subsection{Red-Black Trees and 2-4 Trees} \includegraphics[scale=0.90909]{figs/rb-split-3} \\ \end{tabular} \end{center} - \caption[Simulating a 2-4 tree]{Simulating a 2-4 tree split operation - during an addition in a red-black tree. (This simulates the 2-4 - tree addition shown in \figref{twofour-add}.)} + \caption[Simulando uma árvore 2-4]{Simulando uma operação de reparticionamento de uma árvore 2-4 durante a adição em uma árvore rubro-negra. (Isso simula + a adição da árvore 2-4 mostrada em + \figref{twofour-add}.)} \figlabel{rb-split} \end{figure} -Similarly, implementing #remove(x)# requires a method of merging two nodes -and borrowing a child from a sibling. Merging two nodes is the inverse of -a split (shown in \figref{rb-split}), and involves colouring two (black) -siblings red and colouring their (red) parent black. Borrowing from -a sibling is the most complicated of the procedures and involves both -rotations and recolouring nodes. - -Of course, during all of this we must still maintain the no-red-edge -property and the black-height property. While it is no longer surprising -that this can be done, there are a large number of cases that have to -be considered if we try to do a direct simulation of a 2-4 tree by a -red-black tree. At some point, it just becomes simpler to disregard the -underlying 2-4 tree and work directly towards maintaining the properties -of the red-black tree. - -\subsection{Left-Leaning Red-Black Trees} - -\index{red-black tree}% -\index{left-leaning red-black tree}% -No single definition of red-black trees exists. Rather, there is -a family of structures that manage to maintain the black-height -and no-red-edge properties during #add(x)# and #remove(x)# -operations. Different structures do this in different ways. -Here, we implement a data structure that we call a #RedBlackTree#. +De modo similar, implemtar +#remove(x)# exige um método de unir dois nodos e empresta um filho de um irmão. +A união de dois nodos é o inverse da repartição (mostrado na +\figref{rb-split}), e envolve pintar dois irmãos vermelhos de preto e pintar o pai (que é vermelho) de preto. Emprestar um irmão é o procedimento mais complicado +e envolve ambas as rotações e pintar os nodos. + +Obviamente, durante tudo isso devemos ainda manter a propriedade de nenhuma aresta +vermelha e a propriedade da altura preta. Enquanto não é mais supreendente +que isso pode ser feito, existe um grande número de casos que devem ser considerados +se tentarmos fazer uma simulação direta de uma +árvore 2-4 com uma árvore rubro-negra. +Eventualmente, torna-se mais simples desconsiderar a árvore 2-4 e trabalhar +diretamente com a manutenção das propriedades da árvore rubro-negra. + +\subsection{Árvores Rubro-Negras Pendentes à Esquerda} + +\index{árvore rubro-negra}% +\index{árvore rubro-negra pendente à esquerda}% +Não existe uma única definição de +árvores rubro-negras. Em vez disso, existe uma família +de estruturas que conseguem manter as propriedades da altura preta +e nenhuma aresta vermelha +durante as operações #add(x)# e #remove(x)#. Estruturas diferentes fazem isso diferentemente. +Aqui, implementamos uma estrutura de dados que chamamos de +#RedBlackTree#. \index{RedBlackTree@#RedBlackTree#}% -This structure implements a particular variant of red-black trees that -satisfies an additional property: +Essa estrutura implementar uma variante de árvore rubro-negra que satisfaz +uma propriedade adicional: \begin{prp}[left-leaning]\prplabel{left-leaning}\prplabel{redblack-last} - \index{left-leaning property}% - At any node #u#, if #u.left# is black, then #u.right# is black. + \index{propriedade de pender à esquerda}% + Em qualquer nodo #u#, se #u.left# for preto, então #u.right# é preto. \end{prp} -Note that the red-black tree shown in \figref{redblack-example} does -not satisfy the left-leaning property; it is violated by the parent of -the red node in the rightmost path. - -The reason for maintaining the left-leaning property is that it reduces -the number of cases encountered when updating the tree during #add(x)# -and #remove(x)# operations. In terms of 2-4 trees, it implies that every -2-4 tree has a unique representation: A node of degree two becomes -a black node with two black children. A node of degree three becomes -a black node whose left child is red and whose right child is black. -A node of degree four becomes a black node with two red children. - -Before we describe the implementation of #add(x)# and #remove(x)# in -detail, we first present some simple subroutines used by these methods -that are illustrated in \figref{redblack-flippullpush}. The first two -subroutines are for manipulating colours while preserving the black-height -property. The #pushBlack(u)# method takes as input a black node #u# -that has two red children and colours #u# red and its two children black. -The #pullBlack(u)# method reverses this operation: +Note que a árvore rubro-negra mostrada na +\figref{redblack-example} não satisfaz a propriedade de pender +à esquerda; +ela é violada pelo pai do nodo vermelho no caminho mais à esquerda. + +A razão para manter a propriedade de pender à esquerda é que ela reduz +o número de casos encontrados durante a atualização da árvore nas operações +#add(x)# +e #remove(x)#. Em termos de uma árvore 2-4, isso implica que toda árvore 2-4 tem uma representação única: um nodo de grau dois se torna um nodo preto com dois filhos pretos. +Um nodo de grau três se torna um nodo preto cujo filho à esquerda é vermelho +e cujo filho à direita é preto. +Um nodo de grau quatro se torna um nodo preto com dois filhos vermelhos. + +Antes de descrevermos a implementação de + #add(x)# e #remove(x)# em detalhes, primeiro apresentamos alguma subrotinas + simples usadas por esses métodos que estão ilustradas na + \figref{redblack-flippullpush}. As duas primeiras subrotinas são para manipular + as cores e presentar a propriedade da altura preta. + O método +#pushBlack(u)# recebe como entrada um nodo preto #u# +que tem dois filhos vermelhos e pinta #u# de vermelho e seus dois filhos de preto. +O método +#pullBlack(u)# inverte essa operação \codeimport{ods/RedBlackTree.pushBlack(u).pullBlack(u)} \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/flippullpush} \end{center} - \caption{Flips, pulls and pushes} + \caption{Operações de \emph{flips}, \emph{pulls} e \emph{pushes}} \figlabel{redblack-flippullpush} \end{figure} -The #flipLeft(u)# method swaps the colours of #u# and #u.right# -and then performs a left rotation at #u#. This method reverses the -colours of these two nodes as well as their parent-child relationship: +O método +#flipLeft(u)# troca as cores de #u# e #u.right# +e então realiza uma rotação à esquerda em #u#. +Esse método inverte as cores desses dois nodo assim como seu relacionamento +pai-filho: \codeimport{ods/RedBlackTree.flipLeft(u)} -The #flipLeft(u)# operation -is especially useful in restoring the left-leaning property at a node -#u# that violates it (because #u.left# is black and #u.right# is red). -In this special case, we can be assured that this operation preserves both -the black-height and no-red-edge properties. The #flipRight(u)# operation -is symmetric with #flipLeft(u)#, when the roles of left and right are reversed. +A operação #flipLeft(u)# é especialmente útil ao restaurar a +propriedade de pender à esquerda em um nodo #u# que a viola (porque + #u.left# é preto e #u.right# é vermelho). + Nesse caso especial, temos a certeza de que essa operação preserva + as propriedades de altura preta e de nenhuma aresta vermelha. + A operação +#flipRight(u)# é simétrica +#flipLeft(u)#, quando os papéis de esquerda e direita são invertidos. \codeimport{ods/RedBlackTree.flipRight(u)} -\subsection{Addition} +\subsection{Adição} -To implement #add(x)# in a #RedBlackTree#, we perform a standard -#BinarySearchTree# insertion to add a new leaf, #u#, with $#u.x#=#x#$ and -set $#u.colour#=#red#$. Note that this does not change the black height -of any node, so it does not violate the black-height property. It may, -however, violate the left-leaning property (if #u# is the right child of -its parent), and it may violate the no-red-edge property (if #u#'s parent -is #red#). To restore these properties, we call the method #addFixup(u)#. +Para implementar +#add(x)# em uma #RedBlackTree#, fazems uma inserção de um +#BinarySearchTree# padrão para adicionar uma nova folha #u#, com $#u.x#=#x#$ e atribuímos +$#u.colour#=#red#$. Note que isso não muda a altura preta de nenhum nodo e, portanto, não viola a propriedade de altura preta. +Ela pode, entretanto, violar a propriedade de pender à esquerda (se #u# é o filho +à direita de seu pai) e pode violar a propriedade de nenhuma aresta vermelha (se o pai de #u# for #red#) +Para restaurar essas propriedades, chamamos o método #addFixup(u)#. \codeimport{ods/RedBlackTree.add(x)} -Illustrated in \figref{rb-addfix}, the #addFixup(u)# method takes -as input a node #u# whose colour is red and which may violate the -no-red-edge property and/or the left-leaning property. The following -discussion is probably impossible to follow without referring to -\figref{rb-addfix} or recreating it on a piece of paper. Indeed, the -reader may wish to study this figure before continuing. +Ilustrados na \figref{rb-addfix}, o método #addFixup(u)# recebe como entrada +um nodo #u# cuja cor é vermelha e que pode violar a propriedade de nenhuma +aresta vermelhe e/ou a propriedade pendente à esquerda. +A discussão a seguir é provavelmente impossível de acompanhar sem +observa a \figref{rb-addfix} ou recriá-la em um papel. +Certamente, o leitor deve estudar essa figura antes de continuar este capítulo. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/rb-addfix} \end{center} - \caption{A single round in the process of fixing Property~2 after - an insertion.} + \caption{Uma única rodada no processo de restaurar a Propriedade~2 após uma inserção.} \figlabel{rb-addfix} \end{figure} -If #u# is the root of the tree, then we can colour #u# black to restore -both properties. If #u#'s sibling is also red, then #u#'s parent must be -black, so both the left-leaning and no-red-edge properties already hold. - -Otherwise, we first determine if #u#'s parent, #w#, violates the -left-leaning property and, if so, perform a #flipLeft(w)# operation and -set $#u#=#w#$. This leaves us in a well-defined state: #u# is the left -child of its parent, #w#, so #w# now satisfies the left-leaning property. -All that remains is to ensure the no-red-edge property at #u#. We only -have to worry about the case in which #w# is red, since otherwise #u# -already satisfies the no-red-edge property. - -Since we are not done yet, #u# is red and #w# is red. The no-red-edge -property (which is only violated by #u# and not by #w#) implies that -#u#'s grandparent #g# exists and is black. If #g#'s right child is red, -then the left-leaning property ensures that both #g#'s children are red, -and a call to #pushBlack(g)# makes #g# red and #w# black. This restores -the no-red-edge property at #u#, but may cause it to be violated at #g#, -so the whole process starts over with $#u#=#g#$. - -If #g#'s right child is black, then a call to #flipRight(g)# makes -#w# the (black) parent of #g# and gives #w# two red children, #u# and -#g#. This ensures that #u# satisfies the no-red-edge property and #g# -satisfies the left-leaning property. In this case we can stop. +Se #u# é a raiz da árvore, então podemos pintar #u# de preto para reestabelecer as duas propriedades. Se +o irmão de #u# também for vermelho, então o pai de #u# precisa ser preto, de forma que as propriedades de pender à esquerda e nenhuma aresta vermelha valham. + +Caso contrário, primeiro determinamos se o pai de #u#, que é #w#, viola a +propriedade de pender à esquerda e, se esse for o caso, realizamos uma operação +#flipLeft(w)# e atribuímos +$#u#=#w#$. Isso nos deixa em um estado bem definido: #u# é o filho de seu pai +#w# então #w# agora satisfaz a propriedade de pender à esquerda. + +Resta apenas assegurarmos que não há nenhuma aresta vermelha em #u#. Nós somente devemos nos preocupar com o caso em que #w# é vermelho, pois caso contrário #u# +satisfaz a propriedade de nenhuma aresta vermelha. + +Como ainda não terminamos, #u# é vermelho e #w# é vermelho. A propriedade +de nenhuma aresta vermelha (que é violada por #u# e não por #w#) implica que +o avô de #u#, o nodo #g#, existe e é preto. Se o filho à direita de #g# for +vermelho, então a propriedade de pender à esquerda garante que os dois filhos de #g# sejam vermelhos e uma chamada a +#pushBlack(g)# torna #g# vermelho e #w# preto. Isso restaura +a propriedade nenhuma aresta vermelha em #u#, mas pode fazer que seja violada em #g# e, por isso, o processo reinicia com $#u#=#g#$. + +Se o filho à direita de #g# for preto, então uma chamada a +#flipRight(g)# faz +#w# ser o pai (preto) de #g# e dá a #w# dois filhos vermelhos, #u# e +#g#. Isso assegura que #u# satisfaz a propriedade de nenhuma aresta vermelha e #g# +satisfaz a propriedade de pender à esquerda. Nesse caso podemos parar. \codeimport{ods/RedBlackTree.addFixup(u)} -The #insertFixup(u)# method takes constant time per iteration and each -iteration either finishes or moves #u# closer to the root. Therefore, -the #insertFixup(u)# method finishes after $O(\log #n#)$ iterations in -$O(\log #n#)$ time. - -\subsection{Removal} - -The #remove(x)# operation in a #RedBlackTree# is the most complicated -to implement, and this is true of all known red-black tree variants. -Just like the #remove(x)# operation in a \texttt{BinarySearchTree}, -this operation boils down to finding a node #w# with only one child, -#u#, and splicing #w# out of the tree by having #w.parent# adopt #u#. - -The problem with this is that, if #w# is black, then the black-height -property will now be violated at #w.parent#. We may avoid this problem, -temporarily, by adding #w.colour# to #u.colour#. Of course, this introduces -two other problems: (1)~if #u# and #w# both started out black, then -$#u.colour#+#w.colour#=2$ (double black), which is an invalid colour. -If #w# was red, then it is replaced by a black node #u#, which may -violate the left-leaning property at $#u.parent#$. Both of these -problems can be resolved with a call to the #removeFixup(u)# method. +O método +#insertFixup(u)# leva tempo constante por iteração e cada iteração ou termina ou move #u# mais próximo à raiz. +Portanto, o método +#insertFixup(u)# termina após $O(\log #n#)$ iterações em tempo +$O(\log #n#)$. + +\subsection{Remoção} + +A operação +#remove(x)# em uma #RedBlackTree# é a mais complicada para implementar +e isso é verdade para todas as variantes conhecidas de árvores rubro-negras. +Assim como a operação #remove(x)# em uma \texttt{BinarySearchTree}, +essa operação se resume a achar um nodo #w# com somente um filho #u# +e remover #w# da árvore fazendo o #w.parent# adotar #u#. + +O problema com isso é que, se #w# for preto, então a propriedade da altura preta +será violada em #w.parent#. Podemos evitar esse problema, temporariamente, +ao adicionar #w.colour# a #u.colour#. Isso causa dois outros problemas: +(1)~se #u# e #w# iniciaram-se pretos, então +$#u.colour#+#w.colour#=2$ (preto duplo), que é uma cor inválida. +Se #w# era vermelho, então é substituído por um nodo preto #u#, +que pode violar a propriedade de pender à esquerda em + $#u.parent#$. Esse dois problemas podem ser resolvidos com uma + chamada ao método #removeFixup(u)#. \codeimport{ods/RedBlackTree.remove(x)} -The #removeFixup(u)# method takes as its input a node #u# whose colour is black -(1) or double-black (2). If #u# is double-black, then #removeFixup(u)# -performs a series of rotations and recolouring operations that move the -double-black node up the tree until it can be eliminated. During this -process, the node #u# changes until, at the end of this process, #u# -refers to the root of the subtree that has been changed. The root of -this subtree may have changed colour. In particular, it may have gone -from red to black, so the #removeFixup(u)# method finishes by checking -if #u#'s parent violates the left-leaning property and, if so, fixing it. +O método #removeFixup(u)# recebe como entrada um nodo #u# cuja cor é preto +(1) ou preto duplo(2). Se #u# for preto duplo, então #removeFixup(u)# +realiza uma série de operações de rotações e alterações de cor que +movem o nodo preto duplo acima na árvore até que possa ser eliminado. Durante esse processo, +o nodo #u# muda até que, no final desse processo, #u# +refere-se à raiz da subárvore que foi alterada. +A raiz dessa subárvore pode ter mudado de cor. Em particular, +pode ter ido de vermelho para preto, então o método +#removeFixup(u)# terminar ao verificar se +o pai de #u# viola a propriedade de pender à esquerda e, caso positivo, corrigindo esse problema. \codeimport{ods/RedBlackTree.removeFixup(u)} -The #removeFixup(u)# method is illustrated in \figref{rb-removefix}. -Again, the following text will be difficult, if not impossible, to follow -without referring to \figref{rb-removefix}. Each iteration of the loop -in #removeFixup(u)# processes the double-black node #u#, based on one -of four cases: +O método +#removeFixup(u)# é ilustrado na \figref{rb-removefix}. +Novamente, o texto a seguir será difícil, senão impossível, de acompanhar sem +observar a \figref{rb-removefix}. Cada iteração do laço em +#removeFixup(u)# processa o nodo preto duplo #u#, baseado em um de quatro casos: \begin{figure} \begin{center} \includegraphics[height=\HeightScaleIfNeeded]{figs/rb-removefix} \end{center} - \caption{A single round in the process of eliminating a double-black node - after a removal.} + \caption{Uma rodada no processo de eliminar um nodo preto duplo após uma remoção.} \figlabel{rb-removefix} \end{figure} \noindent -Case 0: #u# is the root. This is the easiest case to treat. We recolour -#u# to be black (this does not violate any of the red-black tree -properties). +Caso 0: #u# é a raiz. Esse é o caso mais fácil de tratar. Repintamos #u# em preto (isso não viola nenhuma das propriedades da árvore rubro-negra). \noindent -Case 1: #u#'s sibling, #v#, is red. In this case, #u#'s sibling is the -left child of its parent, #w# (by the left-leaning property). We perform -a right-flip at #w# and then proceed to the next iteration. Note that -this action causes #w#'s parent to violate the left-leaning property and -the depth of #u# to increase. However, it also implies that the next -iteration will be in Case~3 with #w# coloured red. When examining Case~3 -below, we will see that the process will stop during the next iteration. +Caso 1: o irmão de #u#, #v#, é vermelho. Nesse caso, o irmão de #u# é filho à esquerda de seu pai, #w# (de acordo com a propriedade de pender à esquerda). +Nós realizamos um +right-flip em #w# e então procedemos à próxima iteração. Note que essa ação +faz com que o pai de #w# viole a propriedade de pender à esquerda e a +profundidade de #u# aumentar. Entretando, isso também implica que a próxima iteração será no Caso~3 com #w# pintado de vermelho. Ao examinar o Caso~3 a seguir, veremos que o processo irá parar na próxima iteração. \codeimport{ods/RedBlackTree.removeFixupCase1(u)} \noindent -Case 2: #u#'s sibling, #v#, is black, and #u# is the left child of its -parent, #w#. In this case, we call #pullBlack(w)#, making #u# black, -#v# red, and darkening the colour of #w# to black or double-black. -At this point, #w# does not satisfy the left-leaning property, so we -call #flipLeft(w)# to fix this. - -At this point, #w# is red and #v# is the root of the subtree with which -we started. We need to check if #w# causes the no-red-edge property to -be violated. We do this by inspecting #w#'s right child, #q#. If #q# -is black, then #w# satisfies the no-red-edge property and we can continue -the next iteration with $#u#=#v#$. - -Otherwise (#q# is red), so both the no-red-edge property and the left-leaning -properties are violated at #q# and #w#, respectively. The left-leaning -property is restored with a call to -#rotateLeft(w)#, but the no-red-edge -property is still violated. At this point, #q# is the left child of -#v#, #w# is the left child of #q#, #q# and #w# are both red, and #v# -is black or double-black. A #flipRight(v)# makes #q# the parent of -both #v# and #w#. Following this up by a #pushBlack(q)# makes both #v# -and #w# black and sets the colour of #q# back to the original colour of #w#. - -At this point, the double-black node is has been eliminated and the -no-red-edge and black-height properties are reestablished. Only one possible problem remains: the right child of #v# may be red, in which -case the left-leaning property would be violated. We check this and -perform a #flipLeft(v)# to correct it if necessary. +Caso 2: o irmão de #u#, #v#, é preto, e #u# é o filho à esquerda de seu pai, #w#. +Nesse caso, chamamos +#pullBlack(w)#, fazendo #u# preto, +#v# vermelho, e tornando #w# preto ou duplo preto. +Nesse ponto, #w# não satisfaz a propriedade de pender à esquerda, então +chamamos #flipLeft(w)# para arrumar isso. +Além disso, #w# é vermelho e #v# é a raiz da subárvore com que começamos. +Precisamos verificar se #w# faz a propriedade de não haver nenhuma aresta vermelha ser violada. Fazer isso inspecionando o filho à direita de #w#, #q#. +Se #q# é preto, então #w# satisfaz a propriedade de nenhuma aresta vermelha e podemos continuar a próxima iteração com $#u#=#v#$. + +Caso contrário (#q# é vermelho) então as duas propriedades de não haver nenhuma aresta vermelha e de pender à esquerda são violadas em #q# e #w#, respectivamente. + +A propriedade de pender à esquerda é restaurada com uma chamada a +#rotateLeft(w)#, mas a propriedade de não haver nenhuma aresta vermelha continua sendo violada. Nesse ponto, #q# é o filho à esquerda de #v#, #w# é o filho à esquerda de #q#, #q# e #w# são vermelhos e #v# é preto ou duplo preto. Uma +#flipRight(v)# torna #q# o pai de +#v# e de #w#. +Seguindo isso por um +#pushBlack(q)# faz #v# +e #w# pretos e devolve a cor de #q# de volta à cor original de #w#. + +Nesse ponto, o nodo duplo preto foi eliminado e as propriedades +nenhuma aresta vermelha e altura preta são restabelecidas. +Somente resta resolver um problema: o filho à direita de #v# pode ser vermelho e, nesse caso, a propriedade de pender à esquerda seria violada. Verificamos isso +e realizamos, se necessária uma correção, uma chamada a +#flipLeft(v)#. \codeimport{ods/RedBlackTree.removeFixupCase2(u)} \noindent -Case 3: #u#'s sibling is black and #u# is the right child of its parent, -#w#. This case is symmetric to Case~2 and is handled mostly the same way. -The only differences come from the fact that the left-leaning property -is asymmetric, so it requires different handling. - -As before, we begin with a call to #pullBlack(w)#, which makes #v# red -and #u# black. A call to #flipRight(w)# promotes #v# to the root of -the subtree. At this point #w# is red, and the code branches two ways -depending on the colour of #w#'s left child, #q#. - -If #q# is red, then the code finishes up exactly the same way as Case~2 -does, but is even simpler since there is no danger of #v# not -satisfying the left-leaning property. - -The more complicated case occurs when #q# is black. In this case, -we examine the colour of #v#'s left child. If it is red, then #v# has -two red children and its extra black can be pushed down with a call to -#pushBlack(v)#. At this point, #v# now has #w#'s original colour, and we -are done. - -If #v#'s left child is black, then #v# violates the left-leaning property, -and we restore this with a call to #flipLeft(v)#. We then return the -node #v# so that the next iteration of #removeFixup(u)# then continues -with $#u#=#v#$. +Caso 3: o irmão de #u# é preto e #u# é o filho à direita de seu pai, #w#. +Esse caso é simétrico ao Caso~2 e é tratado quase da mesma forma. +As únicas diferenças vêm do fato que a propriedade de pender à esquerda é assimétrica e, portanto, requer tratamento diferenciado. + +Como antes, começamos com uma chamada a +#pullBlack(w)#, que torna #v# vermelho e #u# preto. Uma chamada a +#flipRight(w)# promove #v# à raiz da subárvore. Nesse momento, #w# é vermelho e existem duas formas de tratamento dependente da cor do filho à esquerda de #w#, #q#. + +Se #q# é vermelho, então o código termina exatamente da mesma maneira que o Caso~2, +mas é ainda mais simples pois não há perigo de #v# não satisfazer a propriedade de pender à esquerda. + +O caso mais complicado ocorre quando #q# é preto. Nesse caso, +examinamos a cor do filho à esquerda de #v#. Se for vermelho, então #v# tem +dois filhos vermelhos e seu extra preto pode ser empurrado abaixo com uma +chamada a +#pushBlack(v)#. Nesse ponto, #v# agora tem a cor original de #w# e terminamos o processo. + +Se o filho à esquerda de #v#'s for preto, então #v# viola a propriedade de pender à esquerda e a restauramos com uma chamada a +#flipLeft(v)#. Então retornamos o nodo #v# tal que a próxima iteração de +#removeFixup(u)# então constinua com +$#u#=#v#$. \codeimport{ods/RedBlackTree.removeFixupCase3(u)}. -Each iteration of #removeFixup(u)# takes constant time. Cases~2 and 3 -either finish or move #u# closer to the root of the tree. Case~0 (where -#u# is the root) always terminates and Case~1 leads immediately to Case~3, -which also terminates. Since the height of the tree is at most $2\log -#n#$, we conclude that there are at most $O(\log #n#)$ iterations of -#removeFixup(u)#, so #removeFixup(u)# runs in $O(\log #n#)$ time. +Cada iteração de + #removeFixup(u)# leva tempo constante. Casos~2 e 3 + ou terminam ou movem #u# mais próximo à raiz da árvore. Caso~0 (one #u# é + a raiz) sempre termina e Caso~1 levam imediatamente ao Caso~3, + que também termina. Como a +altura da árvore é no máximo $2\log +#n#$, concluimos o processo em até $O(\log #n#)$ iterações de +#removeFixup(u)#, então #removeFixup(u)# roda em tempo $O(\log #n#)$. -\section{Summary} +\section{Resumo} \seclabel{redblack-summary} -The following theorem summarizes the performance of the #RedBlackTree# data structure: +O teorema a seguir resume o desempenho de uma estrutura de dados +#RedBlackTree#: \begin{thm} - A #RedBlackTree# implements the #SSet# interface and - supports the operations #add(x)#, #remove(x)#, and #find(x)# in $O(\log - #n#)$ worst-case time per operation. + Uma #RedBlackTree# implementa a interface #SSet# interface e aceita as + operações + #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log + #n#)$ no pior caso, por operação. \end{thm} -Not included in the above theorem is the following extra bonus: +Não incluído no teorema anterior é o extra bônus: \begin{thm}\thmlabel{redblack-amortized} - Beginning with an empty #RedBlackTree#, any sequence of $m$ - #add(x)# and #remove(x)# operations results in a total of $O(m)$ - time spent during all calls #addFixup(u)# and #removeFixup(u)#. +Começando com uma #RedBlackTree# vazia, qualquer sequência de $m$ operações + #add(x)# e #remove(x)# resulta em um total de tempo $O(m)$ + durante todas as chamadas + #addFixup(u)# e #removeFixup(u)#. \end{thm} -We only sketch a proof of \thmref{redblack-amortized}. By comparing -#addFixup(u)# and #removeFixup(u)# with the algorithms for adding or -removing a leaf in a 2-4 tree, we can convince ourselves that this -property is inherited from a 2-4 tree. In particular, if we can show -that the total time spent splitting, merging, and borrowing in a 2-4 -tree is $O(m)$, then this implies \thmref{redblack-amortized}. - -The proof of this theorem for 2-4 trees uses the potential -method -\index{potential method}% -of amortized analysis.\footnote{See the proofs of -\lemref{dualarraydeque-amortized} and \lemref{selist-amortized} for -other applications of the potential method.} Define the potential of an -internal node #u# in a 2-4 tree as +Apenas rascunhamos uma prova do +\thmref{redblack-amortized}. Ao comparar +#addFixup(u)# e #removeFixup(u)# com os algoritmos para adicionar ou remover uma folha em uma +árvore 2-4, podemos nos convencer que essa propriedade é herdada de uma + árvore 2-4. Em particular, se podemos mostrar que o tempo total gasto com + repartições de nodos, uniões e empréstimos em uma árvore 2-4 é $O(m)$ + então isso implica no \thmref{redblack-amortized}. + + A prova desse teorema para + a árvore 2-4 usa o método potencial +\index{método potencial}% +de análise amortizada.\footnote{Veja as prova de +\lemref{dualarraydeque-amortized} e \lemref{selist-amortized} para +outras aplicações do método potencial.} Defina o potencial de um nodo +interno #u# em uma +árvore 2-4 como \[ \Phi(#u#) = \begin{cases} - 1 & \text{if #u# has 2 children} \\ - 0 & \text{if #u# has 3 children} \\ - 3 & \text{if #u# has 4 children} + 1 & \text{se #u# tem 2 filhos } \\ + 0 & \text{se #u# tem 3 filhos} \\ + 3 & \text{se #u# tem 4 filhos} \end{cases} \] -and the potential of a 2-4 tree as the sum of the potentials of its nodes. -When a split occurs, it is because a node with four children becomes -two nodes, with two and three children. This means that the overall -potential drops by $3-1-0 = 2$. When a merge occurs, two nodes that used -to have two children are replaced by one node with three children. The -result is a drop in potential of $2-0=2$. Therefore, for every split -or merge, the potential decreases by two. - -Next notice that, if we ignore splitting and merging of nodes, there are -only a constant number of nodes whose number of children is changed by -the addition or removal of a leaf. When adding a node, one node has -its number of children increase by one, increasing the potential by -at most three. During the removal of a leaf, one node has its number -of children decrease by one, increasing the potential by at most one, -and two nodes may be involved in a borrowing operation, increasing their -total potential by at most one. - -To summarize, each merge and split causes the potential to drop by -at least two. Ignoring merging and splitting, each addition or removal -causes the potential to rise by at most three, and the potential is always -non-negative. Therefore, the number of splits and merges caused by $m$ -additions or removals on an initially empty tree is at most $3m/2$. -\thmref{redblack-amortized} is a consequence of this analysis and the -correspondence between 2-4 trees and red-black trees. - -\section{Discussion and Exercises} - -Red-black trees were first introduced by Guibas and Sedgewick \cite{gs78}. -Despite their high implementation complexity they are found in some of -the most commonly used libraries and applications. Most algorithms and -data structures textbooks discuss some variant of red-black trees. - -Andersson \cite{a93} describes a left-leaning version of balanced trees -that is similar to red-black trees but has the additional constraint -that any node has at most one red child. This implies that these trees -simulate 2-3 trees rather than 2-4 trees. They are significantly simpler, -though, than the #RedBlackTree# structure presented in this chapter. - -Sedgewick \cite{s08} describes two versions of left-leaning red-black -trees. These use recursion along with a simulation of top-down splitting -and merging in 2-4 trees. The combination of these two techniques makes -for particularly short and elegant code. - -A related, and older, data structure is the \emph{AVL tree} \cite{avl62}. -\index{AVL tree}% -AVL trees are \emph{height-balanced}: -\index{height-balanced}% -\index{binary search tree!height balanced}% -At each node $u$, the height -of the subtree rooted at #u.left# and the subtree rooted at #u.right# -differ by at most one. It follows immediately that, if $F(h)$ is the -minimum number of leaves in a tree of height $h$, then $F(h)$ obeys the -Fibonacci recurrence +e o potencial de uma + árvore 2-4 como a soma dos potenciais de seus nodos. + Quando uma repartição ocorre, é porque um nodo com quatro filhos + de torna dois nodos, com dois e três filhos. Isso significa que o potencial geral cai em +$3-1-0 = 2$. Quando uma união ocorre, dois modos que tinham dois filhos +são substituídos por um nodo com três filhos. O resultado é uma queda em potencial +de + $2-0=2$. Portanto, para todas repartição ou união, o potencial diminui em dois. + +A seguir note que se ignorarmos a repartição e união de nodos, há somente +um número constante de nodo cujo número de filhos é alterado pela adição +ou remoção de uma folha. Ao adicionar um nodo, algum nodo tem seu número +número de filhos aumentado em um, aumentando o potencial por até três. +Durante a remoção de uma folha, um nodo tem seu número de filhos reduzido +em um, aumentando o potencial em até um, e dois nodos podem estar envolvidos em +uma operação de empréstimo aumentando seu potencial total em até um. + +Para resumir, cada união e repartição faz que o potencial caia por ao menos dois. +Ignorando uniões e repartições, cada adição ou remoção faz com que o potencial aumentar em até três e o potencial sempre é não-negativo. +Portanto, o número de repartições e uniões causado por $m$ +adições ou remoções em uma árvore inicialmente vazia é até +$3m/2$. +\thmref{redblack-amortized} é uma consequência dessa análise e a correspondência entre árvores 2-4 e árvores rubro-negras. + +\section{Discussão e Exercícios} + +Árvores rubro-negra foi inicialmente desenvolvidas por Guibas e Sedgewick \cite{gs78}. +Embora sua implementação de alta complexidade, ela é encontrada em algumas +das bibliotecas e aplicações mais utilizadas. A maior parte dos livros didáticos +de algoritmos e estruturas de dados discutem alguma variante de +árvores rubro-negras. + +Andersson \cite{a93} descreve uma versão pendente à esquerda de árvores balanceadas que é similar às +árvores rubro-negras mas tem a restrições adicional de que todo nodo tem no máximo um filho vermelho. Isso implica que essas árvores simulam árvores 2-3 em vez de árvores 2-4. Ela são significantemente mais simples que a estrutura +#RedBlackTree# apresentada neste capítulo. + +Sedgewick \cite{s08} descreve duas versões de árvores rubro-negras pendentes à esquerda. Elas usam recursão juntamente com uma simulação top-down de repartição e união em árvore 2-4. A combinação dessas duas técnicas tornam seus códigos particulamente curtos e elegantes. + +Uma estrutura de dados relacionada e mais antigos é a \emph{árvore AVL} +\cite{avl62}. +\index{árvore AVL}% +Árvores AVL são \emph{balanceadas na altura}: +\index{balanceamento na altura}% +\index{árvore binária de busca!balanceamento na altura}% +A cada nodo $u$, a +altura da subárvore enraizada em +#u.left# e a subárvore enraizada em #u.right# difere em até um. +Segue imediatamente que se +$F(h)$ é o número de folhas em uma árvore de altura $h$, então +$F(h)$ segue a recorrência de Fibonacci \[ F(h) = F(h-1) + F(h-2) \] -with base cases $F(0)=1$ and $F(1)=1$. This means $F(h)$ is approximately -$\varphi^h/\sqrt{5}$, where $\varphi=(1+\sqrt{5})/2\approx1.61803399$ is the -\emph{golden ratio}. (More precisely, $|\varphi^h/\sqrt{5} - F(h)|\le 1/2$.) -Arguing as in the proof of \lemref{twofour-height}, this implies +com casos base $F(0)=1$ e $F(1)=1$. Isso significa que $F(h)$ é aproximadamente +$\varphi^h/\sqrt{5}$, onde $\varphi=(1+\sqrt{5})/2\approx1.61803399$ é a +\emph{razão dourada}. (Mais precisamente, $|\varphi^h/\sqrt{5} - F(h)|\le 1/2$.) +Seguindo de modo similar à prova do +\lemref{twofour-height}, isso implica em \[ h \le \log_\varphi #n# \approx 1.440420088\log #n# \enspace , \] -so AVL trees have smaller height than red-black trees. The height -balancing can be maintained during #add(x)# and #remove(x)# operations -by walking back up the path to the root and performing a rebalancing -operation at each node #u# where the height of #u#'s left and right -subtrees differ by two. See \figref{avl-rebalance}. +então as árvores AVL tem altura menor que árvore +rubro-negras. O balanceamento da altura pode ser mantido durante as operações +#add(x)# e #remove(x)# ao percorrer o caminho em direção à raiz realizando +uma operação de rebalanceamento em cada nodo #u# onde a altura das subárvores +à esquerda e à direita de #u# difere em dois. +Veja a \figref{avl-rebalance}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/avl-rebalance} \end{center} - \caption{Rebalancing in an AVL tree. At most two rotations are required - to convert a node whose subtrees have a height of $h$ and $h+2$ into a node - whose subtrees each have a height of at most $h+1$.} + \caption{Rebalanceamento em um árvore AVL. No máximo duas rotações são necessárias para converter um nodo cujas subárvores tem altura de $h$ e $h+2$ em um nodo cujas subárvores tem altura de até $h+1$} \figlabel{avl-rebalance} \end{figure} -Andersson's variant of red-black trees, Sedgewick's variant of red-black -trees, and AVL trees are all simpler to implement than the #RedBlackTree# -structure defined here. Unfortunately, none of them can guarantee that -the amortized time spent rebalancing is $O(1)$ per update. In particular, -there is no analogue of \thmref{redblack-amortized} for those structures. +As variantes de árvores rubro-negras de Andersson e de Sedgewick +e as árvores AVL são mais simples de implementar que a estrutura #RedBlackTree# +aqui definida. Infelizmente nenhuma delas pode garantir que o tempo amortizado gasto rebalanceando é $O(1)$ por atualização. Em particular não há teorema análogo +ao \thmref{redblack-amortized} para essas estruturas. \begin{figure} \centering{\includegraphics[scale=0.90909]{figs/redblack-example}} - \caption{A red-black tree on which to practice.} + \caption{Uma árvore rubro-negra para praticar.} \figlabel{redblack-example2} \end{figure} \begin{exc} - Illustrate the 2-4 tree that corresponds to the #RedBlackTree# in + Desenhe a árvore 2-4 que corresponde à #RedBlackTree# em \figref{redblack-example2}. \end{exc} \begin{exc} - Illustrate the addition of 13, then 3.5, then 3.3 on the #RedBlackTree# - in \figref{redblack-example2}. + Desenhe a adição de 13, então 3.5 e depois 3.3 na #RedBlackTree# + em \figref{redblack-example2}. \end{exc} \begin{exc} - Illustrate the removal of 11, then 9, then 5 on the #RedBlackTree# in + Desenhe a remoção de 11, então 9 e depois 5 na #RedBlackTree# em \figref{redblack-example2}. \end{exc} \begin{exc} - Show that, for arbitrarily large values of #n#, there are red-black - trees with #n# nodes that have height $2\log #n#-O(1)$. + Mostre que, para valores arbitrariamente grandes de #n#, há árvores rubro-negras com #n# nodos com altura $2\log #n#-O(1)$. \end{exc} \begin{exc} - Consider the operations #pushBlack(u)# and #pullBlack(u)#. What do - these operations do to the underlying 2-4 tree that is being simulated - by the red-black tree? + Considere as operações #pushBlack(u)# e #pullBlack(u)#. O que essas operações fazem com a +árvore 2-4 implícita que está sendo simulada pela árvore rubro-negra? \end{exc} \begin{exc} - Show that, for arbitrarily large values of #n#, there exist sequences - of #add(x)# and #remove(x)# operations that lead to red-black trees - with #n# nodes that have height $2\log #n#-O(1)$. +Mostre que, para valores arbitrariamente grandes de #n#, existem sequências de operações +#add(x)# e #remove(x)# que resultam em árvores rubro-negras com + #n# nodos que têm altura $2\log #n#-O(1)$. \end{exc} - - \begin{exc} - Why does the method #remove(x)# in the #RedBlackTree# implementation - perform the assignment #u.parent=w.parent#? Shouldn't this already - be done by the call to #splice(w)#? +Porque o método #remove(x)# na implementação da #RedBlackTree# realiza + a atribuição + #u.parent=w.parent#? Isso não seria feito pela chamada + #splice(w)#? \end{exc} \begin{exc} - Suppose a 2-4 tree, $T$, has $#n#_\ell$ leaves and $#n#_i$ internal nodes. + Suponha que a árvore 2-4, $T$, tem $#n#_\ell$ folhas e $#n#_i$ nodos internos. \begin{enumerate} - \item What is the minimum value of $#n#_i$, as a function of $#n#_\ell$? - \item What is the maximum value of $#n#_i$, as a function of $#n#_\ell$? - \item If $T'$ is a red-black tree that represents $T$, then how many red - nodes does $T'$ have? + \item Qual é o valor mínimo de $#n#_i$, como uma função de $#n#_\ell$? + \item Qual é o valor máximo de $#n#_i$, como uma função de $#n#_\ell$? + \item Se $T'$ é uma árvore rubro-negra que representa $T$, então quantos nodos vermelhos $T'$ tem? \end{enumerate} \end{exc} \begin{exc} - Suppose you are given a binary search tree with #n# nodes and a - height of at most $2\log #n#-2$. Is it always possible to colour the - nodes red and black so that the tree satisfies the black-height and - no-red-edge properties? If so, can it also be made to satisfy the - left-leaning property? + Suponha que você é dado uma árvore binária de busca com #n# nodos e uma altura de até + $2\log #n#-2$. É sempre possível colorir os nodos vermelhos e pretos tal que a árcore satisfaz as propriedades da altura preta e de que não há nenhuma aresta vermelha? Se sim, também é possível fazê-la satisfazer a propriedade de pender à esquerda? \end{exc} \begin{exc}\exclabel{redblack-merge} - Suppose you have two red-black trees $T_1$ and $T_2$ that have the - same black height, $h$, and such that the largest key in $T_1$ is smaller - than the smallest key in $T_2$. Show how to merge $T_1$ and $T_2$ - into a single red-black tree in $O(h)$ time. + Suponha que você tem duas árvores rubro-negras $T_1$ e $T_2$ que tem a + mesma altura preta + , $h$, e tal que a maior chave em $T_1$ é menor que a menor chave em + $T_2$. Mostre como juntar $T_1$ e $T_2$ em uma única árvore rubro-negra em + tempo $O(h)$. \end{exc} \begin{exc} - Extend your solution to \excref{redblack-merge} to the case where the - two trees $T_1$ and $T_2$ have different black heights, $h_1\neq h_2$. - The running-time should be $O(\max\{h_1,h_2\})$. + Extenda sua solução para \excref{redblack-merge} para o caso onde + as duas árvores + $T_1$ e $T_2$ têm diferentes alturas pretas, $h_1\neq h_2$. + O tempo de execução deve ser + $O(\max\{h_1,h_2\})$. \end{exc} - - \begin{exc} - Prove that, during an #add(x)# operation, an AVL tree must perform - at most one rebalancing operation (that involves at most two rotations; - see \figref{avl-rebalance}). Give an example of an AVL tree and a - #remove(x)# operation on that tree that requires on the order of $\log - #n#$ rebalancing operations. + Prove que, durante uma operação #add(x)#, uma árvore AVL deve realizar + até uma operação de rebalanceamento (que envolve até duas rotações; + veja a \figref{avl-rebalance}). Dê um exemplo de uma árvore AVL e uma operação + #remove(x)# nessa árvore que requer na ordem de $\log + #n#$ operações de rebalanceamento. \end{exc} \begin{exc} - Implement an #AVLTree# class that implements AVL trees as described - above. Compare its performance to that of the #RedBlackTree# - implementation. Which implementation has a faster #find(x)# operation? + Implemente uma classe #AVLTree# que implementa árvores AVL conforme descrito acima. Compare seu desempenho à implementação da + #RedBlackTree# . Qual implementação tem uma operação #find(x)# mais rápida? \end{exc} \begin{exc} - Design and implement a series of experiments that compare the relative - performance of #find(x)#, #add(x)#, and #remove(x)# for the #SSet# implemeentations #SkiplistSSet#, - #ScapegoatTree#, #Treap#, and #RedBlackTree#. Be sure to include - multiple test scenarios, including cases where the data is random, - already sorted, is removed in random order, is removed in sorted order, - and so on. + Projete e implemente uma séries de experimentos que comparam o desempenho de + #find(x)#, #add(x)# e #remove(x)# para as implementações #SSet#: #SkiplistSSet#, + #ScapegoatTree#, #Treap# e #RedBlackTree#. Inclua diferentes cenários de teste, incluindo casos onde os dados são aleatórios, previamente ordenados, são removidos em ordem aleatória, são removidos em ordem crescente e assim por diante. \end{exc} From 09d0d5b94310da40e19149a27cb160ab044a8565 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 23 Aug 2020 23:21:20 -0300 Subject: [PATCH 28/66] fixing mistaking using first command to define chapter redblack --- latex/redblack.tex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/latex/redblack.tex b/latex/redblack.tex index 833842cf..c8c974ac 100644 --- a/latex/redblack.tex +++ b/latex/redblack.tex @@ -1,7 +1,4 @@ -%%merge = união -%%split = repartição -\r{ck.tex" 776L, 36435C written - Árvores Rubro-Negras} +\chapter{Árvores Rubro-Negras} \chaplabel{redblack} \index{binary search tree!red-black}% @@ -769,3 +766,5 @@ \section{Discussão e Exercícios} #find(x)#, #add(x)# e #remove(x)# para as implementações #SSet#: #SkiplistSSet#, #ScapegoatTree#, #Treap# e #RedBlackTree#. Inclua diferentes cenários de teste, incluindo casos onde os dados são aleatórios, previamente ordenados, são removidos em ordem aleatória, são removidos em ordem crescente e assim por diante. \end{exc} +%%merge = união +%%split = repartição From 3ae1876a98df180fbfeb785a76768ea35c5bed42 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Tue, 25 Aug 2020 18:29:22 -0300 Subject: [PATCH 29/66] beginning of translation of heaps and minor fixes in sorting --- latex/heaps.tex | 159 ++++++++++++++++++++++------------------------ latex/sorting.tex | 8 +-- 2 files changed, 81 insertions(+), 86 deletions(-) diff --git a/latex/heaps.tex b/latex/heaps.tex index d06caffa..3fa58859 100644 --- a/latex/heaps.tex +++ b/latex/heaps.tex @@ -1,73 +1,69 @@ \chapter{Heaps} \chaplabel{heaps} -In this chapter, we discuss two implementations of the extremely useful -priority #Queue# data structure. Both of these structures are a special -kind of binary tree called a \emph{heap}, +Neste capítulo, nós discutimos duas implementações da estrutura de dados estremamente útil #Queue# de prioridades. Essas duas estruturas são +um tipo especial de árvore binária chamada de \emph{heap}, \index{heap}% -\index{binary heap}% -\index{heap!binary}% -which means ``a disorganized -pile.'' This is in contrast to binary search trees that can be thought -of as a highly organized pile. - -The first heap implementation uses an array to simulate a complete binary -tree. This very fast implementation is the basis of one of the fastest -known sorting algorithms, namely heapsort (see \secref{heapsort}). -The second implementation is based on more flexible binary trees. -It supports a #meld(h)# operation that allows the priority queue to -absorb the elements of a second priority queue #h#. - -\section{#BinaryHeap#: An Implicit Binary Tree} +\index{heap binária}% +\index{heap!binária}% +que significa ``uma pilha desorganizada.'' Isso é em contraste a árvores binárias de busca que podem ser pensadas com pilhas altamente organizadas. + +A primeira implementação da heap usa um array que simula uma árvore binária +completa. Essa implementação muito rápida é a base de um dos algoritmos +mais rápido de ordenação, chamado de heapsort (veja a \secref{heapsort}). +A segunda implementação é baseada em árvores binárias mais flexíveis. +Ela aceita a operação +#meld(h)# que permite que a fila de prioridades absorva os elementos a uma segunda fila de prioridades $h$. + +\section{#BinaryHeap#: Uma Árvore Binária Implícita} \seclabel{binaryheap} \index{BinaryHeap@#BinaryHeap#}% -Our first implementation of a (priority) #Queue# is based on a technique -that is over four hundred years old. \emph{Eytzinger's method} -\index{Eytzinger's method}% -allows us -to represent a complete binary tree as an array by laying out the nodes -of the tree in breadth-first order (see \secref{bintree:traversal}). -In this way, the root is stored at position 0, the root's left child is -stored at position 1, the root's right child at position 2, the left -child of the left child of the root is stored at position 3, and so -on. See \figref{eytzinger}. +Nossa primeira implementação de uma + #Queue# (de prioridades) é baseada em uma técnica criada há quatrocentos anos atrás. O \emph{método de Eytzinger} +\index{método de Eytzinger}% +nos permite representar uma árvore binária completa como um array +listando os nodos da árvore em ordem de largura (veja a + \secref{bintree:traversal}). + Dessa forma, a raiz é guardada na posição 0, o filho à esquerda da raiz é + guardada na posição 1, o filho à direita da raiz na posição 2, o filho à esquerda do filho à esquerda da raiz é guardado na posição 3 e assim por diante. +Veja a +\figref{eytzinger}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/eytzinger} \end{center} - \caption{Eytzinger's method represents a complete binary tree as an array.} + \caption{O método de Eytzinger representa uma árvore binária completa como um array.} \figlabel{eytzinger} \end{figure} -If we apply Eytzinger's method to a sufficiently large tree, some -patterns emerge. The left child of the node at index #i# is at index -$#left(i)#=2#i#+1$ and the right child of the node at index #i# is at -index $#right(i)#=2#i#+2$. The parent of the node at index #i# is at -index $#parent(i)#=(#i#-1)/2$. +Se aplicarmos o método de Eytzinger a alguma árvore grande o suficiente, alguns +padrão emergem. O filho à esquerda do nodo no índice #i# está no índice +$#left(i)#=2#i#+1$ e o filho à direita do nodo no índice #i# está no índice +$#right(i)#=2#i#+2$. O pai do nodo no índice #i# está no índice +$#parent(i)#=(#i#-1)/2$. \codeimport{ods/BinaryHeap.left(i).right(i).parent(i)} -A #BinaryHeap# uses this technique to implicitly represent a complete -binary tree in which the elements are \emph{heap-ordered}: -\index{heap-ordered binary tree}% -\index{binary tree!heap-ordered}% -\index{heap order}% -The value -stored at any index #i# is not smaller than the value stored at index -#parent(i)#, with the exception of the root value, $#i#=0$. It follows -that the smallest value in the priority #Queue# is therefore stored at -position 0 (the root). - -In a #BinaryHeap#, the #n# elements are stored in an array #a#: +Uma #BinaryHeap# usa essa técnica para implicitamente representar uma árvore binária na qual os elementos estão \emph{ordenados em uma pilha}: +\index{árvore binária ordenada em pilha}% +\index{árvore binária!ordenada em pilha}% +\index{ordem em uma pilha}% +O valor guardado em qualquer índice +#i# não é menor que o valor guardado no índice +#parent(i)#, com exceção do valor na raiz, $#i#=0$. Portanto, o menor +valor na #Queue# com prioridades é guardado na posição 0 (a raiz). + +Em uma #BinaryHeap#, os #n# elementos são guardados em um array #a#: \codeimport{ods/BinaryHeap.a.n} -Implementing the #add(x)# operation is fairly straightforward. As with -all array-based structures, we first check to see if #a# is full (by checking if $#a.length#=#n#$) and, if so, we grow #a#. Next, we place #x# at location -#a[n]# and increment #n#. At this point, all that remains is to ensure -that we maintain the heap property. We do this by repeatedly swapping -#x# with its parent until #x# is no longer smaller than its parent. -See \figref{heap-insert}. +Implementar a operação #add(x)# razoavelmente simples. +Assim como todas as estruturas baseadas em array, primeiro verificamos se +#a# está cheio (verificando se + $#a.length#=#n#$) e, caso positivo, expandimos #a#. + A seguir, posicionamos #x# na posição +#a[n]# e incrementamos #n#. Nesse ponto, resta garantirmos que mantemos a propriedade heap. Fazemos isso repetidamente trocando #x# com seu pai, #x#, até não seja mais menor que seu pai. +Veja a \figref{heap-insert}. \codeimport{ods/BinaryHeap.add(x).bubbleUp(i)} \begin{figure} @@ -77,21 +73,21 @@ \section{#BinaryHeap#: An Implicit Binary Tree} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/heap-insert-3} \\ \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/heap-insert-4} \\ \end{center} - \caption[Adding to a BinaryHeap]{Adding the value 6 to a #BinaryHeap#.} + \caption[Adição em uma BinaryHeap]{Adição do valor 6 a uma #BinaryHeap#.} \figlabel{heap-insert} \end{figure} -Implementing the #remove()# operation, which removes the smallest value -from the heap, is a little trickier. We know where the smallest value is -(at the root), but we need to replace it after we remove it and ensure -that we maintain the heap property. - -The easiest way to do this is to replace the root with the value #a[n-1]#, delete -that value, and decrement #n#. Unfortunately, the new root element is now -probably not the smallest element, so it needs to be moved downwards. -We do this by repeatedly comparing this element to its two children. -If it is the smallest of the three then we are done. Otherwise, we swap -this element with the smallest of its two children and continue. +A implementação da +operação #remove()#, que remove o menor valor da heap é um pouco mais complicado. Sabemos onde o menor valor está (na raiz) mas precisamos substituí-lo após sua remoção e garantir a manutenção da propriedade heap. + +O modo mais fácil de fazer isso é substituir a raiz com o valor + #a[n-1]#, remover esse valor e decrementar #n#. delete + Infelizmente, o novo elemento na raiz agora provavelmente não é o menor + elemento, então ele precisa ser movido para baixo na árvore. + Fazemos isso repetidamente, comparando esse elemento aos seus dois filhos. + Se for menor dos três, então paramos. Caso contrário, trocamos esse elemento + com o menor de seus dois filhos e continuamos. + \codeimport{ods/BinaryHeap.remove().trickleDown(i)} \begin{figure} @@ -101,38 +97,37 @@ \section{#BinaryHeap#: An Implicit Binary Tree} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/heap-remove-3} \\ \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/heap-remove-4} \\ \end{center} - \caption[Removing from a BinaryHeap]{Removing the minimum value, 4, from a #BinaryHeap#.} + \caption[Remoção de uma BinaryHeap]{Removendo o valor mínimo, 4, de uma #BinaryHeap#.} \figlabel{heap-remove} \end{figure} - - -As with other array-based structures, we will ignore the time spent in -calls to #resize()#, since these can be accounted for using the amortization -argument from \lemref{arraystack-amortized}. The running times of -both #add(x)# and #remove()# then depend on the height of the (implicit) -binary tree. Luckily, this is a \emph{complete} -\index{binary tree!complete}% -\index{complete binary tree}% -binary tree; every level -except the last has the maximum possible number of nodes. Therefore, -if the height of this tree is $h$, then it has at least $2^h$ nodes. -Stated another way +Assim como em outras estruturas baseadas em array, iremos ignorar o tempo +gasto em chamadas #resize()#, pois essas podem ser consideradas usando o argumento +de amortização do +\lemref{arraystack-amortized}. Os tempos de execução de +#add(x)# e de #remove()# então dependem da altura da árvore binária (implícita). +Felizmente, essa árvore binária está \emph{completa} +\index{árvore binária!completa}% +\index{árvore binária completa % +; todo nível exceto o último têm o maior número possível de nodos. +Portanto, se a altura dessa árvore é $h$, então ele tem pelo menos $2^h$ nodos. +Se outra forma podemos afirmar \[ #n# \ge 2^h \enspace . \] -Taking logarithms on both sides of this equation gives +Fazendo logaritmos nos dois lados dessa equação resulta em \[ h \le \log #n# \enspace . \] -Therefore, both the #add(x)# and #remove()# operation run in $O(\log #n#)$ time. +Portanto, as operações +Therefore, #add(x)# e #remove()# rodam em $O(\log #n#)$ de tempo. -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of a #BinaryHeap#: +O teorema a seguir resume o desempenho de uma #BinaryHeap#: \begin{thm}\thmlabel{binaryheap} - A #BinaryHeap# implements the (priority) #Queue# interface. Ignoring + Uma #BinaryHeap# implementa a interface #Queue# de prioridades. Ignoring the cost of calls to #resize()#, a #BinaryHeap# supports the operations #add(x)# and #remove()# in $O(\log #n#)$ time per operation. diff --git a/latex/sorting.tex b/latex/sorting.tex index 3300142b..0833fe9d 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -5,16 +5,16 @@ \chapter{Algoritmos de Ordenação} Esse pode ser um tópico estranho para um livro em estruturas de dados, mas existem várias boas razões para incluí-lo aqui. A razão mais óbvia é que dois desses algoritmos de ordenação (quicksort e heap-sort) -são intimamente relacionados com duas das estruturas de dados que estudadas neste livro (árvores binárias de busca aleatórias e heaps, respectivamente). +são intimamente relacionados com duas das estruturas de dados que são estudadas neste livro (árvores binárias de busca aleatórias e heaps). A primeira parte deste capítulo discute algoritmos que ordenam usando somente comparações e apresenta três algoritmos que rodam em $O(#n#\log #n#)$ de tempo. -Acontece que, esses três algoritmos são assintóticamente ótimos; +Acontece que, esses três algoritmos são assintoticamente ótimos; nenhum algoritmo que usa somente comparações pode evitar fazer aproximadamente $#n#\log #n#$ comparações no pior caso e mesmo no caso médio. -Antes de continuar, devemos notas que quaisquer implementações do #SSet# +Antes de continuar, devemos notar que quaisquer implementações do #SSet# ou da #Queue# de prioridades apresentadas nos capítulos anteriores também podem ser usadas para obter um algoritmo de ordenação $O(#n#\log #n#)$. @@ -23,7 +23,7 @@ \chapter{Algoritmos de Ordenação} operações #add(x)# seguidas de #n# operações #remove()# em uma #BinaryHeap# ou #MeldableHeap#. Alternativamente, podemos usar #n# operações #add(x)# - em qualquer estrutura de dados de árvore de busca binára e então + em qualquer estrutura de dados de árvore de busca binária e então realizar uma travessia em ordem (\excref{tree-traversal}) para extrair os elementos ordenados Porém, nos dois casos teremos muitos gastos para construir uma estrutura From db3f57836d370604a5ca6b94bc1fa2db56af597d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 26 Aug 2020 15:40:00 -0300 Subject: [PATCH 30/66] translation of heaps --- latex/heaps.tex | 338 +++++++++++++++++++++++------------------------- 1 file changed, 162 insertions(+), 176 deletions(-) diff --git a/latex/heaps.tex b/latex/heaps.tex index 3fa58859..6c437f7f 100644 --- a/latex/heaps.tex +++ b/latex/heaps.tex @@ -108,7 +108,7 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} #add(x)# e de #remove()# então dependem da altura da árvore binária (implícita). Felizmente, essa árvore binária está \emph{completa} \index{árvore binária!completa}% -\index{árvore binária completa % +\index{árvore binária completa}% ; todo nível exceto o último têm o maior número possível de nodos. Portanto, se a altura dessa árvore é $h$, então ele tem pelo menos $2^h$ nodos. Se outra forma podemos afirmar @@ -119,110 +119,108 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} \[ h \le \log #n# \enspace . \] -Portanto, as operações -Therefore, #add(x)# e #remove()# rodam em $O(\log #n#)$ de tempo. +Portanto, as operações + #add(x)# e #remove()# rodam em $O(\log #n#)$ de tempo. \subsection{Resumo} O teorema a seguir resume o desempenho de uma #BinaryHeap#: \begin{thm}\thmlabel{binaryheap} - Uma #BinaryHeap# implementa a interface #Queue# de prioridades. Ignoring - the cost of calls to #resize()#, a #BinaryHeap# supports the operations - #add(x)# and #remove()# in $O(\log #n#)$ time per operation. - - Furthermore, beginning with an empty #BinaryHeap#, any sequence of $m$ - #add(x)# and #remove()# operations results in a total of $O(m)$ - time spent during all calls to #resize()#. + Uma #BinaryHeap# implementa a interface #Queue# de prioridades. + Ignorando o custo de chamadas ao método #resize()#, uma #BinaryHeap# aceita + as operações + #add(x)# e #remove()# em $O(\log #n#)$ de tempo por operação. + Além disso, começando com uma + #BinaryHeap# vazia, qualquer sequência de $m$ operações + #add(x)# e #remove()# resulta em um total de $O(m)$ + de tempo gasto durante todas as chamadas a #resize()#. \end{thm} -\section{#MeldableHeap#: A Randomized Meldable Heap} +\section{#MeldableHeap#: Uma Heap Randomizada Combinável} \seclabel{meldableheap} \index{MeldableHeap@#MeldableHeap#}% -In this section, we describe the #MeldableHeap#, a priority #Queue# -implementation in which the underlying structure is also a heap-ordered -binary tree. However, unlike a #BinaryHeap# in which the underlying -binary tree is completely defined by the number of elements, there -are no restrictions on the shape of the binary tree that underlies -a #MeldableHeap#; anything goes. - -The #add(x)# and #remove()# operations in a #MeldableHeap# are -implemented in terms of the #merge(h1,h2)# operation. This operation -takes two heap nodes #h1# and #h2# and merges them, returning a heap -node that is the root of a heap that contains all elements in the subtree -rooted at #h1# and all elements in the subtree rooted at #h2#. - -The nice thing about a #merge(h1,h2)# operation is that it can be -defined recursively. See \figref{meldable-merge}. If either #h1# or -#h2# is #nil#, then we are merging with an empty set, so we return #h2# -or #h1#, respectively. Otherwise, assume $#h1.x# \le #h2.x#$ since, -if $#h1.x# > #h2.x#$, then we can reverse the roles of #h1# and #h2#. -Then we know that the root of the merged heap will contain #h1.x#, and -we can recursively merge #h2# with #h1.left# or #h1.right#, as we wish. -This is where randomization comes in, and we toss a coin to decide -whether to merge #h2# with #h1.left# or #h1.right#: +Nesta seção, descrevemos a #MeldableHeap#, uma implementação da #Queue# com prioridades na qual a estrutura básica também é uma árvore binária do tipo heap ordenada. Entretanto, diferentemente de uma #BinaryHeap# na qual a estrutura básica é completamente definida pelo número de elementos, não há restrições na forma +da árvore binária que implementa uma #MeldableHeap#, uma heap combinável; qualquer forma vale. + +As operações #add(x)# e #remove()# em uma #MeldableHeap# são implementada +em termos da operação + #merge(h1,h2)#. Essa operação recebe dois nodos de heap #h1# e #h2# os junta, retornando um nodo de heap que é a raiz de uma heap que contém todos os elementos em uma subárvore enraizada em #h1# e todos elementos em uma subárvore enraizada em #h2#. + +O lado bom de uma operação #merge(h1,h2)# é que ela pode ser definida recursivamente. Veja a \figref{meldable-merge}. Se #h1# ou #h2# +forem #nil#, então estaremos combinando com um conjunto vazio, então +retornamos #h1# ou #h2#, respectivamente. Por outro lado, +assumimos +$#h1.x# \le #h2.x#$ pois, +se $#h1.x# > #h2.x#$, então invertemos os papéis de #h1# e #h2#. +Então sabemos que a raiz da heap combinada conterá #h1.x# e podemos +recursivamente combinar #h2# com + #h1.left# ou #h1.right#, conforme desejarmos. +É nisso que a randomização entra e lançamos uma moeda para decidir +se combinamos #h2# com +#h1.left# ou #h1.right#: \codeimport{ods/MeldableHeap.merge(h1,h2)} \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/meldable-merge}} - \caption[Merging in a MeldableHeap]{Merging #h1# and #h2# is done by merging #h2# with one of - #h1.left# or #h1.right#.} + \caption[A combinação em uma MeldableHeap]{A combinação de #h1# e #h2# é feita pela combinação de #h2# com a subárvore + #h1.left# ou a subárvore #h1.right#.} \figlabel{meldable-merge} \end{figure} -In the next section, we show that #merge(h1,h2)# runs in $O(\log #n#)$ -expected time, where #n# is the total number of elements in #h1# and #h2#. +Na próxima seção, veremos que + #merge(h1,h2)# roda em tempo esperado $O(\log #n#)$, onde #n# é o número total + de elementos em #h1# e #h2#. -With access to a #merge(h1,h2)# operation, the #add(x)# operation is easy. We create a new node #u# containing #x# and then merge #u# with the root of our heap: +Com acesso à operação +#merge(h1,h2)#, a operação #add(x)# é fácil. Criamos um novo nodo #u# +contendo #x# e então combinamos #u# com a raiz da nossa heap: \codeimport{ods/MeldableHeap.add(x)} -This takes $O(\log (#n#+1)) = O(\log #n#)$ expected time. +Isso leva $O(\log (#n#+1)) = O(\log #n#)$ de tempo esperado. -The #remove()# operation is similarly easy. The node we want to remove -is the root, so we just merge its two children and make the result the root: +A operação #remove()# também é fácil. O nodo que queremos remover é a raiz, então simplesmente combinamos seus seu filhos e fazemos o resultado ser a raiz: \codeimport{ods/MeldableHeap.remove()} -Again, this takes $O(\log #n#)$ expected time. +Novamente, isso leva +$O(\log #n#)$ de tempo esperado. -Additionally, a #MeldableHeap# can implement many other operations in -$O(\log #n#)$ expected time, including: +Adicionalmente, uma +#MeldableHeap# pode implementar muitas outras operações em +$O(\log #n#)$ de tempo esperado, incluindo: \begin{itemize} -\item #remove(u)#: remove the node #u# (and its key #u.x#) from the heap. -\item #absorb(h)#: add all the elements of the #MeldableHeap# #h# to this heap, emptying #h# in the process. +\item #remove(u)#: remove o nodo #u# (e sua chave #u.x#) da heap. +\item #absorb(h)#: adiciona todos os elementos de uma #MeldableHeap# #h# para essa heap, esvaziando #h# no processo. \end{itemize} -Each of these operations can be implemented using a constant number of -#merge(h1,h2)# operations that each take $O(\log #n#)$ expected time. +Cada uma dessas operações pode ser implementada usando um número constante de operações #merge(h1,h2)# que leva $O(\log #n#)$ de tempo experado. -\subsection{Analysis of #merge(h1,h2)#} +\subsection{Análise de #merge(h1,h2)#} -The analysis of #merge(h1,h2)# is based on the analysis of a random walk -in a binary tree. A \emph{random walk} in a binary tree starts at the -root of the tree. At each step in the random walk, a coin is tossed and, -depending on the result of this coin toss, the walk proceeds to the left -or to the right child of the current node. The walk ends when it falls -off the tree (the current node becomes #nil#). +A análise de +#merge(h1,h2)# é baseada na análise da uma caminhada aleatória em uma árvore binária. Uma \emph{caminhada aleatória} em uma árvore binária inicia-se +na raiz da árvore. A cada passo na caminhada aleatória, uma moeda é lançada e, +dependendo do resultado desse lançamento, a caminhada prossegur ao filho à esquerda +ou à direita do nodo atual. Essa caminhada termina quando sai da árvore (o nodo atual torna-se #nil#). -The following lemma is somewhat remarkable because it does not depend -at all on the shape of the binary tree: +O lema a seguir é de certa forma impressionante porque não depende da forma da árvore binária: \begin{lem}\lemlabel{tree-random-walk} -The expected length of a random walk in a binary tree with #n# nodes is at most #\log (n+1)#. +O comprimento esperado de uma caminhada aleatória em uma árvore binária com #n# nodos é no máximo #\log (n+1)#. \end{lem} \begin{proof} -The proof is by induction on #n#. In the base case, $#n#=0$ and the walk -has length $0=\log (#n#+1)$. Suppose now that the result is true for -all non-negative integers $#n#'< #n#$. - -Let $#n#_1$ denote the size of the root's left subtree, so that -$#n#_2=#n#-#n#_1-1$ is the size of the root's right subtree. Starting at -the root, the walk takes one step and then continues in a subtree of -size $#n#_1$ or $#n#_2$. By our inductive hypothesis, the expected -length of the walk is then + A prova é por indução em #n#. No caso base, +$#n#=0$ e a caminhada tem comprimento +$0=\log (#n#+1)$. Suponha que o resultado é verdadeiro para todos os inteiros não negativos $#n#'< #n#$. + +Considere que $#n#_1$ denota o tamanho da subárvore à esquerda da raiz tal que +$#n#_2=#n#-#n#_1-1$ seja o tamanho da subárvore à direita da raiz. Iniciando na raiz, a caminhada faz um passo e então continua em uma subárvores de tamanho +$#n#_1$ ou $#n#_2$. Pela nossa hipótese indutiva, o comprimento esperado de uma caminhada é então \[ \E[W] = 1 + \frac{1}{2}\log (#n#_1+1) + \frac{1}{2}\log (#n#_2+1) \enspace , \] -since each of $#n#_1$ and $#n#_2$ are less than $#n#$. Since $\log$ -is a concave function, $\E[W]$ is maximized when $#n#_1=#n#_2=(#n#-1)/2$. +pois + $#n#_1$ e $#n#_2$ são menores que $#n#$. Como $\log$ é uma função côncava, +$\E[W]$ é maximizado quando $#n#_1=#n#_2=(#n#-1)/2$. %To maximize this, %over all choices of $#n#_1\in[0,#n#-1]$, we take the derivative and obtain %\[ @@ -230,8 +228,7 @@ \subsection{Analysis of #merge(h1,h2)#} %\] %which is equal to 0 when $#n#_1 = (#n#-1)/2$. We can establish that %this is a maximum fairly easily, so -Therefore, - the expected number of steps taken by the random walk is +Portanto, o número esperado de passos feitos pela caminhada aleatória é \begin{align*} \E[W] & = 1 + \frac{1}{2}\log (#n#_1+1) + \frac{1}{2}\log (#n#_2+1) \\ @@ -241,174 +238,163 @@ \subsection{Analysis of #merge(h1,h2)#} \end{align*} \end{proof} -We make a quick digression to note that, for readers who know a little -about information theory, the proof of \lemref{tree-random-walk} can -be stated in terms of entropy. -\begin{proof}[Information Theoretic Proof of \lemref{tree-random-walk}] -Let $d_i$ denote the depth of the $i$th external node and recall that a -binary tree with #n# nodes has #n+1# external nodes. The probability -of the random walk reaching the $i$th external node is exactly -$p_i=1/2^{d_i}$, so the expected length of the random walk is given by +Fazemos uma rápida digressão para notar que, para leitores que sabem um +pouco sobre teoria da informação, a prova de +\lemref{tree-random-walk} pode ser escrita em termos da entropia. +\begin{proof}[Prova baseada em Teoria da Informação da \lemref{tree-random-walk}] + Seja +$d_i$ a profundidade do $i$-ésimo nodo externo e lembre-se que uma árvore binária com #n# nodos tem #n+1# nodos externos. A probabilidade da caminhada aleatória alcançar o $i$-ésimo nodo externo é exatamente +$p_i=1/2^{d_i}$, então o comprimento esperado da caminhada aleatória é dado por \[ H=\sum_{i=0}^{#n#} p_id_i =\sum_{i=0}^{#n#} p_i\log\left(2^{d_i}\right) = \sum_{i=0}^{#n#}p_i\log({1/p_i}) \] -The right hand side of this equation is easily recognizable as the -entropy of a probability distribution over $#n#+1$ elements. A basic -fact about the entropy of a distribution over $#n#+1$ elements is that -it does not exceed $\log(#n#+1)$, which proves the lemma. +O lado direito dessa equação é facilmente reconhecível como a entropia da uma distribuição de probabilidade sobre + $#n#+1$ elementos. Um fato básico sobre a entropia de uma distribuição sobre +$#n#+1$ elementos é que ela não passa de +$\log(#n#+1)$, o que prova o lema. \end{proof} -With this result on random walks, we can now easily prove that the -running time of the #merge(h1,h2)# operation is $O(\log #n#)$. +Com esse resultado em caminhadas aleatórias, podemos facilmente provar que o tempo +de execução da operação +#merge(h1,h2)# é $O(\log #n#)$. \begin{lem} - If #h1# and #h2# are the roots of two heaps containing $#n#_1$ - and $#n#_2$ nodes, respectively, then the expected running time of - #merge(h1,h2)# is at most $O(\log #n#)$, where $#n#=#n#_1+#n#_2$. + Senda #h1# e #h2# as raízes de duas heaps contendo $#n#_1$ + e $#n#_2$ nodos, respectivamente, então o tempo esperado da execução de + #merge(h1,h2)# é até $O(\log #n#)$, onde $#n#=#n#_1+#n#_2$. \end{lem} \begin{proof} - Each step of the merge algorithm takes one step of a random walk, - either in the heap rooted at #h1# or the heap rooted at #h2#. + Cada passo do algoritmo de combinação #merge# faz um passo da caminhada aleatória, + seja na heap enraizada em #h1# ou na heap enraizada em #h2#. %, depending on whether $#h1.x# < #h2.x#$ or not. - The algorithm terminates when either of these two random walks fall - out of its corresponding tree (when $#h1#=#null#$ or $#h2#=#null#$). - Therefore, the expected number of steps performed by the merge algorithm - is at most + O algoritmo termina quando uma dessas duas caminhadas aleatórias + saem da sua própria árvore (quando $#h1#=#null#$ ou $#h2#=#null#$). + Portanto, o número esperado de passos feitos pelo algoritmo #merge# é no máximo \[ \log (#n#_1+1) + \log (#n#_2+1) \le 2\log #n# \enspace . \qedhere \] \end{proof} -\subsection{Summary} +\subsection{Resumo} -The following theorem summarizes the performance of a #MeldableHeap#: +O teorema a seguir resume o desempenho de uma + #MeldableHeap#: \begin{thm}\thmlabel{meldableheap} - A #MeldableHeap# implements the (priority) #Queue# interface. - A #MeldableHeap# supports the operations #add(x)# and #remove()# - in $O(\log #n#)$ expected time per operation. + Uma #MeldableHeap# implementa a interface de uma #Queue# com prioridades. + Uma + #MeldableHeap# aceita as operações #add(x)# e #remove()# em + $O(\log #n#)$ de tempo esperado por operação. \end{thm} -\section{Discussion and Exercises} - -The implicit representation of a complete binary tree as an array, -or list, seems to have been first proposed by Eytzinger \cite{e1590}. -He used this representation in books containing pedigree family trees -\index{pedigree family tree}% -of noble families. The #BinaryHeap# data structure described here was -first introduced by Williams \cite{w64}. - -The randomized #MeldableHeap# data structure described here appears -to have first been proposed by Gambin and Malinowski \cite{gm98}. -Other meldable heap implementations exist, including -leftist heaps \cite[Section~5.3.2]{c72,k97v3}, -\index{leftist heap}% -\index{heap!leftist}% -binomial heaps \cite{v78}, -\index{binomial heap}% +\section{Discussão e Exercícios} + +A representação implícita de uma árvore binária completa como um array, ou lista, +parece ter sido proposta pela primeira vez por Eytzinger \cite{e1590}. +Ele usou essa representação contendo árvores genealógicas +\index{árvores genealógicas}% +de famílias nobres. A estrutura de dados #BinaryHeap# descrita aqui foi descoberta por Williams \cite{w64}. + +A estrutura de dados randomizada +#MeldableHeap# descrita aqui parece ter sido originalmente proposta por +Gambin e Malinowski \cite{gm98}. +Outras implementações de heaps combináveis existem, incluindo heaps esquerdistas (em inglês, \emph{leftist heaps}) +\cite[Section~5.3.2]{c72,k97v3}, +\index{heap esquerdista}% +\index{heap!esquerdista}% +heaps binomiais\cite{v78}, +\index{heap binomial}% \index{heap!binomial}% -Fibonacci heaps \cite{ft87}, -\index{Fibonacci heap}% +heaps de Fibonacci \cite{ft87}, +\index{heap de Fibonacci}% \index{heap!Fibonacci}% -pairing heaps \cite{fsst86},\ +heaps de pareamento (em inglês, \emph{pairing heaps}) \cite{fsst86},\ \index{pairing heap}% \index{heap!pairing}% - and skew heaps \cite{st83}, +e heap inclinadas (em inglês, \emph{skew heaps}) \cite{st83}, \index{skew heap}% -\index{heap!skew}% -although none of these are as simple as the #MeldableHeap# -structure. +\index{heap inclinada}% +\index{heap!inclinada}% +embora nenhuma dessas seja tão simples quanto a estrutura #MeldableHeap#. -Some of the above structures also support a #decreaseKey(u,y)# operation +Algumas dessas estruturas também aceitam uma operação chamada de +#decreaseKey(u,y)#, na qual o valor no nodo #u# é reduziado a #y#. \index{decreaseKey@#decreaseKey(u,y)#}% -in which the value stored at node #u# is decreased to #y#. (It is a -pre-condition that $#y#\le#u.x#$.) In most of the preceding structures, -this operation can be supported in $O(\log #n#)$ time by removing node -#u# and adding #y#. However, some of these structures can implement -#decreaseKey(u,y)# more efficiently. In particular, #decreaseKey(u,y)# -takes $O(1)$ amortized time in Fibonacci heaps and $O(\log\log #n#)$ -amortized time in a special version of pairing heaps \cite{e09}. -This more efficient #decreaseKey(u,y)# operation has applications in -speeding up several graph algorithms, including Dijkstra's shortest path -algorithm \cite{ft87}. +(É uma pré-condição que $#y#\le#u.x#$.) Na maior parte desses estruturas, essa operação pode ser rodar em + $O(\log #n#)$ de tempo ao remover o nodo +#u# e inserir #y#. Porém, algumas dessas estruturas podem implementar +#decreaseKey(u,y)# mais eficientemente. Em especial, #decreaseKey(u,y)# +leva $O(1)$ de tempo amortizado em uma heap de Fibonacci e $O(\log\log #n#)$ +de tempo amortizado em uma versão especial de \emph{pairing heaps}\cite{e09}. +Essa operação +#decreaseKey(u,y)# mais eficiente tem aplicações em acelerar vários algoritmos de grafos, incluindo o importante algoritmo de encontrar menores caminhos de Dijkstra \cite{ft87}. \begin{exc} - Illustrate the addition of the values 7 and then 3 to the #BinaryHeap# - shown at the end of \figref{heap-insert}. + Ilustre a adição dos valores 7 e então 3 à + #BinaryHeap# mostrada no final da + \figref{heap-insert}. \end{exc} \begin{exc} - Illustrate the removal of the next two values (6 and 8) on the - #BinaryHeap# shown at the end of \figref{heap-remove}. + Simule a remoção dos próximos dois valores (6 e 8) na + #BinaryHeap# mostrada no final da \figref{heap-remove}. \end{exc} \begin{exc} - Implement the #remove(i)# method, that removes the value stored in - #a[i]# in a #BinaryHeap#. This method should run in $O(\log #n#)$ time. - Next, explain why this method is not likely to be useful. + Implemente o método #remove(i)# que remove o valor guardado em + #a[i]# em uma #BinaryHeap#. Esse método devem rodar em tempo $O(\log #n#)$. + A seguirm explique porque esse método provavelmente não é útil. \end{exc} \begin{exc}\exclabel{general-eytzinger} - \index{tree!$d$-ary}% - A $d$-ary tree is a generalization of a binary tree in which each - internal node has $d$ children. Using Eytzinger's method it is also - possible to represent complete $d$-ary trees using arrays. Work out - the equations that, given an index #i#, determine the index of #i#'s - parent and each of #i#'s $d$ children in this representation. + \index{árvore!$d$-ária}% + Uma + árvore $d$-ária é uma generalização de uma árvore binária em que cada nodo interno tem + $d$ filhos. Usando o método de Eytzinger também é possível representar árvores $d$-árias usando arrays. Trabalhe as equações de forma que, dado um índice #i#, determine o pai de #i# e os seus $d$ filhos nessa representação. \end{exc} \begin{exc} \index{DaryHeap@#DaryHeap#}% - Using what you learned in \excref{general-eytzinger}, design and - implement a \emph{#DaryHeap#}, the $d$-ary generalization of a - #BinaryHeap#. Analyze the running times of operations on a #DaryHeap# - and test the performance of your #DaryHeap# implementation against - that of the #BinaryHeap# implementation given here. + Usando o que você aprendeu em + \excref{general-eytzinger}, projete e implemente uma + \emph{#DaryHeap#}, a generalização $d$-ária de uma + #BinaryHeap#. Analise os tempos de execução das operações em uma #DaryHeap# + e teste o desempenho da sua implementação comparando-a com a implementação + da #BinaryHeap# dada neste capítulo. \end{exc} \begin{exc} - Illustrate the addition of the values 17 and then 82 in the - #MeldableHeap# #h1# shown in \figref{meldable-merge}. Use a coin to - simulate a random bit when needed. + Ilustre a adição dos valores 17 e então 82 na + #MeldableHeap# #h1# mostrada em \figref{meldable-merge}. Use uma moeda + para simular um bit aleatório quando necessário. \end{exc} \begin{exc} - Illustrate the removal of the next two values (4 and 8) in the - #MeldableHeap# #h1# shown in \figref{meldable-merge}. Use a coin to - simulate a random bit when needed. + Ilustre a remoção dos valores 4 e 8 na + #MeldableHeap# #h1# mostrada na \figref{meldable-merge}. Use uma moeda + para simular um bit aleatório quando necessário. \end{exc} \begin{exc} - Implement the #remove(u)# method, that removes the node #u# from - a #MeldableHeap#. This method should run in $O(\log #n#)$ expected time. + Implemente o método #remove(u)#, que remove o nodo #u# de uma + #MeldableHeap#. Esse método deve rodar em $O(\log #n#)$ de tempo esperado. \end{exc} \begin{exc} - Show how to find the second smallest value in a #BinaryHeap# or - #MeldableHeap# in constant time. + Mostre como achar o segundo menor valor em uma #BinaryHeap# ou #MeldableHeap# em tempo constante. \end{exc} \begin{exc} - Show how to find the $k$th smallest value in a #BinaryHeap# or - #MeldableHeap# in $O(k\log k)$ time. (Hint: Using another heap - might help.) + Mostre como achar o $k$-ésimo menor valor em uma + #BinaryHeap# ou + #MeldableHeap# em tempo $O(k\log k)$. (Dica: usar uma outra heap pode ajudar.) \end{exc} \begin{exc} - Suppose you are given #k# sorted lists, of total length #n#. Using - a heap, show how to merge these into a single sorted list in $O(n\log - k)$ time. (Hint: Starting with the case $k=2$ can be instructive.) + Suponha que você recebe #k# listas ordenadas, de um comprimento total de #n#. Usando uma heap, mostre como combinar essas listas em uma única lista ordenada em tempo $O(n\log k)$ . (Dica: iniciar com o caso $k=2$ pode ser instrutivo.) \end{exc} - - - - - - - - From b2364df7b266f03ef96e4f2114a05ed4c38b20bf Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 26 Aug 2020 18:41:51 -0300 Subject: [PATCH 31/66] started translation of graphs to portuguese --- latex/graphs.tex | 380 +++++++++++++++++++++++------------------------ 1 file changed, 190 insertions(+), 190 deletions(-) diff --git a/latex/graphs.tex b/latex/graphs.tex index 7a3e2609..55aad5d6 100644 --- a/latex/graphs.tex +++ b/latex/graphs.tex @@ -1,123 +1,120 @@ -\chapter{Graphs} +\chapter{Grafos} \chaplabel{graphs} %\textbf{Warning to the Reader:} This chapter is still being actively %developed, meaning that the code has not been thoroughly tested and/or %the text has not be carefully proofread. -In this chapter, we study two representations of graphs and basic -algorithms that use these representations. - -Mathematically, a \emph{(directed) graph} -\index{graph}% -\index{directed graph}% -is a pair $G=(V,E)$ where -$V$ is a set of \emph{vertices} -\index{vertex}% -and $E$ is a set of ordered pairs -of vertices called \emph{edges}. -\index{edge}% -An edge #(i,j)# is \emph{directed} -\index{directed edge}% -from #i# to #j#; #i# is called the \emph{source} -\index{source} of the edge and #j# -is called the \emph{target}. -\index{target} A \emph{path}% -\index{path} in $G$ is a sequence of -vertices $v_0,\ldots,v_k$ such that, for every $i\in\{1,\ldots,k\}$, -the edge $(v_{i-1},v_{i})$ is in $E$. A path $v_0,\ldots,v_k$ is a -\emph{cycle} -\index{cycle}% -if, additionally, the edge $(v_k,v_0)$ is in $E$. A path (or -cycle) is \emph{simple} -\index{simple path/cycle}% -if all of its vertices are unique. If there -is a path from some vertex $v_i$ to some vertex $v_j$ then we say that -$v_j$ is \emph{reachable} -\index{reachable vertex} from $v_i$. An example of a graph is shown -in \figref{graph}. +Neste capítulo, estudamos duas representações de grafos e algoritmos +básicos que usam essas representações. + +Matematicamente, um \emph{grafo (direcionado)} +\index{grafo}% +\index{grafo direcionado}% +é um par +$G=(V,E)$ onde +$V$ é um conjunto de \emph{vértices} +\index{vértice}% +e $E$ é um conjunto de pares ordenados de vértices chamados de +\emph{arestas}. +\index{aresta}% +Uma aresta #(i,j)# é \emph{direcionada} +\index{aresta direcionada}% +de #i# para #j#; #i# é chamado de \emph{origem} +\index{origem}, ou \emph{source} em inglês, da aresta e #j# +é chamado de destino, ou \emph{target} em inglês. +\index{target} Um caminho, \emph{path},% +\index{path} em $G$ é uma sequência de vértices +$v_0,\ldots,v_k$ tal que, para todo $i\in\{1,\ldots,k\}$, +a aresta $(v_{i-1},v_{i})$ está em $E$. Um caminho $v_0,\ldots,v_k$ é um +\emph{ciclo} +\index{ciclo}% +se, adicionalmente, a aresta + $(v_k,v_0)$ está em $E$. Um caminho (ou ciclo) é +\emph{simples} +\index{caminho/ciclo simples}% +se todos o seus vértices são únicos. Se existir um caminho de algum vértice +$v_i$ para algum vértice $v_j$ então dizemos que +$v_j$ é \emph{alcançável} +\index{vértice alcançável} de $v_i$. Um exemplo de um grafo é mostrado na +\figref{graph}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/graph} \end{center} - \caption{A graph with twelve vertices. Vertices are drawn as numbered - circles and edges are drawn as pointed curves pointing from source - to target.} + \caption{Um grafo com doze vértices. Vértices são desenhados como círculos numerados e arestas são desenhadas como curvas com setas apontando da origem ao destino.} \figlabel{graph} \end{figure} -Due to their ability to model so many phenomena, graphs have an enormous -number of applications. There are many obvious examples. Computer -networks can be modelled as graphs, with vertices corresponding to -computers and edges corresponding to (directed) communication links -between those computers. City streets can be modelled as graphs, -with vertices representing intersections and edges representing streets -joining consecutive intersections. - -Less obvious examples occur as soon as we realize that graphs can model -any pairwise relationships within a set. For example, in a university -setting we might have a timetable \emph{conflict graph} -\index{conflict graph}% -whose vertices represent -courses offered in the university and in which the edge #(i,j)# is present -if and only if there is at least one student that is taking both class -#i# and class #j#. Thus, an edge indicates that the exam for class #i# -should not be scheduled at the same time as the exam for class #j#. - -Throughout this section, we will use #n# to denote the number of vertices -of $G$ and #m# to denote the number of edges of $G$. That is, $#n#=|V|$ -and $#m#=|E|$. Furthermore, we will assume that $V=\{0,\ldots,#n#-1\}$. -Any other data that we would like to associate with the elements of $V$ -can be stored in an array of length $#n#$. - -Some typical operations performed on graphs are: +Devido à sua habilidade de modelar tantos fenômenos, grafos tem um número enorme de aplicações. Existem muitos exemplos óbvios. Redes de computadores podem +ser modeladas como grafos, com vértices correspondentes a computadores e arestas correspondentes a links (direcionados) de comunicação entre computadores. +Ruas de cidades podem ser modeladas como grafos, com vértices representando cruzamentos e arestas representando a ligação entre dois cruzamentos consecutivos. + +Exemplos menos óbvios ocorrem assim que percebemos que grafos podem +modelar qualquer relações que ocorrem em pares de elementos de um conjunto. +Por exemplo, em uma universidade podemos ter um \emph{grafo de conflitos} de horários +\index{grafo de conflitos} cujos vértices representam cursos oferecidos em uma +universidade no qual a aresta #(i,j)# está presente se e somente se existe pelo +menos um estudante que está frequentando a disciplina #i# e a disciplina #j#. +Então, uma aresta indica que a prova para a disciplina #i# não pode ser +agendada no mesmo horário que o exame para a disciplina #j#. + +Ao longo dessa seção, iremos usar #n# para denotar o número de vértices +de $G$ e #m# para denotar o número de arestas de $G$. Isto é, +$#n#=|V|$ e +$#m#=|E|$. Além disso, assumiremos que $V=\{0,\ldots,#n#-1\}$. +Outros dados que gostariamos de associar com os elementos de $V$ +podem ser guardados em um array de tamanho $#n#$. + +Algumas operações típicas realizadas em grafos são: \begin{itemize} - \item #addEdge(i,j)#: Add the edge $(#i#,#j#)$ to $E$. - \item #removeEdge(i,j)#: Remove the edge $(#i#,#j#)$ from $E$. - \item #hasEdge(i,j)#: Check if the edge $(#i#,#j#)\in E$ - \item #outEdges(i)#: Return a #List# of all integers $#j#$ such that + \item #addEdge(i,j)#: Adicionar a aresta $(#i#,#j#)$ a $E$. + \item #removeEdge(i,j)#: Remover a aresta $(#i#,#j#)$ de $E$. + \item #hasEdge(i,j)#: Verificar se a aresta $(#i#,#j#)\in E$ + \item #outEdges(i)#: Retornar uma #List# de todos os inteiros $#j#$ tais que $(#i#,#j#)\in E$ - \item #inEdges(i)#: Return a #List# of all integers $#j#$ such that + \item #inEdges(i)#: Retornar uma #List# de todos os inteiros $#j#$ tais que $(#j#,#i#)\in E$ \end{itemize} -Note that these operations are not terribly difficult to implement -efficiently. For example, the first three operations can be implemented -directly by using a #USet#, so they can be implemented in constant -expected time using the hash tables discussed in \chapref{hashing}. -The last two operations can be implemented in constant time by storing, -for each vertex, a list of its adjacent vertices. +Note que essas oepraçoes não são terrivelmente difíceis de implementar eficientemente. +Por exemplo, as três primeiras operações podem ser implementadas diretamente +usando um #USet#, de forma que podem ser implementadas em tempo constante esperado usando tabelas hash discutidas no \chapref{hashing}. +As duas últimas operações podem ser implementadas em tempo constante guardando, para cada vértice, uma lista de seu vértices adjacentes. -However, different applications of graphs have different performance -requirements for these operations and, ideally, we can use the simplest -implementation that satisfies all the application's requirements. -For this reason, we discuss two broad categories of graph representations. +Entretanto, diferentes aplicações de grafos tem diferentes exigências de desempenho +para essas operações e, idealmente, podemos usar a implementação +mais simples que satisfaz todos os requisitos da aplicação. +Devido essa razão, nós discutimos duas grandes categorias de representações +de grafos. -\section{#AdjacencyMatrix#: Representing a Graph by a Matrix} +\section{#AdjacencyMatrix#: Representando um Grafo com um Matriz} \seclabel{adjacency-matrix} -\index{adjacency matrix}% -An \emph{adjacency matrix} is a way of representing an #n# vertex graph -$G=(V,E)$ by an $#n#\times#n#$ matrix, #a#, whose entries are boolean -values. +\index{matriz de adjacências}% +Uma \emph{matriz de adjacências} é uma forma de representar um grafo com #n# vértices +$G=(V,E)$ com uma matriz $#n#\times#n#$, #a#, cujas entradas são valores booleanos. \codeimport{ods/AdjacencyMatrix.a.n.AdjacencyMatrix(n0)} -The matrix entry #a[i][j]# is defined as +A valor da matriz #a[i][j]# é definido como \[ #a[i][j]#= \begin{cases} - #true# & \text{if $#(i,j)#\in E$} \\ - #false# & \text{otherwise} + #true# & \text{se $#(i,j)#\in E$} \\ + #false# & \text{caso contrário} \end{cases} \] -The adjacency matrix for the graph in \figref{graph} is -shown in \figref{graph-adj}. -In this representation, the operations #addEdge(i,j)#, -#removeEdge(i,j)#, and #hasEdge(i,j)# just -involve setting or reading the matrix entry #a[i][j]#: +A matriz de adjacências para o grafo na \figref{graph} é mostrado na +\figref{graph-adj}. + +Nessa representação, as operações +#addEdge(i,j)#, +#removeEdge(i,j)#, e #hasEdge(i,j)# somente atribuir e ler a entrada +da matriz +#a[i][j]#: \codeimport{ods/AdjacencyMatrix.addEdge(i,j).removeEdge(i,j).hasEdge(i,j)} -These operations clearly take constant time per operation. +Essas operações claramente levam tempo constante por operação. \begin{figure} \begin{center} @@ -138,78 +135,86 @@ \section{#AdjacencyMatrix#: Representing a Graph by a Matrix} 11&0&0&0&0&0&0&0&1&0&0&1 &0\\ \end{tabular} \end{center} - \caption{A graph and its adjacency matrix.} + \caption{Um grafo e seu matriz de adjacências.} \figlabel{graph-adj} \end{figure} -Where the adjacency matrix performs poorly is with the #outEdges(i)# and -#inEdges(i)# operations. To implement these, we must scan all #n# -entries in the corresponding row or column of #a# and gather up all the -indices, #j#, where #a[i][j]#, respectively #a[j][i]#, is true. +A matriz de adjacências tem desempenho ruim com as operações +#outEdges(i)# e +#inEdges(i)#. Para implementá-las, precisamos varrer todas as #n# entradas na correspondente linha ou coluna de #a# e reunir todos os índices #j# +onde #a[i][j]#, respectivamente #a[j][i]#, seja true. \javaimport{ods/AdjacencyMatrix.outEdges(i).inEdges(i)} \cppimport{ods/AdjacencyMatrix.outEdges(i,edges).inEdges(i,edges)} -These operations clearly take $O(#n#)$ time per operation. +Essas operações claramente levam +$O(#n#)$ de tempo por operação. -Another drawback of the adjacency matrix representation is that it -is large. It stores an $#n#\times #n#$ boolean matrix, so it requires at -least $#n#^2$ bits of memory. The implementation here uses a matrix -of \javaonly{#boolean#}\cpponly{#bool#} values so it actually uses on the -order of $#n#^2$ bytes of memory. A more careful implementation, which -packs #w# boolean values into each word of memory, could reduce this -space usage to $O(#n#^2/#w#)$ words of memory. +Outra desvantagem da matriz de adjacências é o seu tamanho. Ela guarda +uma matriz booleana de tamanho $#n#\times #n#$, o que exige o uso de pelo +menos $#n#^2$ bits de memória. A implementação aqui usa uma matriz de +valores \javaonly{#boolean#}\cpponly{#bool#} tal que ela na verdade usa na ordem de +of $#n#^2$ bytes de memória. Uma implementação mais cuidadosa, que empacota #w# valores booleanos em cada palavra de memória, poderia reduzir esse uso de espaço a +$O(#n#^2/#w#)$ palavras de memória. \begin{thm} -The #AdjacencyMatrix# data structure implements the #Graph# interface. -An #AdjacencyMatrix# supports the operations + A estrutura de dados #AdjacencyMatrix# implementa a interface #Graph#. +Uma #AdjacencyMatrix# aceita as operações \begin{itemize} - \item #addEdge(i,j)#, #removeEdge(i,j)#, and #hasEdge(i,j)# in constant - time per operation; and - \item #inEdges(i)#, and #outEdges(i)# in $O(#n#)$ time per operation. + \item #addEdge(i,j)#, #removeEdge(i,j)# e #hasEdge(i,j)# em tempo + constante por operação; e + \item #inEdges(i)# e #outEdges(i)# em $O(#n#)$ tempo por operações. \end{itemize} -The space used by an #AdjacencyMatrix# is $O(#n#^2)$. +O espaço usado por uma +#AdjacencyMatrix# é $O(#n#^2)$. \end{thm} -Despite its high memory requirements and poor performance of the #inEdges(i)# -and #outEdges(i)# operations, an #AdjacencyMatrix# can still be useful for -some applications. In particular, when the graph $G$ is \emph{dense}, -i.e., it has close to $#n#^2$ edges, then a memory usage of $#n#^2$ -may be acceptable. - -The #AdjacencyMatrix# data structure is also commonly used because -algebraic operations on the matrix #a# can be used to efficiently compute -properties of the graph $G$. This is a topic for a course on algorithms, -but we point out one such property here: If we treat the entries of #a# -as integers (1 for #true# and 0 for #false#) and multiply #a# by itself -using matrix multiplication then we get the matrix $#a#^2$. Recall, -from the definition of matrix multiplication, that +Apesar de alto uso de memória e desempenho ruim das operações + #inEdges(i)# +e #outEdges(i)#, uma #AdjacencyMatrix# pode ainda ser útil para +algumas aplicações. Em especial, quando o grafo $G$ é \emph{denso}, +ele tem quase + $#n#^2$ arestas, então um uso de memória de $#n#^2$ pode + ser aceitável. + +A estrutura de dados #AdjacencyMatrix# também é frequentemente usada +porque operações algébricas na matriz #a# podem ser usadas para computar +eficientemente as propriedades do grafo $G$. +Isso é um tópico para uma disciplina sobre algoritmos, mas uma dessas +propriedades é: se tratarmos as entradas de #a# como inteiros (1 para #true# e 0 para +#false#) e multiplicarmos #a# por si mesma usando multiplicação de matrizes +então obtemos a matriz +$#a#^2$. Lembre-se, da definição de multiplicação de matrizes, que \[ #a^2[i][j]# = \sum_{k=0}^{#n#-1} #a[i][k]#\cdot #a[k][j]# \enspace . \] -Interpreting this sum in terms of the graph $G$, this formula counts the -number of vertices, $#k#$, such that $G$ contains both edges #(i,k)# -and #(k,j)#. That is, it counts the number of paths from $#i#$ to $#j#$ -(through intermediate vertices, $#k#$) whose length is exactly two. -This observation is the foundation of an algorithm that computes the -shortest paths between all pairs of vertices in $G$ using only $O(\log -#n#)$ matrix multiplications. - -\section{#AdjacencyLists#: A Graph as a Collection of Lists} +Interpretando essa soma em termos do grafo $G$, essa fórmula conta o +número de vértices, +$#k#$, tal que $G$ contém as arestas #(i,k)# +e #(k,j)#. Isto é, isso conta o número de caminhos de $#i#$ a $#j#$ +(por meio de vértices intermediários, $#k#$) cujo comprimento é exatamente dois. +Essa observação é a fundação de um algoritmo que computa os caminhos +mais curtos entre todos os pares de vértices em +$G$ usando somente $O(\log +#n#)$ multiplicações de matrizes. + +\section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \seclabel{adjacency-list} -\index{adjacency list}% -\emph{Adjacency list} representations of graphs take a more vertex-centric -approach. There are many possible implementations of adjacency lists. -In this section, we present a simple one. At the end of the section, -we discuss different possibilities. In an adjacency list representation, -the graph $G=(V,E)$ is represented as an array, #adj#, of lists. The list -#adj[i]# contains a list of all the vertices adjacent to vertex #i#. -That is, it contains every index #j# such that $#(i,j)#\in E$. +\index{listas de adjacências}% +Representações de grafos com \emph{listas de adjacências} é uma abordagem centrada nos vértices. +Existem muitas implementações possíveis de listas de adjacências. +Nesta seção, apresentamos uma simples. No final da seção, discutimos +diferentes possibilidades. Em uma representação de listas de adjacências, +o grafo +$G=(V,E)$ é representado como um array #adj# de listas. A lista +#adj[i]# contém uma lista de vértices adjacentes ao vértice #i#. +Isto é, ela contém todo índice #j# tal que $#(i,j)#\in E$. \codeimport{ods/AdjacencyLists.adj.n.AdjacencyLists(n0)} -(An example is shown in \figref{graph-adjlist}.) In this particular -implementation, we represent each list in #adj# as \javaonly{an}\cpponly{a -subclass of} #ArrayStack#, because we would like constant time access -by position. Other options are also possible. Specifically, we could -have implemented #adj# as a #DLList#. +(Uma exemplo é mostrado na \figref{graph-adjlist}.) +Nessa implementação em especial, nós representamos cada lista em #adj# como +\javaonly{uma}\cpponly{uma +subclasse de} #ArrayStack#, porque gostariamos acesso pela posição em tempo constante. +Especificamente, poderíamos implementar +#adj# como uma #DLList#. \begin{figure} @@ -224,76 +229,71 @@ \section{#AdjacencyLists#: A Graph as a Collection of Lists} & & & & &4& & & & & & \\ \end{tabular} \end{center} - \caption{A graph and its adjacency lists} + \caption{Uma grafo e suas listas de adjacência.} \figlabel{graph-adjlist} \end{figure} - - -The #addEdge(i,j)# operation just appends the value #j# to the list #adj[i]#: +A operação +#addEdge(i,j)# simplesmente acrescenta o valor #j# no final da lista #adj[i]#: \codeimport{ods/AdjacencyLists.addEdge(i,j)} -This takes constant time. +Isso leva tempo constante. -The #removeEdge(i,j)# operation searches through the list #adj[i]# -until it finds #j# and then removes it: +A operação #removeEdge(i,j)# busca ao longo da lista #adj[i]# +até que ache #j# e então o remove: \codeimport{ods/AdjacencyLists.removeEdge(i,j)} -This takes $O(\deg(#i#))$ time, where $\deg(#i#)$ (the \emph{degree} -\index{degree}% -of -$#i#$) counts the number of edges in $E$ that have $#i#$ as their source. - -The #hasEdge(i,j)# operation is similar; it searches through the list -#adj[i]# until it finds #j# (and returns true), or reaches the end of -the list (and returns false): +Isso leva $O(\deg(#i#))$ de tempo, onde $\deg(#i#)$ (o \emph{grau} +\index{grau}% +de +$#i#$) conta o número de arestas em $E$ que tem $#i#$ como origem. + +A operação +#hasEdge(i,j)# é similar: ela busca ao longo da lista +#adj[i]# até achar #j# (e retorna true), ou chega no final da lista (e retorna false): \codeimport{ods/AdjacencyLists.hasEdge(i,j)} -This also takes $O(\deg(#i#))$ time. +Isso também leva $O(\deg(#i#))$ de tempo. -The #outEdges(i)# operation is very simple; -\pcodeonly{it returns the list #adj[i]#} -\javaonly{it returns the list #adj[i]#} -\cpponly{it copies the values in #adj[i]# into the output list}: +A operação #outEdges(i)# é bem simples: +\pcodeonly{retorna a lista #adj[i]#} +\javaonly{retorna a lista #adj[i]#} +\cpponly{copia os valores em #adj[i]# em uma lista de saída}: \pcodeimport{ods/AdjacencyLists.outEdges(i)} \javaimport{ods/AdjacencyLists.outEdges(i)} \cppimport{ods/AdjacencyLists.outEdges(i,edges)} -\javaonly{This clearly takes constant time.}\cpponly{This clearly takes $O(\deg(#i#))$ time.} +\javaonly{Isso claramente leva tempo constante.}\cpponly{Isso leva $O(\deg(#i#))$ de tempo.} -The #inEdges(i)# operation is much more work. It scans over every -vertex $j$ checking if the edge #(i,j)# exists and, if so, adding #j# -to the output list: +A operação +#inEdges(i)# é muito mais trabalhosa. Ela varre todo vértice $j$ verificando se a aresta #(i,j)# existe e, caso positivo, adicionad #j# a uma lista de saída: \pcodeimport{ods/AdjacencyLists.inEdges(i)} \javaimport{ods/AdjacencyLists.inEdges(i)} \cppimport{ods/AdjacencyLists.inEdges(i,edges)} -This operation is very slow. It scans the adjacency list of every vertex, -so it takes $O(#n# + #m#)$ time. +Essa operação é muito lenta. Ela verifica toda lista de adjacência de todos os vértices, então leva +$O(#n# + #m#)$ de tempo. -The following theorem summarizes the performance of the above data structure: +O teorema a seguir resume o desempenho das estruturas de dados anteriormente descrita: \begin{thm} -The #AdjacencyLists# data structure implements the #Graph# interface. -An #AdjacencyLists# supports the operations +A estrutura de dados #AdjacencyLists# implementa a interface #Graph#. +Uma #AdjacencyLists# aceita as operações \begin{itemize} - \item #addEdge(i,j)# in constant time per operation; - \item #removeEdge(i,j)# and #hasEdge(i,j)# in $O(\deg(#i#))$ time - per operation; - \javaonly{\item #outEdges(i)# in constant time per operation; and} - \cpponly{\item #outEdges(i)# in $O(\deg(#i#))$ time per operation; and} - \item #inEdges(i)# in $O(#n#+#m#)$ time per operation. + \item #addEdge(i,j)# em tempo constante por operação; + \item #removeEdge(i,j)# e #hasEdge(i,j)# em tempo $O(\deg(#i#))$ por operação; + \javaonly{\item #outEdges(i)# em tempo constante por operação; e} + \cpponly{\item #outEdges(i)# em $O(\deg(#i#))$ de tempo por operação; e} + \item #inEdges(i)# em $O(#n#+#m#)$ de tempo por operação. \end{itemize} -The space used by a #AdjacencyLists# is $O(#n#+#m#)$. +O espaço usado por uma #AdjacencyLists# é $O(#n#+#m#)$. \end{thm} -As alluded to earlier, there are many different choices to be made when -implementing a graph as an adjacency list. Some questions that come -up include: +Conforme mencionado anteriormente, existem muitas diferentes escolhas que +podem ser feitas ao implementar um grafo como listas de adjacências. Algumas +escolhas incluem: \begin{itemize} - \item What type of collection should be used to store each element - of #adj#? One could use an array-based list, a linked-list, or even - a hashtable. - \item Should there be a second adjacency list, #inadj#, that stores, - for each #i#, the list of vertices, #j#, such that $#(j,i)#\in E$? - This can greatly reduce the running-time of the #inEdges(i)# - operation, but requires slightly more work when adding or removing - edges. + \item Que tipo de coleção deve ser usada para guardar cada elemento de + #adj#? Podem ser usadas uma listas baseadas em arrays, listas ligadas ou mesmo tabelas hash. + \item Deve haver uma segunda lista de adjacência, #inadj#, que guarda para cada #i#, a lista de vértices, #j#, tal que $#(j,i)#\in E$? + Isso pode reduzir enormemente o tempo de execução da operação + #inEdges(i)#, mas requer ligeiramente mais trabalho ao adicionar ou remover arestas. + %%%%%%%%%%%%%5 \item Should the entry for the edge #(i,j)# in #adj[i]# be linked by a reference to the corresponding entry in #inadj[j]#? \item Should edges be first-class objects with their own associated data? From 110ca30b23a58b782ea6ec805172a10e5c5b2f57 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Afra Brito Date: Thu, 27 Aug 2020 19:09:38 +0200 Subject: [PATCH 32/66] improve writing --- latex/why.tex | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/latex/why.tex b/latex/why.tex index aa9fdfe1..5c44b6b4 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -1,9 +1,9 @@ \chapter*{Por que este livro?} \addcontentsline{toc}{chapter}{Por que este livro?} -Existem muitos livros introdutórios a estruturas de dados. -Alguns deles são muito bons. A maior parte deles são pagos, e a -grande maioridade de estudantes de Ciência da Computação e Sistemas de +Existem muitos livros introdutórios à disciplina de estruturas de dados. +Alguns deles são muito bons. A maior parte deles são pagos, e a +grande maioridade de estudantes de Ciência da Computação e Sistemas de Informação irá gastar algum dinheiro em um livro de estruturas de dados. Vários livros gratuitos sobre estruturas de dados estão disponíveis online. @@ -12,12 +12,12 @@ \chapter*{Por que este livro?} parar de atualizá-los. Atualizar esses livros frequentemente não é possível, por duas razões: (1)~Os direitos autorais pertencem ao autor e/ou editor, os quais podem não autorizar tais atualizações. (2)~O \emph{código-fonte} desses -livros muitas vezes não está disponível. Isto é os arquivos Word, WordPerfect, -FrameMaker ou \LaTeX\ para o livro não está acessível e, ainda, a versão do +livros muitas vezes não está disponível. Isto é, os arquivos Word, WordPerfect, +FrameMaker ou \LaTeX\ para o livro não são acessíveis e, ainda, a versão do software que processa tais arquivos pode não estar mais disponível. A meta deste projeto é libertar estudantes de graduação de Ciência da Computação -de ter que pagar por um livro introdutório a estruturas de dados. +de ter que pagar por um livro introdutório à disciplina de estruturas de dados. Eu \footnote{The translator to Portuguese language is deeply grateful to the original author of this book Pat Morin for his decision which allows the availability of a good quality and free book in the @@ -26,11 +26,11 @@ \chapter*{Por que este livro?} \index{Software Aberta}% . -O arquivos-fonte originais em \LaTeX, \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} +Os arquivos-fonte originais em \LaTeX, \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} e também, de modo mais importante, em um site confiável de gerenciamento de códigos-fonte .\footnote{\url{https://github.com/patmorin/ods}}\footnote{Tradução em português em \url{https://github.com/albertiniufu/ods}} -O código-fonte disponível é publicado sob uma licença +O código-fonte disponível é publicado sob uma licença Creative Commons Attribution, o que quer dizer que qualquer um é livre para \emph{compartilhar}: \index{compartilhar} @@ -43,11 +43,9 @@ \chapter*{Por que este livro?} você deve reconhecer que a obra derivada contém código e/ou texto de \url{opendatastructures.org}. Qualquer pessoa pode contribuir com correções usando o sistema de gerenciamento -de código-fonte \texttt{git} +de códigos-fonte \texttt{git} \index{git@\texttt{git}} -. Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fontes -livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). -Minha esperança é que, ao assim fazer, este livro irá continuar a -ser um livro didático bem depois que o meu interesse no projeto, ou meu batimento cardíaco, (seja qual for que aconteça primeiro) desapareça. - +. Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fonte deste livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). +Minha esperança é que, ao assim fazer, este livro continuará a +ser didático mesmo depois que o meu interesse no projeto, ou meu batimento cardíaco, desapareça (o que quer que aconteça primeiro). From 53d6a81897e5392d4da8b983ecb3cc4f9dec7083 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 27 Aug 2020 19:51:42 -0300 Subject: [PATCH 33/66] translation of graphs --- latex/graphs.tex | 419 ++++++++++++++++++++++------------------------- 1 file changed, 195 insertions(+), 224 deletions(-) diff --git a/latex/graphs.tex b/latex/graphs.tex index 55aad5d6..866e91b5 100644 --- a/latex/graphs.tex +++ b/latex/graphs.tex @@ -293,319 +293,290 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \item Deve haver uma segunda lista de adjacência, #inadj#, que guarda para cada #i#, a lista de vértices, #j#, tal que $#(j,i)#\in E$? Isso pode reduzir enormemente o tempo de execução da operação #inEdges(i)#, mas requer ligeiramente mais trabalho ao adicionar ou remover arestas. - %%%%%%%%%%%%%5 - \item Should the entry for the edge #(i,j)# in #adj[i]# be linked by - a reference to the corresponding entry in #inadj[j]#? - \item Should edges be first-class objects with their own associated data? - In this way, #adj# would contain lists of edges rather than lists of vertices (integers). +\item A entrada para a aresta #(i,j)# na #adj[i]# deve ser ligada por uma referência à entrada correspondente entrada em #inadj[j]#? +\item Arestas devem ser objetos de primeira classe com seus próprios dados associados? Dessa maneira, #adj# conteria listas de arestas em vez de listas de vértices (inteiros). \end{itemize} -Most of these questions come down to a tradeoff between complexity (and -space) of implementation and performance features of the implementation. +A maior parte dessas questões resultam em um balanço entre complexidade (e espaço) de implementação e características de desempenho da implementação. -\section{Graph Traversal} +\section{Travessia em Grafos} -In this section we present two algorithms for exploring a graph, -starting at one of its vertices, #i#, and finding all vertices that -are reachable from #i#. Both of these algorithms are best suited to -graphs represented using an adjacency list representation. Therefore, -when analyzing these algorithms we will assume that the underlying -representation is an #AdjacencyLists#. +Nesta seção, apresentamos dois algoritmos para explorar um grafo, iniciando em um de seus vértices #i# e encontrando todos os vértices que são alcançáveis a partir de #i#. Esses dois algoritmos são mais adequados para grafos representação usando listas de adjacências. Portanto, ao analisar esses algoritmos iremos supor que a representação é uma +#AdjacencyLists#. -\subsection{Breadth-First Search} +\subsection{Busca em Largura} \index{breadth-first-search}% -The \emph{breadth-first-search} algorithm starts at a vertex #i# and visits, -first the neighbours of #i#, then the neighbours of the neighbours of #i#, -then the neighbours of the neighbours of the neighbours of #i#, and so on. - -This algorithm is a generalization of the breadth-first traversal -algorithm for binary trees (\secref{bintree:traversal}), and is -very similar; it uses a queue, #q#, that initially contains only #i#. -It then repeatedly extracts an element from #q# and adds its neighbours -to #q#, provided that these neighbours have never been in #q# before. -The only major difference between the breadth-first-search algorithm -for graphs and the one for trees is that the algorithm for graphs has -to ensure that it does not add the same vertex to #q# more than once. -It does this by using an auxiliary boolean array, #seen#, that tracks -which vertices have already been discovered. +\index{busca em largura}% +O algoritmo de \emph{busca em largura} inicia-se em um vértice #i# e visita primeiramente os vizinhos de #i# e, então, os vizinhos dos vizinhos de #i# e depois os vizinhos dos vizinhos dos vizinhos de #i# e assim por diante. + +Esse algoritmo é uma generalização do algoritmo de travessia em largura para árvores binárias (\secref{bintree:traversal}), e é muito similar a ele; +utiliza-se uma queue, #q#, que inicialmente contém somente #i#. +Então repetidamente extrai um elemento de #q# e adiciona seus vizinhos a #q#, dado que esses vizinhos nunca estiveram em #q# antes. +A maior diferença entre o algoritmo de busca em largura para grafos e o de árvores é que o algoritmo para grafos tem que garantir que não vai adicionar o mesmo vértice a #q# mais de uma vez. + +Isso é feito usando um array booleano auxiliar, #seen# (vistos, em português), que guarda quais vértices foram descubertos até o momento. \codeimport{ods/Algorithms.bfs(g,r)} -An example of running #bfs(g,0)# on the graph from \figref{graph} -is shown in \figref{graph-bfs}. Different executions are possible, -depending on the ordering of the adjacency lists; \figref{graph-bfs} -uses the adjacency lists in \figref{graph-adjlist}. +Um exemplo de execução + #bfs(g,0)# no grafo da \figref{graph} +é mostrado na \figref{graph-bfs}. Execuções diferentes são possíveis, dependendo da ordem das listas de adjacências; a +\figref{graph-bfs} usa as listas de adjacências em \figref{graph-adjlist}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/graph-bfs} \end{center} - \caption[Breadth-first-search]{An example of breadth-first-search starting at node 0. Nodes are - labelled with the order in which they are added to #q#. Edges that - result in nodes being added to #q# are drawn in black, other edges - are drawn in grey.} + \caption[Busca em largura]{Um exemplo de busca em largura iniciando no nodo 0. Nodos são marcados com a ordem em que eles são adicionados a #q#. Arestas que resultam em nodos sendo adicionados a #q# são desenhados em preto, outras arestas são desenhadas em cinza.} \figlabel{graph-bfs} \end{figure} -Analyzing the running-time of the #bfs(g,i)# routine is fairly -straightforward. The use of the #seen# array ensures that no vertex is -added to #q# more than once. Adding (and later removing) each vertex -from #q# takes constant time per vertex for a total of $O(#n#)$ time. -Since each vertex is processed by the inner loop at most once, each -adjacency list is processed at most once, so each edge of $G$ is processed -at most once. This processing, which is done in the inner loop takes -constant time per iteration, for a total of $O(#m#)$ time. Therefore, -the entire algorithm runs in $O(#n#+#m#)$ time. - -The following theorem summarizes the performance of the #bfs(g,r)# algorithm. +Analisar o tempo de execução da rotina +#bfs(g,i)# razoavelmente fácil. +O uso do array +#seen# assegura que nenhum vértice é adicionado a #q# mais de uma vez. Adicionar (e depois remover) cada vértice de #q# leva tempo constante por vértice para um total de tempo +$O(#n#)$. +Como cada vértice é processado pelo laço interno no máximo uma vez, +cada lista de adjacência é processadas no máximo uma vez, então cada aresta de +$G$ é processada somente uma vez. +Esse processamento, que é feito pelo laço interno, leva tempo constante por iteração, totalizando em +$O(#m#)$ de tempo. Portanto, o algoritmo por inteiro roda em +$O(#n#+#m#)$ de tempo. + +O teorema a seguir resume o desempenho do algoritmo + #bfs(g,r)#. \begin{thm}\thmlabel{bfs-graph} - When given as input a #Graph#, #g#, that is implemented using the - #AdjacencyLists# data structure, the #bfs(g,r)# algorithm runs in $O(#n#+#m#)$ - time. + O algoritmo #bfs(g,r)# roda, para um #Graph# #g# implementado usando + a estrutura de dados #AdjacencyLists#, em tempo + $O(#n#+#m#)$. \end{thm} -A breadth-first traversal has some very special properties. Calling -#bfs(g,r)# will eventually enqueue (and eventually dequeue) every vertex -#j# such that there is a directed path from #r# to #j#. Moreover, -the vertices at distance 0 from #r# (#r# itself) will enter #q# before -the vertices at distance 1, which will enter #q# before the vertices at -distance 2, and so on. Thus, the #bfs(g,r)# method visits vertices -in increasing order of distance from #r# and vertices that cannot be -reached from #r# are never visited at all. - -A particularly useful application of the breadth-first-search algorithm -is, therefore, in computing shortest paths. To compute the shortest -path from #r# to every other vertex, we use a variant of #bfs(g,r)# -that uses an auxilliary array, #p#, of length #n#. When a new vertex -#j# is added to #q#, we set #p[j]=i#. In this way, #p[j]# becomes the -second last node on a shortest path from #r# to #j#. Repeating this, -by taking #p[p[j]#, #p[p[p[j]]]#, and so on we can reconstruct the -(reversal of) a shortest path from #r# to #j#. - - - -\subsection{Depth-First Search} - -The \emph{depth-first-search} -\index{depth-first-search}% -algorithm is similar to the standard -algorithm for traversing binary trees; it first fully explores one -subtree before returning to the current node and then exploring the -other subtree. Another way to think of depth-first-search is by saying -that it is similar to breadth-first search except that it uses a stack -instead of a queue. - -During the execution of the depth-first-search algorithm, each vertex, -#i#, is assigned a colour, #c[i]#: #white# if we have never seen -the vertex before, #grey# if we are currently visiting that vertex, -and #black# if we are done visiting that vertex. The easiest way to -think of depth-first-search is as a recursive algorithm. It starts by -visiting #r#. When visiting a vertex #i#, we first mark #i# as #grey#. -Next, we scan #i#'s adjacency list and recursively visit any white vertex -we find in this list. Finally, we are done processing #i#, so we colour -#i# black and return. +Uma travessia em largura tem algumas propriedades especiais. Chamar +#bfs(g,r)# irá eventualmente enfileirar (eventualmente desenfileirar) todo vértice +#j# tal que existe um caminho direto de #r# para #j#. +Além disso, os vértices na uma distância 0 a partir de #r# (o próprio #r#) irá entrar em #q# antes dos vértices com distância 1, que entrarão em #q# antes dos vértices com distância 2 e assim por diante. Portanto, + +o método #bfs(g,r)# visita vértices em ordem crescente de distância de #r# e vértices que não podem ser alcançados a partir de #r# nunca serão visitados de nenhuma forma. + +Uma aplicação especialmente útil do algoritmo de busca em largura é, portanto, +na computação dos menores caminhos. +Para computar o menor caminho de #r# a todo outro vértice, usamos uma variante de +#bfs(g,r)# que usa uma array auxiliar #p# de comprimento #n#. +Quando um novo vértice #j# é adicionado a #q#, fazemos #p[j] =i#. Dessa forma, #p[j]# se torna o penúltimo nodo no caminho mais curto de #r# a #j#. +Ao repetir isso fazendo +#p[p[j]#, #p[p[p[j]]]#, e assim por diante podemos reconstruir o caminho mais curto (invertido) de #r# a #j#. + +\subsection{Busca em Profundidade} + +O algoritmo de \emph{busca em profundidade} +\index{busca em profundidade}% +é similar ao algoritmo padrão para percorrer árvores binárias; +ele primeiro completamente explora uma subárvore antes de retornar ao nodo atual e então explora outra subárvore. Outra forma de pensar na busca em profundidade é dizer que é similar à busca em largura exceto que ele usa uma pilha em vez de uma fila. + +Durante a execução do algoritmo de busca em profundidade, cada vértice #i# +é atribuído a uma cor #c[i]#: #white# se nunca encontramos o vértice antes +#grey# se estamos visitando o vértice, e #black# se terminamos de visitar o vértice. +O jeito mais fácil de pensar da busca em profundidade é na forma de um algoritmo recursivo. Ele inicia com uma visita a #r#. Ao visitar um vértice #i#, primeiro +marcamos #i# como #cinza#. Depois, varremos a lista de adjência de #i# e recursivamente visitamos qualquer vértice branco que não achamos nessa lista. +Finalmente, terminamos o processamento de #i# e então pintamos #i# de preto e retornamos. + \codeimport{ods/Algorithms.dfs(g,r).dfs(g,i,c)} -An example of the execution of this algorithm is shown in \figref{graph-dfs}. +Um exemplo da execução desse algoritmo é mostrado na \figref{graph-dfs}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/graph-dfs} \end{center} - \caption[Depth-first-search]{An example of depth-first-search starting at node 0. Nodes are - labelled with the order in which they are processed. Edges that - result in a recursive call are drawn in black, other edges - are drawn in #grey#.} + \caption[Busca em profundidade]{Um exemplo da busca em profundidade iniciando no nodo 0. Nodos são marcados com a ordem em que são processados. Arestas que resultam em uma chamada recursiva são desenhados em preto, outras arestas são desenhadas em cinza.} \figlabel{graph-dfs} \end{figure} -Although depth-first-search may best be thought of as a recursive -algorithm, recursion is not the best way to implement it. Indeed, the code -given above will fail for many large graphs by causing a stack overflow. -An alternative implementation is to replace the recursion stack with an -explicit stack, #s#. The following implementation does just that: +Embora busca em profundidade possa ser melhor pensada como um algoritmo recursivo, +usar recursão não é a melhor forma de implementá-la. +Realmente, o código dado acima irá falhar para muitos grafos grandes causando +uma estouro da pilha de memória, um \emph{stack overflow}. +Uma implementação alternativa troca a pilha de recusão por uma pilha explícita #s#. +A implementação a seguir faz exatamente isso: \codeimport{ods/Algorithms.dfs2(g,r)} -In the preceding code, when the next vertex, #i#, is processed, #i# is coloured -#grey# and then replaced, on the stack, with its adjacent vertices. -During the next iteration, one of these vertices will be visited. - -Not surprisingly, the running times of #dfs(g,r)# and #dfs2(g,r)# are the -same as that of #bfs(g,r)#: +No código anterior, quando o vértice a seguir #i# é processado, #i# é pintado +de +#grey# e então substituído na pilha com seus vértices adjacentes. +Durante a próxima iteração, um desses vértices será visitado. + +Pouco supreendentemente, os tempos de execução de +#dfs(g,r)# e #dfs2(g,r)# são os mesmos que os de +#bfs(g,r)#: \begin{thm}\thmlabel{dfs-graph} - When given as input a #Graph#, #g#, that is implemented using the - #AdjacencyLists# data structure, the #dfs(g,r)# and #dfs2(g,r)# algorithms - each run in $O(#n#+#m#)$ time. +Ao procesar um #Graph# g que é implementado usando a estrutura de dados + #AdjacencyLists#, os algoritmos #dfs(g,r)# e #dfs2(g,r)# + rodam em tempo $O(#n#+#m#)$. \end{thm} -As with the breadth-first-search algorithm, there is an underlying -tree associated with each execution of depth-first-search. When a node -$#i#\neq #r#$ goes from #white# to #grey#, this is because #dfs(g,i,c)# -was called recursively while processing some node #i'#. (In the case -of #dfs2(g,r)# algorithm, #i# is one of the nodes that replaced #i'# -on the stack.) If we think of #i'# as the parent of #i#, then we obtain -a tree rooted at #r#. In \figref{graph-dfs}, this tree is a path from +Assim como com o algoritmo de busca em largura, existe uma árvore associada com cada + execução da busca em profundidade. Quando um nodo +$#i#\neq #r#$ vai de #white# a #grey#, é porque #dfs(g,i,c)# +foi chamado recursivamente ao processar algum nodo #i'#. (No caso do algoritmo +#dfs2(g,r)#, #i# é um dos nodos que substituem #i'# na pilha.) +Se pensarmos de #i'# como o pai de #i#, então obtemos uma árvore enraizada em #r#. +Na \figref{graph-dfs}, essa árvore é um caminho do vértice 0 ao vértice 11. vertex 0 to vertex 11. -An important property of the depth-first-search algorithm is the -following: Suppose that when node #i# is coloured #grey#, there exists a path -from #i# to some other node #j# that uses only white vertices. Then #j# -will be coloured first #grey# then #black# before #i# is coloured #black#. -(This can be proven by contradiction, by considering any path $P$ from #i# -to #j#.) - -One application of this property is the detection of cycles. -\index{cycle detection}% -Refer -to \figref{dfs-cycle}. Consider some cycle, $C$, that can be reached -from #r#. Let #i# be the first node of $C$ that is coloured #grey#, -and let #j# be the node that precedes #i# on the cycle $C$. Then, -by the above property, #j# will be coloured #grey# and the edge #(j,i)# -will be considered by the algorithm while #i# is still #grey#. Thus, -the algorithm can conclude that there is a path, $P$, from #i# to #j# -in the depth-first-search tree and the edge #(j,i)# exists. Therefore, -$P$ is also a cycle. +Uma propriedade importante do algoritmo de busca em profundidade é a seguinte: +suponha que quando um nodo #i# é pintado de #grey#, existe um caminho de #i# +a algum outro nodo #j# que usa somente vértices brancos. Então #j# +será pintado primeiro de #grey# e depois de #preto# antes que #i# seja pintado de #black#. +(Isso pode ser provado por contradição, ao considerar qualquer caminho $P$ de #i# +para #j#.) + +Uma aplicação dessa propriedade é a detecção de ciclos. +\index{detecção de ciclos}% +Veja a +\figref{dfs-cycle}. Considere algum ciclo $C$, que pode ser alcançado a partir de #r#. Seja #i# o primeiro nodo de $C$ que está em #grey# e seja +#j# o nodo que precede #i# no ciclo $C$. +Então, pela propriedade acima, #j# irá ser colorido #grey# e a aresta #(j,i)# +será considerada pelo algoritmo enquanto #i# ainda é #grey#. Portanto, +o algoritmo pode concluir que existe um caminho $P$ partindo de #i# a #j# +na árvore de busca em profundidade e que a aresta +#(j,i)# existe. Portanto, $P$ também é um ciclo. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/dfs-cycle} \end{center} - \caption[Cycle detection]{The depth-first-search algorithm can be used to detect cycles - in $G$. The node #j# is coloured #grey# while #i# is still #grey#. This - implies that there is a path, $P$, from #i# to #j# in the depth-first-search - tree, and the edge #(j,i)# implies that $P$ is also a cycle.} + \caption[Detecção de ciclos]{O algoritmo de busca em profundidade pode ser usado para detectar ciclos em $G$. O nodo #j# é pintado de #grey# enquanto #i# ainda for #grey#. Isso implica que existe um caminho $P$ de $i$ a $j$ na árvore de busca em profundidade e a aresta + #(j,i)# implica que $P$ também é um ciclo.} \figlabel{dfs-cycle} \end{figure} -\section{Discussion and Exercises} - -The running times of the depth-first-search and breadth-first-search -algorithms are somewhat overstated by the Theorems~\ref{thm:bfs-graph} and -\ref{thm:dfs-graph}. Define $#n#_{#r#}$ as the number of vertices, #i#, -of $G$, for which there exists a path from #r# to #i#. Define $#m#_#r#$ -as the number of edges that have these vertices as their sources. -Then the following theorem is a more precise statement of the running -times of the breadth-first-search and depth-first-search algorithms. -(This more refined statement of the running time is useful in some of -the applications of these algorithms outlined in the exercises.) +\section{Discussão e Exercícios} + +Os tempos de execução dos algoritmos da busca em profundidade e da busca em largura +são de certa forma exagerados pelos Teoremas~ +Teoremas~\ref{thm:bfs-graph} e +\ref{thm:dfs-graph}. Defina $#n#_{#r#}$ como sendo o número de vértices #i# de $G$ para os quais existem um caminho de #r# a #i#. +Defina $#m#_#r#$ como o número de arestas que tem esses vértices como suas origens. +Então o teorema a seguir é mais preciso na descrição dos tempos +de execução dos algoritmos de busca em largura e em profundidade. +(Essa teorema mais preciso do tempo de execução é útil em algumas das aplicações de algoritmos dos exercícios.) \begin{thm}\thmlabel{graph-traversal} - When given as input a #Graph#, #g#, that is implemented using the - #AdjacencyLists# data structure, the #bfs(g,r)#, #dfs(g,r)# and #dfs2(g,r)# - algorithms each run in $O(#n#_{#r#}+#m#_{#r#})$ time. +Ao processar um + #Graph#, #g#, que é implementado usando a estrutura de dados + #AdjacencyLists#, os algoritmos #bfs(g,r)#, #dfs(g,r)# e #dfs2(g,r)# + rodam em $O(#n#_{#r#}+#m#_{#r#})$ de tempo. \end{thm} -Breadth-first search seems to have been discovered independently by -Moore \cite{m59} and Lee \cite{l61} in the contexts of maze exploration -and circuit routing, respectively. - -Adjacency-list representations of graphs were presented by -Hopcroft and Tarjan \cite{ht73} as an alternative to the (then more -common) adjacency-matrix representation. This representation, as well as -depth-first-search, played a major part in the celebrated Hopcroft-Tarjan -planarity testing algorithm -\index{planarity testing}% -that can determine, in $O(#n#)$ time, if -a graph can be drawn, in the plane, and in such a way that no pair of -edges cross each other \cite{ht74}. - -In the following exercises, an undirected graph is one in which, for -every #i# and #j#, the edge $(#i#,#j#)$ is present if and only if the -edge $(#j#,#i#)$ is present. -\index{undirected graph}% -\index{graph!undirected}% +Busca em largura parece ter sido descuberta independentemente por +Moore \cite{m59} e Lee \cite{l61} nos contextos de exploração de labirintos e roteamente de circuitos, respectivamente. + +Representações de grafos baseadas em listas de adjacências foram propostas +por +Hopcroft e Tarjan \cite{ht73} como uma alternativa à (então mais comum) representação baseada em matriz de adjacências. +Essa representação, assim como busca em profundidade, desempenhou um papel importante no famoso algoritmo de teste de planaridade de Hopcroft-Tarjan +\index{teste de planaridade}% +que pode determinar, em tempo $O(#n#)$ se um grafo pode ser desenhado no plano de forma que nenhum par de arestas se cruzem \cite{ht74}. + +Nos exercícios a seguir, um grafo não direcionado é um em que, para todo +#i# e #j#, a aresta +$(#i#,#j#)$ existe se e somente se a aresta +$(#j#,#i#)$ existe. +\index{grafo não direcionado}% +\index{grafo!não direcionado}% \begin{exc} - Draw an adjacency list representation and an adjacency matrix - representation of the graph in \figref{graph-example2}. + Desenhe uma representação baseada em uma lista de adjacências e uma + baseada em matriz de adjacências do grafo na +\figref{graph-example2}. \end{exc} \begin{figure} \centering{\includegraphics[scale=0.90909]{figs/graph-example2}} - \caption{An example graph.} + \caption{Um exemplo de grafo.} \figlabel{graph-example2} \end{figure} \begin{exc} - \index{incidence matrix}% - The \emph{incidence matrix} representation of a graph, - $G$, is an $#n#\times#m#$ matrix, $A$, where + \index{matriz de incidência}% +A representação baseada em \emph{matriz de incidência} de um grafo, + $G$, é uma $#n#\times#m#$ matriz, $A$, onde \[ A_{i,j} = \begin{cases} - -1 & \text{if vertex $i$ the source of edge $j$} \\ - +1 & \text{if vertex $i$ the target of edge $j$} \\ - 0 & \text{otherwise.} + -1 & \text{se o vértice $i$ for a origem da aresta para $j$} \\ + +1 & \text{se o vértice $i$ for o destino da aresta iniciada em $j$} \\ + 0 & \text{caso contrário.} \end{cases} \] \begin{enumerate} - \item Draw the incident matrix representation of the graph in + \item Desenhe a representação baseada em matriz de incidência do grafo na \figref{graph-example2}. - \item Design, analyze and implement an incidence matrix representation - of a graph. Be sure to analyze the space, the cost of + \item Projete, analise e implemente uma representação baseada em matriz de incidência de um grafo. Analise o custo de espaço e o custo de tempo das operações #addEdge(i,j)#, #removeEdge(i,j)#, #hasEdge(i,j)#, #inEdges(i)#, - and #outEdges(i)#. + e #outEdges(i)#. \end{enumerate} \end{exc} \begin{exc} - Illustrate an execution of the #bfs(G,0)# and #dfs(G,0)# on the graph, $#G#$, + Desenhe uma execução do algoritmos + #bfs(G,0)# e #dfs(G,0)# no grafo $#G#$, in \figref{graph-example2}. \end{exc} \begin{exc} - \index{connected graph}% + \index{grafo conectado}% \index{graph!connected}% - Let $G$ be an undirected graph. We say $G$ is \emph{connected} if, - for every pair of vertices #i# and #j# in $G$, there is a path from - $#i#$ to $#j#$ (since $G$ is undirected, there is also a path from #j# - to #i#). Show how to test if $G$ is connected in $O(#n#+#m#)$ time. + Seja um grafo não direcionado $G$. Dizemos que $G$ é \emph{conectado} se, +para todo par de vértices #i# e #j# em $G$, existe um caminho de + $#i#$ a $#j#$ (como $G$ não é direcionado, também existe um caminho de #j# a #i#). + Mostre como testar se $G$ é conectado em tempo + $O(#n#+#m#)$. \end{exc} \begin{exc} - \index{connected components}% - Let $G$ be an undirected graph. A \emph{connected-component labelling} - of $G$ partitions the vertices of $G$ into maximal sets, each of which - forms a connected subgraph. Show how to compute a connected component - labelling of $G$ in $O(#n#+#m#)$ time. + \index{componentes conectados}% + Seja $G$ um grafo não direcionado. \emph{Marcações de componentes conectados} (em inglês, \emph{connected-component labelling}) de $G$ particiona os vértices de $G$ em conjuntos maximais, cada qual forma um subgrafo conectado. Mostre como computar as marcações de componentes conectos de $G$ em + $O(#n#+#m#)$ de tempo. \end{exc} \begin{exc} - \index{spanning forest}% - Let $G$ be an undirected graph. A \emph{spanning forest} of $G$ is a - collection of trees, one per component, whose edges are edges of $G$ - and whose vertices contain all vertices of $G$. Show how to compute - a spanning forest of of $G$ in $O(#n#+#m#)$ time. + \index{floresta geradora}% + Seja $G$ um grafo não direcionado. Uma \emph{floresta geradora} de $G$ é uma coleção de árvores, uma por componente, cujas arestas são arestas de $G$ + e cujos vértices contêm todos os vértices de $G$. Mostre como computar + uma floresta geradora de $G$ em + $O(#n#+#m#)$ de tempo. \end{exc} \begin{exc} - \index{strongly-connected graph}% - \index{graph!strongly-connected}% - We say that a graph $G$ is \emph{strongly-connected} if, for every - pair of vertices #i# and #j# in $G$, there is a path from $#i#$ to - $#j#$. Show how to test if $G$ is strongly-connected in $O(#n#+#m#)$ - time. + \index{grafo fortemente contectado}% + \index{grafo!fortemente conectado}% + Dizemos que um grafo $G$ é \emph{fortemente conectado} se, para todo + par de vértices #i# e #j# em $G$, existe um caminho de + $#i#$ para + $#j#$. Mostre como testar se $G$ é fortemente conectado em $O(#n#+#m#)$ de tempo. \end{exc} \begin{exc} - Given a graph $G=(V,E)$ and some special vertex $#r#\in V$, show how - to compute the length of the shortest path from $#r#$ to #i# for every - vertex $#i#\in V$. +Dado um grafo $G=(V,E)$ e algum vértice especial $#r#\in V$, mostre como + computar o comprimento do caminho mais curto a partir de + $#r#$ para #i# para todo vértice + $#i#\in V$. \end{exc} \begin{exc} - Give a (simple) example where the #dfs(g,r)# code visits the nodes of a - graph in an order that is different from that of the #dfs2(g,r)# code. - Write a version of #dfs2(g,r)# that always visits nodes in exactly - the same order as #dfs(g,r)#. (Hint: Just start tracing the execution - of each algorithm on some graph where #r# is the source of more than - 1 edge.) + Ache um exemplo (simples) em que o código #dfs(g,r)# visita os nodos de um grafo em uma ordem que é diferente daquela do código + do algoritmo #dfs2(g,r)#. + Escreva uma versão de + #dfs2(g,r)# que sempre visita nodos exatamente na mesma ordem + que #dfs(g,r)#. (Dica: simplesmente inicie verificando a execução de cada algoritmo em algum grafo onde #r# é a fonte de mais de 1 aresta.) \end{exc} \begin{exc} \index{universal sink}% - \index{celebrity|see{universal sink}}% - A \emph{universal sink} in a graph $G$ is a vertex that is the target - of $#n#-1$ edges and the source of no edges.\footnote{A universal sink, - #v#, is also sometimes called a \emph{celebrity}: Everyone in the room - recognizes #v#, but #v# doesn't recognize anyone else in the room.} - Design and implement an algorithm that tests if a graph $G$, represented - as an #AdjacencyMatrix#, has a universal sink. Your algorithm should - run in $O(#n#)$ time. + \index{celebridade|see{universal sink}}% + Uma \emph{universal sink} em um grafo $G$ é um vértice que é o alvo de + $#n#-1$ arestas e a origem de nenhuma aresta. \footnote{Uma universal sink, + #v#, é também às vezes chamada de \emph{celebridade}: Todos na sala reconhecem + #v#, mas #v# não reconhece ninguém na sala.} + Projete e implemente um algoritmo que esta se um grafo $G$, representado por + uma + #AdjacencyMatrix#, tem uma universal sink. O seu algoritmo deve rodar em + $O(#n#)$ de tempo. \end{exc} From e44dd41b6d481dac665183d026dfb57812cee489 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 31 Aug 2020 11:56:04 -0300 Subject: [PATCH 34/66] translated to portuguese --- latex/integers.tex | 761 +++++++++++++++++++++++---------------------- 1 file changed, 393 insertions(+), 368 deletions(-) diff --git a/latex/integers.tex b/latex/integers.tex index e7651b5f..0e4cb1e9 100644 --- a/latex/integers.tex +++ b/latex/integers.tex @@ -1,425 +1,450 @@ -\chapter{Data Structures for Integers} - -In this chapter, we return to the problem of implementing an #SSet#. -The difference now is that we assume the elements stored in the #SSet# are -#w#-bit integers. That is, we want to implement #add(x)#, #remove(x)#, -and #find(x)# where $#x#\in\{0,\ldots,2^{#w#}-1\}$. It is not too hard -to think of plenty of applications where the data---or at least the key -that we use for sorting the data---is an integer. - -We will discuss three data structures, each building on the ideas of -the previous. The first structure, the #BinaryTrie# performs all three -#SSet# operations in $O(#w#)$ time. This is not very impressive, since -any subset of $\{0,\ldots,2^{#w#}-1\}$ has size $#n#\le 2^{#w#}$, so that -$\log #n# \le #w#$. All the other #SSet# implementations discussed in -this book perform all operations in $O(\log #n#)$ time so they are all -at least as fast as a #BinaryTrie#. - -The second structure, the #XFastTrie#, speeds up the search in a -#BinaryTrie# by using hashing. With this speedup, the #find(x)# -operation runs in $O(\log #w#)$ time. However, #add(x)# and #remove(x)# -operations in an #XFastTrie# still take $O(#w#)$ time and the space used -by an #XFastTrie# is $O(#n#\cdot#w#)$. - -The third data structure, the #YFastTrie#, uses an #XFastTrie# to store -only a sample of roughly one out of every $#w#$ elements and stores the -remaining elements in a standard #SSet# structure. This trick reduces the -running time of #add(x)# and #remove(x)# to $O(\log #w#)$ and decreases -the space to $O(#n#)$. - -The implementations used as examples in this chapter can store any type of -data, as long as an integer can be associated with it. In the code samples, -the variable #ix# is always the integer value associated with #x#, and the method \javaonly{#in.#}#intValue(x)# converts #x# to its associated integer. In -the text, however, we will simply treat #x# as if it is an integer. - -\section{#BinaryTrie#: A digital search tree} +\chapter{Estruturas de Dados para Inteiros} + +Neste capítulo, retornaremos ao problema de implementar uma #SSet#. +A diferença é que agora iremos assumir que os elementos guardados no #SSet# +são inteiros de #w# bits. Isto é, queremos implementar +#add(x)#, #remove(x)#, +e #find(x)# onde $#x#\in\{0,\ldots,2^{#w#}-1\}$. Não é muito difícil +de pensar em muitas aplicações em que os dados --- ou pelo menos +a chave que usamos para organizar os dados --- é um inteiro. + +Iremos discutir três estruturas de dados, cada uma estendendo as ideias da +anterior. A primeira estrutura, a #BinaryTrie# cobre as três operações da +#SSet# em tempo $O(#w#)$. Isso não é muito impressionante, pois +qualquer subconjunto de +$\{0,\ldots,2^{#w#}-1\}$ tem tamanho $#n#\le 2^{#w#}$, tal que +$\log #n# \le #w#$. As outras implementações de #SSet# discutidas neste livro tem operações que rodam em +$O(\log #n#)$ de tempo sendo tão rápidas quanto uma +#BinaryTrie#. + +A segunda estrutura, a #XFastTrie#, acelera a busca em uma +#BinaryTrie# usando hashing. Com essa aceleração, a operação #find(x)# +roda em tempo $O(\log #w#)$. Entretanto, as operações #add(x)# e #remove(x)# +em uma #XFastTrie# ainda leva $O(#w#)$ de tempo e o espaço usado por uma +#XFastTrie# é $O(#n#\cdot#w#)$. + +A terceira estrutura de dados, a + #YFastTrie#, usa uma #XFastTrie# para guardar somente uma amostra de + aproximadamente um a cada +$#w#$ elementos e guarda os elementos restantes em uma estrutura padrão #SSet#. +Esse truque reuz o tempo de execução de #add(x)# e #remove(x)# a +$O(\log #w#)$ e reduz o espaço a +$O(#n#)$. + +As implementações usadas como exemplos neste capítulo podem guardar qualquer +tipo de dado, desde que um inteiro possa ser associado a ele. Nos trechos +de código, a variável #ix# é sempre o valor inteiro associado com #x# e o método + \javaonly{#in.#}#intValue(x)# converte #x# ao seu inteiro associado. +Porém, neste texto trataremos #x# como se fosse um inteiro. + +\section{#BinaryTrie#: Uma Árvore de Busca Digital} \seclabel{binarytrie} \index{BinaryTrie@#BinaryTrie#}% -A #BinaryTrie# encodes a set of #w# bit integers in a binary tree. -All leaves in the tree have depth #w# and each integer is encoded as a -root-to-leaf path. The path for the integer #x# turns left at level #i# -if the #i#th most significant bit of #x# is a 0 and turns right if it -is a 1. \figref{binarytrie-ex} shows an example for the case $#w#=4$, -in which the trie stores the integers 3(0011), 9(1001), 12(1100), -and 13(1101). +Uma +#BinaryTrie# codifica um conjunto de inteiros com #w# bits em uma árvore binária. +Todas as folhas em uma árvore tem profundidade #w# e cada inteiro é codificado como um caminho da raiz para uma folha. O caminho para o inteiro #x# vai para a esquerda no nível #i# se o #i#-ésimo bit mais significativo de #x# for um 0 e vai para a direita se for um 1. +A \figref{binarytrie-ex} mostra um exemplo para o caso $#w#=4$, +no qual a trie guarda inteiros +3(0011), 9(1001), 12(1100) e 13(1101). \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/binarytrie-ex-1} \end{center} - \caption{The integers stored in a binary trie are encoded as - root-to-leaf paths.} + \caption{Os inteiros guardados em uma trie binária são codificados como caminhos da raiz a uma folha.} \figlabel{binarytrie-ex} \end{figure} -Because the search path -\index{search path!in a #BinaryTrie#}% -for a value #x# depends on the bits of #x#, it will -be helpful to name the children of a node, #u#, #u.child[0]# (#left#) -and #u.child[1]# (#right#). These child pointers will actually serve -double-duty. Since the leaves in a binary trie have no children, the -pointers are used to string the leaves together into a doubly-linked list. -For a leaf in the binary trie #u.child[0]# (#prev#) is the node that -comes before #u# in the list and #u.child[1]# (#next#) is the node that -follows #u# in the list. A special node, #dummy#, is used both before -the first node and after the last node in the list (see \secref{dllist}). -\cpponly{In the code samples, #u.child[0]#, #u.left#, and #u.prev# refer to the same field in the node #u#, as do #u.child[1]#, #u.right#, and #u.next#.} - -Each node, #u#, also contains an additional pointer #u.jump#. If #u#'s -left child is missing, then #u.jump# points to the smallest leaf in -#u#'s subtree. If #u#'s right child is missing, then #u.jump# points -to the largest leaf in #u#'s subtree. An example of a #BinaryTrie#, -showing #jump# pointers and the doubly-linked list at the leaves, is -shown in \figref{binarytrie-ex2}. +Como o caminho de busca +\index{caminho de busca!em uma #BinaryTrie#}% +por um valor #x# depende nos bits de #x#, é útil nomear +os filhos de um nodo #u#, #u.child[0]# (#left#) e +#u.child[1]# (#right#). Esses ponteiros dos filhos vão ter dupla utilidade. +Como as folhas em uma trie binária não tem filhos, os ponteiros são +usados para conectar as folhas em uma lista duplamente ligada. +Para uma folha em uma trie binária, + #u.child[0]# (#prev#) é o nodo que antecede #u# na lista e + #u.child[1]# (#next#) é o nodo que sucede #u# na lista. + Um nodo especial, #dummy#, é usado tanto antes do primeiro nodo quanto depois do último nodo na lista (veja a \secref{dllist}). +\cpponly{Nos trechos de código, #u.child[0]#, #u.left#, e #u.prev# referem-se ao mesmo campo no nodo #u#, assim como #u.child[1]#, #u.right# e #u.next#.} + +Cada nodo, #u#, também possui um ponteiro adicional + #u.jump#. Se o filho à esquerda da #u# não existe, então + #u.jump# aponta para a menor folha na subárvore de #u#. + Se o filho à direita de #u# não existe então #u.jump# aponta + para o maior folha na subárvore de #u#. Um exemplo de uma +#BinaryTrie#, mostrando ponteiros #jump# a lista duplamente ligada na folhas +é mostrado na +\figref{binarytrie-ex2}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/binarytrie-ex-2} \end{center} - \caption[A BinaryTrie]{A #BinaryTrie# with #jump# pointers shown as curved dashed - edges.} + \caption[Uma BinaryTrie]{Uma #BinaryTrie# com ponteiros #jump# mostrados como arestas curvas tracejadas.} \figlabel{binarytrie-ex2} \end{figure} - %\jxavaimport{ods/BinaryTrie.Node>>(w-i))#}% \cpponly{#t[i].find(x>>(w-i))#}% \pcodeonly{#t[i].find(x>>(w-i))#}% -The hash tables $#t[0]#,\ldots,#t[w]#$ allow us to use binary search -to find #u#. Initially, we know that #u# is at some level #i# with -$0\le #i#< #w#+1$. We therefore initialize $#l#=0$ and $#h#=#w#+1$ -and repeatedly look at the hash table #t[i]#, where $#i#=\lfloor -(#l+h#)/2\rfloor$. If $#t[i]#$ contains a node whose label matches -#x#'s highest-order #i# bits then we set #l=i# (#u# is at or below level -#i#); otherwise we set #h=i# (#u# is above level #i#). This process -terminates when $#h-l#\le 1$, in which case we determine that #u# is -at level #l#. We then complete the #find(x)# operation using #u.jump# -and the doubly-linked list of leaves. +As tabelas hash $#t[0]#,\ldots,#t[w]#$ nos permitem usar busca binária +para achar #u#. +Inicialmente, sabemos que +#u# está em algum nível #i# com +$0\le #i#< #w#+1$. Portanto inicializamos $#l#=0$ e $#h#=#w#+1$ +e repetidamente olhamos na tabela hash + #t[i]#, onde $#i#=\lfloor +(#l+h#)/2\rfloor$. Se $#t[i]#$ contém um nodo cuja marcação corresponde +aos #i# bits de maior ordem de #x# então fazemos a atribuição #l=i# (#u# está no mesmo nível ou abaixo de +#i#); caso contrário +fazemos +#h=i# (#u# está acima do nível #i#). Esse processo termina quando +$#h-l#\le 1$ e neste caso determinamos que #u# está no nível #l#. +Então completamos a operação #find(x)# usando +#u.jump# +e a lista duplamente ligada de folhas. \codeimport{ods/XFastTrie.find(x)} -Each iteration of the #while# loop in the above method decreases #h-l# -by roughly a factor of two, so this loop finds #u# after $O(\log #w#)$ -iterations. Each iteration performs a constant amount of work and one -#find(x)# operation in a #USet#, which takes a constant expected amount -of time. The remaining work takes only constant time, so the #find(x)# -method in an #XFastTrie# takes only $O(\log#w#)$ expected time. - -The #add(x)# and #remove(x)# methods for an #XFastTrie# are almost -identical to the same methods in a #BinaryTrie#. The only modifications -are for managing the hash tables #t[0]#,\ldots,#t[w]#. During the -#add(x)# operation, when a new node is created at level #i#, this node -is added to #t[i]#. During a #remove(x)# operation, when a node is -removed form level #i#, this node is removed from #t[i]#. Since adding -and removing from a hash table take constant expected time, this does -not increase the running times of #add(x)# and #remove(x)# by more than -a constant factor. We omit a code listing for #add(x)# and #remove(x)# -since the code is almost identical to the (long) code listing already -provided for the same methods in a #BinaryTrie#. - -The following theorem summarizes the performance of an #XFastTrie#: +Cada iteração do laço #while# no método anteriormente descrito +reduz #h-l# por aproximadamente um fator de dois, então esse laço encontra #u# +após +$O(\log #w#)$ +iterações. Cada iteração realiza uma quantidade constante de trabalho e uma operação +#find(x)# em uma #USet#, que leva uma quantidade de tempo esperado constante. +O restante da operação leva somente tempo constante, então o método #find(x)# +em uma #XFastTrie# usa somente $O(\log#w#)$ de tempo esperado. + +Os métodos #add(x)# e #remove(x)# para uma #XFastTrie# são quase +idênticos aos mesmos mesmos em uma +#BinaryTrie#. As únicas modificações são para gerenciar as tabelas hash +#t[0]#,\ldots,#t[w]#. Durante a operação +#add(x)#, quando um novo nodo é criado no nível #i#, esse nodo é adicionado a +#t[i]#. Durante a operação #remove(x)#, quando um nodo é +removido do nível #i#, esse nodo é removido de + #t[i]#. Como adição e remoção de uma tabela hash leva tempo esperado constante, isso não aumenta os tempos de execução de +#add(x)# e #remove(x)# em mais do que um fator constante. +Omitimos os códigos para +#add(x)# e #remove(x)# pois são quase idênticos ao código longo previamente fornecido para os mesmos métodos em uma +#BinaryTrie#. + +O teorema a seguir resume o desempenho de uma +#XFastTrie#: \begin{thm} -An #XFastTrie# implements the #SSet# interface for #w#-bit integers. An -#XFastTrie# supports the operations +Uma #XFastTrie# implementa a interface #SSet# para inteiros com #w# bits. Uma +#XFastTrie# aceita as operações \begin{itemize} -\item #add(x)# and #remove(x)# in $O(#w#)$ expected time per operation and -\item #find(x)# in $O(\log #w#)$ expected time per operation. +\item #add(x)# e #remove(x)# em tempo esperado $O(#w#)$ per operation e +\item #find(x)# em tempo esperado $O(\log #w#)$ por operação. \end{itemize} -The space used by an #XFastTrie# that -stores #n# values is $O(#n#\cdot#w#)$. +O espaço usado por uma +#XFastTrie# que guarda #n# valores é +$O(#n#\cdot#w#)$. \end{thm} \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} \seclabel{yfast} -The #XFastTrie# is a vast---even exponential---improvement over the -#BinaryTrie# in terms of query time, but the #add(x)# and #remove(x)# -operations are still not terribly fast. Furthermore, the space usage, -$O(#n#\cdot#w#)$, is higher than the other #SSet# implementations -described in this book, which all use $O(#n#)$ space. These two -problems are related; if #n# #add(x)# operations build a structure of -size $#n#\cdot#w#$, then the #add(x)# operation requires at least on the -order of #w# time (and space) per operation. +A +#XFastTrie# é uma melhoria exponencial sobre a +#BinaryTrie# em termos de tempo de consulta, mas as operações #add(x)# eand #remove(x)# ainda não são muito rápidas. +Além disso, o uso de espaço +$O(#n#\cdot#w#)$ é mais que em outras implementações de #SSet# descritas neste livro, que usam +$O(#n#)$ de espaço. Esses dois problemas são relacionados; se +#n# operações #add(x)# construirem uma estrutura de tamanho +$#n#\cdot#w#$, então a operação #add(x)# exige pelo menos na ordem de +#w# de tempo (e espaço) por operação. \index{YFastTrie@#YFastTrie#}% -The #YFastTrie#, discussed next, simultaneously improves the space and -speed of #XFastTrie#s. A #YFastTrie# uses an #XFastTrie#, #xft#, but only -stores $O(#n#/#w#)$ values in #xft#. In this way, the total space used by -#xft# is only $O(#n#)$. Furthermore, only one out of every #w# #add(x)# -or #remove(x)# operations in the #YFastTrie# results in an #add(x)# or -#remove(x)# operation in #xft#. By doing this, the average cost incurred -by calls to #xft#'s #add(x)# and #remove(x)# operations is only constant. - -The obvious question becomes: If #xft# only stores #n#/#w# elements, -where do the remaining $#n#(1-1/#w#)$ elements go? These elements move -into \emph{secondary structures}, -\index{secondary structure}% -in this case an extended version of -treaps (\secref{treap}). There are roughly #n#/#w# of these secondary -structures so, on average, each of them stores $O(#w#)$ items. Treaps -support logarithmic time #SSet# operations, so the operations on these -treaps will run in $O(\log #w#)$ time, as required. - -More concretely, a #YFastTrie# contains an #XFastTrie#, #xft#, -that contains a random sample of the data, where each element -appears in the sample independently with probability $1/#w#$. -For convenience, the value $2^{#w#}-1$, is always contained in #xft#. -Let $#x#_0<#x#_1<\cdots<#x#_{k-1}$ denote the elements stored in #xft#. -Associated with each element, $#x#_i$, is a treap, $#t#_i$, that stores -all values in the range $#x#_{i-1}+1,\ldots,#x#_i$. This is illustrated -in \figref{yfast}. +A #YFastTrie#, discutida a seguir, simultaneamente melhora os custos de espaço e tempo da +#XFastTrie#s. Uma #YFastTrie# uma #XFastTrie#, #xft#, mas guarda +somente +$O(#n#/#w#)$ valores em #xft#. Dessa maneira, o espaço total usado por +#xft# é somente $O(#n#)$. Adicionalmente, somente uma a cada #w# operações #add(x)# ou +#remove(x)# +da #YFastTrie# resulta em uma operação #add(x)# ou +#remove(x)# em #xft#. Fazendo isso, o custo médio +das chamadas às operações +#add(x)# e #remove(x)# da #xft# é somente uma constante. + +A pergunta óbvia é: se +#xft# somente guarda #n#/#w# elementos, +onde o resto dos +$#n#(1-1/#w#)$ elementos vão? Esses elementos são movidos para +\emph{estruturas secundárias}, +\index{estrutura secundária}% +nesse caso uma versão estendida de uma treap +(\secref{treap}). Existem aproximadamente #n#/#w# dessas estruturas secundárias +então, em média, cada uma delas guarda +$O(#w#)$ itens. Treaps aceitam operações #SSet# em tempo logaritmico, então +as operações nessas treaps irão rodar em tempo +$O(\log #w#)$, conforme exigido. + +Mais concretamente, uma +#YFastTrie# contém uma #XFastTrie#, #xft#, +que contém uma amostra aleatória dos dados, onde cada elemento +aparece na amostra independentemente com probabilidade +$1/#w#$. +Por conveniência, o valor $2^{#w#}-1$ estará sempre contido em #xft#. +Considere que +$#x#_0<#x#_1<\cdots<#x#_{k-1}$ representam os elementos guardados em #xft#. +Uma treap $#t#_i$ é associada a cada elemento +$#x#_i$ que guarda todos os valores no intervalo +$#x#_{i-1}+1,\ldots,#x#_i$. Isso é ilustrado em +\figref{yfast}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/yfast} \end{center} - \caption[A YFastTrie]{A #YFastTrie# containing the values 0, 1, 3, 4, - 6, 8, 9, 10, 11, and 13.} + \caption[Uma YFastTrie]{Uma #YFastTrie# contendo os valores 0, 1, 3, 4, + 6, 8, 9, 10, 11 e 13.} \figlabel{yfast} \end{figure} -The #find(x)# operation in a #YFastTrie# is fairly easy. We search -for #x# in #xft# and find some value $#x#_i$ associated with the treap -$#t#_i$. We then use the treap #find(x)# method on $#t#_i$ to answer -the query. The entire method is a one-liner: +A operação #find(x)# em uma #YFastTrie# razoavelmente simples. Buscamos por +#x# em #xft# e achamos algum valor $#x#_i$ associado com a treap +$#t#_i$. Então usamos o método #find(x)# da treap $#t#_i$ para responder a +consulta. O método completo se resume a uma linha: \codeimport{ods/YFastTrie.find(x)} -The first #find(x)# operation (on #xft#) takes $O(\log#w#)$ time. -The second #find(x)# operation (on a treap) takes $O(\log r)$ time, where -$r$ is the size of the treap. Later in this section, we will show that -the expected size of the treap is $O(#w#)$ so that this operation takes -$O(\log #w#)$ time.\footnote{This is an application of \emph{Jensen's Inequality}: If $\E[r]=#w#$, then $\E[\log r] +A primeira operação #find(x)# (em #xft#) leva $O(\log#w#)$ de tempo. +A segunda operação #find(x)# (em uma treap) leva + $O(\log r)$ de tempo, onde +$r$ é o tamanho da treap. Mais adiante nesta seção, mostraremos que o tamanho esperado da treap é +$O(#w#)$ tal que essa operação usa +$O(\log #w#)$ de tempo.\footnote{Essa é uma aplicação da \emph{Desigualdade de Jensen}: Se $\E[r]=#w#$, então $\E[\log r] \le \log w$.} -Adding an element to a #YFastTrie# is also fairly simple---most of -the time. The #add(x)# method calls #xft.find(x)# to locate the treap, -#t#, into which #x# should be inserted. It then calls #t.add(x)# to -add #x# to #t#. At this point, it tosses a biased coin that comes up as -heads with probability $1/#w#$ and as tails with probability $1-1/#w#$. -If this coin comes up heads, then #x# will be added to #xft#. - -This is where things get a little more complicated. When #x# is added to -#xft#, the treap #t# needs to be split into two treaps, #t1# and #t'#. -The treap #t1# contains all the values less than or equal to #x#; -#t'# is the original treap, #t#, with the elements of #t1# removed. -Once this is done, we add the pair #(x,t1)# to #xft#. \figref{yfast-add} -shows an example. +Adicionar um elemento a uma +#YFastTrie# é razoavelmente simples -- a maior parte do tempo. +O método #add(x)# chama #xft.find(x)# para achar a treap +#t#, onde #x# deve ser inserido. Então utiliza-se #t.add(x)# para +adicionar #x# a #t#. Nesse ponto, lançamos uma moeda tendenciosa +que sai cara com probabilidade + $1/#w#$ e coroa com probabilidade $1-1/#w#$. + Se nesse lançamento sair cara, então #x# será adicionado a +#xft#. + +É aqui que as coisas ficam um pouco mais complicadas. Quando #x# é +adicionado a +#xft#, a treap #t# precisa ser dividida em duas treaps, #t1# e #t'#. +A treap + #t1# contém todos os valore menores ou iguais a #x#; +#t'# é a treap original, #t#, com os elementos de #t1# removidos. +Assim que isso é feito, adicionamos o par +#(x,t1)# em #xft#. A \figref{yfast-add} mostra um exemplo. \codeimport{ods/YFastTrie.add(x)} \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/yfast-add} \end{center} - \caption[Adding to a YFastTrie]{Adding the values 2 and 6 to a #YFastTrie#. The coin toss - for 6 came up heads, so 6 was added to #xft# and the treap containing - $4,5,6,8,9$ was split.} + \caption[Adicionando em uma YFastTrie]{Adicionando os valores 2 e 6 em uma #YFastTrie#. O lançamento de moeda para 6 sai cara, então 6 foi adicionada a + #xft# e a treap contendo $4,5,6,8,9$ foi dividida.} \figlabel{yfast-add} \end{figure} -Adding #x# to #t# takes $O(\log #w#)$ time. \excref{treap-split} shows -that splitting #t# into #t1# and #t'# can also be done in $O(\log #w#)$ -expected time. Adding the pair (#x#,#t1#) to #xft# takes $O(#w#)$ time, -but only happens with probability $1/#w#$. Therefore, the expected -running time of the #add(x)# operation is +Adicionar #x# a #t# leva + $O(\log #w#)$ de tempo. \excref{treap-split} mostra que dividir #t# em #t1# + e #t'# também pode ser feito em +$O(\log #w#)$ de tempo esperado. Adicionar o par +(#x#,#t1#) em #xft# custa $O(#w#)$ de tempo, mas acontece com +probabilidade $1/#w#$. Portanto, o tempo esperado de execução da operação +#add(x)# é \[ O(\log#w#) + \frac{1}{#w#}O(#w#) = O(\log #w#) \enspace . \] -The #remove(x)# method undoes the work performed by #add(x)#. -We use #xft# to find the leaf, #u#, in #xft# that contains the answer -to #xft.find(x)#. From #u#, we get the treap, #t#, containing #x# -and remove #x# from #t#. If #x# was also stored in #xft# (and #x# -is not equal to $2^{#w#}-1$) then we remove #x# from #xft# and add the -elements from #x#'s treap to the treap, #t2#, that is stored by #u#'s -successor in the linked list. This is illustrated in +O método +#remove(x)# desfaz o trabalho de #add(x)#. +Usamos +#xft# para achar a folha, #u#, em #xft# que contém a resposta a +#xft.find(x)#. A partir de #u#, pegamos a treap, #t#, contendo #x# +e +removemos #x# de #t#. Se #x# também foi guardado em #xft# (e #x# +não é igual a +$2^{#w#}-1$) então removemos #x# de #xft# e adicionamos os elementos +da treap de +#x# à treap #t2#, que é guardada pelo sucessor de #u# na lista ligada. +Isso é ilustrado na \figref{yfast-remove}. \codeimport{ods/YFastTrie.remove(x)} \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/yfast-remove} \end{center} - \caption[Removing from a YFastTrie]{Removing the values 1 and 9 from a #YFastTrie# in \figref{yfast-add}.} + \caption[Remoção de uma YFastTrie]{Remoção dos valores 1 e 9 de uma #YFastTrie# na \figref{yfast-add}.} \figlabel{yfast-remove} \end{figure} -Finding the node #u# in #xft# takes $O(\log#w#)$ expected time. -Removing #x# from #t# takes $O(\log#w#)$ expected time. Again, -\excref{treap-split} shows that merging all the elements of #t# into -#t2# can be done in $O(\log#w#)$ time. If necessary, removing #x# -from #xft# takes $O(#w#)$ time, but #x# is only contained in #xft# with -probability $1/#w#$. Therefore, the expected time to remove an element -from a #YFastTrie# is $O(\log #w#)$. - -Earlier in the discussion, we delayed arguing about the sizes of treaps -in this structure until later. Before finishing this chapter, we prove -the result we need. +Achar o nodo #u# em #xft# leva +$O(\log#w#)$ de tempo esperado. +Remover #x# de #t# leva +$O(\log#w#)$ de tempo esperado. Novamente, +\excref{treap-split} mostra que a junção de todos os elementos de #t# em #t2# +#t2# pode ser feito em tempo $O(\log#w#)$. Se necessária, a remoção de #x# +de #xft# leva tempo +$O(#w#)$, mas #x# é somente contido em #xft# com probabilidade +$1/#w#$. Portanto, o tempo esperado para remover um elemento de uma +#YFastTrie# é $O(\log #w#)$. + +Anteriormente, postergamos a discussão sobre os tamanhos das treaps nesta +estrutura. Antes de terminar este capítulo, provamos o resultado que necessitamos. \begin{lem}\lemlabel{yfast-subtreesize} -Let #x# be an integer stored in a #YFastTrie# and let $#n#_#x#$ -denote the number of elements in the treap, #t#, that contains #x#. -Then $\E[#n#_#x#] \le 2#w#-1$. +Seja #x# um inteiro guardado em uma #YFastTrie# e seja $#n#_#x#$ +o número de elementos na +#t# que contém #x#. +Então $\E[#n#_#x#] \le 2#w#-1$. \end{lem} \begin{proof} -Refer to \figref{yfast-sample}. Let -$#x#_1<#x#_2<\cdots<#x#_i=#x#<#x#_{i+1}<\cdots<#x#_#n#$ denote -the elements stored -in the #YFastTrie#. The treap #t# contains some elements greater than -or equal to #x#. These are $#x#_i,#x#_{i+1},\ldots,#x#_{i+j-1}$, -where $#x#_{i+j-1}$ is the only one of these elements in which the -biased coin toss performed in the #add(x)# method turned up as heads. -In other words, $\E[j]$ is equal to the expected number of biased coin -tosses required to obtain the first heads.\footnote{This analysis ignores -the fact that $j$ never exceeds $#n#-i+1$. However, this only decreases -$\E[j]$, so the upper bound still holds.} Each coin toss is independent -and turns up as heads with probability $1/#w#$, so $\E[j]\le#w#$. -(See \lemref{coin-tosses} for an analysis of this for the case $#w#=2$.) - -Similarly, the elements of #t# smaller than #x# are -$#x#_{i-1},\ldots,#x#_{i-k}$ where all these $k$ coin tosses turn up as -tails and the coin toss for $#x#_{i-k-1}$ turns up as heads. Therefore, -$\E[k]\le#w#-1$, since this is the same coin tossing experiment considered -in the preceding paragraph, but one in which the last toss is not counted. -In summary, $#n#_#x#=j+k$, so +Veja a \figref{yfast-sample}. Sejam +$#x#_1<#x#_2<\cdots<#x#_i=#x#<#x#_{i+1}<\cdots<#x#_#n#$ os +elementos guardados na +#YFastTrie#. A treap #t# contém alguns elementos maiores que ou iguais a +#x#. Esses são $#x#_i,#x#_{i+1},\ldots,#x#_{i+j-1}$, +onde $#x#_{i+j-1}$ é o único desses elementos para o qual o lançamento de moeda tendenciosa no método +#add(x)# saiu no lado cara. +Em outras palavras, +$\E[j]$ é igual ao número esperado de lançamentos tendenciosos necessários para obter a primeira cara. +\footnote{Essa análise ignora o fato que +$j$ nunca passa de $#n#-i+1$. Entretanto, isso somente reduz + $\E[j]$, então o limitando superior ainda vale.} Cada lançamento é independente e +sai cara com probabilidade +$1/#w#$, então $\E[j]\le#w#$. +(Veja o \lemref{coin-tosses} para uma análise para o caso $#w#=2$.) + +De modo similar, os elementos de #t# menores que #x# são +$#x#_{i-1},\ldots,#x#_{i-k}$ onde esses $k$ lançamentos resultam em coroa e o lançamento para +$#x#_{i-k-1}$ sai cara. Portanto, +$\E[k]\le#w#-1$, como esse é o mesmo experimento de lançamento de moedas considerados no parágrafo anterior, mas um em que o último lançamento não é contado. Em resumo, + $#n#_#x#=j+k$, então \[ \E[#n#_#x#] = \E[j+k] = \E[j] + \E[k] \le 2#w#-1 \enspace . \qedhere \] \end{proof} \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/yfast-sample} \end{center} - \caption[The query time in a YFastTrie]{The number of elements in - the treap #t# containing #x# is determined by two coin tossing - experiments.} + \caption[Tempo de uma consulta em uma YFastTrie]{O número de elementos na treap #t# contendo #x# é determinado por dois experimentos de lançamento de moedas.} \figlabel{yfast-sample} \end{figure} %Surprisingly, the bound in \lemref{yfast-subtreesize} is tight. (If this @@ -433,92 +458,92 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} %and therefore #x# is more likely to be in a larger subtree than a smaller %one. -\lemref{yfast-subtreesize} was the last piece in the proof of the -following theorem, which summarizes the performance of the #YFastTrie#: +O \lemref{yfast-subtreesize} foi a última peça na prova do teorema a seguir, que resume o desempenho da +#YFastTrie#: \begin{thm} -A #YFastTrie# implements the #SSet# interface for #w#-bit integers. A -#YFastTrie# supports the operations #add(x)#, #remove(x)#, and #find(x)# -in $O(\log #w#)$ expected time per operation. The space used by a -#YFastTrie# that stores #n# values is $O(#n#+#w#)$. +Uma #YFastTrie# implementa a interface #SSet# para inteiros de #w# bits. Uma +#YFastTrie# aceita as operações #add(x)#, #remove(x)# e #find(x)# +em tempo esperado $O(\log #w#)$ por operação. O espaço usado por uma +#YFastTrie# que guarda #n# valores é $O(#n#+#w#)$. \end{thm} -The #w# term in the space requirement comes from the fact that #xft# always -stores the value $2^#w#-1$. The implementation could be modified (at the -expense of adding some extra cases to the code) so that it is unnecessary -to store this value. In this case, the space requirement in the theorem -becomes $O(#n#)$. - -\section{Discussion and Exercises} - -The first data structure to provide $O(\log#w#)$ time #add(x)#, -#remove(x)#, and #find(x)# operations was proposed by van~Emde~Boas and -has since become known as the \emph{van~Emde~Boas} -\index{van Emde Boas tree}% -(or \emph{stratified}) -\index{stratified tree}% -\emph{tree} \cite{e77}. The original van~Emde~Boas structure had size -$2^{#w#}$, making it impractical for large integers. - -The #XFastTrie# and #YFastTrie# data structures were discovered by -Willard \cite{w83}. The #XFastTrie# structure is closely related -to van~Emde~Boas trees; for instance, the hash tables in an #XFastTrie# -replace arrays in a van~Emde~Boas tree. That is, instead of storing -the hash table #t[i]#, a van~Emde~Boas tree stores an array of length +O temo #w# nos custos de espaço vem do fato que #xft# sempre guarda o +valor $2^#w#-1$. A implementação pode ser modificada (em troca de alguns casos extras para serem considerados no código) tal que é desnecessário guardar esse +valor. Nesse caso, o custo de espaço no teorema torna-se $O(#n#)$. + +\section{Discussão e Exercícios} + +A primeira estrutura de dados a prover +as operações #add(x)#, +#remove(x)# e #find(x)# com tempo $O(\log#w#)$ foi proposta por van~Emde~Boas e é conhecida como a + \emph{árvore de van~Emde~Boas} +\index{árvore de van Emde Boas}% +(ou \emph{árvore estratificada}) +\index{árvore estratificada}% +\cite{e77}. A estrutura original de van~Emde~Boas tinha tamanho +$2^{#w#}$, tornando-a imprática para inteiros grandes. + +As estruturas de dados #XFastTrie# e #YFastTrie# foram descubertas por +Willard \cite{w83}. A estrutura #XFastTrie# é relacionada às árvores de +van~Emde~Boas; por exemplo, as tabelas hash em uma #XFastTrie# +substituem arrays em uma árvore de +van~Emde~Boas. Isto é, em vez de guardar a tabela hash +a tabela hash #t[i]#, uma árvore de van~Emde~Boas guarda um array de comprimento $2^{#i#}$. -Another structure for storing integers is Fredman and Willard's fusion -trees \cite{fw93}. -\index{fusion tree}% -This structure can store #n# #w#-bit integers in -$O(#n#)$ space so that the #find(x)# operation runs in $O((\log #n#)/(\log -#w#))$ time. By using a fusion tree when $\log #w# > \sqrt{\log #n#}$ and -a #YFastTrie# when $\log #w# \le \sqrt{\log #n#}$, one obtains an $O(#n#)$ -space data structure that can implement the #find(x)# operation in -$O(\sqrt{\log #n#})$ time. Recent lower-bound results of P\v{a}tra\c{s}cu -and Thorup \cite{pt07} show that these results are more or less optimal, -at least for structures that use only $O(#n#)$ space. +Outra estrutura para guardar inteiros é a árvore de fusão Fredman e Willard +\cite{fw93}. +\index{árvore de fusão}% +Essa estrutura pode guardar + #n# inteiros de #w# bits em espaço +$O(#n#)$ tal que a operação #find(x)# roda em tempo $O((\log #n#)/(\log +#w#))$. -\begin{exc} - Design and implement a simplified version of a #BinaryTrie# that - does not have a linked list or jump pointers, but for which #find(x)# +Ao usar uma árvore de fusão quando $\log #w# > \sqrt{\log #n#}$ e uma +#YFastTrie# quando $\log #w# \le \sqrt{\log #n#}$, é possível obter uma estrutura de dados que usa $O(#n#)$ de espaço que pode implementar a operação #find(x)# em tempo +$O(\sqrt{\log #n#})$. Resultados recentes de limitantes inferiores de P\v{a}tra\c{s}cu +e Thorup \cite{pt07} mostram que esses resultados são mais ou menos ótimos, +pelo menos para estruturas que usam somente $O(#n#)$ de espaço. - still runs in $O(#w#)$ time. +\begin{exc} + Projete e implemente uma versão simplificada de uma +#BinaryTrie# que não tem uma lista ligada nem ponteiros de salto + mas que ainda tem #find(x)# que roda em tempo + $O(#w#)$. \end{exc} \begin{exc} - Design and implement a simplified implementation of an #XFastTrie# - that doesn't use a binary trie at all. Instead, your implementation - should store everything in a doubly-linked list and $#w#+1$ - hash tables. + Projete e implemente uma implementação simplificada de uma +#XFastTrie# que não usa uma trie binária. Em vez disso, a sua +implementação deve guardar tudo em uma lista duplamente ligada em +$#w#+1$ tabelas hash. \end{exc} \begin{exc} - We can think of a #BinaryTrie# as a structure that stores bit strings - of length #w# in such a way that each bitstring is represented as a - root to leaf path. Extend this idea into an #SSet# implementation that - stores variable-length strings and implements #add(s)#, #remove(s)#, - and #find(s)# in time proporitional to the length of #s#. - - \noindent Hint: Each node in your data structure should store a hash - table that is indexed by character values. + Podemos pensar da #BinaryTrie# como usa estrutura que guarda strings de bits + de comprimento #w# de forma que cada bitstring é representada como um caminho + da raiz para uma folha. + Amplie essa ideia em uma implementação #SSet# que guarda strings + de comprimento váriavel e implementa +#add(s)#, #remove(s)# e + #find(s)# em tempo proporcional ao comprimento de #s#. + + \noindent Dica: Cada nodo na sua estrutura de dados deve guardar uma tabela hash que é indexada por valores de caracteres. \end{exc} \begin{exc} - For an integer $#x#\in\{0,\ldots2^{#w#}-1\}$, let $d(#x#)$ denote - the difference between #x# and the value returned by #find(x)# - [if #find(x)# returns #null#, then define $d(#x#)$ as $2^#w#$]. - For example, if #find(23)# returns 43, then $d(23)=20$. +Para um inteiro $#x#\in\{0,\ldots2^{#w#}-1\}$, seja $d(#x#)$ a + diferença entre #x# e o valor retornado por #find(x)# + [se #find(x)# retorna #null#, então defina $d(#x#)$ como $2^#w#$]. + Por exemplo, se #find(23)# retorna 43, então +$d(23)=20$. \begin{enumerate} - \item Design and implement a modified version of the #find(x)# - operation in an #XFastTrie# that runs in $O(1+\log d(#x#))$ - expected time. Hint: The hash table $t[#w#]$ contains all the - values, #x#, such that $d(#x#)=0$, so that would be a good place - to start. - \item Design and implement a modified version of the #find(x)# - operation in an #XFastTrie# that runs in $O(1+\log\log d(#x#))$ - expected time. + \item Projete e implemente uma versão modificada da operação #find(x)# + em uma #XFastTrie# que roda em tempo esperado $O(1+\log d(#x#))$ + . Dica: a tabela hash $t[#w#]$ contém todos os valores + #x# tal que $d(#x#)=0$, tal que seria um bom lugar para começar. + \item Projete e implemente uma versão modificada da operação #find(x)# + em uma #XFastTrie# que roda em tempo esperado $O(1+\log\log d(#x#))$. \end{enumerate} \end{exc} - - From 90a7dda12557bde85b5a86c9f97bc5580096412c Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 3 Sep 2020 19:35:07 -0300 Subject: [PATCH 35/66] Translated btree. Fixing bad references to labels. --- latex/btree.tex | 1171 ++++++++++++++++++++++--------------------- latex/hashing.tex | 4 +- latex/skiplists.tex | 2 +- 3 files changed, 609 insertions(+), 568 deletions(-) diff --git a/latex/btree.tex b/latex/btree.tex index fbcb559b..dc6f052c 100644 --- a/latex/btree.tex +++ b/latex/btree.tex @@ -1,28 +1,24 @@ -\chapter{External Memory Searching} +\chapter{Busca em Memória Externa} \chaplabel{btree} -Throughout this book, we have been using the #w#-bit word-RAM model -of computation defined in \secref{model}. An implicit assumption of -this model is that our computer has a large enough random access memory -to store all of the data in the data structure. In some situations, -this assumption is not valid. There exist collections of data so large -that no computer has enough memory to store them. In such cases, the -application must resort to storing the data on some external storage -medium such as a hard disk, a solid state disk, or even a network file -server (which has its own external storage). - -\index{external storage}% -\index{external memory}% -\index{hard disk}% +Ao longo deste livro, consideremos o modelo de computação RAM com palavras de #w# bits definido na \secref{model}. Uma premissa implícita desse +modelo é que nosso computador tem memória de acesso aleatório grande o suficiente para guardar todos os dados na estrutura de dados. +Em algumas situação essa premissa não é válida. Existem coleções de dados +tão grandes que nenhum computador tem memória suficiente para guardá-las. +Nesses casos, a aplicação deve guardar os dados em alguma mídia de armazenamento +externo tal como um disco rígido, um disco em estado sólido ou mesmo um servidor de arquivos em rede (que possui seu próprio armazenamento externo). + +\index{armazenamento externo}% +\index{memória externa}% +\index{disco rígido}% \index{solid-state drive}% -Accessing an item from external storage is extremely slow. The hard -disk attached to the computer on which this book was written has an -average access time of 19ms and the solid state drive attached to the -computer has an average access time of 0.3ms. In contrast, the random -access memory in the computer has an average access time of less than -0.000113ms. Accessing RAM is more than 2\,500 times faster than accessing -the solid state drive and more than 160\,000 times faster than accessing -the hard drive. +Acessar um item em um armazenamento externo é extremamente lento. +O disco rígido conectado ao computador no qual este livro foi escrito um +um tempo de acesso médio de 19ms e o disco em estado sólido conectado ao computador +tem tempo médio de acesso de 0.3ms. Em comparação, a memória de acesso aleatório +no computador tem tempo médio de acesso de menor que +0.000113ms. +Acessar a RAM é mais que 2\,500 vezes mais rápido que acessar um disco em estado sólido e mais de 160\,000 vezes mais ráído que acessar o disco rígido. % HDD: Fantom ST3000DM001-9YN166 USB 3 external drive (3TB) % SSD: ATA OCZ-AGILITY 3 (60GB) @@ -49,263 +45,292 @@ \chapter{External Memory Searching} % return 0; % } -These speeds are fairly typical; accessing a random byte from RAM is -thousands of times faster than accessing a random byte from a hard disk -or solid-state drive. Access time, however, does not tell the whole -story. When we access a byte from a hard disk or solid state disk, an -entire \emph{block} -\index{block}% -of the disk is read. Each of the drives attached -to the computer has a block size of 4\,096; each time we read one byte, -the drive gives us a block containing 4\,096 bytes. If we organize our -data structure carefully, this means that each disk access could yield -4\,096 bytes that are helpful in completing whatever operation we are doing. +Essas velocidades são típicas; acessar um byte aleatório na RAM é +milhares de vezes mais rápido que acessar um byte aleatório em um disco +rígido ou um disco de estado sólido. Tempo de acesso, entretando, não +conta a história por completo. Ao acessar um byte de um disco rígido ou +disco de estado sólido, um \emph{bloco} inteiro +\index{bloco}% +do disco é lido. Cada um dos discos conectados ao computador temum tamanho +de bloco de 4\,096; cada vez que lemos um byte, o disco nos fornece um bloco +contendo 4\,096 bytes. Se organizarmos nossa estrutura de dados cuidadosamente, isso +significa que cada acesso a disco pode nos enviar 4\,096 bytes que podem +ser úteis em completar quaisquer operação que estamos executando. % morin@peewee:~/git/ods/latex$ sudo blockdev --report % RO RA SSZ BSZ StartSec Size Device % rw 256 512 4096 0 60022480896 /dev/sda SSD % rw 256 4096 4096 504 3000581885952 /dev/sdb1 HDD -This is the idea behind the \emph{external memory model} -\index{external memory model}% -of computation, -illustrated schematically in \figref{em}. In this model, the computer -has access to a large external memory in which all of the data resides. -This memory is divided into memory \emph{blocks} -\index{block}% -each containing $B$ -words. The computer also has limited internal memory on which it can -perform computations. Transferring a block between internal memory and -external memory takes constant time. Computations performed within the -internal memory are \emph{free}; they take no time at all. The fact -that internal memory computations are free may seem a bit strange, but -it simply emphasizes the fact that external memory is so much slower -than RAM. +Essa é a ideia por trás do \emph{modelo de memória externa} +\index{modelo de memória externa}% +de computação, ilutrado esquematicamente na +\figref{em}. Neste modelo, o computador tem acesso a uma grande memória externa +no qual todos os dados são guardados. +Essa memória é dividida em \emph{blocos} de memória +\index{bloco}% +cada qual contendo + $B$ palavras. +O computador também tem memória interna limitada na qual é possível realizar +computações. Transferir um bloco entre a memória interna e a memória externa +leva tempo constante. Computações realizadas na memória interna são \emph{grátis}; +elas não gastam nenhum instante de tempo. +O fato que computações em memória externa são grátis são parecer estranho, mas +isso enfatiza o fato que memória externa é muito mais lenta que a RAM. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/em}} - \caption[The external memory model]{In the external memory model, - accessing an individual item, #x#, in the external memory requires - reading the entire block containing #x# into RAM.} + \caption[O modelo de memória externa]{No modelo de memória externa, + acessar um único item #x# na memória externa exige a cópia bloco inteiro contendo #x# para a RAM.} \figlabel{em} \end{figure} -In the full-blown external memory model, the size of the internal -memory is also a parameter. However, for the data structures described -in this chapter, it is sufficient to have an internal memory of size -$O(B+\log_B #n#)$. That is, the memory needs to be capable of storing -a constant number of blocks and a recursion stack of height $O(\log_B -#n#)$. In most cases, the $O(B)$ term dominates the memory requirement. -For example, even with the relatively small value $B=32$, $B\ge \log_B -#n#$ for all $#n#\le 2^{160}$. In decimal, $B\ge \log_B #n#$ for any +Em um modelo de memória externa mais detalhado, o tamanho da memória interna também é um parâmetro. Entretanto, para as estruturas de dados descritas neste capítulo, é +suficiente ter uma memória interna de tamanho +$O(B+\log_B #n#)$. Por isso, a memória precisa ser apaz de guardar um um número +constante de blocos e uma pilha de recursão de altura +$O(\log_B +#n#)$. Na maior parte dos casos, o termo $O(B)$ domina os custos de memória. +Por exemplo, mesmo com um relativamente pequeno + $B=32$, $B\ge \log_B +#n#$ para todo $#n#\le 2^{160}$. Em decimal, $B\ge \log_B #n#$ para qualquer \[ #n# \le 1\,461\,501\,637\,330\,902\,918\,203\,684\,832\,716\,283\,019\,655\,932\,542\,976 \enspace . \] -\section{The Block Store} +\section{A \emph{Block Store}} \index{block store}% \index{BlockStore@#BlockStore#}% -The notion of external memory includes a large number of possible -different devices, each of which has its own block size and is -accessed with its own collection of system calls. To simplify the -exposition of this chapter so that we can focus on the common ideas, we -encapsulate external memory devices with an object called a #BlockStore#. -A #BlockStore# stores a collection of memory blocks, each of size $B$. -Each block is uniquely identified by its integer index. A #BlockStore# -supports these operations: +A noção de memória externa inclui um grande número de diferentes +dispositivos, cada qual com seu próprio tamanho de bloco e é +acessado com sua própria coleção de chamadas de sistema. +Para simplificar a exposição deste capítulo para que possamos nos +concentrar nas ideias em comum entre eles, encapsulamos os +dispositivos de memória externa com um objeto chamado de #BlockStore# (em português, armazém de blocos). +Uma #BlockStore# guarda uma coleção de blocos de memória, cada uma com tamanho $B$. +Cada bloco é unicamente identificado por seu índice inteiro. Uma + #BlockStore# +aceita as operações: \begin{enumerate} - \item #readBlock(i)#: Return the contents of the block whose index is #i#. + \item #readBlock(i)#: retorna o conteúdo do bloco cujo índice é #i#. - \item #writeBlock(i,b)#: Write contents of #b# to the block whose - index is #i#. + \item #writeBlock(i,b)#: escreve o conteúdo de #b# ao bloco cujo índice é #i#, - \item #placeBlock(b)#: Return a new index and store the contents of #b# - at this index. + \item #placeBlock(b)#: retorna um novo índice e guarda o conteúdo de #b# nesse índice. - \item #freeBlock(i)#: Free the block whose index is #i#. This indicates - that the contents of this block are no longer used so the external - memory allocated by this block may be reused. + \item #freeBlock(i)#: Libera o bloco cujo índice é #i#. Isso indica + que o conteúdo desse bloco não serão mais usados então a memória externa alocada por esse bloco pode ser reusada. \end{enumerate} -The easiest way to imagine a #BlockStore# is to imagine it as storing a -file on disk that is partitioned into blocks, each containing $B$ bytes. -In this way, #readBlock(i)# and #writeBlock(i,b)# simply read and write -bytes $#i#B,\ldots,(#i#+1)B-1$ of this file. In addition, a simple -#BlockStore# could keep a \emph{free list} of blocks that are available -for use. Blocks freed with #freeBlock(i)# are added to the free list. -In this way, #placeBlock(b)# can use a block from the free list or, -if none is available, append a new block to the end of the file. - +O jeito mais fácil de usar #BlockStore# é imaginá-lo como guardar um +arquivo em disco que é particionado em blocos, cada contendo $B$ bytes. +Dessa maneira, +#readBlock(i)# e #writeBlock(i,b)# simplesmente lêem e escrevem +bytes $#i#B,\ldots,(#i#+1)B-1$ desse arquivo. Adicionalmente, +um simples +#BlockStore# poderia manter uma \emph{lista de blocos livres} que lista aqueles +que estão disponíveis para uso. +Blocos liberados com +#freeBlock(i)# são adicionados à lista de blocos livres. +Dessa forma, + #placeBlock(b)# pode usar um bloco da lista de blocos livres, ou + caso nenhum esteja livre, adiciona um novo bloco no final do arquivo. \section{B-Trees} \seclabel{btree} -In this section, we discuss a generalization of binary trees, -called $B$-trees, which is efficient in the external memory model. -Alternatively, $B$-trees can be viewed as the natural generalization of -2-4 trees described in \secref{twofour}. (A 2-4 tree is a special case -of a $B$-tree that we get by setting $B=2$.) +Nesta seção, discutimos uma generalização de árvores binárias chamadas +$B$-trees, que é eficiente no modelo de memória externa. +Alternativamente, $B$-trees podem ser vistas como generalização natural +da árvore 2-4 descrita na +\secref{twofour}. (Uma árvore 2-4 é um caso especial de uma +$B$-tree quando $B=2$.) \index{B-tree@$B$-tree}% -For any integer $B\ge 2$, a \emph{$B$-tree} is a tree in which all of -the leaves have the same depth and every non-root internal node, #u#, -has at least $B$ children and at most $2B$ children. The children of #u# -are stored in an array, #u.children#. The required number of children is -relaxed at the root, which can have anywhere between 2 and $2B$ children. - -If the height of a $B$-tree is $h$, then it follows that the number, -$\ell$, of leaves in the $B$-tree satisfies +Para qualquer inteiro $B\ge 2$, uma \emph{$B$-tree} é uma árvore na qual todas +as folhas tem a mesma profundidade e todo nodo interno (exceto a raiz) +#u# tem pelo menos +$B$ filhos e no máximo $2B$ filhos. Os filhos de #u# são guardados +em um array + #u.children#. O número obrigatório de filhos é diferente na raiz, que pode tem entre 2 e $2B$ filhos. + + Se a altura de uma +$B$-tree é $h$, então o número +$\ell$, de folha em uma $B$-tree satisfaz \[ 2B^{h-1} \le \ell \le (2B)^{h} \enspace . \] -Taking the logarithm of the first inequality and rearranging terms yields: +Aplicando o logaritmo da primeira desigualdade e rearranjando termos chegamos em: \begin{align*} h & \le \frac{\log \ell-1}{\log B} + 1 \\ & \le \frac{\log \ell}{\log B} + 1 \\ & = \log_B \ell + 1 \enspace . \end{align*} -That is, the height of a $B$-tree is proportional to the base-$B$ -logarithm of the number of leaves. - -Each node, #u#, in $B$-tree stores an array of keys -$#u.keys#[0],\ldots,#u.keys#[2B-1]$. If #u# is an internal node with $k$ -children, then the number of keys stored at #u# is exactly $k-1$ and these -are stored in $#u.keys#[0],\ldots,#u.keys#[k-2]$. The remaining $2B-k+1$ -array entries in #u.keys# are set to #null#. If #u# is a non-root leaf -node, then #u# contains between $B-1$ and $2B-1$ keys. The keys in a -$B$-tree respect an order similar to the keys in a binary search tree. -For any node, #u#, that stores $k-1$ keys, +Isto é, a altura de uma + $B$-tree é proporcional ao logaritmo base-$B$ do número de folhas. + +Cada nodo #u# na $B$-tree guarda um array de chaves +$#u.keys#[0],\ldots,#u.keys#[2B-1]$. Se #u# é um nodo interno com $k$ +filhos, então o número de chaves guardado em #u# é exatamente +$k-1$ e esses são guardados em +$#u.keys#[0],\ldots,#u.keys#[k-2]$. O restante das $2B-k+1$ entradas do array +em #u.keys# são atribuídos a #null#. + +Se #u# for uma folha não rais +, então #u# contém entre $B-1$ e $2B-1$ chaves. As chaves em uma +$B$-tree respeitam uma ordem similar às chaves em uma árvore binária de busca. +Para qualquer nodo #u# que guarda $k-1$ chaves, \[ #u.keys[0]# < #u.keys[1]# < \cdots < #u.keys#[k-2] \enspace . \] -If #u# is an internal node, then for every $#i#\in\{0,\ldots,k-2\}$, -$#u.keys[i]#$ is larger than every key stored in the subtree rooted at -#u.children[i]# but smaller than every key stored in the subtree rooted -at $#u.children[i+1]#$. Informally, +Se +#u# for um nodo interno, então para todo $#i#\in\{0,\ldots,k-2\}$, +$#u.keys[i]#$ é maior que toda chave guardada na subárvore enraizada em +#u.children[i]# mas menos que toda chave gurda na subárvores enraizada em +at $#u.children[i+1]#$. Informalmente, \[ #u.children[i]# \prec #u.keys[i]# \prec #u.children[i+1]# \enspace . \] -An example of a $B$-tree with $B=2$ is shown in \figref{btree}. +Um exemplo de uma $B$-tree com $B=2$ é mostrado na \figref{btree}. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/btree-1}} - \caption{A $B$-tree with $B=2$.} + \caption{Uma $B$-tree com $B=2$.} \figlabel{btree} \end{figure} -Note that the data stored in a $B$-tree node has size $O(B)$. Therefore, -in an external memory setting, the value of $B$ in a $B$-tree is chosen -so that a node fits into a single external memory block. In this way, -the time it takes to perform a $B$-tree operation in the external memory -model is proportional to the number of nodes that are accessed (read or -written) by the operation. +Note que os dados guardados em um nodo de uma +$B$-tree tem tamanho $O(B)$. Portanto, +em um contexto de memória externa, o valor de $B$ em uma $B$-tree é +escolhido tal que um nodo cabe en um único bloco de memória externo. +Dessa forma, o tempo que leva para realizar uma operação da $B$-tree +no modelo de memória externa é proporcional ao número de nodos que +são acessados (lidos ou escritos) pela operação. -For example, if the keys are 4 byte integers and the node indices are -also 4 bytes, then setting $B=256$ means that each node stores +Por exemplo, se as chaves são inteiros de 4 bytes e os índices dos nodos +também ocupam 4 bytes, então fazer $B=256$ significa que cada nodo guarda \[ (4+4)\times 2B = 8\times512=4096 \] -bytes of data. This would be a perfect value of $B$ for the hard disk -or solid state drive discussed in the introduction to this chaper, -which have a block size of $4096$ bytes. - -The #BTree# class, which implements a $B$-tree, stores a #BlockStore#, -#bs#, that stores #BTree# nodes as well as the index, #ri#, of the -root node. As usual, an integer, #n#, is used to keep track of the number -of items in the data structure: +bytes de dados. + +Esse seria um valor perfeito de $B$ para o disco rígido ou o disco de estado +sólido discutido na introdução deste capítulo, que tem um tamanho de bloco de +$4096$ bytes. + +A classe +#BTree#, que implementa uma $B$-tree, mantém uma #BlockStore#, +#bs#, que guarda nodos #BTree# assim nodo o índice #ri# do nodo raiz. + +Como de costume, um inteiro +#n#, é usado para registrar o número de itens na estrutura de dados: \cppimport{ods/BTree.n.ri.bs} \javaimport{ods/BTree.n.ri.bs} \pcodeimport{ods/BTree.initialize(b)} -\subsection{Searching} +\subsection{Busca} -The implementation of the #find(x)# operation, which is illustrated in -\figref{btree-find}, generalizes the #find(x)# operation in a binary -search tree. The search for #x# starts at the root and uses the keys -stored at a node, #u#, to determine in which of #u#'s children the search -should continue. +A implementação da operação +#find(x)#, que é ilustrada em +\figref{btree-find}, generaliza a operação #find(x)# em uma árvore binária de busca. A busca por #x# inicia na raiz e usa as chaves guardadas em um nodo #u# para +determinar em qual dos filhos de #u# a busca deve continuar. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/btree-2}} - \caption[Searching in a $B$-tree]{A successful search (for the value 4) - and an unsuccessful search (for the value 16.5) in a $B$-tree. Shaded nodes show where the value of #z# is updated during the searches.} - \figlabel{btree-find} + \caption[Busca em uma $B$-tree]{Uma busca bem sucedida (pelo valor 4) e uma busca mal sucedida (pelo valor 16.5) em uma $B$-tree. Nodos sombreados onde o valor de #z# fio atualizado durante as buscas.}\figlabel{btree-find} \end{figure} -More specifically, at a node #u#, the search checks if #x# is stored -in #u.keys#. If so, #x# has been found and the search is complete. -Otherwise, the search finds the smallest integer, #i#, such that -$#u.keys[i]# > #x#$ and continues the search in the subtree rooted at -#u.children[i]#. If no key in #u.keys# is greater than #x#, then the -search continues in #u#'s rightmost child. Just like binary search -trees, the algorithm keeps track of the most recently seen key, #z#, -that is larger than #x#. In case #x# is not found, #z# is returned as -the smallest value that is greater or equal to #x#. +Mais especificamente, em um nodo #u#, a busca verifica se #x# está guardada em +#u.keys#. Caso positivo, #x# foi encontrado e a busca está completa. + +Por outro lado, a busca acha o menor inteiro #i# tal que +$#u.keys[i]# > #x#$ e continua a busca na subárvore enraizada em +#u.children[i]#. Se nenhuma chave em #u.keys# for maior que #x#, então +a busca continua no filho mais à direita de #u#. Assim como em árvores +binárias de busca, o algoritmo registra a chave mais recentemente vista #z# +que é maior que #x#. No caso de #x# não ser encontrado, #z# é retornado +como o menor valor que é maior ou igual a #x#. + \codeimport{ods/BTree.find(x)} -Central to the #find(x)# method is the #findIt(a,x)# method that -searches in a #null#-padded sorted array, #a#, for the value #x#. -This method, illustrated in \figref{findit}, works for any array, -#a#, where $#a#[0],\ldots,#a#[k-1]$ is a sequence of keys in sorted -order and $#a#[k],\ldots,#a#[#a.length#-1]$ are all set to #null#. -If #x# is in the array at position #i#, then #findIt(a,x)# returns -$-#i#-1$. Otherwise, it returns the smallest index, #i#, such that -$#a[i]#>#x#$ or $#a[i]#=#null#$. + +O método #findIt(a,x)# é central para o método #find(x)# ao +buscar em um array ordenado completada com valores #null# #a# +pelo valor #x#. Esse método, ilustrado em +\figref{findit}, funciona para qualquer array +#a#, onde $#a#[0],\ldots,#a#[k-1]$ é uma sequência de chaves +ordenadas +$#a#[k],\ldots,#a#[#a.length#-1]$ são todas atribuídas a #null#. +Se #x# está na posição #i# array, então #findIt(a,x)# retorna +$-#i#-1$. Caso contrário, ele retorna o menor índice #i# tal que +$#a[i]#>#x#$ ou $#a[i]#=#null#$. \begin{figure} \centering{\includegraphics[scale=0.90909]{figs/findit}} - \caption[The findIt(a,x) method]{The execution of #findIt(a,27)#.} + \caption[O método findIt(a,x)]{A execução de #findIt(a,27)#.} \figlabel{findit} \end{figure} \codeimport{ods/BTree.findIt(a,x)} -The #findIt(a,x)# method uses a binary search -\index{binary search}% -that halves the search -space at each step, so it runs in $O(\log(#a.length#))$ time. In our setting, $#a.length#=2B$, so #findIt(a,x)# runs in $O(\log B)$ time. - -We can analyze the running time of a $B$-tree #find(x)# operation both -in the usual word-RAM model (where every instruction counts) and in the -external memory model (where we only count the number of nodes accessed). -Since each leaf in a $B$-tree stores at least one key and the height -of a $B$-Tree with $\ell$ leaves is $O(\log_B\ell)$, the height of a -$B$-tree that stores #n# keys is $O(\log_B #n#)$. Therefore, in the -external memory model, the time taken by the #find(x)# operation is -$O(\log_B #n#)$. To determine the running time in the word-RAM model, -we have to account for the cost of calling #findIt(a,x)# for each node -we access, so the running time of #find(x)# in the word-RAM model is +O méotdo #findIt(a,x)# usa uma busca binária +\index{busca binária}% +que divide o espaço de busca a cada passo, tal que ele roda em tempo +$O(\log(#a.length#))$. No nosso cenário, $#a.length#=2B$, então #findIt(a,x)# roda em tempo $O(\log B)$. + +Podemos analisar o tempo de execução de uma operação #find(x)# da $B$-tree +tanto no modelo RAM com palavras de #w# bits usual (onde cada instrução conta) +quanto no modelo de memória externa (onde somente contamos o número de nodos +acessados). + +Como cada folha em uma +$B$-tree guarda pelo menos uma chave e a altura de uma +$B$-Tree com $\ell$ folhas é $O(\log_B\ell)$, a altura de uma +$B$-tree que guarda #n# chaves é $O(\log_B #n#)$. Portanto, no modelo +de memória externa, o tempo gasto pela operação #find(x)# é +$O(\log_B #n#)$. +Para determinar o tempo de execução no modelo RAM, +temos que considerar o custo de chamar +#findIt(a,x)# para cada nodo que acessamos, então o tempo de execução +de #find(x)# no modelo RAM é \[ O(\log_B #n#)\times O(\log B) = O(\log #n#) \enspace . \] -\subsection{Addition} +\subsection{Adição} -One important difference between $B$-trees and the #BinarySearchTree# -data structure from \secref{binarysearchtree} is that the nodes of a -$B$-tree do not store pointers to their parents. The reason for this -will be explained shortly. The lack of parent pointers means that -the #add(x)# and #remove(x)# operations on $B$-trees are most easily -implemented using recursion. +Uma diferença importante entre as estruturas de dados +$B$-tree e #BinarySearchTree# da +\secref{binarysearchtree} é que os nodos de uma +$B$-tree não guardam ponteiro para nodos pai. A razão disso vai ser explicada +logo a seguir. A falta de ponteiros dos nodos pai significa que as operações +#add(x)# e #remove(x)# em $B$-trees são mais facilmente implementadas usando +recursão. -Like all balanced search trees, some form of rebalancing is required -during an #add(x)# operation. In a $B$-tree, this is done by -\emph{splitting} nodes. +Assim como todas as árvores binárias balanceadas, alguma forma de balanceamento +é necessária durante uma operação #add(x)#. Em uma +$B$-tree, isso é feito pela +\emph{divisão} (em inglês, \emph{split}) de nodos. \index{split}% -Refer to \figref{btree-split} for what follows. -Although splitting takes place across two levels of recursion, it is -best understood as an operation that takes a node #u# containing $2B$ -keys and having $2B+1$ children. It creates a new node, #w#, that -adopts $#u.children#[B],\ldots,#u.children#[2B]$. The new node #w# -also takes #u#'s $B$ largest keys, $#u.keys#[B],\ldots,#u.keys#[2B-1]$. -At this point, #u# has $B$ children and $B$ keys. The extra key, -$#u.keys#[B-1]$, is passed up to the parent of #u#, which also adopts #w#. - -Notice that the splitting operation modifies three nodes: #u#, #u#'s -parent, and the new node, #w#. This is why it is important that the -nodes of a $B$-tree do not maintain parent pointers. If they did, then -the $B+1$ children adopted by #w# would all need to have their parent -pointers modified. This would increase the number of external memory -accesses from 3 to $B+4$ and would make $B$-trees much less efficient for -large values of $B$. +Observe na +\figref{btree-split} como isso ocorre. +Embora a divisão acontece entre dois níveis de recursão, ela é +melhor entendida como uma operação que recebe um nodo #u# contendo +$2B$ chave e com +$2B+1$ filhos. +A operação cria um novo nodo #w# que adota +$#u.children#[B],\ldots,#u.children#[2B]$. O novo nodo #w# +também recebe as $B$ maiores chaves de #u#, $#u.keys#[B],\ldots,#u.keys#[2B-1]$. +Neste ponto, #u# tem + $B$ filhos e $B$ chaves. A chave extra, +$#u.keys#[B-1]$, é promovida para o pai de #u#, que também adota #w#. + +Note que a operação de divisão modifica três nodos: + #u#, o pai de #u# e o novo nodo #w#. + Por isso é importante que os nodos de uma +$B$-tree não mantém ponteiros para os nodos pai. Se assim fosse, então +os +$B+1$ filhos adotados por #w# todos necessitariam ter seu ponteiros para o nodo pai modificados. Isso aumentaria o número de acessos à memória externa de 3 para +$B+4$ e faria $B$-trees muito menos eficiente para valores altos de +$B$. \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -314,22 +339,22 @@ \subsection{Addition} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-split-2} \\ \end{tabular}} - \caption[Splitting a $B$-tree node]{Splitting the node #u# in a - $B$-tree ($B=3$). Notice that the key $#u.keys#[2]=\mathrm{m}$ - passes from #u# to its parent.} + \caption[Divisão de um nodo de uma $B$-tree ]{Divisão do nodo #u# em uma + $B$-tree ($B=3$). Note que a chave $#u.keys#[2]=\mathrm{m}$ + passa de #u# ao seu pai.} \figlabel{btree-split} \end{figure} -The #add(x)# method in a $B$-tree is illustrated in \figref{btree-add}. -At a high level, this method finds a leaf, #u#, at which to add the -value #x#. If this causes #u# to become overfull (because it already -contained $B-1$ keys), then #u# is split. If this causes #u#'s parent to -become overfull, then #u#'s parent is also split, which may cause #u#'s -grandparent to become overfull, and so on. This process continues, -moving up the tree one level at a time until reaching a node that -is not overfull or until the root is split. In the former case, the -process stops. In the latter case, a new root is created whose two -children become the nodes obtained when the original root was split. +O método #add(x)# em uma $B$-tree está ilustrado na \figref{btree-add}. +Em uma descrição sem se ater a detalhes, esse método acha uma folha #u# +para achar o valor #x#. Se isso faz com que #u# fique cheia demais, +então o pai de #u# também é dividido, o que pode fazer que o avô de #u# +fique cheio demais e assim por diante. + +Esse processor continua, subindo na árvore um nível por vez até alcançar +um nodo que não está cheio e até que a raiz seja dividida. +Ao encontrar um nodo com espaço, o processo é interrompido. +Caso contrário, uma nova raiz é criada com dois filhos obtidos da divisão da raiz original. \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -339,79 +364,91 @@ \subsection{Addition} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-add-3} \end{tabular}} - \caption[Adding to a $B$-tree]{The #add(x)# operation in a - #BTree#. Adding the value 21 results in two nodes being split.} + \caption[Adição a uma $B$-tree]{A operação #add(x)# em uma + #BTree#. A adição do valor 21 resulta em dois nodos sendo divididos.} \figlabel{btree-add} \end{figure} -The executive summary of the #add(x)# method is that it walks -from the root to a leaf searching for #x#, adds #x# to this leaf, and -then walks back up to the root, splitting any overfull nodes it encounters -along the way. With this high level view in mind, we can now delve into -the details of how this method can be implemented recursively. - -The real work of #add(x)# is done by the #addRecursive(x,ui)# method, -which adds the value #x# to the subtree whose root, #u#, has the -identifier #ui#. If #u# is a leaf, then #x# is simply inserted into -#u.keys#. Otherwise, #x# is added recursively into the appropriate -child, $#u#'$, of #u#. The result of this recursive call is normally -#null# but may also be a reference to a newly-created node, #w#, that -was created because $#u#'$ was split. In this case, #u# adopts #w# -and takes its first key, completing the splitting operation on $#u#'$. - -After the value #x# has been added (either to #u# or to a descendant of #u#), -the #addRecursive(x,ui)# method checks to see if #u# is storing too many -(more than $2B-1$) keys. If so, then #u# needs to be \emph{split} -with a call to the #u.split()# method. The result of calling #u.split()# -is a new node that is used as the return value for #addRecursive(x,ui)#. +O resumo do método #add(x)# é que é caminha da raiz para a folha +em busca de #x#, adiciona #x# a essa folha e retorna de volta +para a raiz, divindindo qualquer nodo cheio demais que +encontre no caminho. Com essa visão em mente, podemos entrar +nos detalhes de como esse método pode ser implementado recursivamente. + +O principal trabalho de #add(x)# é feito pelo método +#addRecursive(x,ui)#, que adiciona o valor #x# à subárvore cuja raiz #u# +tem o identificar #ui#. Se #u# é uma folha, então #x# simplesmente é +adicionado em +#u.keys#. Caso contrário, #x# é adicionado recursivamente no filho +$#u#'$ de #u#. O resultado dessa chamada recursiva é normalmente +#null# mas também pode ser uma raferência a um novo nodo #w# +que foi criado porque +$#u#'$ foi dividido. +Nesse caso, #u# adota #w# e recebe sua primeira chave, completando a operação +de divisão em +$#u#'$. + +Após o valor +#x# ser adicionado (em #u# ou em um descendente de #u#), +o método +#addRecursive(x,ui)# verifica se #u# está cheio demais +(mais de $2B-1$) chaves. Desse modo, então #u# precisa ser dividido +com uma chamada ao método +#u.split()#. O resultado de chamar #u.split()# +é um novo nodo que é usado como um valor de retorno para +#addRecursive(x,ui)#. \codeimport{ods/BTree.addRecursive(x,ui)} -The #addRecursive(x,ui)# method is a helper for the #add(x)# method, which -calls #addRecursive(x,ri)# to insert #x# into the root of the $B$-tree. -If #addRecursive(x,ri)# causes the root to split, then a new root is -created that takes as its children both the old root and the new node -created by the splitting of the old root. +O método #addRecursive(x,ui)# é auxiliar ao método #add(x)#, que chama +#addRecursive(x,ri)# para inserir #x# na raiz da $B$-tree. +Se #addRecursive(x,ri)# faz a raiz ser dividida, então uma nova raiz é criada +e ela recebe seus filhos tanto a antiga raiz quanto o novo nodo criado pela +divisão da antiga raiz. \codeimport{ods/BTree.add(x)} -The #add(x)# method and its helper, #addRecursive(x,ui)#, can be analyzed -in two phases: +O método #add(x)# e seu auxiliar, #addRecursive(x,ui)#, podem ser analisados em duas fases: \begin{description} - \item[Downward phase:] - During the downward phase of the recursion, before #x# has been added, - they access a sequence of #BTree# nodes and call #findIt(a,x)# on each node. - As with the #find(x)# method, this takes $O(\log_B #n#)$ time in the - external memory model and $O(\log #n#)$ time in the word-RAM model. + \item[Fase da descida:] + Durante a fase da descida da recursão, antes que #x# foi adicionado, + é acessada uma sequência de nodos da #BTree# e é chamado +#findIt(a,x)# em cada nodo. + Assim como o método #find(x)# isso leva + $O(\log_B #n#)$ de tempo no modelo de memória externa + $O(\log #n#)$ de tempo no modelo RAM com palavras de #w# bits. - \item[Upward phase:] - During the upward phase of the recursion, after #x# has been added, - these methods perform a sequence of at most $O(\log_B #n#)$ splits. - Each split involves only three nodes, so this phase takes $O(\log_B - #n#)$ time in the external memory model. However, each split - involves moving $B$ keys and children from one node to another, so - in the word-RAM model, this takes $O(B\log #n#)$ time. + \item[Fase de subida:] + Durante a fase de subdia da recursão, após a adição de #x#, + esses métodos realizam uma sequência de até +$O(\log_B #n#)$ divisões. +Cada divisão envolve somente de três nodos e, então, essa fase +leva +$O(\log_B + #n#)$ de tempo no modelo de memória externa. Entretanto, cada divisão + envolve mover $B$ chaves e filhos de um nodo a outro, então no modelo RAM com palavras de #w# bits, isso leva +$O(B\log #n#)$ de tempo. \end{description} -Recall that the value of $B$ can be quite large, much larger -than even $\log #n#$. Therefore, in the word-RAM model, adding a value -to a $B$-tree can be much slower than adding into a balanced binary -search tree. Later, in \secref{btree-amortized}, we will show that the -situation is not quite so bad; the amortized number of split operations -done during an #add(x)# operation is constant. This shows that the -(amortized) running time of the #add(x)# operation in the word-RAM model -is $O(B+\log #n#)$. - - -\subsection{Removal} - -The #remove(x)# operation in a #BTree# is, again, most easily implemented -as a recursive method. Although the recursive implementation of -#remove(x)# spreads the complexity across several methods, the overall -process, which is illustrated in \figref{btree-remove-full}, is fairly -straightforward. By shuffling keys around, removal is reduced to the -problem of removing a value, $#x#'$, from some leaf, #u#. Removing $#x#'$ -may leave #u# with less than $B-1$ keys; this situation is called -an \emph{underflow}. +Lembre-se que o valor de $B$ pode ser bem alto, muito maior que +$\log #n#$. Portanto no modelo RAM com palavras de #w# bits, adicionar um valor a uma +$B$-tree pode ser bem mais lento que adicionar em uma árvore binária de busca balanceada. Porteriormente, na +\secref{btree-amortized}, mostraremos que a situação não é tão ruim; +o número amortizado de operações de divisão feito durante uma operação #add(x)# é constante. +Isso mostra que o tempo de execução amortizado da operação #add(x)# no modelo RAM é +$O(B+\log #n#)$. + + +\subsection{Remoção} + +A operação +#remove(x)# em uma #BTree# é, novamente, mais facilmente implementada como um método recursivo. Embora a implementação recursiva de +#remove(x)# distribui sua complexidade entre vários métodos, +o processo como um todo, que é ilustrado na +\figref{btree-remove-full}, é razoavelmente direto. +Ao trabalhar com as chaves, remoção é reduzida ao problema de remoção de um +valor $#x#'$, de alguma folha #u#. Remover $#x#'$ +pode deixar #u# com menos que $B-1$ chaves; essa situação é +chamada de \emph{underflow}. \index{underflow}% \begin{figure} @@ -426,30 +463,32 @@ \subsection{Removal} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-remove-full-4} \\[2ex] \end{tabular}} - \caption[Removing from a $B$-tree]{Removing the value 4 from a $B$-tree - results in one merge and one borrowing operation.} + \caption[Remoção de uma $B$-tree]{Remoção do valor 4 de uma $B$-tree + resulta em uma operação de junção e um empréstimo.} \figlabel{btree-remove-full} \end{figure} -When an underflow occurs, #u# either borrows keys from, or is merged with, -one of its siblings. If #u# is merged with a sibling, then #u#'s parent -will now have one less child and one less key, which can cause #u#'s -parent to underflow; this is again corrected by borrowing or merging, -but merging may cause #u#'s grandparent to underflow. This process -works its way back up to the root until there is no more underflow or -until the root has its last two children merged into a single child. -When the latter case occurs, the root is removed and its lone child -becomes the new root. - -Next we delve into the details of how each of these steps is implemented. -The first job of the #remove(x)# method is to find the element #x# that -should be removed. If #x# is found in a leaf, then #x# is removed from -this leaf. Otherwise, if #x# is found at #u.keys[i]# for some internal -node, #u#, then the algorithm removes the smallest value, #x'#, in the -subtree rooted at #u.children[i+1]#. The value #x'# is the smallest -value stored in the #BTree# that is greater than #x#. The value of #x'# -is then used to replace #x# in #u.keys[i]#. This process is illustrated -in \figref{btree-remove}. +Quando um underflow acontece, #u# pode emprestar chaves ou ser juntado com seus irmão. Se #u# é juntado com um irmão, então o pai de #u# +terá agora um filho a menos e uma chave a menos, o que pode +fazer o pai de #u# sofrer um underflow; isso é novamente corregido +por meio de um empréstimo ou de uma junção, embora a junção pode +fazer que o avô de #u# sofra underflow. +Esse processo se repete até a raiz até que não ocorra mais underflows +ou até que a raiz tenha seus dois filhos juntados em um filho único. +Quando os filhos são juntados, a raiz é removida e seu único filho +se tona a nova raiz. + +A seguir, entramos nos detalhes de como esses passos são implementados. +O primeiro trabalho do método #remove(x)# é achar o elemento #x# que +deve ser removido. Se #x# é achado em uma folha, então #x# é removido +dessa folha. Caso contrário, se #x# é achado em #u.keys[i]# para algum nodo +interno #u# então o algoritmo remove o menor valor +#x'#, na subárvore enraizada em +#u.children[i+1]#. O valor #x'# é o menor valor guardado na +#BTree# que é maior que #x#. O valor de #x'# é então +usado para substituir #x# em + #u.keys[i]#. Esse processo é ilustrado na +\figref{btree-remove}. \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -457,48 +496,49 @@ \subsection{Removal} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-remove-2} \end{tabular}} - \caption[The remove operation in a $B$-tree] {The #remove(x)# operation - in a #BTree#. To remove the value $#x#=10$ we replace it with the - the value $#x'#=11$ and remove 11 from the leaf that contains it.} + \caption[Remoção em uma $B$-tree] {A operação #remove(x)# + em uma #BTree#. Para remover o valor $#x#=10$ substituimos com o valor + $#x'#=11$ e removemos 11 da folha que o contém. } \figlabel{btree-remove} \end{figure} -The #removeRecursive(x,ui)# method is a recursive implementation of the -preceding algorithm: +O método #removeRecursive(x,ui)# é uma implementação recursiva do algoritmo anteriormente discutido: \codeimport{ods/BTree.removeRecursive(x,ui).removeSmallest(ui)} -Note that, after recursively removing the value #x# from the #i#th child of #u#, -#removeRecursive(x,ui)# needs to ensure that this child still has at -least $B-1$ keys. In the preceding code, this is done using a -method called #checkUnderflow(x,i)#, which checks for and corrects an -underflow in the #i#th child of #u#. Let #w# be the #i#th child of #u#. -If #w# has only $B-2$ keys, then this needs to be fixed. The fix -requires using a sibling of #w#. This can be either child $#i#+1$ of -#u# or child $#i#-1$ of #u#. We will usually use child $#i#-1$ of #u#, -which is the sibling, #v#, of #w# directly to its left. The only time -this doesn't work is when $#i#=0$, in which case we use the sibling -directly to #w#'s right. +Note que, após remover recursivamente o valor #x# o #i#-ésimo filho de #u#, +#removeRecursive(x,ui)# precisa garantir que esse filho ainda tem pelo menos +$B-1$ chaves. No código precedente, isso é feito usando um método chamado +#checkUnderflow(x,i)#, que verifica se ocorreu underflow e o corrije no #i#-ésimo filho de #u#. Seja #w# o #i#-ésimo filho de #u#. +Se #w# tem somente +$B-2$ chaves, então isso precisa se corrigido. +A correção requer usar um irmão de #w#. +Ele pode ser o filho +$#i#+1$ ou $#i#-1$ de #u#. +Normalmente, iremos usar o filho +$#i#-1$ de #u#, que é o irmão #v# de #w# diretamente à sua esquerda. +A única vez que isso não funciona é quando +$#i#=0$ e nesse caso em que usamos o irmão de #w# diretamente à sua direita. \codeimport{ods/BTree.checkUnderflow(u,i)} -In the following, we focus on the case when $#i#\neq 0$ so that any -underflow at the #i#th child of #u# will be corrected with the help -of the $(#i#-1)$st child of #u#. The case $#i#=0$ is similar and the -details can be found in the accompanying source code. +A seguir, nos concentramos no caso em que + $#i#\neq 0$ tal que qualquer underflow +no #i#-ésimo filho de #u# será corrigido com a ajuda +do filho +$(#i#-1)$ de #u#. O caso $#i#=0$ é similar e os detalhes podem ser encontrados +no código que acompanha este livro. -To fix an underflow at node #w#, we need to find more keys (and possibly -also children), for #w#. There are two ways to do this: +Para corrigir um underflow no nodo #w#, precisamos achar mais chaves +(e possivelmente também filhos) para #w#. Existem duas formas de fazer isso: \begin{description} - \item[Borrowing:] - \index{borrow}% - If #w# has a sibling, #v#, with more than $B-1$ keys, - then #w# can borrow some keys (and possibly also children) from #v#. - More specifically, if #v# stores #size(v)# keys, then between them, - #v# and #w# have a total of + \item[Empréstimo:] + \index{empréstimo}% + Se #w# tem um irmão #v# com mais de $B-1$ chaves, então #w# pode emprestar algumas chaves (e possivelmente também filhos) de #v#. + Mais especificamente, se #v# guarda #size(v)# chaves, então entre eles, + #v# e #w# tem um total de \[ B-2 + #size(w)# \ge 2B-2 \] - keys. We can therefore shift keys from #v# to #w# so that each of - #v# and #w# has at least $B-1$ keys. This process is illustrated in + chaves. Podemos portanto deslocar chaves de #v# a #w# tal que #v# e #w# têm pelo menos $B-1$ chaves. Esse processo está ilustrado na \figref{btree-borrow}. \begin{figure} @@ -508,21 +548,22 @@ \subsection{Removal} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-borrow-2} \\ \end{tabular}} - \caption[Borrowing in a $B$-tree]{If #v# has more than $B-1$ keys, - then #w# can borrow keys from #v#.} + \caption[Empréstimo em uma $B$-tree]{Se #v# tem mais que $B-1$ chaves, então + #w# pode emprestar chaves de #v#.} \figlabel{btree-borrow} \end{figure} - \item[Merging:] - \index{merge}% - If #v# has only $B-1$ keys, we must do something more - drastic, since #v# cannot afford to give any keys to #w#. Therefore, - we \emph{merge} #v# and #w# as shown in \figref{btree-merge}. The merge - operation is the opposite of the split operation. It takes two nodes - that contain a total of $2B-3$ keys and merges them into a single - node that contains $2B-2$ keys. (The additional key comes from the - fact that, when we merge #v# and #w#, their common parent, #u#, now - has one less child and therefore needs to give up one of its keys.) + \item[Junção:] + \index{junção}% + Se #v# tem somente $B-1$ chaves, precisamos fazer algo mais drástico + , como #v# não pode emprestar chaves a #w#. Portanto, + \emph{juntamos} #v# e #w# conforme mostrado na \figref{btree-merge}. + A operação de junção é o oposto da operação de divisão. + + Ela usa dois nodos que contém um total de $2B-3$ chaves e junta elees em um + único nodo que contém $2B-2$ chaves. (A chave adicional vem do + fato que, quando juntamos #v# e #w#, o pai deles #u# agora tem + um filho a menos e portanto precisa ceder uma de suas chaves.) \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -531,312 +572,312 @@ \subsection{Removal} \multicolumn{1}{c}{$\Downarrow$} \\[2ex] \includegraphics[width=\ScaleIfNeeded]{figs/btree-merge-2} \\ \end{tabular}} - \caption[Merging in a $B$-tree]{Merging two siblings #v# and #w# - in a $B$-tree ($B=3$).} + \caption[Junção em uma $B$-tree]{Junção de dois irmãos #v# e #w# + em uma $B$-tree ($B=3$).} \figlabel{btree-merge} \end{figure} \end{description} \codeimport{ods/BTree.checkUnderflowNonZero(u,i).checkUnderflowZero(u,i)} -To summarize, the #remove(x)# method in a $B$-tree follows a root to -leaf path, removes a key #x'# from a leaf, #u#, and then performs zero -or more merge operations involving #u# and its ancestors, and performs -at most one borrowing operation. Since each merge and borrow operation -involves modifying only three nodes, and only $O(\log_B #n#)$ of these -operations occur, the entire process takes $O(\log_B #n#)$ time in the -external memory model. Again, however, each merge and borrow operation -takes $O(B)$ time in the word-RAM model, so (for now) the most we can -say about the running time required by #remove(x)# in the word-RAM model -is that it is $O(B\log_B #n#)$. - -\subsection{Amortized Analysis of $B$-Trees} +Para resumir, o método #remove(x)# em uma $B$-tree segue um caminho da raiz para a folha, remove uma chave #x'# de uma folha #u# e então realiza zero +ou mais operação de junção envolvendo #u# e seus ancestrais e também +realiza no máximo uma operação de empréstimo. + +Como cada operação de junção e empréstimo, envolve modificar somente três nodos +e somente +$O(\log_B #n#)$ dessas operações acontecem, o processo completo leva +$O(\log_B #n#)$ de tempo no modelo de memória externa. Novamente, entretanto, cada operação de junção e empréstimo leva cerca de $O(B)$ de tempo no modelo RAM, +então (por ora) o máximo que podemos dizer sobre o tempo de execução necessário +para #remove(x)# no modelo RAM é que é +$O(B\log_B #n#)$. + +\subsection{Análise Amortizada da $B$-Tree} \seclabel{btree-amortized} -Thus far, we have shown that +Até agora, mostramos que \begin{enumerate} - \item In the external memory model, the running time of #find(x)#, - #add(x)#, and #remove(x)# in a $B$-tree is $O(\log_B #n#)$. - \item In the word-RAM model, the running time of #find(x)# is $O(\log #n#)$ - and the running time of #add(x)# and #remove(x)# is $O(B\log #n#)$. + \item No modelo de memória externa, o tempo de execução de #find(x)#, + #add(x)# e #remove(x)# em uma $B$-tree é $O(\log_B #n#)$. + \item No modelo RAM com palavras de #w# bits, o tempo de execução de #find(x)# é $O(\log #n#)$ e o tempo de execução de #add(x)# e #remove(x)# é $O(B\log #n#)$. \end{enumerate} -The following lemma shows that, so far, we have overestimated the number of merge and split operations performed by $B$-trees. +O lema a seguir mostra que, até o momento, superestimamos o número de operação de junção e divisão realizado por uma +$B$-tree. \begin{lem}\lemlabel{btree-split} - Starting with an empty $B$-tree and performing any sequence of - $m$ #add(x)# and #remove(x)# operations results in at most $3m/2$ splits, - merges, and borrows being performed. + Começando com uma $B$-tree vazia e realizando qualquer sequência de + $m$ operações #add(x)# e #remove(x)# resulta em até $3m/2$ divisões, + junções e empréstimos realizados. \end{lem} \begin{proof} - The proof of this has already been sketched in - \secref{redblack-summary} for the special case in which $B=2$. - The lemma can be proven using a credit scheme, - \index{credit scheme}% - in which + A prova desse fato foi anteriormente rascunhada na + \secref{redblack-summary} para o caso especial em que $B=2$. + O lema pode ser provado usando um esquema de créditos, + \index{esquema de créditos}% + em que \begin{enumerate} - \item each split, merge, or borrow operation is paid for with two - credits, i.e., a credit is removed each time one of these operations - occurs; and - \item at most three credits are created during any #add(x)# or - #remove(x)# operation. + \item cada operação de divisão, junção e empréstimo é pago com dois créditos, isto é, um crédito é removido cada vez que uma dessas operações ocorre; e + \item no máximo três créditos são criados durante qualquer operação #add(x)# ou + #remove(x)#. \end{enumerate} - Since at most $3m$ credits are ever created and each split, - merge, and borrow is paid for with with two credits, it follows - that at most $3m/2$ splits, merges, and borrows are performed. - These credits are illustrated using the \cent\ symbol in - Figures~\ref{fig:btree-split}, \ref{fig:btree-borrow}, and + Como no máximo $3m$ créditos são criados e cada divisão, junção e + empréstimo é pago com dois créditos, segue que no máximo + $3m/2$ divisões, junções e empréstimos são realizados. + Esses créditos são ilustrados usando o símbolo + \cent\ nas + Figuras~\ref{fig:btree-split}, \ref{fig:btree-borrow} e \ref{fig:btree-merge}. - To keep track of these credits the proof maintains the following - \emph{credit invariant}: - \index{credit invariant}% - Any non-root node with $B-1$ keys stores one - credit and any node with $2B-1$ keys stores three credits. A node - that stores at least $B$ keys and most $2B-2$ keys need not store - any credits. What remains is to show that we can maintain the credit - invariant and satisfy properties 1 and 2, above, during each #add(x)# - and #remove(x)# operation. - - \paragraph{Adding:} - The #add(x)# method does not perform any merges or borrows, so we - need only consider split operations that occur as a result of calls - to #add(x)#. - - Each split operation occurs because a key is added to a node, #u#, that - already contains $2B-1$ keys. When this happens, #u# is split into two - nodes, #u'# and #u''# having $B-1$ and $B$ keys, respectively. Prior to - this operation, #u# was storing $2B-1$ keys, and hence three credits. - Two of these credits can be used to pay for the split and the other - credit can be given to #u'# (which has $B-1$ keys) to maintain the - credit invariant. Therefore, we can pay for the split and maintain - the credit invariant during any split. - - The only other modification to nodes that occur during an #add(x)# - operation happens after all splits, if any, are complete. This - modification involves adding a new key to some node #u'#. If, prior - to this, #u'# had $2B-2$ children, then it now has $2B-1$ children and - must therefore receive three credits. These are the only credits given - out by the #add(x)# method. - - \paragraph{Removing:} - During a call to #remove(x)#, zero or more merges occur and are possibly - followed by a single borrow. Each merge occurs because two nodes, - #v# and #w#, each of which had exactly $B-1$ keys prior to calling - #remove(x)# were merged into a single node with exactly $2B-2$ keys. - Each such merge therefore frees up two credits that can be used to - pay for the merge. - - After any merges are performed, at most one borrow operation occurs, - after which no further merges or borrows occur. This borrow operation - only occurs if we remove a key from a leaf, #v#, that has $B-1$ keys. - The node #v# therefore has one credit, and this credit goes towards - the cost of the borrow. This single credit is not enough to pay for - the borrow, so we create one credit to complete the payment. - - At this point, we have created one credit and we still need to show - that the credit invariant can be maintained. In the worst case, - #v#'s sibling, #w#, has exactly $B$ keys before the borrow so that, - afterwards, both #v# and #w# have $B-1$ keys. This means that #v# and - #w# each should be storing a credit when the operation is complete. - Therefore, in this case, we create an additional two credits to give to - #v# and #w#. Since a borrow happens at most once during a #remove(x)# - operation, this means that we create at most three credits, as required. - - If the #remove(x)# operation does not include a borrow operation, this - is because it finishes by removing a key from some node that, prior - to the operation, had $B$ or more keys. In the worst case, this node - had exactly $B$ keys, so that it now has $B-1$ keys and must be given - one credit, which we create. - - In either case---whether the removal finishes with a borrow - operation or not---at most three credits need to be created during a - call to #remove(x)# to maintain the credit invariant and pay for all - borrows and merges that occur. This completes the proof of the lemma. + Para manter o controle desses créditos, a prova mantém + o seguinte \emph{invariante de crédito}: + \index{invariante de crédito}% + Qualquer nodo que não seja a raiz com $B-1$ chaves guarda um crédito + e qualquer nodo com + $2B-1$ chaves guarda três créditos. Um nodo que guarda pelo menos + $B$ três créditos. + Um nodo que guarda pelo menos $B$ chaves e até +$2B-2$ chaves não precisa guardar nenhum crédito. + O que resta mostrar é que podemos manter o invariante de crédito e satisfazer as propriedades 1 e 2 acima durante cada operação +#add(x)# + e #remove(x)#. + + \paragraph{Adição:} + O método #add(x)# não faz nenhuma junção ou empréstimo, então + precisamos somente considerar operações de divisão que como resultado de chamadas a #add(x)#. + + Cada operação de divisão ocorre porque uma chave é adicionada a um nodo #u# + que possui + $2B-1$ chaves. Quando isso acontece, #u# é dividido em dois nodos, #u'# e #u''# com $B-1$ e $B$ chaves, respectivamente. Antes de operação, #u# guardava + $2B-1$ chaves e portanto três créditos. + Dois desses créditos podem ser usados para pagar pela divisão e o outro + crédito pode ser dado para #u'# (que tem $B-1$ chaves) para manter a + invariente de crédito. Portanto, podemos pagar pela divisão e manter + a invariante de crédito durante qualquer divisão. + + O única outra modificação em nodos que ocorrem durante uma operação + #add(x)# acontece após todas divisões, se houverem, forem completadas. +Essa modificação envolve adicionar uma nova chave a algum nodo #u'#. +Se, antes disso, +#u'# tinha $2B-2$ filhos, então ele tem agora $2B-1$ filhos e deve portanto + receber três créditos. Esses são os únicos créditos cedidos pelo método #add(x)#. + + \paragraph{Remoção:} + Durante uma chamada a +#remove(x)#, zero ou mais junções ocorrem e são possivelmente seguidas por um único empréstimo. Cada junção ocorre porque dois nodos, + #v# e #w#, possuem cada um $B-1$ chaves antes da chamada ao método + #remove(x)# são juntados em um único nodo com exatamente $2B-2$ chaves. +Cada uma dessas junçõesa portanto liberar dois créditos que podem ser usados para pagar pela junção. + + Após as junções são realizada, ocorre no máximo uma operação e depois dela nenhuma junção ou empréstimo adicional é realizada. +Essa operação de empréstimo somente ocorre se removemos uma chave de uma +folha #v# que tem $B-1$ chaves. +O nodo #v# portanto tem um crédito e esse crédito vai pagar o custo do +empréstimo. Esse crédito não é suficiente para pagar o empréstimo, então +criamos um crédito para completar o pagamento. + +Nesse ponto, criamos um crédito e ainda necessitamos mostrar que a invariante +de crédito pode ser mantida. No pior caso, o irmão de #v#, #w#, tem +exatamento $B$ chaves antes do empréstimo tal que, após isso, tanto #v# quanto #w# +tem $B-1$ chaves. Isso significa que #v# e #w# devem estar guardando um +crédito cada quando a operação terminar. +Portanto, nesse caso, criamos dois créditos adicionais para dar para #v# e #w#. +Como um empréstimo acontece no máximo uma vez durante uma operação +#remove(x)#, isso significa que criamos no máximo três créditos, conforme necessário. + +Se a operação #remove(x)# não inclui uma operação de empréstimo, isso é porque ela termina removendo uma chave de algum nodo que, antes da operação, tinha $B$ ou mais chaves. No pior caso, esse nodo tinha $B$ chaves, então que ele tem agora $B-1$ chaves e precisa receber um crédito, que criamos. + +Nos dois casos---se a remoção termina com uma operação de empréstimo ou não--- +no máximo três créditos precisam ser criados durante uma chamada a + #remove(x)# para manter a invariante de crédito e pagar por todos + os empréstimos e junções que ocorrerem. Isso completa a prova do lema. \end{proof} -The purpose of \lemref{btree-split} is to show that, in the word-RAM -model the cost of splits, merges and joins during a sequence of $m$ -#add(x)# and #remove(x)# operations is only $O(Bm)$. That is, the -amortized cost per operation is only $O(B)$, so the amortized cost -of #add(x)# and #remove(x)# in the word-RAM model is $O(B+\log #n#)$. -This is summarized by the following pair of theorems: - -\begin{thm}[External Memory $B$-Trees] - A #BTree# implements the #SSet# interface. In the external memory model, - a #BTree# supports the operations #add(x)#, #remove(x)#, and #find(x)# - in $O(\log_B #n#)$ time per operation. +O propósito do + \lemref{btree-split} é mostrar que, no modelo RAM com palavras de #w# bits, + o custo de divisões e junções durante %and joins??? - seria borrows?? +uma sequência de $m$ operações +#add(x)# e #remove(x)# é somente $O(Bm)$. Isto é, o custo amortizado +por operação é somente +$O(B)$, então o custo amortizado de +#add(x)# e #remove(x)# no modelo RAM com palavras de #w# bits é $O(B+\log #n#)$. +Isso é resumido no seguinte par de teoremas: + +\begin{thm}[$B$-Tree em Memória Externa] + Uma #BTree# implementa a interface #SSet#. No modelo de memória externa, + uma #BTree# suporta as operações #add(x)#, #remove(x)# e #find(x)# + em tempo $O(\log_B #n#)$ por operação. \end{thm} -\begin{thm}[Word-RAM $B$-Trees] - A #BTree# implements the #SSet# interface. In the word-RAM model, and - ignoring the cost of splits, merges, and borrows, a #BTree# supports - the operations #add(x)#, #remove(x)#, and #find(x)# in $O(\log #n#)$ - time per operation. - Furthermore, beginning with an empty #BTree#, any sequence of $m$ - #add(x)# and #remove(x)# operations results in a total of $O(Bm)$ - time spent performing splits, merges, and borrows. +\begin{thm}[$B$-Tree em RAM com palavras de #w# bits] + Uma #BTree# implementa a interface #SSet#. No modelo RAM com palavras de #w# bits, e ignorando o custo das divisões, junções e empréstimos, uma +#BTree# provê as operações + #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log #n#)$ por operação. + Adicionalmente, iciando com uma +#BTree# vazia, qualquer sequência de $m$ operações + #add(x)# e #remove(x)# resulta em um total de $O(Bm)$ tempo gasto + realizando divisões, junções e empréstimos. \end{thm} -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -The external memory model of computation was introduced by Aggarwal and -Vitter \cite{av88}. It is sometimes also called the \emph{I/O model} -\index{I/O model}% -or the \emph{disk access model}. -\index{disk access model}% +O modelo de memória externa de computação foi introduzido por +Aggarwal e Vitter \cite{av88}. Ele também é conhecido como o \emph{modelo de I/O} +\index{modelo de I/O}% +ou como o \emph{modelo de acesso a disco}. +\index{modelo de acesso a disco}% -$B$-Trees are to external memory searching what binary search trees -are to internal memory searching. $B$-trees were introduced by Bayer -and McCreight \cite{bm70} in 1970 and, less than ten years later, the -title of Comer's ACM Computing Surveys article referred to -them as ubiquitous \cite{c79}. +$B$-Tree é para busca em memória externa o que árvores binárias de busca são para busca em memória interna. +$B$-trees foram inventadas por Bayer +e McCreight \cite{bm70} em 1970 e, em menos de 10 anos, o título do artigo de Comer na revista \emph{ACM Computing Surveys} as referia como onipresente \cite{c79}. -Like binary search trees, there are many variants of $B$-Trees, including +Assim como para árvores binárias de busca, existem muitas variantes de $B$-Trees, incluindo $B^+$-trees, \index{B+-tree@$B^+$-tree}% $B^*$-trees, \index{B*-tree@$B^*$-tree}% -and counted $B$-trees. +e \emph{counted $B$-trees}. \index{conted $B$-tree}% -$B$-trees are indeed -ubiquitous and are the primary data structure in many file systems, -including Apple's HFS+, +$B$-trees são realmente onipresentes e são a estrutura de dados primária +em muitos sistemas de arquivos, incluindo o HFS+ da Apple, \index{HFS+}% -Microsoft's NTFS, +o NTFS da Microsoft, \index{NTFS}% -and Linux's Ext4; +e o Ext4 usado no Linux; \index{Ext4}% -every -major database system; and key-value stores used in cloud computing. -Graefe's recent survey \cite{g10} provides a 200+ page overview of the -many modern applications, variants, and optimizations of $B$-trees. - -$B$-trees implement the #SSet# interface. If only the #USet# interface -is needed, then external memory hashing -\index{external memory hashing}% -could be used as an alternative -to $B$-trees. External memory hashing schemes do exist; see, for example, -Jensen and Pagh~\cite{jp08}. These schemes implement the #USet# operations -in $O(1)$ expected time in the external memory model. However, for a -variety of reasons, many applications still use $B$-trees even though -they only require #USet# operations. - -One reason $B$-trees are such a popular choice is that they often perform -better than their $O(\log_B #n#)$ running time bounds suggest. The -reason for this is that, in external memory settings, the value of $B$ -is typically quite large---in the hundreds or even thousands. This means -that 99\% or even 99.9\% of the data in a $B$-tree is stored in the -leaves. In a database system with a large memory, it may be possible -to cache all the internal nodes of a $B$-tree in RAM, since they only -represent 1\% or 0.1\% of the total data set. When this happens, -this means that a search in a $B$-tree involves a very fast search in -RAM, through the internal nodes, followed by a single external memory -access to retrieve a leaf. +todos os principais sistemas de banco de dados; e +armazéns de chave-valor usados em computação em núvem. +O levantamento recente de Graefe \cite{g10} provê uma visão geral +de mais de 200 páginas de muitas aplicações modernas, variantes e otimizações +de $B$-trees. + +$B$-trees implementa a interface #SSet#. Se a interface #USet# for necessária, então hashing em memória externa +\index{hashing em memória externa}% +pode ser usado como alternativa à $B$-tree. +Esquemas de hashing em memória externa existem; ver, por exemplo, +Jensen e Pagh~\cite{jp08}. Esse esquemas implementam as operações #USet# +em tempo $O(1)$ esperado no modelo de memória externa. Entretanto, +devido a vários motivos, muitas aplicações ainda usam +$B$-tree embora somente sejam necessárias operações +#USet#. + +Uma razão de $B$-trees serem tão populares é elas funcionam melhor que o limitante +$O(\log_B #n#)$ de tempo de execução sugere. A razão para isso é, em cenários de memória externa, o valor de $B$ é tipicamente bem alto -- na casa das centenas ou milhares. Isso significa que +99\% ou mesmo 99.9\% dos dados em uma $B$-tree está guardado nas folhas. + +Em um sistema de banco de dados com muita memória, pode ser possível +manter todos os nodos internos de uma $B$-tree em RAM, pois eles representam +somente +1\% ou 0.1\% do conjunto total de dados. Quando isso acontece, +isso significa que uma busca em uma $B$-tree envolve uma busca bem rápida na RAM, +nos nodos internos, seguida por um único acesso à memória externa para obter uma folha. \begin{exc} - Show what happens when the keys 1.5 and then 7.5 are added to the - $B$-tree in \figref{btree}. + Mostre o que acontece quando as chaves +1.5 e então 7.5 são adicionadas à + $B$-tree em \figref{btree}. \end{exc} \begin{exc} - Show what happens when the keys 3 and then 4 are removed from the - $B$-tree in \figref{btree}. + Mostre o que acontece quando as chaves +3 e então 4 são removidas da + $B$-tree em \figref{btree}. \end{exc} \begin{exc} - What is the maximum number of internal nodes in a $B$-tree that stores - #n# keys (as a function of #n# and $B$)? + Qual é o número máximo de nodos internos em uma +$B$-tree que guarda + #n# chaves (como uma função de #n# e $B$)? \end{exc} \begin{exc} - The introduction to this chapter claims that $B$-trees only need an - internal memory of size $O(B+\log_B#n#)$. However, the implementation - given here actually requires more memory. + A introdução deste capítulo afirma que $B$-trees somente + precisam de uma memória interna de tamanho + $O(B+\log_B#n#)$. Porém, a implementação dada neste livro na verdade precisa de mais memória. \begin{enumerate} - \item Show that the implementation of the #add(x)# and #remove(x)# - methods given in this chapter use an internal memory - proportional to $B\log_B #n#$. - \item Describe how these methods could be modified in order to reduce their memory - consumption to $O(B + \log_B #n#)$. + \item Mostre que a implementação dos métodos #add(x)# e #remove(x)# + dados neste capítulo usam memória interna proporcional a + $B\log_B #n#$. + \item Descreve como esses métodos poderiam ser modificados a fim de reduzir o consumo de memória a +$O(B + \log_B #n#)$. \end{enumerate} \end{exc} \begin{exc} - Draw the credits used in the proof of \lemref{btree-split} on the trees - in Figures~\ref{fig:btree-add} and \ref{fig:btree-remove-full}. Verify - that (with three additional credits) it is possible to pay for the splits, - merges, and borrows and maintain the credit invariant. + Desenhe os créditos usados na prova do + \lemref{btree-split} nas árvores das + Figuras~\ref{fig:btree-add} e \ref{fig:btree-remove-full}. Verifique] + que (com três créditos adicionais) é possível pagar pelas divisões, + junções e empréstimos e manter a invariante de crédito. \end{exc} \begin{exc} - Design a modified version of a $B$-tree in which nodes can have anywhere - from $B$ up to $3B$ children (and hence $B-1$ up to $3B-1$ keys). - Show that this new version of $B$-trees performs only $O(m/B)$ splits, - merges, and borrows during a sequence of $m$ operations. (Hint: - For this to work, you will have to be more agressive with merging, - sometimes merging two nodes before it is strictly necessary.) + Projete uma versão modificada de uma +$B$-tree em que nodos pode ter de + $B$ a $3B$ filhos (e portanto de $B-1$ até $3B-1$ chaves). + Mostre que essa nova versão de +$B$-trees realiza somente $O(m/B)$ divisões, junções e empréstimos durante + uma sequência de $m$ operações. (Dica: + para isso funcionar, você deve ser mais agressivo com a junção e + às vezes juntar dois novos antes que seja estritamente necessário.) \end{exc} \begin{exc} - In this exercise, you will design a modified method of splitting and - merging in $B$-trees that asymptotically reduces the number of splits, - borrows and merges by considering up to three nodes at a time. + Neste exercício, você irá projetar um método modificado de dividir e + juntar em + $B$-trees que assintoticamente reduz o número de divisões, empréstimos e junções ao considerar três nodos por vez. \begin{enumerate} - \item Let #u# be an overfull node and let #v# be a sibling immediately - to the right of #u#. There are two ways to fix the overflow at #u#: + \item Seja #u# um nodo sobrecarregado e seja #v# um irmão imediatamente à direita de #u#. + Existem duas forma de concertar o excesso de chaves em #u#: \begin{enumerate} - \item #u# can give some of its keys to #v#; or - \item #u# can be split and the keys of #u# and #v# can be evenly - distributed among #u#, #v#, and the newly created node, #w#. + \item #u# pode dar algumas de suas chaves a #v#; ou + \item #u# pode ser dividido e as chaves de #u# e #v# podem ser igualmente distribuídas entre +#u#, #v# e o novo nodo #w#. \end{enumerate} - Show that this can always be done in such a way that, after the - operation, each of the (at most 3) affected nodes has at least - $B+\alpha B$ keys and at most $2B-\alpha B$ keys, for some constant + Mostre que isso sempre pode ser feito de maneira que, após a operação, + cada um dos nodos afetados (no máximo 3) tem pelo menos + $B+\alpha B$ chaves e no máximo $2B-\alpha B$ chaves, para alguma constante $\alpha > 0$. - \item Let #u# be an underfull node and let #v# and #w# be siblings of #u# - There are two ways to fix the underflow at #u#: + \item Seja #u# um nodo com falta de chaves e sejam #v# e #w# irmão de #u#, + existem duas maneiras de corrigir a falta de chaves em #u#: \begin{enumerate} - \item keys can be redistributed among #u#, #v#, and #w#; or - \item #u#, #v#, and #w# can be merged into two nodes and the keys - of #u#, #v#, and #w# can be redistributed amongst these nodes. + \item chaves podem ser redistribuídas entre #u#, #v# e #w#; ou + \item #u#, #v# e #w# podem ser juntados em dois nodos e as chaves de + #u#, #v# e #w# podem ser redistribuídos entre esses nodos. \end{enumerate} - Show that this can always be done in such a way that, after the - operation, each of the (at most 3) affected nodes has at least - $B+\alpha B$ keys and at most $2B-\alpha B$ keys, for some constant + Mostre que isso pode ser sempre feito de forma que, após a operações, + cada um dos nodos afetados (no máximo 3) tem pelo menos + $B+\alpha B$ chaves e no máximo $2B-\alpha B$ chaves, para alguma constante $\alpha > 0$. - \item Show that, with these modifications, the number of - merges, borrows, and splits that occur during $m$ operations is $O(m/B)$. + \item Mostre que, com essas modificaões, o número de junções, empréstimos e divisões que ocorrem durante $m$ operações é $O(m/B)$. \end{enumerate} \end{exc} \begin{exc} - A $B^+$-tree, illustrated in \figref{bplustree} stores every key in a - leaf and keeps its leaves stored as a doubly-linked list. As usual, - each leaf stores between $B-1$ and $2B-1$ keys. Above this list is - a standard $B$-tree that stores the largest value from each leaf but - the last. + Uma $B^+$-tree, ilustrada na \figref{bplustree} guarda todas as chaves em folhas e mantém suas folhas guardadas como uma lista duplamente encadeada. +Como de costume, cada folha guarda entre + $B-1$ e $2B-1$ chaves. Acima dessa lista está uma + $B$-tree padrão que gaurda o maior valor de cada folha exceto o último. \begin{enumerate} - \item Describe fast implementations of #add(x)#, #remove(x)#, - and #find(x)# in a $B^+$-tree. - \item Explain how to efficiently implement the #findRange(x,y)# method, - that reports all values - greater than #x# and less than or equal to #y#, in - a $B^+$-tree. - \item Implement a class, #BPlusTree#, that implements #find(x)#, - #add(x)#, #remove(x)#, and #findRange(x,y)#. + \item Descreva implementações rápidas de #add(x)#, #remove(x)#x, + e #find(x)# em uma $B^+$-tree. + \item Explique como implementar eficientemente o método #findRange(x,y)#, + que encontra todos os valores maior que #x# e menores que ou iguais a #y#, + em uma $B^+$-tree. + \item Implemente uma classe, #BPlusTree#, que implementa #find(x)#, + #add(x)#, #remove(x)# e #findRange(x,y)#. \index{BPlusTree@#BPlusTree#}% - \item $B^+$-trees duplicate some of the keys because they are stored - both in the $B$-tree and in the list. Explain why this duplication - does not add up to much for large values of $B$. + \item $B^+$-trees duplicam algumas das chaves porque elas são guardados na + $B$-tree e na lista. Explique porque essa duplicação não é excessiva para + valores altos de $B$. \end{enumerate} \end{exc} \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/bplustree}} - \caption{A $B^+$-tree is a $B$-tree on top of a doubly-linked list of blocks.} + \caption{Uma $B^+$-tree é uma $B$-tree emcima de uma lista duplamente encadeada de blocos.} \figlabel{bplustree} \end{figure} diff --git a/latex/hashing.tex b/latex/hashing.tex index 922ae922..26286937 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -596,7 +596,7 @@ \subsection{Resumo} \end{thm} \subsection{Hashing por tabulação } -\seclabel{tabulação} +\seclabel{tabulation} \index{hashing por tabulação}% Ao analisar a estrutura @@ -979,7 +979,7 @@ \section{Discussão e Exercícios} \index{hashing universal }% \index{universal!hashing}% e descreveram várias funções hash para diferentes cenários \cite{cw79}. -Hashing por tabulação, descrita em \secref{tabulation}, foi proposta por Carter +Hashing por tabulação, descrita na \secref{tabulation}, foi proposta por Carter e Wegman \cite{cw79}, mas sua análise, quando aplicada a sondagem linear (e vários outros esquemas de tabela hash) é creditada a P\v{a}tra\c{s}cu e Thorup \cite{pt12}. diff --git a/latex/skiplists.tex b/latex/skiplists.tex index 50d54d6e..6db96df8 100644 --- a/latex/skiplists.tex +++ b/latex/skiplists.tex @@ -357,7 +357,7 @@ \section{Análise de Skiplists} A probabilidade que qualquer elemento em particular, #x#, seja incluído na lista $L_{#r#}$ é $1/2^{#r#}$, então o número esperado de nodos em $L_{#r#}$ - é $#n#/2^{#r#}$.\footnote{Veja \secref{randomização} para ver como isso é obtido usando variáveis indicadores e linearidade de esperança.} + é $#n#/2^{#r#}$.\footnote{Veja \secref{randomization} para ver como isso é obtido usando variáveis indicadores e linearidade de esperança.} Portanto, o número esperado total de nodos em todas as listas é \[ \sum_{#r#=0}^\infty #n#/2^{#r#} = #n#(1+1/2+1/4+1/8+\cdots) = 2#n# \enspace . \qedhere \] \end{proof} From 26a4a15968a4c70d5030ee9fb3ebdcc3a83eb0fc Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 4 Sep 2020 18:40:37 -0300 Subject: [PATCH 36/66] starting a complete first revision of translation to portuguese --- latex/ack.tex | 5 +---- latex/btree.tex | 2 +- latex/why.tex | 13 ++++++------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/latex/ack.tex b/latex/ack.tex index f07e1dcf..7bb3c159 100644 --- a/latex/ack.tex +++ b/latex/ack.tex @@ -1,10 +1,7 @@ \chapter*{Agradecimentos} \addcontentsline{toc}{chapter}{Agradecimentos} -Agradeço à todos que ajudaram e motivaram a fazer esta tradução. - -Devido à natureza pessoal de agradecimentos, vou manter os agradecimentos originais deste -livro a seguir. +Agradeço à todos que ajudaram e motivaram a fazer esta tradução. Devido à natureza pessoal de agradecimentos, vou manter os agradecimentos originais deste livro a seguir. I am grateful to Nima~Hoda, who spent a summer tirelessly proofreading many of the chapters in this book; to the students in the Fall 2011 diff --git a/latex/btree.tex b/latex/btree.tex index dc6f052c..a6474b14 100644 --- a/latex/btree.tex +++ b/latex/btree.tex @@ -850,7 +850,7 @@ \section{Discussão e Exercícios} cada um dos nodos afetados (no máximo 3) tem pelo menos $B+\alpha B$ chaves e no máximo $2B-\alpha B$ chaves, para alguma constante $\alpha > 0$. - \item Mostre que, com essas modificaões, o número de junções, empréstimos e divisões que ocorrem durante $m$ operações é $O(m/B)$. + \item Mostre que, com essas modificações, o número de junções, empréstimos e divisões que ocorrem durante $m$ operações é $O(m/B)$. \end{enumerate} \end{exc} diff --git a/latex/why.tex b/latex/why.tex index 5c44b6b4..4048baaf 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -2,7 +2,7 @@ \chapter*{Por que este livro?} \addcontentsline{toc}{chapter}{Por que este livro?} Existem muitos livros introdutórios à disciplina de estruturas de dados. -Alguns deles são muito bons. A maior parte deles são pagos, e a +Alguns deles são muito bons. A maior parte deles são pagos e a grande maioridade de estudantes de Ciência da Computação e Sistemas de Informação irá gastar algum dinheiro em um livro de estruturas de dados. @@ -14,21 +14,20 @@ \chapter*{Por que este livro?} os quais podem não autorizar tais atualizações. (2)~O \emph{código-fonte} desses livros muitas vezes não está disponível. Isto é, os arquivos Word, WordPerfect, FrameMaker ou \LaTeX\ para o livro não são acessíveis e, ainda, a versão do -software que processa tais arquivos pode não estar mais disponível. +software que processa esses arquivos pode não estar mais disponível. A meta deste projeto é libertar estudantes de graduação de Ciência da Computação de ter que pagar por um livro introdutório à disciplina de estruturas de dados. Eu \footnote{The translator to Portuguese language is deeply grateful to the original author of this book Pat Morin for his decision which allows the availability of a good quality and free book in the -native language of my Brazilian students.} decidi implementar essa meta ao tratar esse livro como um projeto de Software Aberto +native language of my Brazilian students.} decidi implementar essa meta ao tratar esse livro como um projeto de Software Aberto% \index{Open Source}% \index{Software Aberta}% . Os arquivos-fonte originais em \LaTeX, \lang\ e scripts para montar este livro estão disponíveis para download a partir do website do autor\footnote{\url{http://opendatastructures.org}} -e também, de modo mais importante, em um site confiável de gerenciamento de códigos-fonte -.\footnote{\url{https://github.com/patmorin/ods}}\footnote{Tradução em português em \url{https://github.com/albertiniufu/ods}} +e também, de modo mais importante, em um site confiável de gerenciamento de códigos-fonte.\footnote{\url{https://github.com/patmorin/ods}}\footnote{Tradução em português em \url{https://github.com/albertiniufu/ods}} O código-fonte disponível é publicado sob uma licença Creative Commons Attribution, @@ -43,8 +42,8 @@ \chapter*{Por que este livro?} você deve reconhecer que a obra derivada contém código e/ou texto de \url{opendatastructures.org}. Qualquer pessoa pode contribuir com correções usando o sistema de gerenciamento -de códigos-fonte \texttt{git} -\index{git@\texttt{git}} +de códigos-fonte \texttt{git}% +\index{git@\texttt{git}}% . Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fonte deste livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). Minha esperança é que, ao assim fazer, este livro continuará a ser didático mesmo depois que o meu interesse no projeto, ou meu batimento cardíaco, desapareça (o que quer que aconteça primeiro). From 9e7f13d6f31df098d46c7f1dbaade14066489ef7 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 6 Sep 2020 09:21:34 -0300 Subject: [PATCH 37/66] including babel package and starting 1st revision in portuguese version --- latex/intro.tex | 10 +++++++--- latex/ods.tex | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/latex/intro.tex b/latex/intro.tex index 7751f8a7..f371106e 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -49,11 +49,15 @@ \section{A Necessidade de Eficiência} Imagine uma aplicação com um conjunto de dados de tamanho moderado, digamos de um milhão ($10^6$) de items. É razoável, na maior parte das aplicações, presumir que a aplicação vai precisar verificar cada item pelo menos uma vez. Isso significa que podemos esperar fazermos pelo menos um milhão ($10^6$) de buscas nesses dados. Se cada uma dessas $10^6$ buscas inspecionas cada um dos $10^6$ itens, isso resulta em um total de $10^6\times 10^6=10^{12}$ (mil bilhões) inspeções. -\paragraph{Velocidades do Processador:} No momento de escrita, mesmo um computador de mesa bem rápido não pode fazer mais de um bilhão ($10^9$) de operações por segundo.\footnote{Velocidades de computadores são até pouco gigaherts (bilhões de ciclos por segundo) e cada operação tipicamente exige alguns ciclos.} +\paragraph{Velocidades do Processador:} + +No momento de escrita, mesmo um computador de mesa bem rápido não pode fazer mais de um bilhão ($10^9$) de operações por segundo.\footnote{Velocidades de computadores são até pouco gigaherts (bilhões de ciclos por segundo) e cada operação tipicamente exige alguns ciclos.} Isso significa que essa aplicação vai levar pelo menos $10^{12}/10^9 = 1000$ -segundos, ou aproximadamente 16 minutos e 40 segundos. Dezesseis minutos é uma eternidade em tempos computacionais, mas uma pessoa pode estar disposta a suportá-lo (se ela estiver indo para um café). +segundos, ou aproximadamente 16 minutos e 40 segundos. Dezesseis minutos é uma eternidade em tempos computacionais, mas uma pessoa pode estar disposta a aceitá-lo (se ela estiver indo para um café). + +\paragraph{Conjuntos de dados maiores:} -\paragraph{Conjuntos de dados maiores:} Agora considere que uma empresa como a Google, +Agora considere que uma empresa como a Google, \index{Google}% que indexa mais de 8.5 bilhões de páginas web. De acordo com nossos cálculos, fazer qualquer tipo de consulta nesses dados levaria pelo menos 8.5 segundos. diff --git a/latex/ods.tex b/latex/ods.tex index a78c78a0..b5a0f776 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -1,4 +1,5 @@ \documentclass[10pt]{book} +\usepackage[portuguese]{babel} \usepackage{makeidx} \usepackage{framed} \setlength{\FrameSep}{0mm} @@ -253,8 +254,9 @@ } \fancyhead[CE]{\small\nouppercase{\leftmark}} % chapter title, left center -\fancyhead[CO]{\small\rightmarktitle} % section title, right center -\fancyhead[RO,LE]{\small\rightmarksection} +%%% The following fancyhead do not work with \usepackage{babel} FIXME +%\fancyhead[CO]{\small\rightmarktitle} % section title, right center % +%\fancyhead[RO,LE]{{\small\rightmarksection}} %% Include all the chapters one at a time \include{intro-lang} From 1cb3d75d97c00251c810a78f9ecdd1cffd59f6c0 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 6 Sep 2020 15:28:16 -0300 Subject: [PATCH 38/66] adding note to format numbers according to ABNT --- latex/ods.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/latex/ods.tex b/latex/ods.tex index b5a0f776..0b1f70b7 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -1,5 +1,6 @@ \documentclass[10pt]{book} \usepackage[portuguese]{babel} +%\usepackage{ziffer}%for presenting numbers according to brazilian ABNT norms FIXME-- not working \usepackage{makeidx} \usepackage{framed} \setlength{\FrameSep}{0mm} From 6812b42bad06d64d6b151feafef558f3aff68154 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 6 Sep 2020 15:28:41 -0300 Subject: [PATCH 39/66] continuing review of portuguese translation --- latex/intro.tex | 119 ++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/latex/intro.tex b/latex/intro.tex index f371106e..cf743668 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -2,8 +2,8 @@ \chapter{Introdução} \pagenumbering{arabic} Todo currículo de Ciência da Computação no mundo inclui ao menos uma disciplina sobre estruturas de dados e algoritmos. Estruturas de dados são \emph{realmente} importantes; -elas melhoram nossa qualidade de vida e até salvam vidas com certa frequência. -Muitas companhias que valem múltiplos milhões de dados and algumas que valem bilhões foram construídas em torno de estruturas de dados. +elas melhoram nossa qualidade de vida e até salvam vidas. +Muitas companhias que valem múltiplos milhões e bilhões de dólares foram construídas em torno de estruturas de dados. Como isso é possível? Se pararmos para pensar nisso, percebemos que interagimos com estruturas de dados constantemente. @@ -11,89 +11,88 @@ \chapter{Introdução} \item Abrir um arquivo: Sistema de arquivos \index{sistema de arquivos}% estruturas de dados são usadas para encontrar - as partes daquele arquivo em disco para serem recuperads. + as partes daquele arquivo em disco para serem recuperadas. Isso não é fácil; discos contém centenas de milhões de blocos. O conteúdo do seu arquivo pode estar armazenado em qualquer um deles. \item Procurar contato no telefone: Uma estrutura de dados é usada para procurar por um número de telefone na sua lista de contatos \index{lista de contatos}% baseada em informação parcial mesmo antes de você terminar de discar/digitar Isso não é fácil; -seu telefone pode ter informações sobre muitas pessoas---todos que você já entrou em contato via telefone ou email---e seu telefone não tem um processador muito rápido ou muita memória. +o seu telefone pode ter informações sobre muitas pessoas---todos que você já entrou em contato via telefone ou email---e seu telefone não tem um processador muito rápido ou muita memória. \item Entrar na sua rede social preferida: \index{rede social}% Os servidores da rede - usam sua informação de login para procurar as informações da sua conta. + usam a sua informação de login para procurar as informações da sua conta. Isso não é fácil; as redes sociais mais populares tem centenas de milhões de usuários ativos. \item Fazer uma busca na web: \index{busca na web}% - O motor de busca usa estruturas de dados para buscar - as páginas na web que contêm os seus termos de busca. + Um motor de busca usa estruturas de dados para buscar + por páginas na web que contêm os seus termos de busca. Isso não é fácil; existem mais de 8.5 bilhões de páginas web na Internet e cada página tem muitos potenciais termos de busca. \item Serviços de telefone de emergência (9-1-1): \index{serviços de emergência}\index{9-1-1}% - Os serviços de emergência procuram pelo seu número de telefone em uma estrutura de dados que mapeia números de telefone para endereços de forma tal que carros de polícia, ambulâncias ou os bombeiros possam ser enviados imediatamente. - Isso é importante; a pessoa fazendo a chamada pode não conseguir fornecer o exato endereço onde está e um atraso pode significar a diferença entre vida e morte. + Os serviços de emergência procuram pelo seu número de telefone em uma estrutura de dados que mapeia números de telefone em endereços para que carros de polícia, ambulâncias ou os bombeiros possam ser enviados imediatamente. + Isso é importante; a pessoa fazendo a chamada pode não conseguir fornecer o endereço exato de onde está e um atraso pode significar a diferença entre a vida e a morte. \end{itemize} \section{A Necessidade de Eficiência} -Na próxima seção, nós veremos as operações aceitas pelas estruturas de dados mais comuns. -Qualquer um com um pouco de experiência em programação verá que essas operações não são difíceis de implementar corretamente. +Na próxima seção, estudaremos as operações aceitas pelas estruturas de dados mais comuns. +Qualquer pessoa com um pouco de experiência em programação verá que essas operações não são difíceis de implementar corretamente. Podemos implementar ao percorrer/iterar todos os elementos de um array ou lista e possivelmente adicionar ou remover um elemento. Esse tipo de implementação é fácil, mas não muito eficiente. Isso realmente importa? Computadores estão ficando cada vez mais rápidos. -Talvez a implementação mais óbvia é boa o suficiente. +Talvez a implementação mais óbvia seja boa o suficiente. Vamos fazer alguns cálculos aproximados para verificar isso. \paragraph{Número de operações:} -Imagine uma aplicação com um conjunto de dados de tamanho moderado, digamos de um milhão ($10^6$) de items. -É razoável, na maior parte das aplicações, presumir que a aplicação vai precisar verificar cada item pelo menos uma vez. Isso significa que podemos esperar fazermos pelo menos um milhão ($10^6$) de buscas nesses dados. Se cada uma dessas $10^6$ buscas inspecionas cada um dos $10^6$ itens, isso resulta em um total de $10^6\times 10^6=10^{12}$ (mil bilhões) inspeções. +Imagine uma aplicação com um conjunto de dados de tamanho moderado, digamos de um milhão ($10^6$) de itens. +É razoável, na maior parte das aplicações, presumir que a aplicação vai precisar verificar cada item pelo menos uma vez. Isso significa que podemos esperar fazermos pelo menos um milhão ($10^6$) de buscas nesses dados. Se cada uma dessas $10^6$ buscas inspecionar cada um dos $10^6$ itens, isso resulta em um total de $10^6\times 10^6=10^{12}$ (mil bilhões) inspeções. \paragraph{Velocidades do Processador:} -No momento de escrita, mesmo um computador de mesa bem rápido não pode fazer mais de um bilhão ($10^9$) de operações por segundo.\footnote{Velocidades de computadores são até pouco gigaherts (bilhões de ciclos por segundo) e cada operação tipicamente exige alguns ciclos.} +No momento de escrita deste livro, mesmo um computador de mesa bem rápido não pode fazer mais de um bilhão ($10^9$) de operações por segundo.\footnote{Velocidades de computadores vão até poucos gigahertz (bilhões de ciclos por segundo) e cada operação tipicamente exige alguns ciclos.} Isso significa que essa aplicação vai levar pelo menos $10^{12}/10^9 = 1000$ -segundos, ou aproximadamente 16 minutos e 40 segundos. Dezesseis minutos é uma eternidade em tempos computacionais, mas uma pessoa pode estar disposta a aceitá-lo (se ela estiver indo para um café). +segundos, ou aproximadamente 16 minutos e 40 segundos. Dezesseis minutos é uma eternidade em tempos computacionais, mas uma pessoa pode estar disposta a aceitá-lo (por exemplo, se ela estiver indo para um café). \paragraph{Conjuntos de dados maiores:} Agora considere que uma empresa como a Google, \index{Google}% que indexa mais de 8.5 bilhões de páginas web. -De acordo com nossos cálculos, fazer qualquer tipo de consulta nesses dados levaria pelo menos 8.5 segundos. -Nós já sabemos que esse não é o caso; buscas web são feitas em bem menos de 8.5 segundos, e elas fazem consultas bem mais complicadas que somente pedindo se uma dadoa página está na lista de páginas indexadas. -No momento de escrita, Google recebe aproximadamente $4,500$ consultas por segundo, o que significa que eles precisariam de pelo menos $4,500 \times 8.5 =38,250$ servidores muito rápidos somente para manter esse tempo de resposta. +De acordo com os nossos cálculos, fazer qualquer tipo de consulta nesses dados levaria pelo menos 8.5 segundos. +Nós já sabemos que esse não é o caso; buscas web são respondidas em bem menos de 8.5 segundos e elas são bem mais complicadas do que somente verificar se uma dada página está na lista de páginas indexadas. +No momento de escrita deste livro, a Google recebe aproximadamente $4500$ consultas por segundo, o que significa que eles precisariam de pelo menos $4500 \times 8.5 =38,250$ excelentes servidores somente para manter esse tempo de resposta. \paragraph{A solução:} -Esses exemplos nos dizem que as implementações mais óbvias de estruturas de dados não escalam bem quando o número de itens, #n#, na estrutura de dados e o número de operações $m$, feitos na estrutura de dados são altos. -Nesses casos, o tempo (medido em, digamos, instruções de máquina) é aproximadamente $#n#\times m$. +Esses exemplos nos dizem que as implementações mais óbvias de estruturas de dados não funcionam bem quando o número de itens, #n#, na estrutura de dados e o número de operações $m$, feitas na estrutura de dados são altos. +Nesses casos, o tempo (medido em termos de, digamos, duração da execução de instruções de máquina) é proporcional a $#n#\times m$. -A solução, é claro, cuidadosamente organizar dados na estrutura de dados de forma que nem toda operação requer que todos os itens de dados sejam inspecionados. -Embora pareça inicialmente impossível, nós veremos estruturas de dados em que a busca requer olhar em apenas dois itens em média, independentemente do número de itens guardados na estrutura de dados. No nosso computador de um bilhão de instruções por segundo leva somente $0.000000002$ -segundos para buscar em uma estrutura de dados contendo um bilhão de itens (ou um trilhão ou um quatrilhão ou mesmo um quintilhão de items) +A solução, é claro, cuidadosamente organizar os dados na estrutura de dados de forma que nem toda operação exija que todos os itens de dados sejam inspecionados. +Embora pareça inicialmente impossível, descreveremos estruturas de dados em que a busca requer olhar em apenas dois itens em média, independentemente do número de itens guardados na estrutura de dados. No nosso computador de um bilhão de instruções por segundo, levaria somente $0.000000002$ +segundos para buscar em uma estrutura de dados contendo um bilhão de itens (ou um trilhão ou um quatrilhão ou mesmo um quintilhão de itens). Nós veremos também implementações de estruturas de dados que mantêm os itens em ordem, onde o número de itens inspecionados durante uma operação cresce muito lentamente em função do número de itens na estrutura de dados. Por exemplo, podemos manter um conjunto ordenado com um bilhão de itens e inspecionar no máximo 60 itens para qualquer operação. No nosso computador de um bilhão de instruções por segundo, essas operações levam $0.00000006$ segundos cada. -O resto deste capítulo revisa brevemente alguns dos principais conceitos usados ao longo do resto do livro. \secref{interfaces} descreve as interfaces implementadas por todas as estruturas de dados descritas neste livro e -deve ser consideradas como leitura obrigatória. +O resto deste capítulo revisa brevemente alguns dos principais conceitos usados ao longo do resto do livro. \secref{interfaces} descreve as interfaces implementadas por todas as estruturas de dados descritas neste livro e devem ser consideradas como leitura obrigatória. O restante das seções discute: \begin{itemize} \item revisão de conceitos matemáticos incluindo exponenciais, logaritmos, fatoriais, notação assintótica (big-Oh), probabilidades e aleatorização; - \item modelo de computação; - \item corretudo, tempo de execução e espaço; + \item modelos de computação; + \item corretude, tempo de execução e uso de espaço; \item uma visão geral do resto dos capítulos; e - \item um código de amostra juntamente com convenções de escrita de código. + \item uma amostra de código juntamente com convenções de escrita de código usadas neste livro. \end{itemize} -Um leitor com ou sem um experiência nessas áreas pode simplesmente pulá-las agora e voltar se necessário. +Um leitor com (ou mesmo sem) conhecimento desses assuntos pode simplesmente pulá-las agora e voltar se necessário. \section{Interfaces} \seclabel{interfaces} -Ao discutir sobre estruturas de dados, é importante entender a diferença entre a interface de uma estrutura de dados e sua imeplementação. +Ao discutir sobre estruturas de dados, é importante entender a diferença entre a interface de uma estrutura de dados e sua implementação. Uma interface descreve o que uma estrutura de dados faz, enquanto uma implementação descreve como a estrutura o faz. Uma \emph{interface}, @@ -102,38 +101,39 @@ \section{Interfaces} às vezes também chamada de \emph{tipo abstrato de dados}(\emph{abstract data type}, em inglês), define o conjunto de operações aceitas por uma estrutura de dados e a semântica, ou significado, dessas operações. -Uma interface nos diz nada sobre como a estrutura de dados implementa essas operações; ela somente provê uma lsita de operações aceitas juntamente com as especificações sobre quais tipos de argumentos cada operação aceita e o valor retornado por cada operação. +Uma interface nos diz nada sobre como a estrutura de dados implementa essas operações; ela somente provê uma lista de operações aceitas juntamente com as especificações sobre quais tipos de argumentos cada operação aceita e o valor retornado por cada operação. A \emph{implementação} de uma estrutura de dados, por outro lado, inclui a representação interna da estrutura de dados assim como as definições dos algoritmos que implementam as operações aceitas pela estrutura de dados. -Então, pode haver muitas implemetações de uma dada interface. +Então, pode haver muitas implementações de uma dada interface. Por exemplo, em \chapref{arrays}, veremos implementações da interface #List# usando arrays e em \chapref{linkedlists} veremos implementações da interface #List# usando estruturas de dados baseadas em ponteiros. Ambas implementam a mesma interface, #List#, mas de modos distintos. \subsection{As Interfaces #Queue#, #Stack# e #Deque#} A interface #Queue# (Fila, em português) representa uma coleção de elementos à qual podemos +\index{fila}% +\index{queue}% adicionar elementos e remover o próximo elemento. Mais precisamente, as operações -aceitas pela interface #Queue# são +realizadas pela interface #Queue# são \begin{itemize} \item #add(x)#: adiciona o valor #x# à #Queue# \item #remove()#: remove o próximo (previamente adicionado) valor, #y#, da #Queue# e retorna #y# \end{itemize} -Note que -a operação #remove()# não recebe argumentos. +Note que a operação #remove()# não recebe argumentos. A \emph{política de enfileiramento} da #Queue# decide qual é o próximo elemento a ser removido. Existem muitas políticas de enfileiramento possíveis, sendo que entre as mais comuns estão FIFO, por prioridades e LIFO. -Uma \emph{#Queue# FIFO (first-in-first-out -- primeiro a entrar, primeiro a sair) }, +Uma \emph{#Queue# FIFO (first-in-first-out -- primeiro a entrar, primeiro a sair)}, \index{FIFO queue}% \index{queue!FIFO}% que é ilustrada em \figref{queue}, remove itens na mesma ordem em que são adicionados, do meio jeito que uma fila de compras em um supermercado funciona. Esse é o tipo mais comum de #Queue# de forma que o qualificador FIFO é frequentemente omitido. -Em outros textos, as operações #add(x)# e #remove()# em uma #Queue# FIFO são frequentemente chamadas de #enqueue(x)# e #dequeue()#, respectivamente. +Em outros textos, as operações #add(x)# e #remove()# em uma #Queue# FIFO são frequentemente chamadas de #enqueue(x)# (enfileirar) e #dequeue()# (desenfileirar), respectivamente. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/queue}} - \caption[Uma queue (fila) FIFO]{Uma #Queue# FIFO.} + \caption[Uma queue (fila) FIFO]{Uma #Queue# FIFO --- primeiro a entrar, primeiro a sair.} \figlabel{queue} \end{figure} @@ -143,13 +143,12 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} \index{priority queue|seealso{heap}}% \index{queue!priority}% ilustrada em \figref{prioqueue}, sempre -remove o menor elemento da #Queue#, deciding empates arbitrariamente. -Isso é similar ao modo no qual pacientes passam por triagem em uma sala de emergência de um hospital. Conforme pacientes chegam eles são avalias e então -vão à sala de espera. Quando um médico torna-se disponível, ele primeiro trata o paciente na situação mais grave. A operação #remove()# em um #Queue# com prioridades é geralmente chamada de #deleteMin()# em outros textos. +remove o menor elemento da #Queue#, decidindo empates arbitrariamente. +Isso é similar ao modo no qual pacientes passam por triagem em uma sala de emergência de um hospital. Conforme os pacientes chegam, eles são avaliados e, então, vão à sala de espera. Quando um médico torna-se disponível, ele primeiro trata o paciente na situação mais grave. A operação #remove()# em um #Queue# com prioridades é geralmente chamada de #deleteMin()# em outros textos. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/prioqueue}} - \caption[Uma fila com prioridades (no inglês, priority queue)]{Uma #Queue# com prioridades.} + \caption[Uma fila com prioridades]{Uma #Queue# com prioridades --- itens são removidos de acordo com suas prioridades (em inglês, priority queue).} \figlabel{prioqueue} \end{figure} @@ -158,7 +157,7 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} %When a business-class seat becomes available it is given to the most %important customer waiting on an upgrade. -Uma política de enfileiramento muito comum é a política LIFO (last-in-first-out, último-entrar-primeiro-a-sair, em português) +Uma política de enfileiramento muito comum é a política LIFO (last-in-first-out, último-a-entrar-primeiro-a-sair, em português) \index{LIFO queue}% \index{LIFO queue|seealso{stack}}% \index{fila LIFO}% @@ -167,14 +166,14 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} \index{pilha}% , ilustrada em \figref{stack}. Em uma \emph{Queue LIFO}, o elemento mais recentemente adicionado é o próximo a ser removido. -Isso é melhor vizualiados em termos de uma pilha de pratos; pratos são +Isso é melhor visualizado em termos de uma pilha de pratos; pratos são posicionados no topo da pilha a também removidos do topo. Essa estrutura é tão comum que recebe seu próprio nome: #Stack# (pilha, em português). Frequentemente, ao referenciar uma #Stack#, as operações #add(x)# e #remove()# recebem os nomes de #push(x)# e #pop()#; isso é para evitar confusões entre as políticas LIFO e FIFO. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/stack}} - \caption[Uma stack (pilha, em português]{Uma stack (pilha, em português).} + \caption[Uma stack]{Uma stack (pilha, em português) --- LIFO (último-a-entrar-primeiro-a-sair).} \figlabel{stack} \end{figure} @@ -183,8 +182,8 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} \index{deque}% é uma generalização de ambos #Queue# FIFO e #Queue# LIFO (#Stack#). -Uma #Deque# representa uma sequência de elementos, com uma frente e verso (um início e um fim). -Elementos podem ser adicionados na frente da sequência ou no final da sequência. +Uma #Deque# representa uma sequência de elementos, com uma frente e um verso (um início e um fim). +Elementos podem ser adicionados no início da sequência ou no final da sequência. Os nomes das operações da #Deque# são auto-explicativos: #addFirst(x)#, #removeFirst()#, #addLast(x)# e #removeLast()#. Vale notar que uma #Stack# pode ser implementada usando somente #addFirst(x)# @@ -207,13 +206,13 @@ \subsection{A Interface #List#: Sequências Lineares} \item #add(i,x)#: adicionar #x# à posição #i#, deslocando $#x#_{#i#},\ldots,#x#_{#n#-1}$; \\ Atribua $#x#_{j+1}=#x#_j$, para todo - $j\in\{#n#-1,\ldots,#i#\}$, incremente #n#, and faça $#x#_i=#x#$ + $j\in\{#n#-1,\ldots,#i#\}$, incremente #n#, e faça $#x#_i=#x#$ \item #remove(i)# remove o valor $#x#_{#i#}$, deslocando $#x#_{#i+1#},\ldots,#x#_{#n#-1}$; \\ Atribua $#x#_{j}=#x#_{j+1}$, para todo $j\in\{#i#,\ldots,#n#-2\}$ e decremente #n# \end{enumerate} -Note que essas operações are facilmente suficientes para implementar +Note que essas operações são facilmente suficientes para implementar a interface #Deque#: \begin{eqnarray*} #addFirst(x)# &\Rightarrow& #add(0,x)# \\ @@ -231,7 +230,7 @@ \subsection{A Interface #List#: Sequências Lineares} Embora normalmente não discutiremos as interfaces #Stack#, #Deque# e #Queue# FIFO nos seguintes capítulos, os termos #Stack# e #Deque# -são às vezes usados nos nomes das estruturas de dados que implementam a interface #List#. Quando isso acontece, destaca-se que essas estruturas de dados podem ser usadas para implementar a interface #Stack# ou #Deque# eficientemente. +são às vezes usados nos nomes das estruturas de dados que implementam a interface #List#. Quando isso acontece, queremos destacar que essas estruturas de dados podem ser usadas para implementar a interface #Stack# ou #Deque# eficientemente. Por exemplo, a classe #ArrayDeque# é uma implementação da interface #List# que implementa todas as operações #Deque# em tempo constante por operação. \subsection{A Interface #USet#: Unordered Sets (Conjuntos Desordenados)} @@ -240,11 +239,11 @@ \subsection{A Interface #USet#: Unordered Sets (Conjuntos Desordenados)} \index{USet@#USet#}% representa um conjunto desordenado de elementos únicos (sem repetições), que simulam um \emph{conjunto} (em inglês, \emph{set}) matemático. Um #USet# contém #n# elementos \emph{distintos}; nenhum elemento aparece mais de uma vez; os elementos não estão em nenhuma ordem específica. -Um #USet# aceita as seguintes operações: +Um #USet# possui as seguintes operações: \begin{enumerate} \item #size()#: retorna o número, #n#, de elementos no conjunto - \item #add(x)#: adiciona o elemento #x# ao conjunto se não já presente; \\ + \item #add(x)#: adiciona o elemento #x# ao conjunto, se não já presente; \\ Adiciona #x# ao conjunto considerando que não há elemento #y# no conjunto tal que #x# é considerado igual a #y#. Retorna #true# se #x# foi adicionado ao conjunto e #false# caso contrário. \item #remove(x)#: remove #x# do conjunto; \\ Achar um elemento #y# no conjunto tal que #x# iguala @@ -253,10 +252,10 @@ \subsection{A Interface #USet#: Unordered Sets (Conjuntos Desordenados)} Achar um elemento #y# no conjunto tal que #y# seja igual a #x#. Retornar #y# ou #null# se tal elemento não exista no conjunto. \end{enumerate} -Essas definições são um pouco cuidados ao distinguir #x#, o elemento que estamos a remover ou buscar, de #y#, o elemento que poderemos remover ou achar. +Essas definições são um pouco cuidadosas ao distinguir #x#, o elemento que estamos a remover ou buscar, de #y#, o elemento que poderemos remover ou achar. Isso é porque #x# e #y# podem ser na realidade objetos distintos que são, para todos os efeitos nessa situação, tratados como iguais. -\javaonly{\footnote{Em Java, isso é realizado ao sobrescrever os métodos da classe dos elementos #equals(y)# e #hashCode()#.}} +\javaonly{\footnote{Em Java, isso é realizado ao sobrescrever, #@Override#, os métodos da classe dos elementos #equals(y)# e #hashCode()#.}} Tal distinção é útil porque permite a criação de \emph{dicionários} ou \emph{mapas} que mapeiam chaves em valores. \index{dictionary}% @@ -280,7 +279,7 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} \index{SSet@#SSet#}% A interface #SSet# representa um conjunto ordenado de elementos. -Um #SSet# guarda elementos provientes de alguma ordem total, tal que quaisquer dois elementos #x# e #y# podem ser comparados. Em exemplos de código, isso será feito com um método chamado #compare(x,y)# no qual +Um #SSet# guarda elementos provientes de alguma ordem total, tal que quaisquer dois elementos #x# e #y# podem ser comparados. Em código, isso será feito com um método chamado #compare(x,y)# no qual \[ #compare(x,y)# \begin{cases} @@ -290,13 +289,13 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} \end{cases} \] \index{compare@#compare(x,y)#}% -Um #SSet# aceita os métodos #size()#, #add(x)# e #remove(x)# com +Um #SSet# possui os métodos #size()#, #add(x)# e #remove(x)# com exatamente a mesma semântica que a interface #USet#. A diferença entre um #USet# e um #SSet# está no método #find(x)#: \begin{enumerate} \setcounter{enumi}{3} \item #find(x)#: localizar #x# no conjunto ordenado; \\ - Achar o menor elemento $y$ no conjunto tal que $#y# \ge #x#$. + Achar o menor elemento #y# no conjunto tal que $#y# \ge #x#$. Retornar #y# ou #null# se o elemento não existir. \end{enumerate} @@ -307,9 +306,9 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} A distinção entre a operação #find(x)# de #USet# e de #SSet# é muito importante e frequentemente esquecida ou não percebida. A funcionalidade extra provida por um #SSet# frequentemente vem com um preço que inclui tanto tempo de execução mais alto quanto uma complexidade de implementação maior. Por exemplo, a maior parte das implementações #SSet# discutidas neste livro tem operações #find(x)# com tempo de execução que são logaritmicas no tamanho do conjunto. -Por outro lado, a implementação de um #USet# com uma #ChainedHashTable# em +Por outro lado, a implementação de um #USet# com uma #ChainedHashTable# no \chapref{hashing} tem uma operação #find(x)# que roda em tempo esperado constante. -Ao escolher quais dessas estruturas usar, deve-se sempre usar um #USet# a menos que a funcionalidade extra oferecida por um #SSet# é verdadeiramente necessária. +Ao escolher quais dessas estruturas usar, deve-se sempre usar um #USet# a menos que a funcionalidade extra oferecida por um #SSet# seja verdadeiramente necessária. % \subsection{The DynamicString Interface} From 4ecb332bfaadb98e0af0985f09982fefccca788d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 7 Sep 2020 17:58:51 -0300 Subject: [PATCH 40/66] finished revision of portuguese translation of intro --- latex/intro.tex | 210 +++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 110 deletions(-) diff --git a/latex/intro.tex b/latex/intro.tex index cf743668..82d4461a 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -314,16 +314,16 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} \section{Conceitos Matemáticos} Nesta seção, revisaremos algumas noções matemáticas e ferramentas usadas -ao longo deste livro, incluindo logaritmos, notação big-Oh, e +ao longo deste livro, incluindo logaritmos, notação big-Oh e teoria das probabilidades. Esta revisão será breve e não tem a intenção -de ser uma introdução. Leitores que acham que precisam saber mais destes conceitos +de ser uma introdução. Leitores que acham que precisam saber mais desses conceitos são encorajados a ler, e fazer os exercícios relacionados, as seções apropriadas do excelente (e livre) texto didático sobre Matemática para Ciência da Computação \cite{llm11}. +%% CONTINUAR AQUI TODO \subsection{Exponenciais e Logaritmos} -\index{exponential}% \index{exponencial}% A expressão $b^x$ denota o número $b$ elevado à potência $x$. Se $x$ é um inteiro positivo, então o resultado é somente o valor de $b$ multiplicado por ele mesmo $x-1$ vezes: @@ -351,25 +351,25 @@ \subsection{Exponenciais e Logaritmos} Um jeito informal, mas útil, de pensar sobre logaritmos é pensar de $\log_b k$ como o número de vezes que temos que dividir $k$ por $b$ -antes do resultador ser menor ou igual a 1. Por exemplo, quando alguém realiza +antes do resultado ser menor ou igual a 1. Por exemplo, quando alguém usa uma busca binária, cada comparação reduz o número de possíveis respostas por -um fator de 2. Isso é repetido até que existe no máximo uma única resposta +um fator de 2. Isso é repetido até que exista no máximo uma única resposta possível. Portanto, o número de comparações feitas pela busca binária -existem inicialmente até $n+1$ possíveis respostas é, no máximo, +quando existem inicialmente até $n+1$ possíveis respostas é, no máximo, $\lceil\log_2(n+1)\rceil$. \index{logaritmo natural}% \index{logaritmo!natural}% Outro logaritmo que aparece várias vezes neste livro é o \emph{logaritmo natural}. Aqui usamos a notação $\ln k$ para denotar -$\log_e k$, onde $e$ --- \emph{Constante de Euler} --- é dado por -\index{Constante de Euler}% -\index{e@$e$ (Constante de Euler)}% +$\log_e k$, onde $e$ --- a \emph{constante de Euler} --- é dada por +\index{constante de Euler}% +\index{e@$e$ (constante de Euler)}% \[ e = \lim_{n\rightarrow\infty} \left(1+\frac{1}{n}\right)^n \approx 2.71828 \enspace . \] -O logaritmo natural apareces frequentemente porque é o valor +O logaritmo natural aparece frequentemente porque é o valor de uma integral particularmente comum: \[ \int_{1}^{k} 1/x\,\mathrm{d}x = \ln k \enspace . @@ -404,7 +404,7 @@ \subsection{Fatoriais} Para o caso especial $n=0$, $0!$ é definido como 1. \index{Aproximação de Stirling}% -A quantidade $n!$ pode ser aproximada usando a \emph{Aproximação de Stirling}: +A quantidade $n!$ pode ser aproximada usando a \emph{aproximação de Stirling}: \[ n! = \sqrt{2\pi n}\left(\frac{n}{e}\right)^{n}e^{\alpha(n)} \enspace , @@ -413,12 +413,12 @@ \subsection{Fatoriais} \[ \frac{1}{12n+1} < \alpha(n) < \frac{1}{12n} \enspace . \] -a Aproximação de Stirling também aproxima $\ln(n!)$: +a aproximação de Stirling também aproxima $\ln(n!)$: \[ \ln(n!) = n\ln n - n + \frac{1}{2}\ln(2\pi n) + \alpha(n) \] -(De fato, a Aproximação de Stirling é mais facilmente provada por aproximando -$\ln(n!)=\ln 1 + \ln 2 + \cdots + \ln n$ pela integral +(De fato, a aproximação de Stirling é mais facilmente provada aproximando +$\ln(n!)=\ln 1 + \ln 2 + \cdots + \ln n$ com a integral $\int_1^n \ln n\,\mathrm{d}n = n\ln n - n +1$.) \index{coeficientes binomiais}% @@ -432,7 +432,7 @@ \subsection{Fatoriais} O coeficiente binomial $\binom{n}{k}$ (pronunciado ``$n$, escolhidos $k$ a $k$'') conta o número de subconjuntos com $k$ elementos de um conjunto de tamanho $n$ -i.e., o número de forma de escolher $k$ inteiros distintos do conjunto $\{1,\ldots,n\}$. +i.e., o número de formas de escolher $k$ inteiros distintos do conjunto $\{1,\ldots,n\}$. \subsection{Notação assintótica} @@ -440,12 +440,12 @@ \subsection{Notação assintótica} \index{notação big-Oh}% \index{O@$O$ notation}% Ao analisar estruturas de dados neste livro, queremos discutir -os tempos de execução de várias operações. O exato tempo de execução irá, -é claro, variar de computar a computador e até entre diferentes execuções +os tempos de execução de várias operações. O tempo exato de execução irá, +é claro, variar de computador para computador e até entre diferentes execuções no mesmo computador. Ao discutirmos sobre o tempo de execução de uma operação estamos nos -referindo ao número de intruções de computador executadas durante a operação. -Mesmo para códigos simples, essa quantidade pode ser difícil para computar com exatidão. +referindo ao número de instruções de computador executadas durante a operação. +Mesmo para códigos simples, essa quantidade pode ser difícil de computar com exatidão. Portanto, em vez de analisar tempos de execução exatos, iremos usar a famosa \emph{notação big-Oh}: Para uma função $f(n)$, $O(f(n))$ denota um conjunto de funções, \[ @@ -460,17 +460,16 @@ \subsection{Notação assintótica} $c\cdot f(n)$ começa a dominar $g(n)$ quando $n$ é suficientemente grande. Geralmente usamos notação assintótica para simplificar funções. Por exemplo, - -em vez de usar $5n\log n + 8n - 200$ podemos escrever $O(n\log n)$. +em vez de usarmos $5n\log n + 8n - 200$ podemos escrever $O(n\log n)$. Isso é provado da seguinte forma: \begin{align*} 5n\log n + 8n - 200 & \le 5n\log n + 8n \\ - & \le 5n\log n + 8n\log n & \mbox{ for $n\ge 2$ (so that $\log n \ge 1$)} + & \le 5n\log n + 8n\log n & \mbox{ for $n\ge 2$ (tal que $\log n \ge 1$)} \\ & \le 13n\log n \enspace . \end{align*} -Isso demontra que a função $f(n)=5n\log n + 8n - 200$ está no conjunto +Isso demonstra que a função $f(n)=5n\log n + 8n - 200$ está no conjunto $O(n\log n)$ usando as constantes $c=13$ e $n_0 = 2$. Vários atalhos podem ser aplicados ao usar notação assintótica. @@ -510,17 +509,17 @@ \subsection{Notação assintótica} A notação Big-Oh não é nova nem exclusiva à Ciência da Computação. Ela foi usada pelo matemático especialista em Teoria dos Números -Paul Bachmann desde pelo menos 1894, e é imensamente útil +Paul Bachmann desde pelo menos 1894 e é imensamente útil para descrever o tempo de execução de algoritmos de computadores. Considere o seguinte trecho de código: \javaimport{junk/Simple.snippet()} \cppimport{ods/Simple.snippet()} -Uma execução desse método involve: +Uma execução desse método envolve: \begin{itemize} \item $1$ atribuição (#int\, i\, =\, 0#), \item $#n#+1$ comparações (#i < n#), \item #n# incrementos (#i++#), - \item #n# cálculo de deslocamentos em array (#a[i]#), e + \item #n# cálculos de deslocamentos em array (#a[i]#), e \item #n# atribuições indiretas (#a[i] = i#). \end{itemize} Então podemos escrever esse tempo de execução como @@ -529,9 +528,9 @@ \subsection{Notação assintótica} \] onde $a$, $b$, $c$, $d$ e $e$ são constantes que dependem da máquina rodando o código e que representam o tempo para realizar atribuições, -comparações, incrementos, cálculos de deslocamento em array, e atribuições indiretas, respectivamente. +comparações, incrementos, cálculos de deslocamento em array e atribuições indiretas, respectivamente. Entretanto, se essa expressão representa o tempo de execução de duas linhas de -código, então claramente esse tpo de análise não será viável para códigos ou algoritmos complicados. +código, então claramente esse tipo de análise não será viável para códigos ou algoritmos complicados. Ao usar a notação big-Oh, o tempo de execução pode ser simplificado a \[ T(#n#)= O(#n#) \enspace . @@ -541,25 +540,23 @@ \subsection{Notação assintótica} $a$, $b$, $c$, $d$ e $e$ no exemplo anterior significa que, em geral, não será possível comparar dois tempos de execução para saber qual é mais rápido sem saber os valores dessas constantes. -Mesmo se fizermos esforços para determinar essas constantes (digamos, usando medindo o tempo de testes), então nossa conclusão será válida somente para a máquina +Mesmo se fizermos esforços para determinar essas constantes (digamos, usando testes de medição de tempo), então nossa conclusão será válida somente para a máquina na qual rodamos nossos testes. Notação Big-Oh nos permite avaliar a situação a um nível mais alto, possibilitando a análise de funções mais complicadas. -Se dois algoritmos tem o tempo tempo Big-Oh, então não saberemos qual é mais rápido -e que não pode não haver um ganhador em todas as situações. Um algoritmo pode ser mais rápido em uma máquina enquanto o outro em uma máquina diferente. Porém, se os dois algoritmos tem tempos de execução big-Oh distintos, então -teremos certeza que aquele com menor função Big-Oh será mais rápido \emph{para valores #n# grandes o suficientes}. +Se dois algoritmos têm o mesmo tempo Big-Oh, então não saberemos qual é mais rápido +e que não pode não haver um ganhador em todas as situações. Um algoritmo pode ser mais rápido em uma máquina enquanto o outro em uma máquina diferente. Porém, se os dois algoritmos têm tempos de execução big-Oh distintos, então +teremos certeza que aquele com menor função Big-Oh será mais rápido \emph{para valores #n# grandes o suficiente}. Um exemplo de como a notação big-Oh nos permite comparar duas diferentes funções é mostrado em \figref{intro-asymptotics}, que compara a taxa de crescimento -de $f_1(#n#)=15#n#$ versus $f_2(n)=2#n#\log#n#$. -Hipotéticamente, $f_1(n)$ seria o tempo de execução de um complicado algoritmo de tempo linear enquanto $f_2(n)$ é o tempo de execução de um algoritmo bem mais simples baseado no paradigma de divisão e conquista. +de $f_1(#n#)=15#n#$ versus $f_2(n)=2#n#\log#n#$. +Hipoteticamente, $f_1(n)$ seria o tempo de execução de um complicado algoritmo de tempo linear enquanto $f_2(n)$ é o tempo de execução de um algoritmo bem mais simples baseado no paradigma de divisão e conquista. Isso exemplica essa situação, -embora - $f_1(#n#)$ seja maior que $f_2(n)$ para valores baixos de #n#, +embora $f_1(#n#)$ seja maior que $f_2(n)$ para valores baixos de #n#, o oposto é verdade para valores mais altos de #n#. -Eventualmente $f_1(#n#)$ ganha, e por uma margem crescente. -Análise usando notação big-Oh -nos indica que isso aconteceria, pois +Eventualmente $f_1(#n#)$ ganha e por uma margem crescente. +Análise usando notação big-Oh nos indica que isso aconteceria, pois $O(#n#)\subset O(#n#\log #n#)$. \begin{figure} @@ -574,7 +571,7 @@ \subsection{Notação assintótica} \end{figure} Em alguns casos, usaremos notação assintótica em funções com mais de uma variável. -Pode parecer que não padrão para isso, mas para nossos objetivos, a seguinte definição é suficiente: +Pode não ser comum mas, para nossos objetivos, a seguinte definição é suficiente: \[ O(f(n_1,\ldots,n_k)) = \left\{\begin{array}{@{}l@{}} @@ -588,7 +585,7 @@ \subsection{Notação assintótica} $n_1,\ldots,n_k$ faz $g$ assumir valores altos. Essa definição também está de acordo com a definição univariada de $O(f(n))$ quando $f(n)$ é uma função crescente de $n$. -O leitor deve ficar alerta de que, embora isso funciona para nosso objetivo neste livro, outros textos podem tratar funções multivariadas e notação assintótica diferentemente. +O leitor deve ficar atento ao fato de que, embora isso funcione para o objetivo deste livro, outros textos podem tratar funções multivariadas e notação assintótica diferentemente. \subsection{Randomização e Probabilidades} \seclabel{randomization} @@ -598,16 +595,16 @@ \subsection{Randomização e Probabilidades} \index{randomized data structure}% \index{estrutura de dados aleatorizada}% \index{randomized algorithm}% -Algumas das estruturas de dados apresentadas neste livro são \emph{randomizadas}\footnote{Randomização é um neologismo para indicar o uso de números aleatórios que influencia na execução de um algoritmo}; +Algumas das estruturas de dados apresentadas neste livro são \emph{randomizadas}\footnote{Randomização é um neologismo para indicar o uso de números aleatórios que influenciam na execução de um algoritmo}; eles fazem escolhas aleatórias que são independentes dos dados nelas armazenadas -os nas operações realizadas neles. Por essa razão, +ou das operações realizadas neles. Por essa razão, realizar o mesmo conjunto de operações mais de uma vez usando essas estruturas pode resultar em tempos de execução variáveis. Ao analisar essas estruturas de dados estamos interessados no seu tempo de execução médio ou \emph{esperado}. \index{tempo de execução esperado}% \index{tempo de execução!esperado}% -Formalmente, o tempo de execução de uma operação em uma estrutura de dados randomizada e queremos estudar o seu \emph{valor esperado}; +Formalmente, o tempo de execução de uma operação em uma estrutura de dados randomizada é aleatório e queremos estudar o seu \emph{valor esperado}; \index{valor esperado}% Para uma variável aleatória discreta $X$ assumindo valores em um universo contável @@ -623,7 +620,7 @@ \subsection{Randomização e Probabilidades} Uma das propriedades mais importantes dos valores esperados é \emph{linearidade da esperança}. \index{linearidade da esperança}% -Para duas quaisquer variáveis aleatórias $X$ e $Y$, +Para duas variáveis aleatórias $X$ e $Y$, \[ \E[X+Y] = \E[X] + \E[Y] \enspace . \] @@ -635,10 +632,10 @@ \subsection{Randomização e Probabilidades} Um truque útil, que usaremos repetidamente, é definir \emph{variáveis aleatórias indicadoras}. \index{variável aleatória indicadora}% -Essas variáveis binárias são úteis quando queremos contar algo e são melhor ilustradas por um exemplo. Suponha we lancemos uma moeda honesta $k$ vezes e que queremos saber o número esperado de vezes que o lado cara aparece. +Essas variáveis binárias são úteis quando queremos contar algo e são melhor ilustradas por um exemplo. Suponha que lancemos uma moeda honesta $k$ vezes e que queremos saber o número esperado de vezes que o lado cara aparece. \index{lançamento de moeda}% Intuitivamente, sabemos que a resposta é $k/2$, -mas se tentamos provar usando a definição de valor esperado, temos +mas se tentarmos provar usando a definição de valor esperado, temos \begin{align*} \E[X] & = \sum_{i=0}^k i\cdot\Pr\{X=i\} \\ & = \sum_{i=0}^k i\cdot\binom{k}{i}/2^k \\ @@ -667,31 +664,29 @@ \subsection{Randomização e Probabilidades} & = k/2 \enspace . \end{align*} Esse caminho é mais longo, mas não exige que saibamos identidades mágicas ou que obtenhamos expressões não triviais de probabilidades. Melhor ainda, -ele vai ao encontro à intuição que temos sobre metade das moedas cairem do lado da cara precisamente porque cada moeda individual cai como moeda com probabilidade $1/2$. +ele vai de encontro à intuição que temos sobre metade das moedas saírem cara precisamente porque cada moeda individual sai cara com probabilidade $1/2$. \section{O Modelo de Computação} \seclabel{model} -Neste libro, iremos analisar o tempo teórico de execução das operações das estruturas de dados que estudamos. Para fazer precisamente isso, precisamos um modelo matemático de computação. Para isso, usamos -o modelo \emph{#w#-bit -word-RAM} +Neste livro, iremos analisar o tempo teórico de execução das operações das estruturas de dados que estudamos. Para fazer precisamente isso, precisamos um modelo matemático de computação. Para isso, usamos +o modelo \emph{#w#-bit word-RAM} \index{word-RAM}% \index{RAM}% -. RAM é uma sigla para Random Access Machine --- Máquina de Acesso Aleatório. Nesse modelo, temos acesso a uma memória de acesso aleatório consistindo de \emph{cells}, cada qual armazena -uma #w#-bit \emph{word}, ou seja, um apalavra com #w# bits de memória. +. RAM é uma sigla para Random Access Machine --- Máquina de Acesso Aleatório. Nesse modelo, temos acesso a uma memória de acesso aleatório consistindo de \emph{células}, cada qual armazena uma #w#-bit \emph{word}, ou seja, uma palavra com #w# bits de memória. \index{word}% -Isso implica que uma célula de memória pode representar, por exemplo, qualquer inteiro no conjunto $\{0,\ldots,2^{#w#}-1\}$. +Isso implica que uma célula de memória pode representar, por exemplo, qualquer inteiro no conjunto $\{0,\ldots,2^{#w#}-1\}$. No modelo word-RAM, operações básicas em words levam tempo constante. -Isso inclui operações aritméticas (#+#, #-#, #*#, #/#, #%#), comparisons -($<$, $>$, $=$, $\le$, $\ge$) e operações booleana bit-a-bit (AND, OR e OR exclusivo bit-a-bit). +Isso inclui operações aritméticas (#+#, #-#, #*#, #/#, #%#), comparações +($<$, $>$, $=$, $\le$, $\ge$) e operações booleanas bit-a-bit (AND, OR e OR exclusivo bit-a-bit). -Qualquer célula pode ler lida com escrita em tempo constante. -A memória do computador é gerenciada por um sistema gerenciador de memória a partir do qual podemos alocar or desalocar um bloco de memória de qualquer tamanho que quisermos. Alocar um bloco de memória de tamanho $k$ leva $O(k)$ tempo e retorna uma referência (um ponteiro) para o bloco recém alocado. Essa referência é pequena o suficiente para ser representada por uma única word. +Qualquer célula pode ler lida ou escrita em tempo constante. +A memória do computador é gerenciada por um sistema gerenciador de memória a partir do qual podemos alocar ou desalocar um bloco de memória de qualquer tamanho que quisermos. Alocar um bloco de memória de tamanho $k$ leva tempo $O(k)$ e retorna uma referência (um ponteiro) para o bloco recém alocado. Essa referência é pequena o suficiente para ser representada por uma única word. O tamanho da word #w# é um parâmetro muito importante desse modelo. A única premissa que faremos sobre #w# é um limitante inferior $#w# \ge \log #n#$, -onde #n# é o número de elmentos guardados em qualquer estrutura de dados. +onde #n# é o número de elementos guardados em qualquer estrutura de dados. Essa premissa é bem razoável, pois caso contrário uma word não seria grande o suficiente para contar o número de elementos guardados na estrutura de dados. @@ -699,11 +694,11 @@ \section{O Modelo de Computação} Espaço é medido em words, de forma que quando falarmos sobre a quantidade de espaço usado por uma estrutura de dados, estamos nos referindo ao número de words de memória usada pela estrutura. Todas as nossas estruturas de dados guardam valores de um tipo genérico #T#, e nós presumimos que um elemento de tipo #T# ocupa uma word de memória. -\javaonly{(Na realidade, estamos guardando referencias para objetos do tipo #T#, e essas referencias ocupam somente uma word de memória.)} +\javaonly{(Na realidade, estamos guardando referências para objetos do tipo #T#, e essas referências ocupam somente uma word de memória.)} -\javaonly{O modelo word-RAM com #w# bits é bem próximo à Java Virtual Machine (JVM) de 32 bits quando $#w#=32$.} +\javaonly{O modelo word-RAM com #w# bits é bem próximo à Java Virtual Machine (JVM) de 32 bits quando $#w#=32$.} \cpponly{O modelo word-RAM com #w# bits é bem representativo a computadores desktop modernos quando $#w#=32$ or $#w#=64$.} -As estruturas de dados apresentadas neste livro não usam truqes especiais que não são implementáveis \javaonly{na JVM em mais parte de outras arquiteturas.}\cpponly{em C++ na maior parte das arquiteturas.} +As estruturas de dados apresentadas neste livro não usam truques especiais que não são implementáveis \javaonly{na JVM ou na maior parte das arquiteturas.}\cpponly{em C++ ou na maior parte das arquiteturas.} \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} @@ -750,23 +745,23 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} então uma sequência de $m$ operações leva de tempo, no máximo, $mf(#n#)$. Algumas operações individuais podem levar mais tempo que - $f(#n#)$ mas a média, sobre a sequência inteira de operações, é até $f(#n#)$. + $f(#n#)$ mas o tempo médio, sobre a sequência inteira de operações, é até $f(#n#)$. \item[Tempo de Execução Esperado:] \index{tempo de execução!esperado}% \index{tempo de execução esperado}% Se dizemos que o tempo de esperado de execução de uma operação em uma estrutura de dados é $f(#n#)$, isso significa que o tempo real de execução é uma variável aleatória - (veja \secref{randomization}) + (veja a \secref{randomization}) e o valor esperado dessa variável aleatória é no máximo $f(#n#)$. -A randomização aqui é em respeito a escolha aleatória feitas pela estrutura de dados. +A randomização aqui é em respeito às escolhas aleatórias feitas pela estrutura de dados. \end{description} -Para entender a diferença entre pior caso, amortizado e tempo esperado, ajuda a considerar um exemplo financeiro. Considere o custo de comprar uma casa: +Para entender a diferença entre o pior caso, o tempo amortizado e o tempo esperado, ajuda se considerarmos um exemplo financeiro. Considere o custo de comprar uma casa: \paragraph{Pior caso versus custo amortizado:} \index{custo amortizado}% -Suponha que uma casa custa \$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de +Suponha que uma casa custe \$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de \$1\,200. Nesse caso, o custo mensal no pior caso de pagar o empréstimo é \$1\,200. @@ -780,9 +775,9 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} \paragraph{Pior caso versus custo esperado:} \index{custo esperado}% A seguir, considere o caso de um seguro de incêndio na nossa casa de \$120\,000. -Por analisar centenas de milhares de casos, companias de seguro tem determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de +Por analisar centenas de milhares de casos, companias de seguro têm determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de \$10 por mês. -Esse é uma valor bem baixo, pois a maior parte das casa nunca pegam fogo, algumas poucas tem incêndios pequenos que causam algum dano e um número minúsculo de casa queimam até as cinzas. Baseada nessa informação, a seguradora cobra +Esse é uma valor bem baixo, pois a maior parte das casa nunca pegam fogo, algumas poucas tem incêndios pequenos que causam algum dano e um número minúsculo de casas queimam até as cinzas. Baseada nessa informação, a seguradora cobra \$15 mensais para a contratação de um seguro. Agora é o momento da decisão. Devemos pagar os @@ -793,8 +788,7 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} No evento improvável que a casa queime inteira, o custo real será de \$120\,000. Esses exemplos financeiros também oferecem a oportunidade de vermos porque às vezes aceitamos um tempo de execução amortizado ou esperado em vez de considerarmos o tempo de execução no pior caso. Frequentemente é possível obter um tempo de execução esperado ou amortizado menor que o obtido no pior caso. -No mínimo, frequentemente é possível obter uma estrutura de dados bem mais simples se estamos dispostos a aceitar tempo de execução amortizado ou esperado. - +No mínimo, frequentemente é possível obter uma estrutura de dados bem mais simples se estivermos dispostos a aceitar tempo de execução amortizado ou esperado. \section{Trechos de Código} @@ -803,21 +797,21 @@ \section{Trechos de Código} Os trechos de código neste livro são escritos em pseudocódigo. \index{pseudocode}% -Eles devem ser de fácil leitura para qualquer um que teve alguma experiência de programação em qualquer linguagens de programação comum dos últimos 40 anos. +Eles devem ser de fácil leitura para qualquer um que teve alguma experiência de programação em qualquer linguagem de programação comum dos últimos 40 anos. Para ter uma ideia do que o pseudocódigo neste livro parece, segue uma função que computa a média de um array, #a#: \pcodeimport{ods/Algorithms.average(a)} Como esse código exemplifica, a atribuição a uma variável é feita usando a notação $\gets$. % WARNING: graphic typesetting of assignment operator -Nós usamos a convenção de que o comprimento de uma array, #a#, é denotado por +Nós usamos a convenção de que o comprimento de um array, #a#, é denotado por #len(a)# e os índices do array começam com zero, tal que #range(len(a))# são índices válidos para #a#. -Para encurtar o código, e algumas vezes facilitar a leitura, nosso pseudocódigo permite atribuições de subarrays. +Para encurtar o código e algumas vezes facilitar a leitura, nosso pseudocódigo permite atribuições de subarrays. As duas funções a seguir são equivalentes: \pcodeimport{ods/Algorithms.left_shift_a(a).left_shift_b(a)} O código a seguir atribui todos os valores em um array para zero: \pcodeimport{ods/Algorithms.zero(a)} -Ao analysis o tempo de execução de um código desse tipo, é necessário cuidado; +Ao analisar o tempo de execução de um código desse tipo, é necessário cuidado; comandos como #a[0:len(a)] = 1# ou @@ -829,7 +823,7 @@ \section{Trechos de Código} os valores das variáveis #x# e #y#. \index{swap} -Nosso pseudocódigo usa alguns operadores que podem não ser conhecidos. +Nosso pseudocódigo usa alguns operadores que podem ser desconhecidos. Como é padrão em matemática, divisão (normal) é denotada pelo operador $/$ Em muitos casos, queremos fazer divisão inteira e, nesse caso, usamos o operador @@ -841,7 +835,7 @@ \section{Trechos de Código} da divisão inteira, mas isso será definido quando for o caso. \index{operador mod}% \index{operador div}% -Mais adiante no livro, podemos usar alguns operadores bit-a-bit incluindo o deslocamento à esquerda (#<<#), à direita (#>>#), E bit-a-bit (#&#) e XOR bit-a-bit (#^#). +Mais adiante no livro, podemos usar alguns operadores bit-a-bit incluindo o deslocamento à esquerda (#<<#), à direita (#>>#), AND bit-a-bit (#&#) e XOR bit-a-bit (#^#). \index{deslocamento à esquerda}% \index{left shift}% \index{#<<#|see {left shift}}% @@ -857,24 +851,24 @@ \section{Trechos de Código} \index{#^#|see {bitwise exclusive-or}}% \index{#^#|see {XOR bit-a-bit}}% -Os trechos de pseudocódigo neste livro são traduções de máquina do código Python que pode ser baixado do website do livro. +Os trechos de pseudocódigo neste livro são traduções automáticas do código Python que pode ser baixado do website do livro. \footnote{ \url{http://opendatastructures.org}} Se você encontrar qualquer ambiguidade no pseudocódigo que você não consegue resolver por si só, então você pode resolver essa questões com o correspondente código em Python. -Se você não lê Python, o código também está disponível em Java e C++. Se você não consegue decifrar o pseudocódigo, ou ler Python, C++, ou Java, então você pode não estar pronto para este livro. +Se você não lê Python, o código também está disponível em Java e C++. Se você não consegue decifrar o pseudocódigo, ou ler Python, C++, ou Java, então talvez você não esteja pronto para ler este livro. } \notpcode{ Os trechos de código neste livro são escritos na linguagem \lang -. Entretanto, para fazer o livro acessível para leitores não familiares com todas as construções e keywords definidas pela linguagem \lang, os trechos de código foram simplificados. Por exemplo, um leitor não irá encontrar keywords como +. Entretanto, para fazer o livro acessível para leitores não familiares com todas as construções e \emph{keywords} definidas pela linguagem \lang, os trechos de código foram simplificados. Por exemplo, um leitor não irá encontrar keywords como #public#, #protected#, #private# ou #static#. O leitor também não encontrará discussão aprofundada sobre hierarquia de classes. Quais interfaces uma determinada classes implementa ou qual classes ela extende, se relevante para a discussão, deve estar claro do texto do contexto em questão. -Essas convenções deve fazer os trechos de código mais fáceis de entender por qualquer um com background linguagens que variantes da linha ALGOL, incluindo B, C, C++, C\#, Objective-C, D, Java, JavaScript, e assim por diante. -Leitores em busca de detalhes de todas as implementações são encorajados a olhar no código fonte da linguagem \lang\ que acompanha este livro. +Essas convenções deve fazer os trechos de código mais fáceis de entender por qualquer um com background linguagens que variantes da linha ALGOL, incluindo B, C, C++, C\#, Objective-C, D, Java, JavaScript e assim por diante. +Leitores em busca de detalhes das implementações são encorajados a olhar no código fonte da linguagem \lang\ que acompanha este livro. -Este livro mixtura análise matemática de tempos de execução com o código-fonte da linguagem \lang\ para os algoritmos em análise. Isso significa que algumas equações contêm variáveis variáveis também encontradas no código-fonte. +Este livro mistura análise matemática de tempos de execução com o código-fonte da linguagem \lang\ para os algoritmos em análise. Isso significa que algumas equações contêm variáveis também encontradas no código-fonte. Essas variáveis são formatadas consistentemente, tanto dentro do código-fonte quanto nas equações. A variável mais comum desse tipo é a variável #n# \index{n@#n#}% @@ -883,9 +877,9 @@ \section{Trechos de Código} \section{Lista de Estruturas de Dados} -Tabelas~\ref{tab:summary-i} e \ref{tab:summary-ii} resumem o desempenho -das estruturas de dados neste livro que implementam cada uma das interfaces, #List#, #Uset# e #SSet#, descritas em \secref{interfaces}. -\Figref{dependencies} mostra as dependências entre vários capítulos neste livro. +As tabelas~\ref{tab:summary-i} e \ref{tab:summary-ii} resumem o desempenho +das estruturas de dados que neste livro implementam cada uma das interfaces, #List#, #Uset# e #SSet#, descritas em \secref{interfaces}. +A \Figref{dependencies} mostra as dependências entre vários capítulos neste livro. \index{dependências}% Uma linha tracejada indica somente uma dependência fraca, na qual somente uma pequena parte do capítulo depende em um capítulo anterior ou somente nos resultados principais do capítulo anterior. @@ -916,7 +910,7 @@ \section{Lista de Estruturas de Dados} \end{tablenotes} \end{threeparttable}} \end{center} -\caption[Resumo de implementações de List e USet.]{Resumo de implementações de #List# e #USet#.} +\caption[Resumo das implementações de List e USet.]{Resumo das implementações de #List# e #USet#.} \tablabel{summary-i} \end{table} @@ -936,19 +930,19 @@ \section{Lista de Estruturas de Dados} \javaonly{#BTree# & $O(\log #n#)$ & $O(B+\log #n#)$\tnote{A} & \sref{btree} \\ #BTree#\tnote{X} & $O(\log_B #n#)$ & $O(\log_B #n#)$ & \sref{btree} \\ } \hline \multicolumn{4}{c}{} \\[2ex] \hline - \multicolumn{4}{|c|}{Implementações de #Queue# (de prioridade)} \\ \hline + \multicolumn{4}{|c|}{Implementações da #Queue# (de prioridades)} \\ \hline & #findMin()# & #add(x)#/#remove()# & \\ \hline #BinaryHeap# & $O(1)$ & $O(\log #n#)$\tnote{A} & \sref{binaryheap} \\ #MeldableHeap# & $O(1)$ & $O(\log #n#)$\tnote{E} & \sref{meldableheap} \\ \hline \end{tabular} \begin{tablenotes} -\item[I]{Essa estrutura pode somente guardar dados inteiros de #w#-bit.} -\javaonly{\item[X]{Isso denota o tempo de execução no modelo de memória externa; veja \chapref{btree}.}} +\item[I]{Essa estrutura somente pode guardar dados inteiros de #w#-bit.} +\javaonly{\item[X]{Isso denota o tempo de execução no modelo de memória externa; veja o \chapref{btree}.}} \end{tablenotes} %\renewcommand{\thefootnote}{\arabic{footnote}} \end{threeparttable} \end{center} -\caption[Resumo das implementações SSet e priority Queue.]{Resumo de implementações de #SSet# e priority #Queue#.} +\caption[Resumo das implementações SSet e priority Queue.]{Resumo de implementações de #SSet# e #Queue# de prioridades.} \tablabel{summary-ii} \end{table} @@ -956,7 +950,7 @@ \section{Lista de Estruturas de Dados} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/dependencies} \end{center} - \caption{Dependências entre capítulos neste livro.} + \caption{Dependências entre capítulos deste livro.} \figlabel{dependencies} \end{figure} @@ -970,17 +964,17 @@ \section{Discussão e Exercícios} Elas são essencialmente versões simplificadas das interfaces #List#, #Set#, #Map#, #SortedSet# e #SortedMap# encontradas no Java Collections Framework. \javaonly{O código-fonte que acompanha este livro inclui -classes auxiliares para fazer das implementações #USet# e #SSet# -implementaçoes #Set#, #Map#, #SortedSet# e #SortedMap#.} +classes auxiliares para fazer das implementações #USet# e #SSet# também +implementações de #Set#, #Map#, #SortedSet# e #SortedMap#.} -Para um soberbo (e livre) tratamento da matemática discutida neste capítulo, incluindo notação assintótica, logaritmos, fatoriais, aproximação de Sterling, probabilidade básica e muito mais, veja o livro texto por +Para um soberbo (e livre) tratamento da matemática discutida neste capítulo, incluindo notação assintótica, logaritmos, fatoriais, aproximação de Sterling, probabilidade básica e muito mais, veja o livro-texto por Leyman, Leighton e Meyer \cite{llm11}. Para um texto de cálculo suave que inclui definições formais de exponenciais e logaritmos, veja o (disponível livremente) texto clássico de Thompson \cite{t14}. Para maiores informações em probabilidade básica, especialmente como ela se relaciona com Ciência da Computação, veja o livro didático de Ross \cite{r01}. Outra boa referência, que cobre notação assintótica e probabilidades, é o livro-texto de Graham, Knuth e Patashnik \cite{gkp94}. -\javaonly{Leitures que desejam afinar suas habilidades de programação Java +\javaonly{Leitores que desejam afinar suas habilidades de programação Java podem achar muitos tutoriais online em \cite{oracle_tutorials}.} \begin{exc} @@ -993,16 +987,16 @@ \section{Discussão e Exercícios} Suas implementações devem ser rápidas o suficiente tal que até arquivos com um milhão de linhas podem ser processadas em alguns segundos. \begin{enumerate} \item Leia a entrada uma linha por vez e então escreva as linha em ordem invertida, tal que a última linha lida é a primeira imprimida, e então a penúltima lida é a segunda a ser imprimida e assim por diante. - \item Leia as primeiras 50 linhas da entrada e então as escreva na saída em ordem reserva. Leia as seguintes 50 linhas e então escreva na saída em ordem reserva. Faça isso até que não haja mais linhas a serem linhas. Nesse ponto as linhas restantes lidas também devem ser imprimidas invertidas. -Em outras palavras, sua saída inicirá com a 50-ésima linha, então 49-ésima linha, então a 48-ésima e assim até a primeira linha. Isso deverá ser seguido pela centésima linha, seguida pela 99-ésima até a 51-ésima linha. Assim por diante. + \item Leia as primeiras 50 linhas da entrada e então as escreva na saída em ordem reversa. Leia as seguintes 50 linhas e então escreva na saída em ordem reversa. Faça isso até que não haja mais linhas a serem linhas. Nesse ponto as linhas restantes lidas também devem ser imprimidas invertidas. +Em outras palavras, sua saída iniciará com a 50ª linha, então 49ª linha, então a 48ª e assim até a primeira linha. Isso deverá ser seguido pela centésima linha, seguida pela 99ª até a 51ª linha. Assim por diante. O seu código nunca deverá manter mais de 50 linhas a qualquer momento. \item Leia a entrada uma linha por vez. A qualquer momento após ler as primeiras 42 linhas, se alguma linha está em branco (i.e., uma string de comprimento 0), então produza a linha que ocorreu 42 linhas antes dela. Por exemplo, se Linha 242 está em branco, então o seu programa deve imprimir a linha 200. Esse programa deve ser implementado tal que ele nunca guarda mais que 43 linhas da entrada a qualquer momento. -\item Leia a entrada uma linha por vez e imprima cada linha se não for uma duplicata de alguma linha anterior. Tenha cuidado especial de forma que um arquivo com muitas linhas duplicadas não use mais memória do que é necessário para o número de linhas únicas. +\item Leia a entrada uma linha por vez e imprima cada linha se não for uma duplicata de alguma linha anterior. Tenha cuidado especial para que um arquivo com muitas linhas duplicadas não use mais memória do que é necessário para o número de linhas únicas. -\item Leia a entrada uma linha por vez e imprima cada linha somente se você já encontrou uma linha igual antes. (O resultado final é que você remove a primeira ocorrência de cada linha.). Tenha cuidado tal que um arquivo com minhas linhas duplicadas não usa mais memória que é necessário para o número de linhas únicas. +\item Leia a entrada uma linha por vez e imprima cada linha somente se você já encontrou uma linha igual antes. (O resultado final é que você remove a primeira ocorrência de cada linha.). Tenha cuidado para que um arquivo com muitas linhas duplicadas não use mais memória do que seja necessário para o número de linhas únicas. \item Leia a entrada uma linha por vez. Então, imprima todas as linhas ordenadas por comprimento, com as linhas mais curtas primeiro. No caso em que duas linhas tem o mesmo tamanho, decida sua ordem usando a ordem usual de texto. Linhas duplicadas devem ser impressas somente uma vez. @@ -1011,14 +1005,14 @@ \section{Discussão e Exercícios} \item Leia a entrada uma linha por vez e então imprima as linhas pares (começando com a primeira linha, linha 0) seguidas das linhas ímpares. -\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimí-las. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser imprimida, mas em ordem aleatória. +\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimí-las. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser impressa, mas em ordem aleatória. \end{enumerate} \end{exc} \begin{exc} \index{Palavra Dyck}% - Uma \emph{palavra Dyck} é uma sequência de +1s e -1s com propriedade de que soma de qualquer prefix da sequência nunca é negativa. Por exemplo, + Uma \emph{palavra Dyck} é uma sequência de +1s e -1s com propriedade de que soma de qualquer prefixo da sequência nunca é negativa. Por exemplo, $+1,-1,+1,-1$ é uma palavra Dyck, mas $+1,-1,-1,+1$ não é uma palavra Dyck pois o prefixo $+1-1-1<0$. Descreva qualquer relação entre palavras Dyck e as operações da interface #Stack#, #push(x)# e #pop()#. @@ -1029,9 +1023,9 @@ \section{Discussão e Exercícios} \index{string!pareada}% Uma \emph{string pareada} é uma sequência de caracteres \{, \}, (, ), [, e ] que são apropriadamente pareados. Por exemplo, ``\{\{()[]\}\}'' - é uma string pareada, mas este ``\{\{()]\}'' não é, uma vez que a segunda \{ - é pareada ]. Mostre como usar uma stack tal que, dada uma string de comprimento - #n#, você pode determinar se é uma string pareada em tempo $O(#n#)$. + é uma string pareada, mas esta ``\{\{()]\}'' não é, uma vez que a segunda \{ + é pareada ]. Mostre como usar uma stack para que, dada uma string de comprimento + #n#, você possa determinar se é uma string pareada em tempo $O(#n#)$. \end{exc} \begin{exc} @@ -1056,9 +1050,5 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} -Trabalhe para melhorar o desempenho das suas implementações da questão anterior usando quaisquer truques que puder imaginar. Experimente e pense sonre como você pode melhorar o desempenho de #add(i,x)# e #remove(i)# na sua implementação de #List#. Pense como você poderia melhorar o desempenho da operação #find(x)# na suas implementações dos #USet# e #SSet#. Este exercício é pensado para te dar uma ideia da dificuldade de conseguir implementações eficientes dessas interfaces. +Trabalhe para melhorar o desempenho das suas implementações da questão anterior usando quaisquer truques que puder imaginar. Experimente e pense como você pode melhorar o desempenho de #add(i,x)# e #remove(i)# na sua implementação de #List#. Pense como você poderia melhorar o desempenho da operação #find(x)# na suas implementações dos #USet# e #SSet#. Este exercício é tem o intuito de te dar uma ideia da dificuldade de conseguir implementações eficientes dessas interfaces. \end{exc} - - - - From fde08be2dcf5a7ff741999a01f0400784521a2f0 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 7 Sep 2020 19:44:46 -0300 Subject: [PATCH 41/66] finished revision of portuguese translation of arrays --- latex/arrays.tex | 416 ++++++++++++++++++++++------------------------- 1 file changed, 197 insertions(+), 219 deletions(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index 70e9cd54..cca794e6 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -1,4 +1,4 @@ -\chapter{Listas Baseadas em Array} +\chapter{Listas Baseadas em Arrays} \chaplabel{arrays} Neste capítulo, iremos estudar implementações das interfaces #List# e #Queue#, @@ -30,30 +30,30 @@ \chapter{Listas Baseadas em Array} \item Arrays não são muito dinâmicos. Adicionar ou remover um elemento perto do meio de uma lista significa que um grande número de elemento no array precisam ser deslocados para abrir espaço para o elemento recentemente adicionado ou preencher a lacuna criada pelo elemento removido. Essa é a razão pela qual as operações - #add(i,x)# e #remove(i)# tem tempos de execução que dependem de + #add(i,x)# e #remove(i)# têm tempos de execução que dependem de #n# e #i#. \item Arrays não podem expandir ou encolher por si só. Quando o número de elementos na estrutura de dados excede o tamanho do array de apoio, um novo array precisa - ser alocado e os dados do antigo array precisa ser copiado + ser alocado e os dados do array antigo precisa ser copiado no novo array. Essa é uma operação cara. \end{itemize} Um terceiro ponto é importante. Os tempos de execução citados na tabela acima não incluem o custo associado com expandir ou encolher o array de apoio. -Veremos que, se não gerenciado com cuidado, o custo de expandir ou encolher o array de apoio não aumenta muito o custo de uma operação \emph{média}. +Veremos que, se não gerenciado com cuidado, o custo de expandir ou encolher o array de apoio não aumenta muito o custo \emph{médio} de uma operação. Mais precisamente, se iniciarmos com um estrutura de dados vazia e realizarmos qualquer sequência de $m$ operações #add(i,x)# ou #remove(i)# , então o custo total de expandir e encolher o array de apoio, sobre a sequência inteira de $m$ operações é $O(m)$. Embora algumas operações individuais sejam mais caras, o custo amortizado, quando dividido por todas as $m$ operações, é somente $O(1)$ por operação. \cpponly{ -Neste capítulo, e ao longo deste livro, seria conveniente ter arrays que guardam seus tamanhos. Os arrays típicos do C++ não fazem isso, então definimos uma classe, #array#, que registra seu tamanho. A implementação dessa classe direta. Ela é feita como um array C++ padrão, #a#, e um inteiro, #length#: +Neste capítulo, e ao longo deste livro, seria conveniente ter arrays que guardam seus tamanhos. Os arrays típicos do C++ não fazem isso, então definimos uma classe, #array#, que registra seu tamanho. A implementação dessa classe é direta. Ela é feita como um array C++ padrão, #a#, e um inteiro, #length#: } \cppimport{ods/array.a.length} \cpponly{ O tamanho de um #array# é especificado no momento de criação: } \cppimport{ods/array.array(len)} -\cpponly{Os eleementos de um array podem ser indexados:} +\cpponly{Os elementos de um array podem ser indexados:} \cppimport{ods/array.operator[]} \cpponly{Finalmente, quando um array é atribuído para outro, ocorre apenas uma manipulação de ponteiros que leva um tempo constante:} \cppimport{ods/array.operator=} @@ -62,8 +62,7 @@ \section{#ArrayStack#: Operações de Stack Rápida Usando um Array} \seclabel{arraystack} \index{ArrayStack@#ArrayStack#}% -Um -#ArrayStack# implementa a interface lista usando um array #a#, chamado de +Um #ArrayStack# implementa a interface lista usando um array #a#, chamado de \emph{array de apoio}. O elemento da lista com índice #i# é armazenado em #a[i]#. Na maior parte do tempo, #a# é maior que o estritamente necessário, então um inteiro @@ -83,17 +82,17 @@ \subsection{O Básico} As operações de adicionar e remover elementos de um #ArrayStack# -estão ilustradas em +estão ilustradas na \figref{arraystack}. Para implementar a operação #add(i,x)#, primeiro verificamos se #a# está cheio. Caso positivo, chamamos o método #resize()# para aumentar o tamanho de #a#. Como #resize()# é implementado será discutido depois. Por ora, é suficiente saber que, após uma chamada para #resize()#, temos certeza que $#a.length# > #n#$. -Com isso resolvido, agora nós deslocamos os elementos +Com isso resolvido, deslocamos os elementos $#a[i]#,\ldots,#a[n-1]#$ para uma posição à direita para abrir espaço para #x#, atribuir -#a[i]# igual a #x#, e incrementar #n#. +#a[i]# igual a #x# e incrementar #n#. \begin{figure} \begin{center} @@ -102,13 +101,13 @@ \subsection{O Básico} \caption[Adicionando a um ArrayStack]{Uma sequência de operações #add(i,x)# e #remove(i)# em um #ArrayStack#. Flechas denotam elementos sendo copiados. Operações que resultam em uma chamada para - #resize()# são marcados em um arterisco.} + #resize()# são marcados com um asterisco.} \figlabel{arraystack} \end{figure} \codeimport{ods/ArrayStack.add(i,x)} Se ignorarmos o custo de uma potencial chamada a -#resize()#, então o cusot da operação +#resize()#, então o custo da operação #add(i,x)# é proporcional ao número de elementos que temos que deslocar para abrir espaço para #x#. Portanto o custo dessa operação @@ -117,22 +116,22 @@ \subsection{O Básico} Implementar a operação #remove(i)# é similar. Desloca-se os elementos $#a[i+1]#,\ldots,#a[n-1]#$ à esquerda por uma posição (sobrescrevendo #a[i]#) -e decrementar o valor de - #n#. Após fazer isso, verificamos se #n# muito menor +e decrementa-se o valor de + #n#. Após fazer isso, verificamos se #n# é muito menor que #a.length# ao verificar se $#a.length# \ge 3#n#$. Caso positivo, então chamamos #resize()# para reduzir o tamanho de #a#. \codeimport{ods/ArrayStack.remove(i)} % TODO: Add shifting figure -Ao ignorar o custo do método #resize()#, o custo de uma operação #remove(i)# +Ignorando o custo do método #resize()#, o custo de uma operação #remove(i)# é proporcional ao número de elementos que deslocamos, que é $O(#n#-#i#)$. \subsection{Expansão e Redução} O método - #resize()# razoavelmente direto; ele aloca um novo array + #resize()# é razoavelmente simples; ele aloca um novo array #b# de tamanho $2#n#$ e copia os #n# elementos de #a# nas primeiras -#n# posições em #b#, e então atribui #a# em #b#. Então, após isso faz uma chamada a #resize()#, $#a.length# = 2#n#$. +#n# posições em #b# e então atribui #a# em #b#. Após isso, faz uma chamada a #resize()#, $#a.length# = 2#n#$. \codeimport{ods/ArrayStack.resize()} @@ -140,28 +139,26 @@ \subsection{Expansão e Redução} #resize()# é fácil. Ela aloca um array #b# de tamanho $2#n#$ e copia os #n# elementos de #a# em -#b#. Isso leva $O(#n#)$ de tempo. +#b#. Isso leva tempo $O(#n#)$. A análise de tempo de execução da seção anterior ignorou o custo de chamadas a #resize()#. Nesta seção analisaremos esse custo usando uma técnica chamada de \emph{análise amortizada}. Essa técnica não tenta determinar o custo de redimensionar o array durante cada operação -#add(i,x)# e #remove(i)#. Em vez disso, ela considera o custo de todas as chamadas a -#resize()# durante a sequência de $m$ chamadas a #add(i,x)# ou #remove(i)#. +#add(i,x)# e #remove(i)#. Em vez disso, ela considera o custo de todas as chamadas a #resize()# durante a sequência de $m$ chamadas a #add(i,x)# ou #remove(i)#. Em particular, mostraremos que: \begin{lem}\lemlabel{arraystack-amortized} Se um - #ArrayStack# vazio é criado e qualquer sequência de $m\ge 1$ chamadas a + #ArrayStack# vazio é criado e uma sequência de $m\ge 1$ chamadas a #add(i,x)# e #remove(i)# são executadas, então o tempo total gasto durante - todas as chamadas a - #resize()# é $O(m)$. + todas as chamadas a #resize()# é $O(m)$. \end{lem} \begin{proof} - Nós iremos mostra que em qualquer momento que + Mostraremos que em qualquer momento que #resize()# é chamada, o número de chamadas a - #add# ou #remove# desde a última chamada a #resize()# é pelo menos + #add()# ou #remove()# desde a última chamada a #resize()# é pelo menos $#n#/2-1$. Portanto, se $#n#_i$ denota o valor de #n# durante a $i$-ésima chamada a #resize()# e $r$ denota o número de chamadas a #resize()#, então o número total de chamadas a #add(i,x)# ou @@ -173,7 +170,7 @@ \subsection{Expansão e Redução} \[ \sum_{i=1}^{r} #n#_i \le 2m + 2r \enspace . \] - Por outro lado, o tempo total gasto durante todas as chamadas a #resize()# é + Por outro lado, o tempo total gasto durante todas as chamadas a #resize()# é \[ \sum_{i=1}^{r} O(#n#_i) \le O(m+r) = O(m) \enspace , \] @@ -224,37 +221,36 @@ \subsection{Resumo} \begin{thm}\thmlabel{arraystack} Uma #ArrayStack# implementa a interface #List#. Ignorando o custo de chamadas a - #resize()#, uma #ArrayStack# aceita as operações + #resize()#, uma #ArrayStack# possui as operações \begin{itemize} \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e - \item #add(i,x)# e #remove(i)# em $O(1+#n#-#i#)$ tempo por operação. + \item #add(i,x)# e #remove(i)# em tempo $O(1+#n#-#i#)$ por operação. \end{itemize} Além disso, ao comerçarmos com um - #ArrayStack# vazio e realizarmos qualquer sequência de $m$ operações + #ArrayStack# vazio e realizarmos uma sequência de $m$ operações #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ tempo gasto durante todas as chamadas a #resize()#. \end{thm} O #ArrayStack# é um jeito eficiente de implementar a #Stack#. -Em especial, podemos implementar - #push(x)# como #add(n,x)# e #pop()# -como #remove(n-1)# e nesse caso essas operações rodarão $O(1)$ -de tempo amortizado. +Em especial, podemos implementar #push(x)# como #add(n,x)# e #pop()# +como #remove(n-1)# e nesse caso essas operações rodarão em tempo $O(1)$ +amortizado. \section{#FastArrayStack#: Uma ArrayStack otimizada} \seclabel{fastarraystack} \index{FastArrayStack@#FastArrayStack#}% Muito do trabalho feito por uma - #ArrayStack# envolver o deslocamento (por + #ArrayStack# envolve o deslocamento (por #add(i,x)# e #remove(i)#) e cópias (pelo #resize()#) de dados. \notpcode{Nas implementações mostradas acima, isso era feito usando laços #for#.}% -\pcodeonly{Em uma implementação naive, isso seria feito usando laços #for#.} -Acontece que muitos ambientes de programação tem funções específicas que são muito +\pcodeonly{Em uma implementação ingênua, isso seria feito usando laços #for#.} +Acontece que muitos ambientes de programação têm funções específicas que são muito eficientes em copiar e mover blocos de dados. Na linguagem C, existem as funções #memcpy(d,s,n)# e #memmove(d,s,n)#. -Na linguagem C++ existe o algoritmo #std::copy(a0,a1,b)#. +Na linguagem C++, existe o algoritmo #std::copy(a0,a1,b)#. Em Java, existe o método #System.arraycopy(s,i,d,j,n)#. \index{memcpy@#memcpy(d,s,n)#}% @@ -264,16 +260,15 @@ \section{#FastArrayStack#: Uma ArrayStack otimizada} \cppimport{ods/FastArrayStack.add(i,x).remove(i).resize()} \javaimport{ods/FastArrayStack.add(i,x).remove(i).resize()} -Essas funções são em geral altamente otimizadas e podem usar até mesmo de -instruções de máquina especiais que podem fazer esse copiar muito mais rápido +Essas funções são, em geral, altamente otimizadas e podem usar até mesmo +instruções de máquina especiais que podem fazer esse cópia muito mais rápida do que poderíamos usando um laço #for#. -Embora o uso dessas funções não diminuam o tempo de execução assintoticamente falando, -pode ser uma otimização que vale a pena. +Embora o uso dessas funções não diminuam o tempo de execução assintoticamente falando, pode ser uma otimização que vale a pena. \pcodeonly{Nas nossas implementações em C++ e Java, o uso de funções de cópia rápida de arrays } \notpcode{Nas implementações em \lang\ aqui, o uso do nativo \javaonly{#System.arraycopy(s,i,d,j,n)#}\cpponly{#std::copy(a0,a1,b)#}} -resultaram em um fator de speedups (aceleração) entre 2 e 3, dependendo dos tipos de operações realizadas. +resultaram em um fator de aceleração, \emph{speedup}, entre 2 e 3, dependendo dos tipos de operações realizadas. O resultados podem variar de acordo com o caso. \section{#ArrayQueue#: Uma Queue Baseada Em Array} @@ -281,16 +276,16 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} \index{ArrayQueue@#ArrayQueue#}% -Nesta seção, apresentamos a estrutura de dados +Nesta seção apresentamos a estrutura de dados #ArrayQueue#, que implementa uma queue do tipo FIFO (first-in-first-out, primeiro-que-chega-primeiro-que-sai); elementos são removidos (usando a operação #remove()#) da queue na mesma ordem em que são adicionados (usando a operação #add(x)#). -Note uma +Note que uma #ArrayStack# é uma escolha ruim para uma implementação de uma -queue do tipo FIFO. Não é uma boa escolha porque precisamos escolher um fim da lista ao qual adicionaremos elementos e então remover elementos do outro lado. +queue do tipo FIFO. Não é uma boa escolha porque precisamos escolher um lado da lista ao qual adicionaremos elementos e então remover elementos do outro lado. Uma das duas operações precisa trabalhar na cabeça da lista, o que envolve chamar #add(i,x)# ou #remove(i)# com um valor de $#i#=0$. Isso resulta em um tempo de execução proporcional a #n#. @@ -325,17 +320,14 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} 15 \bmod 12 = 3 \enspace . \] -De modo mais geral, para um inteiro -$a$ e um inteiro positivo $m$, $a \bmod m$ -é único inteiro +De modo mais geral, para um inteiro $a$ e um inteiro positivo +$m$, $a \bmod m$ é o único inteiro $r\in\{0,\ldots,m-1\}$ tal que $a = r + km$ para algum inteiro $k$. Informalmente, o valor $r$ é o resto obtido quando dividimos $a$ por $m$. -\pcodeonly{Em muitas linguagens de programação, incluindo C, C++ e Java, o operador mod é representado -usando o símbolo \%.} +\pcodeonly{Em muitas linguagens de programação, incluindo C, C++ e Java, o operador mod é representado usando o símbolo \%.} \notpcode{Em muitas linguagens de programação incluindo \javaonly{Java}\cpponly{C++}, o operador $\bmod$ é representado -usando o símbolo - #%# symbol.\footnote{Isso às vezes é chamado de +usando o símbolo #%# symbol.\footnote{Isso às vezes é chamado de operador mod com \emph{morte cerebral}, pois não implementa corretamente o operador matemático mod quando o primeiro argumento é negativo.}} @@ -346,15 +338,14 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} os elementos da queue em posições do array \[ #a[j%a.length]#,#a[(j+1)%a.length]#,\ldots,#a[(j+n-1)%a.length]# \enspace. \] -Desse jeito trata-se o array - #a# como um \emph{array circular} +Desse jeito trata-se o array #a# como um \emph{array circular} \index{array circular}% \index{circular!array}% -no qual os índices do array maiores que -$#a.length#-1$ ``dão a volta'' ao começo do array. +no qual os índices do array maiores que $#a.length#-1$ ``dão a volta'' +ao começo do array. % TODO: figure -A única coisa que falta considerar é cuidar que o número de elementos no +A única coisa que falta é cuidar para que o número de elementos no #ArrayQueue# não ultrapasse o tamanho de #a#. \codeimport{ods/ArrayQueue.a.j.n} @@ -364,7 +355,7 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} ilustrado na \figref{arrayqueue}. Para implementar #add(x)#, primeiro verificamos se #a# está cheio e, se necessário, chamamos #resize()# para aumentar o tamanho de -#a#. Em seguida, guardamos #x# em +#a#. Em seguida, guardamos #x# em #a[(j+n)%a.length]# e incrementamos #n#. \begin{figure} @@ -372,15 +363,14 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} \includegraphics[scale=0.90909]{figs/arrayqueue} \end{center} \caption[Adicionar e remover de um ArrayQueue]{Uma sequência de operações #add(x)# e #remove(i)# em um - #ArrayQueue#. Flechas denotam elementos sendo copiados. Operações que resultam em uma chamada a - #resize()# são marcadas com um asterisco.} + #ArrayQueue#. Flechas denotam elementos sendo copiados. Operações que resultam em uma chamada a #resize()# são marcadas com um asterisco.} \figlabel{arrayqueue} \end{figure} \codeimport{ods/ArrayQueue.add(x)} Para implementar -#remove()#, primeiro guardamos #a[j]# para que reutilizá-lo depois. +#remove()#, primeiro guardamos #a[j]# para que possamos reutilizá-lo depois. A seguir, decrementamos #n# e incrementamos #j# (módulo #a.length#) ao atribuir $#j#=(#j#+1)\bmod #a.length#$. Finalmente, retornamos o valor guardado de @@ -412,7 +402,7 @@ \subsection{Resumo} #resize()#, uma #ArrayQueue# aceita as operações #add(x)# e #remove()# em tempo $O(1)$ por operação. Além disso, ao começar com um #ArrayQueue# vazio, qualquer sequência de $m$ -operações #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ tempo gasto +operações #add(i,x)# e #remove(i)# resulta em um total de tempo $O(m)$ gasto durante todas as chamadas a #resize()#. \end{thm} @@ -422,8 +412,8 @@ \section{#ArrayDeque#: Operações Rápidas para Deque Usando um Array} \seclabel{arraydeque} \index{ArrayDeque@#ArrayDeque#}% -O #ArrayQueue# da seção anterior é uma estrutura de daos para -representar a sequência que nos permite eficientemente adicionar a +O #ArrayQueue# da seção anterior é uma estrutura de dados para +representação de sequências que nos permite eficientemente adicionar a um lado da sequência e remover do outro. A estrutura de dados #ArrayDeque# permite a edição e remoção eficiente em ambos lados. @@ -441,15 +431,14 @@ \section{#ArrayDeque#: Operações Rápidas para Deque Usando um Array} A implementação de #add(i,x)# é um pouco mais interessante. Como sempre, primeiro - verificamos se - #a# está cheio e, se necessário, chamados + verificamos se #a# está cheio e, se necessário, chamados #resize()# para redimensionar #a#. Lembre-se que queremos que essa operação seja rápida quando #i# for pequeno (perto de 0) ou quando #i# é grande (perto de -#n#). Portanto, verificamos se $#i#<#n#/2$. Caso positiov, deslocamos os +#n#). Portanto, verificamos se $#i#<#n#/2$. Caso positivo, deslocamos os elementos $#a[0]#,\ldots,#a[i-1]#$ à esquerda por uma posição. Caso contrário, -($#i#\ge#n#/2$), deslocamos os elementos $#a[i]#,\ldots,#a[n-1]#$ à direito por uma posição -. Veja \figref{arraydeque} para uma ilustração das operações +($#i#\ge#n#/2$), deslocamos os elementos $#a[i]#,\ldots,#a[n-1]#$ à direita por uma posição. +Veja a \figref{arraydeque} para uma representação das operações #add(i,x)# e #remove(x)# em um #ArrayDeque#. \begin{figure} @@ -466,12 +455,11 @@ \section{#ArrayDeque#: Operações Rápidas para Deque Usando um Array} Ao deslocar dessa maneira, nós garantimos que #add(i,x)# nunca tem que deslocar mais de $\min\{ #i#, #n#-#i# \}$ elementos. Então, o tempo de execução da operação -#add(i,x)# (ignorando o custo de uma operação #resize()# -) é $O(1+\min\{#i#,#n#-#i#\})$. +#add(i,x)# (ignorando o custo de uma operação #resize()#) é $O(1+\min\{#i#,#n#-#i#\})$. -A implementação da operação #remove(i)# é similar. Ela ou desloca elementos -$#a[0]#,\ldots,#a[i-1]#$ à direita uma posição ou desloca elementos -$#a[i+1]#,\ldots,#a[n-1]#$ à esquerda uma posição dependendo se +A implementação da operação #remove(i)# é similar. Ela desloca elementos +$#a[0]#,\ldots,#a[i-1]#$ à direita em uma posição ou desloca elementos +$#a[i+1]#,\ldots,#a[n-1]#$ à esquerda em uma posição dependendo se $#i#<#n#/2$. Novamente, isso significa que #remove(i)# nunca gasta mais de $O(1+\min\{#i#,#n#-#i#\})$ tempo para deslocar elementos. @@ -485,12 +473,12 @@ \subsection{Resumo} Uma #ArrayDeque# implementa a interface #List#. Ignorando o custo de chamadas a #resize()#, um #ArrayDeque# aceita as operações \begin{itemize} - \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e + \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e \item #add(i,x)# e #remove(i)# em tempo $O(1+\min\{#i#,#n#-#i#\})$ por operação. \end{itemize} - Além disso, começar com um - #ArrayDeque# vazio, realizar qualquer sequência de $m$ operações + Além disso, começando com um + #ArrayDeque# vazio, uma sequência de $m$ operações #add(i,x)# e #remove(i)# resulta em um total de tempo $O(m)$ gasto durante todas as chamadas a #resize()#. \end{thm} @@ -500,16 +488,15 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \index{DualArrayDeque@#DualArrayDeque#}% A seguir, apresentamos uma estrutura de dados, o - #DualArrayDeque# que atinge os mesmos desempenhos -que um #ArrayDeque# ao usar + #DualArrayDeque#, que tem o mesmo desempenho que um #ArrayDeque# ao usar duas #ArrayStack#s. Embora o desempenho assintótico do -#DualArrayDeque# não é melhor que do #ArrayDeque#, ainda vale estudá-lo -,pois oferece um bom exemplo de como fazer uma estrutura de dados sofisticada pela combinação de duas estruturas de dados mais simples. +#DualArrayDeque# não seja melhor que do #ArrayDeque#, ainda vale estudá-lo +, pois oferece um bom exemplo de como obter uma estrutura de dados mais sofisticada pela combinação de duas estruturas de dados mais simples. -Um #DualArrayDeque# representa uma lista usando duas #ArrayStack#s. Relembre que um +Um #DualArrayDeque# representa uma lista usando duas #ArrayStack#s. Relembre-se que um #ArrayStack# é rápido quando as operações dele modificam elementos perto do final. Uma #DualArrayDeque# posiciona duas #ArrayStack#s, chamadas de #frontal# -and #traseira#, de modo complementar de tal forma que as operações são rápidas em ambas as direções. +e #traseira#, de modo complementar para que as operações sejam rápidas em ambas as direções. \codeimport{ods/DualArrayDeque.front.back} @@ -535,9 +522,9 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} Note que se um índice $#i#<#front.size()#$, então ele corresponde ao elemento de #front# na posição $#front.size()#-#i#-1$, pois -elementos de #front# são guardados em ordem inversa. +os elementos de #front# são guardados em ordem inversa. -A adição e remoção de elementos de uma #DualArrayDeque# é ilustrado em +A adição e a remoção de elementos de uma #DualArrayDeque# são ilustradas na \figref{dualarraydeque}. A operação #add(i,x)# manipula #front# ou #back#, conforme apropriado: @@ -554,13 +541,13 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \codeimport{ods/DualArrayDeque.add(i,x)} O método -#add(i,x)# realiza balanceamento das duas #ArrayStack#s +#add(i,x)# realiza o balanceamento das duas #ArrayStack#s #front# e #back#, ao chamar o método #balance()#. A implementação de -#balance()# é descrita a seguir, mas no memento é suficiente -saber que #balance()# garantes que, a não ser que $#size()#<2$, +#balance()# é descrita a seguir, mas no momento é suficiente +saber que #balance()# garante que, a não ser que $#size()#<2$, #front.size()# e #back.size()# não diferem por mais de um fator -de 3. Em particular, $3\cdot#front.size()# \ge #back.size()#$ e +de 3. Em especial, $3\cdot#front.size()# \ge #back.size()#$ e $3\cdot#back.size()# \ge #front.size()#$. A seguir, nós analisamos o custo de @@ -575,7 +562,7 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} \end{equation} Por outro lado, se $#i#\ge#front.size()#$, então #add(i,x)# é -implementado como $#back.add(i-front.size(),x)#$. O custo disso é +implementada como $#back.add(i-front.size(),x)#$. O custo disso é \begin{equation} O(#back.size()#-(#i#-#front.size()#)+1) = O(#n#-#i#+1) \enspace . \eqlabel{das-back} @@ -586,8 +573,8 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} O segundo caso \myeqref{das-back} ocorre quando $#i#\ge 3#n#/4$. Quando $#n#/4\le#i#<3#n#/4$, não temos certeza se a operação afeta -#front# ou #back#, mas nos dois casos, a operação leva -$O(#n#)=O(#i#)=O(#n#-#i#)$ de tempo, pois $#i#\ge #n#/4$ e $#n#-#i#> +#front# ou #back#, mas nos dois casos, a operação leva tempo +$O(#n#)=O(#i#)=O(#n#-#i#)$, pois $#i#\ge #n#/4$ e $#n#-#i#> #n#/4$. Resumindo a situação, temos \[ \mbox{Tempo de execução } #add(i,x)# \le @@ -613,10 +600,9 @@ \subsection{Balanceamento} e #remove(i)#. Essa operação garante que nem #front# nem #back# se tornem grandes demais (ou pequenos demais). Ela garante que, ao menos que haja menos de dois elementos, o - #front# e o #back# contém $#n#/4$ elementos cada. + #front# e o #back# possuem $#n#/4$ elementos cada. Se esse não for o caso, então ele move elementos entre elas -de tal forma que #front# e #back# contêm exatamente $\lfloor#n#/2\rfloor$ elementos -e $\lceil#n#/2\rceil$ elementos, respectivamente. +de tal forma que #front# e #back# contenham exatamente $\lfloor#n#/2\rfloor$ elementos e $\lceil#n#/2\rceil$ elementos, respectivamente. \codeimport{ods/DualArrayDeque.balance()} @@ -626,7 +612,7 @@ \subsection{Balanceamento} Isso é ruim, pois #balance()# é chamada a cada operação #add(i,x)# e #remove(i)#. Porém, o lema a seguir mostra que -na média, #balance()# somente gasta um tempo constante por operação. +na média, #balance()# gasta somente um tempo constante por operação. \begin{lem}\lemlabel{dualarraydeque-amortized} Se uma @@ -641,22 +627,22 @@ \subsection{Balanceamento} #balance()# é forçada a deslocar elementos, então o número de operações #add(i,x)# e #remove(i)# desde a última vez que elementos foram deslocador por #balance()# é pelo menos $#n#/2-1$. - Como na prova de + Assim como na prova do \lemref{arraystack-amortized}, é suficiente provar que o tempo total gasto por #balance()# é $O(m)$. - Iremos realizar nossa análise usando uma técnica conhecida como o \emph{método do potencial}. + Iremos realizar a nossa análise usando uma técnica conhecida como o \emph{método do potencial}. \index{potencial}% \index{método do potential}% Definimos o \emph{potencial}, $\Phi$, do - #DualArrayDeque# como a diferença em tamanho entre #front# e #back#: + #DualArrayDeque# como a diferença dos tamanhos entre #front# e #back#: \[ \Phi = |#front.size()# - #back.size()#| \enspace . \] - A propriedade interessante desse potencial é que uma chamada a + Uma propriedade interessante desse potencial é que uma chamada a #add(i,x)# ou #remove(i)# que não faz nenhum balanceamento pode aumentar o potencial em até 1. - Observe que, imediatamente depois de uma chamada a #balance()# que desloca elementos - , o potencial , $\Phi_0$, é no máximo 1, pois + Observe que, imediatamente após uma chamada a #balance()# que desloca elementos + a ,o potencial, $\Phi_0$, é no máximo 1, pois \[ \Phi_0 = \left|\lfloor#n#/2\rfloor-\lceil#n#/2\rceil\right|\le 1 \enspace .\] Considere a situação no momento exatamente anterior a uma chamada #balance()# que @@ -685,21 +671,21 @@ \subsection{Balanceamento} \subsection{Resumo} -O teorma a seguir resume as propriedades de uma #DualArrayDeque#: +O teorema a seguir resume as propriedades de uma #DualArrayDeque#: \begin{thm}\thmlabel{dualarraydeque} Uma #DualArrayDeque# implementa a interface #List#. Ignorando o custo de chamadas a #resize()# e #balance()#, uma #DualArrayDeque# - aceita as operações + possui as operações \begin{itemize} \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e \item #add(i,x)# e #remove(i)# em tempo $O(1+\min\{#i#,#n#-#i#\})$ por operação. \end{itemize} Além disso, ao começar com uma -#DualArrayDeque# vazia, qualquer sequência de $m$ operações - #add(i,x)# e #remove(i)# resulta em um total de tempo $O(m)$ +#DualArrayDeque# vazia, uma sequência de $m$ operações + #add(i,x)# e #remove(i)# resulta em um tempo total $O(m)$ durante todas as chamadas a #resize()# e #balance()#. \end{thm} @@ -708,40 +694,39 @@ \section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} \seclabel{rootisharraystack} \index{RootishArrayStack@#RootishArrayStack#}% -Uma das desvantagem de todas as estruturas de dados anteriores neste capítulo -é que, porque elas guardam os dados um array ou dois e evitam redimensionar +Uma das desvantagens de todas as estruturas de dados anteriores neste capítulo +é que, porque elas guardam os dados em um array ou dois e evitam redimensionar esses arrays com frequência, os arrays frequentemente não estão muito cheios. Por exemplo, imediatamente após uma operação #resize()# em uma #ArrayStack#, o array de apoio #a# tem somente metade do espaço em uso. E pior, às vezes somente um terço de #a# contém dados. -Nesta seção, distimos a estrutura de dados +Nesta seção, discutimos a estrutura de dados #RootishArrayStack#, que resolve o problema de espaço desperdiçado. A #RootishArrayStack# guarda #n# elementos usando arrays de tamanho $O(\sqrt{#n#})$. Nesses arrays, no máximo $O(\sqrt{#n#})$ posições do array estão vazias a qualquer momento. -Todo o restante do array está usando para guardar dados. Portanto, +Todo o restante do array está sendo usado para guardar dados. Portanto, essas estruturas de dados gastam até - $O(\sqrt{#n#})$ de espaço ao guardar #n# -elementos. + $O(\sqrt{#n#})$ de espaço ao guardar #n# elementos. Uma #RootishArrayStack# guarda seus elementos em uma lista de #r# arrays chamados de \emph{blocos} que são numerados $0,1,\ldots,#r#-1$. -Ver \figref{rootisharraystack}. O bloco $b$ contém $b+1$ elementos. +Veja a \figref{rootisharraystack}. O bloco $b$ contém $b+1$ elementos. Então, todos os - #r# blocos contém um total de + #r# blocos contêm um total de \[ 1+ 2+ 3+\cdots +#r# = #r#(#r#+1)/2 \] -elementos. A fórmula acima pode ser obtida conforme mostrado em \figref{gauss}. +elementos. A fórmula acima pode ser obtida conforme mostrado na \figref{gauss}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/rootisharraystack} \end{center} - \caption[Adição e remoção em uma RootishArrayStack]{Uma sequÊncia de operações #add(i,x)# e #remove(i)# em uma + \caption[Adição e remoção em uma RootishArrayStack]{Uma sequência de operações #add(i,x)# e #remove(i)# em uma #RootishArrayStack#. Flechas denotam elementos sendo copiados. } \figlabel{rootisharraystack} \end{figure} @@ -768,14 +753,14 @@ \section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} #i# e também o índice correspondente a #i# naquele bloco. Determinar o índice de #i# no bloco acaba sendo fácil. -Se o índice -#i# está no bloco #b#, então o número de elementos nos blocos +Se o índice #i# está no bloco #b#, então o número de elementos nos blocos $0,\ldots,#b#-1$ é $#b#(#b#+1)/2$. Portanto, #i# é guardado na posição \[ #j# = #i# - #b#(#b#+1)/2 \] -dentro do bloco #b#. Um pouco mais desafiador é o problema de determinar o valor de -#b#. O número de elementos que tem índices menores que ou iguais a +dentro do bloco #b#. Um pouco mais desafiador é o problema de +determinar o valor de +#b#. O número de elementos que têm índices menores que ou iguais a #i# é $#i#+1$. Por outro lado, o número de elementos nos blocos $0,\ldots,b$ é $(#b#+1)(#b#+2)/2$. Portanto, #b# é o menor inteiro tal que @@ -801,19 +786,20 @@ \section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} \codeimport{ods/RootishArrayStack.i2b(i)} -With this out of the way, the #get(i)# and #set(i,x)# methods are straightforward. We first compute the appropriate block #b# and the appropriate index #j# within the block and then perform the appropriate operation: +Com isso fora do caminho, os métodos +#get(i)# e #set(i,x)# são diretos. Primeiramente computamos +o bloco #b# apropriado e o índice #j# dentro do bloco e realizar a operação apropriada: \codeimport{ods/RootishArrayStack.get(i).set(i,x)} -Se usarmos quaisquer estruturas de dados neste capítulo para representar a lista de +Se usarmos quaisquer estruturas de dados deste capítulo para representar a lista de #blocks#, então #get(i)# e #set(i,x)# irão rodar em tempo constante. -O método #add(i,x)# irá, a esta altura, parecer familiar. Primeiro verificamos -se nossa estrutura de dados está cheia ao ver se o número de blocos +O método #add(i,x)# irá, a esta altura, parecer familiar. Primeiro verificamos +se nossa estrutura de dados está cheia ao verificar se o número de blocos , #r#, é tal que $#r#(#r#+1)/2 = #n#$. Caso positivo, chamamos #grow()# para adicionar outro bloco. Com isso feito, deslocamos elementos com índices -$#i#,\ldots,#n#-1$ à direita por uma posição para abrir espaço para o novo elemento com índice - #i#: +$#i#,\ldots,#n#-1$ à direita em uma posição para abrir espaço para o novo elemento com índice #i#: \codeimport{ods/RootishArrayStack.add(i,x)} @@ -821,13 +807,13 @@ \section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} \codeimport{ods/RootishArrayStack.grow()} -Ignorando o custo da operação - #grow()#, o custo da operação #add(i,x)# é dominado pelo custo de deslocamento e é portanto -$O(1+#n#-#i#)$, como um #ArrayStack#. +Ignorando o custo da operação #grow()#, o custo da +operação #add(i,x)# é dominado pelo custo de deslocamento +e é portanto $O(1+#n#-#i#)$, como um #ArrayStack#. A operação #remove(i)# é similar a #add(i,x)#. Ela desloca os elementos com índices -$#i#+1,\ldots,#n#$ à esquerda por uma posição e então, se há mais de um bloco vazio, ela chama o método -#shrink()# para remover todos menos um dos blocos não usados: +$#i#+1,\ldots,#n#$ à esquerda em uma posição e então, se há mais de um bloco vazio, ela chama o método +#shrink()# para remover todos, exceto um dos blocos não usados: \codeimport{ods/RootishArrayStack.remove(i)} \codeimport{ods/RootishArrayStack.shrink()} @@ -839,24 +825,25 @@ \section{#RootishArrayStack#: Uma Stack Array Eficiente No Uso de Espaço} \subsection{Análise de expandir e encolher} -A análise acima de - #add(i,x)# e #remove(i)# não leva em conta o custo de -#grow()# e #shrink()#. Note que, diferentemente da operação -#ArrayStack.resize()#, #grow()# e #shrink()# não copiam nenhum dado -. Elas simplesmente alocam ou liberam um array de tamanho #r#. -Em alguns ambientes, isso leva apenas tempo constante, enquanto em outros, pode ser necessário tempo proporcional a #r#. +A análise acima de #add(i,x)# e #remove(i)# +não leva em conta o custo de #grow()# e #shrink()#. +Note que, diferentemente da operação +#ArrayStack.resize()#, #grow()# e #shrink()# não copiam nenhum dado. +Elas simplesmente alocam ou liberam um array de tamanho #r#. +Em alguns ambientes, isso leva apenas tempo constante, enquanto +em outros, pode ser necessário tempo proporcional a #r#. Note que, imediatamente após uma chamada a -#grow()# ou #shrink()#, a situação é clara -. O bloco final está completamente vazio e todos os outros blocos estão completamente cheios. -Outra chamada a - #grow()# ou #shrink()# não acontecerá até após pelo menos +#grow()# ou #shrink()#, a situação é clara. O bloco final +está completamente vazio e todos os outros blocos estão +completamente cheios. +Outra chamada a #grow()# ou #shrink()# não acontecerá +até que pelo menos $#r#-1$ elementos tenham sido adicionados ou removidos. Portanto, mesmo se -#grow()# e #shrink()# leve tempo $O(#r#)$, esse custo pode ser amortizado sobre pelo menos -$#r#-1$ operações #add(i,x)# ou #remove(i)# +#grow()# e #shrink()# leve tempo $O(#r#)$, esse custo pode ser amortizado em pelo menos $#r#-1$ operações #add(i,x)# ou #remove(i)# fazendo com que o custo amortizado de - #grow()# e #shrink()# seja + #grow()# e #shrink()# sejam $O(1)$ por operação. \subsection{Uso de Espaço} @@ -865,41 +852,40 @@ \subsection{Uso de Espaço} A seguir, analisaremos a quantidade de espaço extra usada por uma #RootishArrayStack#. -Em particular, queremos contar qualquer espaço usado por uma #RootishArrayStack# que não é um elemento de array usado no momento para guardar um elemento da lista. Chamamos todo esse espaço de \emph{espaço desperdiçado}. +Em especial, queremos contar qualquer espaço usado por uma #RootishArrayStack# que não seja de um elemento de array usado no momento para guardar um elemento da lista. Chamamos todo esse espaço de \emph{espaço desperdiçado}. \index{espaço desperdiçado}% -% TODO continuar A operação -#remove(i)# assegura que uma #RootishArrayStack# nunca tem mais que dois -blocos que não estão complementamente cheios. +#remove(i)# assegura que uma #RootishArrayStack# nunca tenha mais do que dois +blocos que não estejam completamente cheios. O número de blocos #r#, usados por uma #RootishArrayStack# que guarda #n# elementos portanto satisfaz \[ (#r#-2)(#r#-1)/2 \le #n# \enspace . \] -De novo, usando a equação quadrática nisso resulta em +De novo, usando a equação quadrática resulta em \[ #r# \le \frac{1}{2}\left(3+\sqrt{8#n#+1}\right) = O(\sqrt{#n#}) \enspace . \] -Os últimos dois blocos tem tamanhos #r# e #r-1#, então o espaço desperdiçado por esses dois blocos é até +Os últimos dois blocos têm tamanhos #r# e #r-1#, então o espaço desperdiçado por esses dois blocos é até $2#r#-1 = O(\sqrt{#n#})$. Se guardarmos os blocos em (por exemplo) uma #ArrayStack#, então a quantidade de espaço gasto pela #List# que guarda esses #r# blocks também é $O(#r#)=O(\sqrt{#n#})$. -O espaço adicional ncessário para guardar #n# e outras informações auxiliares é $O(1)$. +O espaço adicional necessário para guardar #n# e outras informações auxiliares é $O(1)$. Portanto, a quantidade total de espaço desperdiçado em uma #RootishArrayStack# é $O(\sqrt{#n#})$. -A seguir, demonstramos que esse uso de espaço é ótimo qualquer estrutura -de dados que inicia vazia e permite a adição de um item por vez. +A seguir, demonstramos que esse uso de espaço é ótimo para qualquer estrutura +de dados que inicia-se vazia e permite a adição de um item por vez. Mais precisamente, mostraremos que, em algum momento durante a adição de #n# itens, a estrutura de dados está desperdiçando pelo menos um espaço de $\sqrt{#n#}$ (embora possa ser por apenas um curto momento). Suponha que iniciamos com uma estrutura de dados vazia e adicionamos #n# itens, -um por vez. No fim desse processo, todos os #n# intens são guardados na +um por vez. No fim desse processo, todos os #n# itens são guardados na estrutura e distribuídos entre uma coleção de #r# blocos de memória. Se $#r#\ge \sqrt{#n#}$, então a estrutura de dados precisa usar #r# -ponteiros (ou referências) para gerenciar esses #r# blocos, e esses +ponteiros (ou referências) para gerenciar esses #r# blocos e esses ponteiros são espaço desperdiçado. Por outro lado, se $#r# < \sqrt{#n#}$ @@ -918,7 +904,7 @@ \subsection{Resumo} \begin{thm}\thmlabel{rootisharraystack} Uma #RootishArrayStack# implementa a interface #List#. Ignorando o custo das chamadas -a #grow()# e #shrink()#, uma #RootishArrayStack# aceita as operações +a #grow()# e #shrink()#, uma #RootishArrayStack# possui as operações \begin{itemize} \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operações; e \item #add(i,x)# e #remove(i)# em tempo $O(1+#n#-#i#)$ por operação. @@ -927,30 +913,30 @@ \subsection{Resumo} #RootishArrayStack# vazia, qualquer sequência de $m$ operações #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ tempo gasto durante todas as chamadas de #grow()# e #shrink()#. - O espaço (medido em palavras)\footnote{Reveja \secref{model} para uma discussão de como a memória é medida.} usada por uma #RootishArrayStack# que guarda + O espaço (medido em palavras)\footnote{Reveja a \secref{model} para uma discussão de como a memória é medida.} usada por uma #RootishArrayStack# que guarda #n# elementos é $#n# +O(\sqrt{#n#})$. \end{thm} \notpcode{ -\subsection{Computando Raizes Quadradas} +\subsection{Computando Raízes Quadradas} \index{raizes quadradas}% Um leitor que tenha tido alguma exposição a modelos de computação -podem notar que -a #RootishArrayStack#, conforme descrita anteriormente, +pode notar que a #RootishArrayStack#, conforme descrita anteriormente, não se encaixa no modelo de computação normal word-RAM -(\secref{model}) porque requer a computação de raizes quadradas. -A operação raiz quadrada não é geralmente considerada uma operação básica e portante não é normalmente parte do modelo -word-RAM. +(\secref{model}) porque requer a computação de raízes quadradas. +A operação raiz quadrada não é geralmente considerada uma operação +básica e portanto não é normalmente parte do modelo word-RAM. Nesta seção, mostraremos que a operação raiz quadrada pode ser implementada eficientemente. Em particular, mostramos que para qualquer inteiro $#x#\in\{0,\ldots,#n#\}$, $\lfloor\sqrt{#x#}\rfloor$ pode ser computado em tempo constante, após um pré-processamento com tempo de execução de -$O(\sqrt{#n#})$ que cria dois array de tamanho -$O(\sqrt{#n#})$. O lema a seguir mostra que podemos reduzir o problema de computar a raiz quadrada de #x# à raiz quadrada de um valor relacionado #x'#. -root of a related value #x'#. +$O(\sqrt{#n#})$ que cria dois arrays de tamanho +$O(\sqrt{#n#})$. O lema a seguir mostra que podemos reduzir o problema +de computar a raiz quadrada de #x# à raiz quadrada de um valor +relacionado #x'#. \begin{lem}\lemlabel{root} Seja $#x#\ge 1$ e seja $#x'#=#x#-a$, onde $0\le a\le\sqrt{#x#}$. Então @@ -966,29 +952,27 @@ \subsection{Computando Raizes Quadradas} \[ #x#-\sqrt{#x#} \ge #x#-2\sqrt{#x#}+1 \] -e juntar termos para obter +e junta-se os termos para obter \[ \sqrt{#x#} \ge 1 \] o que é claramente verdadeiro para qualquer $#x#\ge 1$. \end{proof} -Inicia-se restringindo o problema um pouco, e assume que +Inicia-se restringindo o problema um pouco e assume-se que $2^{#r#} \le #x# < 2^{#r#+1}$, tal que $\lfloor\log #x#\rfloor=#r#$, isto é, #x# é um -inteiro com -$#r#+1$ bits na sua representação binária. Podemos fazer que +inteiro com $#r#+1$ bits na sua representação binária. Podemos fazer que $#x'#=#x# - (#x#\bmod 2^{\lfloor r/2\rfloor})$. Agora, #x'# satisfaz -as condições de \lemref{root}, então $\sqrt{#x#}-\sqrt{#x'#} \le 1$. +as condições do \lemref{root}, então $\sqrt{#x#}-\sqrt{#x'#} \le 1$. Além disso, - #x'# tem todos os seu bits de baixa ordem $\lfloor #r#/2\rfloor$ bits + #x'# tem todos os seu bits de baixa ordem $\lfloor #r#/2\rfloor$ iguais a 0, então só há \[ 2^{#r#+1-\lfloor #r#/2\rfloor} \le 4\cdot2^{#r#/2} \le 4\sqrt{#x#} \] -valores possíveis de #x'#. Isso significa que podemos umas um array #sqrttab#, -que guarda o valor de - $\lfloor\sqrt{#x'#}\rfloor$ para cada possível valor de +valores possíveis para #x'#. Isso significa que podemos um array #sqrttab#, +que guarda o valor de $\lfloor\sqrt{#x'#}\rfloor$ para cada possível valor de #x'#. Um pouco mais precisamente, temos \[ #sqrttab#[i] @@ -996,31 +980,29 @@ \subsection{Computando Raizes Quadradas} \sqrt{i 2^{\lfloor #r#/2\rfloor}} \right\rfloor \enspace . \] -Desse jeito, $#sqrttab#[i]$ tem um diferença de até 2 em relação a $\sqrt{#x#}$ para todo +Desse modo, $#sqrttab#[i]$ tem uma diferença de até 2 em relação a $\sqrt{#x#}$ para todo $#x#\in\{i2^{\lfloor #r#/2\rfloor},\ldots,(i+1)2^{\lfloor #r#/2\rfloor}-1\}$. De outra forma, a entrada do array -$#s#=#sqrttab#[#x##>>#\lfloor #r#/2\rfloor]$ é ou igual a -$\lfloor\sqrt{#x#}\rfloor$, -$\lfloor\sqrt{#x#}\rfloor-1$, ou +$#s#=#sqrttab#[#x##>>#\lfloor #r#/2\rfloor]$ é igual a +$\lfloor\sqrt{#x#}\rfloor$, a +$\lfloor\sqrt{#x#}\rfloor-1$ ou a $\lfloor\sqrt{#x#}\rfloor-2$. A partir de #s# podemos determinar o valor - $\lfloor\sqrt{#x#}\rfloor$ ao incrementar + $\lfloor\sqrt{#x#}\rfloor$ ao incrementar #s# até u $(#s#+1)^2 > #x#$. } % notpcode \javaimport{ods/FastSqrt.sqrt(x,r)} \cppimport{ods/FastSqrt.sqrt(x,r)} \notpcode{ -Isso é funciona para +Isso somente funciona para $#x#\in\{2^{#r#},\ldots,2^{#r#+1}-1\}$ e -#sqrttab# é uma tabela especial que funciona somente para um valor particular de +#sqrttab# é uma tabela especial que funciona somente para um valor especial de $#r#=\lfloor\log #x#\rfloor$. Para superar isso, poderíamos computar $\lfloor\log #n#\rfloor$ arrays #sqrttab# diferentes, um para cada valor possível de $\lfloor\log #x#\rfloor$. Os tamanhos dessas tabelas formam uma sequência exponencial cujo maior valor é até $4\sqrt{#n#}$, então o tamanho total de todas as tabelas é $O(\sqrt{#n#})$. -Porém, acontece que mais um array - #sqrttab# é desnecessário; - somente precisamos de um array - #sqrttab# para o valor $#r#=\lfloor\log +Porém, acontece que mais um array #sqrttab# é desnecessário; + somente precisamos de um array #sqrttab# para o valor $#r#=\lfloor\log #n#\rfloor$. Qualquer valor #x# com $\log#x#=#r'#<#r#$ pode ser \emph{atualizado} ao multiplicar #x# por $2^{#r#-#r'#}$ e usar a equação @@ -1037,7 +1019,7 @@ \subsection{Computando Raizes Quadradas} \javaimport{ods/FastSqrt.sqrt(x)} \cppimport{ods/FastSqrt.sqrt(x)} \notpcode{ - Algo que tinhamos tido como certo até agora depende de como computamos + Algo que tínhamos como certo até agora depende de como computamos $#r#'=\lfloor\log#x#\rfloor$. De novo, esse problema pode ser resolvido com um array, #logtab#, de tamanho $2^{#r#/2}$. Nesse caso, o código é particularmente simples, pois @@ -1052,7 +1034,7 @@ \subsection{Computando Raizes Quadradas} \javaimport{ods/FastSqrt.log(x)} \cppimport{ods/FastSqrt.log(x)} \notpcode{ - Finalmente, para completude, incluimos o código a seguir que inicializa + Finalmente, para completude, incluímos o código a seguir que inicializa #logtab# e #sqrttab#: } % notpcode \javaimport{ods/FastSqrt.inittabs()} @@ -1061,28 +1043,28 @@ \subsection{Computando Raizes Quadradas} Para resumir, as computações feitas pelo método #i2b(i)# podem ser implementadas em tempo constante no modelo word-RAM usando $O(\sqrt{n})$ de memória extra para guardar -os arrays #sqrttab# e #logtab#. Esses arrays podem ser recontruídos +os arrays #sqrttab# e #logtab#. Esses arrays podem ser reconstruídos quando - #n# aumenta ou diminui por um fator de dois, e o custo dessa reconstrução + #n# aumenta ou diminui por um fator de dois e o custo dessa reconstrução pode ser amortizado sobre o número de operações #add(i,x)# e #remove(i)# que causaram a mudança do mesmo jeito que o custo de #resize()# é analisado na implementação da #ArrayStack#. } % notpcode -\section{Discussion and Exercises} +\section{Discussão e Exercícios} -A maior parte das estruturas de daos descritas neste capítula são folclore. +A maior parte das estruturas de dados descritas neste capítulo são folclore. Elas podem ser encontradas em implementações de pelo menos 30 anos atrás. Por exemplo, implementações de stacks, queues e deques que generalizam facilmente para as estruturas #ArrayStack#, #ArrayQueue# e #ArrayDeque# descritas aqui, são discutidas por Knuth \cite[Section~2.2.2]{k97v1}. -Brodnik \etal\ \cite{bcdms99} parece ter sido o primeiro a descrever -a #RootishArrayStack# e provar um limitante inferior de $\sqrt{n}$ como em +Brodnik \etal\ \cite{bcdms99} parecem terem sido os primeiros a descrever +a #RootishArrayStack# e provar um limitante inferior de $\sqrt{n}$ como descrito na \secref{rootishspaceusage}. Eles também apresentam uma estrutura diferente que -usa uma escolha de tamanhos de blocos mais sofisticada a fim de evitar a computação de raizes quadradas usando o método +usa uma escolha de tamanhos de blocos mais sofisticada a fim de evitar a computação de raízes quadradas usando o método #i2b(i)#. Com o esquema deles, o bloco contendo #i# é o bloco $\lfloor\log (#i#+1)\rfloor$, que é simplesmente o índice do primeiro bit 1 na representação binária de $#i#+1$. Algumas arquiteturas de computadores provêm uma instrução para computar o índice do primeiro bit 1 em um inteiro. @@ -1098,15 +1080,15 @@ \section{Discussion and Exercises} Essa estrutura suporta as operações #get(i,x)# e #set(i,x)# em tempo constante e #add(i,x)# e #remove(i)# em $O(\sqrt{#n#})$ de tempo. -Esses tempos de execução são similare ao que pode ser conseguido com uma implementação mais cuidadosa de uma +Esses tempos de execução são similares ao que pode ser conseguido com uma implementação mais cuidadosa de uma #RootishArrayStack# discutida em \excref{rootisharraystack-fast}. \javaonly{ \begin{exc} Na implementação da #ArrayStack#, após a primeira chamada a #remove(i)#, - o array de apoio -, #a#, contém $#n#+1$ valores não #null# apesar do fato de que + o array de apoio, #a#, contém $#n#+1$ valores + não #null# apesar do fato de que a #ArrayStack# somente contém #n# elementos. Onde está o valor não #null# extra? Discuta as consequências que esse valor não #null# possa ter no gerenciador de memória da Java Runtime Environment. @@ -1119,8 +1101,7 @@ \section{Discussion and Exercises} O método da #List# #addAll(i,c)# insere todos os elementos da #Collection# #c# na posição #i# da lista. (O método #add(i,x)# é um caso especial onde - $#c#=\{#x#\}$.) Explique porque, para as estruturas de dados deste capítulo - , não é eficiente implementar #addAll(i,c)# fazendo chamadas repetidas a + $#c#=\{#x#\}$.) Explique porque, para as estruturas de dados deste capítulo, não é eficiente implementar #addAll(i,c)# fazendo chamadas repetidas a #add(i,x)#. Projete e codifique uma implementação mais eficiente. \end{exc} @@ -1131,7 +1112,7 @@ \section{Discussion and Exercises} Essa é uma implementação da interface #Queue# na qual a operação #remove()# remove um elemento que é escolhido de uma distribuição aleatória uniforme entre todos os elementos atualmente na queue. (Considere uma #RandomQueue# como sendo uma sacola na qual - podemos adicionar elementos ou por a mão e sem ver remover algum elemento aleatório.) + podemos adicionar elementos ou por a mão e, sem ver, remover algum elemento aleatório.) As operações #add(x)# e #remove()# em uma #RandomQueue# deve rodar em um tempo constante amortizado por operação. \end{exc} @@ -1141,12 +1122,12 @@ \section{Discussion and Exercises} #Treque# (uma queue com três pontas). \index{Treque@#Treque#}% Essa é uma implementação de uma #List# - na qual #get(i)# e #set(i,x)# rodas em tempo constante + na qual #get(i)# e #set(i,x)# rodam em tempo constante e #add(i,x)# e #remove(i)# rodam em tempo \[ O(1+\min\{#i#, #n#-#i#, |#n#/2-#i#|\}) \enspace . \] - Em outras palabras, modificações são rápidas se elas estão perto ou do fim, ou do meio da lista. + Em outras palavras, modificações são rápidas se elas estão perto ou do fim, ou do meio da lista. \end{exc} \begin{exc} @@ -1161,15 +1142,15 @@ \section{Discussion and Exercises} #rotate(r)# que ``rotaciona'' uma #List# tal que o item da lista #i# se torna o item $(#i#+#r#)\bmod #n#$. Ao rodar em uma #ArrayDeque# ou uma #DualArrayDeque#, #rotate(r)# deve rodar em - $O(1+\min\{#r#,#n#-#r#\})$ de tempo. + tempo $O(1+\min\{#r#,#n#-#r#\})$. \end{exc} \begin{exc} \pcodeonly{Esse exercício não é incluído na edição da linguagem\lang\ .} \notpcode{ Modifique a implementação - da #ArrayDeque# tal que o deslocamente feito por - #add(i,x)#, #remove(i)#, e #resize()# é realizado usando o método + da #ArrayDeque# tal que o deslocamento feito por + #add(i,x)#, #remove(i)# e #resize()# é realizado usando o método rápido #System.arraycopy(s,i,d,j,n)#.} \end{exc} @@ -1177,8 +1158,7 @@ \section{Discussion and Exercises} Modifique a implementação da #ArrayDeque# tal que não use o operador #%# (que é caro em alguns sistemas). Em vez disso, deve fazer uso do - fato que se - #a.length# é uma potência de 2, + fato que se #a.length# é uma potência de 2, então \[ #k%a.length#=#k&(a.length-1)# \enspace . \] @@ -1188,23 +1168,22 @@ \section{Discussion and Exercises} \begin{exc} Projete e implemente uma variante da #ArrayDeque# que não faz uso de aritmética modular. Em vez disso, todos os dados ficam em um bloco consecutivo, em ordem, dentro de um array. - Quando os dados sobrescrevem o começo ou fim desse array, uma operação #rebuild()# modificada é realizada. + Quando os dados sobrescrevem o começo ou o fim desse array, uma operação #rebuild()# modificada é realizada. O custo amortizado de todas as operações deve ser o mesmo que em uma #ArrayDeque#. - \noindent Dica: Fazer isso funcionar tem a ver como você implementa a operação #rebuild()#. Deve-se fazer #rebuild()# para colocar dados na estrutura de dados em um estado onde os dados não podem sair pelas duas pontas até que pelo menos + \noindent Dica: fazer isso funcionar tem a ver com a forma que você implementa a operação #rebuild()#. Deve-se fazer #rebuild()# para colocar dados na estrutura de dados em um estado onde os dados não podem sair pelas duas pontas até que pelo menos $#n#/2$ operações tenham sido realizadas. Teste o desempenho da sua implementação em comparação à - #ArrayDeque#. Otimize sua implementação - (usando #System.arraycopy(a,i,b,i,n)#) + #ArrayDeque#. Otimize sua implementação (usando #System.arraycopy(a,i,b,i,n)#) e veja se consegue fazê-la mais rápida que a implementação da #ArrayDeque#. \end{exc} \begin{exc} - Projete e implemente uma versão de uma - #RootishArrayStack# que desperdiça somente - $O(\sqrt{#n#})$ de espaço, mas que pode realizar as operações #add(i,x)# + Projete e implemente uma versão de uma #RootishArrayStack# + que desperdiça somente $O(\sqrt{#n#})$ de espaço, mas que + pode realizar as operações #add(i,x)# e #remove(i,x)# de $O(1+\min\{#i#,#n#-#i#\})$ de tempo. \end{exc} @@ -1213,7 +1192,7 @@ \section{Discussion and Exercises} #RootishArrayStack# que desperdiça somente $O(\sqrt{#n#})$ de espaço, mas que pode rodar as operações #add(i,x)# e #remove(i,x)# em $O(1+\min\{\sqrt{#n#},#n#-#i#\})$ - de tempo. (Para uma ideia de como fazer isso, veja \secref{selist}.) + de tempo. (Para uma ideia de como fazer isso, veja a \secref{selist}.) \end{exc} \begin{exc} @@ -1221,7 +1200,7 @@ \section{Discussion and Exercises} uma #RootishArrayStack# que desperdiça somente $O(\sqrt{#n#})$ de espaço, mas que pode rodar as operações #add(i,x)# e #remove(i,x)# em $O(1+\min\{#i#,\sqrt {#n#},#n#-#i#\})$ de tempo. - (Veja \secref{selist} para ideia de como conseguir isso.) + (Veja a \secref{selist} para uma ideia de como conseguir isso.) \end{exc} \begin{exc} @@ -1233,4 +1212,3 @@ \section{Discussion and Exercises} #add(i,x)# e #remove(i)# levam $O(#n#^{1/3})$ de tempo amortizado. \end{exc} - From d04ed1e04e3604974ae175ba8dd40fb4586810ba Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 9 Sep 2020 18:11:45 -0300 Subject: [PATCH 42/66] finished revision of portuguese translation of --- latex/linkedlists.tex | 220 ++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 116 deletions(-) diff --git a/latex/linkedlists.tex b/latex/linkedlists.tex index 3a618e84..e36eb49c 100644 --- a/latex/linkedlists.tex +++ b/latex/linkedlists.tex @@ -3,18 +3,18 @@ \chapter{Listas Ligadas} \index{linked list}% \index{lista ligada}% -Neste capítulo, constinuamos a estudar implementações da +Neste capítulo, continuamos a estudar implementações da interface #List# e agora usando estruturas de dados baseadas em ponteiros -em vez de arrays. As estruturas neste capítulo são feitas de nodos +em vez de arrays. As estruturas neste capítulo são compostas de nodos (ou nós) que contêm os itens da lista. Usando referências (ponteiros), os nodos são -ligados entre si em uma sequência. Primeiro estudamos listas simplestmente ligadas (listas com somente uma ligação), que podem implementar operação da #Stack# e #Queue# (FIFO) em tempo constante por operação e então seguimos para listas +ligados entre si em uma sequência. Primeiro estudamos listas simplesmente ligadas (listas com somente uma ligação), que podem implementar operação da #Stack# e #Queue# (FIFO) em tempo constante por operação e então seguimos para listas duplamente ligadas, que podem implementar operações #Deque# em tempo constante. -Listas ligadas tem vantagens e desvantagens quando comparadas a implementações +Listas ligadas têm vantagens e desvantagens quando comparadas a implementações baseadas em arrays da interface #List#. A principal desvantagem é que perdemos -a habilidade de acessar qualquer elementos usando +a habilidade de acessar elementos usando #get(i)# ou #set(i,x)# em tempo constante. -Em vez disso, temos que percorrer a lista, um elemento por vez, até chegarmos no #i#-ésimo elemento. A principal vantagem é que são mais dinâmicos: dada uma referência a qualquer nodo da lista #u#, podemos deletar #u# ou inserir um nodo adjacente a #u# em tempo constante. Isso vale não importa on #u# esteja na lista. +Em vez disso, temos que percorrer a lista, um elemento por vez, até chegarmos no #i#-ésimo elemento. A principal vantagem é que são mais dinâmicos: dada uma referência a qualquer nodo da lista #u#, podemos deletar #u# ou inserir um nodo adjacente a #u# em tempo constante. Isso vale não importa onde #u# esteja na lista. \section{#SLList#: Uma Lista Simplesmente Ligada} \seclabel{sllist} @@ -23,10 +23,11 @@ \section{#SLList#: Uma Lista Simplesmente Ligada} \index{linked list!singly-}% \index{singly-linked list}% \index{lista simplesmente ligada}% -Uma - #SLList# (em inglês, singly-linked list) é uma sequência de #Node#s (em português, nodo). Cada nodo -#u# guarda um valor de dado #u.x# e uma referência #u.next# ao próximo nodo na sequência -. Para o último nodo #w# na sequência vale $#w.next# = #null#$ +Uma #SLList# (em inglês, singly-linked list) é uma +sequência de #Node#s (em português, nodo). Cada nodo +#u# guarda um valor de dado #u.x# e uma referência #u.next# +ao próximo nodo na sequência. +Para o último nodo #w# na sequência vale $#w.next# = #null#$ % TODO: Remove constructors from SLList.Node \javaimport{ods/SLList.Node} @@ -38,7 +39,7 @@ \section{#SLList#: Uma Lista Simplesmente Ligada} #n# para guardar o comprimento da sequência: \codeimport{ods/SLList.head.tail.n} Uma sequência de operações #Stack# e #Queue# em uma #SLList# está ilustrada -em \figref{sllist}. +na \figref{sllist}. \begin{figure} \begin{center} @@ -63,7 +64,7 @@ \section{#SLList#: Uma Lista Simplesmente Ligada} \codeimport{ods/SLList.pop()} -Claramente, ambas operações #push()# e #pop()# roda em tempo #O(1)#. +Claramente, ambas operações #push()# e #pop()# rodam em tempo #O(1)#. \subsection{Operações de Queue} @@ -73,7 +74,7 @@ \subsection{Operações de Queue} \codeimport{ods/SLList.remove()} Adições, por outro lado, são feitas na cauda da lista. Na maior parte dos casos, -isso é feito ao atribuir $#tail.next#=#u#$, onde #u# é mais novo nodo que contém #x#. Entretato, um caso especial ocorre quando $#n#=0$, no qual $#tail#=#head#=#null#$. Nesse caso, tanto #tail# +isso é feito ao atribuir $#tail.next#=#u#$, onde #u# é mais novo nodo que contém #x#. Entretanto, um caso especial ocorre quando $#n#=0$, no qual $#tail#=#head#=#null#$. Nesse caso, tanto #tail# quanto #head# são atribuídos a #u#. \codeimport{ods/SLList.add(x)} @@ -88,14 +89,13 @@ \subsection{Resumo} \begin{thm}\thmlabel{sllist} Uma #SLList# implementa as interfaces #Stack# e #Queue# (FIFO). As operações #push(x)#, #pop()#, #add(x)# e #remove()# rodam em - $O(1)$ de tempo por operação. + tempo $O(1)$ por operação. \end{thm} -Uma -#SLList# quase implementa o conjunto completo de operações de uma #Deque#. +Uma #SLList# quase implementa o conjunto completo de operações de uma #Deque#. A única operação que falta é remoção da cauda de uma #SLList#. - Remover da cauda de um #SLList# é difícil porquê requer a atualização + Remover da cauda de um #SLList# é difícil porque requer a atualização do valor da #tail# de forma que ela aponte ao nodo #w# que precede #tail# na #SLList#; esse é o nodo #w# tal que $#w.next#=#tail#$. Infelizmente, o único jeito de chegar a #w# é percorrendo @@ -110,24 +110,24 @@ \section{#DLList#: Uma Lista Duplamente Ligada} \index{lista ligada!dupla}% \index{lista duplamente ligada}% -Uma #DLList# (do inglês, doubly-linked list) é muito similar a uma #SLList# exceto que cada nodo #u# em uma #DLList# tem referências ao nodo que ele precede #u.next# e ao nodo que sucede #u.prev#. +Uma #DLList# (do inglês, doubly-linked list) é muito similar a uma #SLList# exceto que cada nodo #u# em uma #DLList# tem referências ao nodo que ele precede, #u.next#, e ao nodo que sucede, #u.prev#. \javaimport{ods/DLList.Node} \cppimport{ods/DLList.Node} -Ao implementar uma #SLList#, vimos que haviam vários casos especiais para tomar cuidado. Por exemplo, ao remover o último elemento de uma #SLList# ou adicionar um elemento a uma #SLList# vazia precisamos tomar cuidade especial para garantir +Ao implementar uma #SLList#, vimos que haviam vários casos especiais para tomar cuidado. Por exemplo, ao remover o último elemento de uma #SLList# ou adicionar um elemento a uma #SLList# vazia precisamos tomar cuidado especial para garantir que #head# e #tail# sejam corretamente atualizados. Em uma #DLList#, o número desses casos especiais aumentam consideravelmente. -Talvez o modo mais limpo de cuidar de todos esses casos especiais em uma +Talvez o modo mais simples de cuidar de todos esses casos especiais em uma #DLList# é introduzir um nodo #dummy#, também conhecido como \emph{sentinela}. \index{dummy node}% \index{nodo dummy}% \index{sentinela}% -Esse nodo não contém nenhum dado, mas atua como um marcador para que não tenhamos nenhum nodo especial -; todo nodo tem um #next# e um #prev#, com um #dummy# agindo como o nodo -que sucede o último nodo na lista e que precede o primeiro nodo da lista. -Desse jeito, os nodos da lista são (duplamente) ligados em um ciclo, como -ilustrado na \figref{dllist}. +Esse nodo não contém nenhum dado, mas atua como um marcador para que não tenhamos nenhum nodo especial; todo nodo tem um #next# e um #prev#, +com um #dummy# agindo como o nodo que sucede o último nodo +na lista e que precede o primeiro nodo da lista. +Desse jeito, os nodos da lista são (duplamente) ligados em um ciclo, +como ilustrado na \figref{dllist}. \begin{figure} \begin{center} @@ -137,13 +137,12 @@ \section{#DLList#: Uma Lista Duplamente Ligada} \figlabel{dllist} \end{figure} - %TODO: Remove constructors from class Node \codeimport{ods/DLList.n.dummy.DLList()} -Encontrar o nodo com índice em particular em um #DLList# é fácil; -podemos ou começar na cabeça da lista (#dummy.next#) e ir para frente, +Encontrar o nodo com um dado índice em uma #DLList# é fácil; +podemos começar na cabeça da lista (#dummy.next#) e ir para frente, ou começar na cauda da lista (#dummy.next#) e ir para trás. Isso permite alcançarmos o #i#-ésimo nodo em $O(1+\min\{#i#,#n#-#i#\})$ de tempo: @@ -162,7 +161,7 @@ \subsection{Adicionando e Removendo} Se temos uma referência a um nodo #w# em uma #DLList# e queremos inserir um nodo #u# antes de #w#, então isso é só uma questão de atribuir $#u.next#=#w#$, -$#u.prev#=#w.prev#$, e então ajustar #u.prev.next# e #u.next.prev#. (Ver \figref{dllist-addbefore}.) +$#u.prev#=#w.prev#$, e então ajustar #u.prev.next# e #u.next.prev#. (Veja a \figref{dllist-addbefore}.) Graças o nodo dummy, não há necessidade de se preocupar sobre #w.prev# existir ou não. @@ -172,12 +171,12 @@ \subsection{Adicionando e Removendo} \begin{center} \includegraphics[scale=0.90909]{figs/dllist-addbefore} \end{center} - \caption[Adicionando em uma DLList]{Adicionando o nodeo #u# antes do nodo #w# em uma #DLList#.} + \caption[Adicionando em uma DLList]{Adicionando o nodo #u# antes do nodo #w# em uma #DLList#.} \figlabel{dllist-addbefore} \end{figure} Agora, a operação da lista #add(i,x)# é trivial para implementar. -Achamos o #i#-ésimo nodo na #DLList# and inserimos um novo nodo #u# que contém +Achamos o #i#-ésimo nodo na #DLList# e inserimos um novo nodo #u# que contém #x# logo antes dele. \codeimport{ods/DLList.add(i,x)} @@ -199,15 +198,15 @@ \subsection{Adicionando e Removendo} #getNode(i)#, e então #remove(i)# roda em tempo $O(1+\min\{#i#, #n#-#i#\})$. \subsection{Resumo} -O teorema a seguir resumo o desempenho de uma #DLList#: +O teorema a seguir resume o desempenho de uma #DLList#: \begin{thm}\thmlabel{dllist} - Uma #DLList# implementa a interface #List# interface. Nessa implementação , + Uma #DLList# implementa a interface #List#. Nessa implementação, as operações #get(i)#, #set(i,x)#, #add(i,x)# e #remove(i)# rodam em - $O(1+\min\{#i#,#n#-#i#\})$ de tempo por operação. +tempo $O(1+\min\{#i#,#n#-#i#\})$ por operação. \end{thm} -Vale notar que, se ignoramos o custo de #getNode(i)#, então todas +Vale notar que, se ignorarmos o custo de #getNode(i)#, então todas as operações em uma #DLList# leva tempo constante. Então, a única parte cara das operações em uma #DLList# é achar o nodo relevante. Uma vez que temos o nodo de interesse, adicionar, remover ou acessar os @@ -216,7 +215,7 @@ \subsection{Resumo} Há um nítido contraste em relação às implementações da #List# baseadas em arrays do \chapref{arrays}; naquelas implementações, o item buscado no array pode ser achado em tempo constante. Entretanto, adicionar ou remover requer o deslocamento de elementos no array e, -em geral, leva tempo não constante. +em geral, não leva tempo constante. Por essa razão, as estruturas de listas ligadas são adequadas para aplicações em que referências a nodos da lista podem ser obtidos por meios @@ -224,11 +223,11 @@ \subsection{Resumo} \javaonly{Um exemplo disso é a estrutura de dados #LinkedHashSet# existente na Java Collections Framework, na qual um conjunto de itens é guardado em uma lista duplamente encadeada e os nodos dessa lista são guardados em uma tabela hash -(discutido em \chapref{hashing}). +(discutido no \chapref{hashing}). Quando um elemento é removido de uma #LinkedHashSet#, a tabela hash é usada para achar o nodo em questão com tempo constante e então o nodo da lista é removido (também em tempo constante).} \cpponly{Por exemplo, ponteiros aos nodos de uma lista ligada poderiam ser guardados em uma - #USet#. Então para remover um item #x# de uma lista ligada, + #USet#. Então para remover um item #x# de uma lista ligada, o nodo que contém #x# pode ser achado rapidamente usando #Uset# e o nodo pode ser removido da lista em tempo constante.} @@ -248,8 +247,7 @@ \section{#SEList#: Uma Lista Ligada Eficiente no Espaço} #SEList# é parametrizada pelo tamanho do bloco \emph{block size} #b#. Cada nodo individual em uma #SEList# guarda um bloco que pode guardar até #b+1# elementos. -Para razões que vão se tornar claras depois, será útil se podermos fazer operações de -#Deque# em cada bloco. +Para razões que vão se tornar claras depois, será útil se podermos fazer operações de #Deque# em cada bloco. A estrutura de dados que escolhemos para isso é uma #BDeque# (do inglês, bounded deque -- deque delimitado), @@ -295,8 +293,8 @@ \subsection{Requisitos de Espaço} Isso significa que o espaço desperdiçado em uma #SEList# é somente $O(#b#+#n#/#b#)$. Ao escolher um valor de #b# dentro de um fator constante de -$\sqrt{#n#}$, podemos custos de espaço extras de uma SEList aproximar -o limitante inferior $\sqrt{#n#}$ descrito em \secref{rootishspaceusage}. +$\sqrt{#n#}$, fazemos o custo de espaço extra de uma SEList se aproximar +ao limitante inferior $\sqrt{#n#}$ descrito em \secref{rootishspaceusage}. \subsection{Procurando Elementos} @@ -331,7 +329,7 @@ \subsection{Procurando Elementos} $O(1+#i#/#b#)$ passos. Se procuramos de trás para frente, então alcançamos o nodo que queremos após -$O(1+(#n#-#i#)/#b#)$ passo. O algoritmo usa a menor dessas duas quantidades +$O(1+(#n#-#i#)/#b#)$ passos. O algoritmo usa a menor dessas duas quantidades dependendo do valor de #i#, então o tempo de localizar o item com índice #i# é $O(1+\min\{#i#,#n#-#i#\}/#b#)$. @@ -341,17 +339,17 @@ \subsection{Procurando Elementos} \codeimport{ods/SEList.get(i).set(i,x)} Os tempos de execução dessas operações são dominados pelo tempo que -leva para achar o item, então eles também rodam em $O(1+\min\{#i#,#n#-#i#\}/#b#)$ de tempo. +leva para achar o item, então eles também rodam em tempo $O(1+\min\{#i#,#n#-#i#\}/#b#)$. \subsection{Adicionando um Elemento} Adicionar elementos a uma #SEList# é um pouco mais complicado. - Antes de considerar o caso geral, considerarmos a operação mais fácil, #add(x)#, - na qual #x# é adicinada ao final da lista. Se o último bloco está cheio + Antes de considerar o caso geral, consideraremos a operação mais fácil, #add(x)#, + na qual #x# é adicionada ao final da lista. Se o último bloco está cheio (ou não existe porque não há blocos ainda), então primeiro alocamos um novo bloco e o acrescentamos na lista de blocos. - Agora que temos certeza que o último bloco existe e não está cheio, + Agora que temos certeza de que o último bloco existe e não está cheio, acrescentamos #x# ao último bloco. \cppimport{ods/SEList.add(x)} @@ -361,7 +359,7 @@ \subsection{Adicionando um Elemento} A situação complica quando adicionamos ao interior da lista usando #add(i,x)# Primeiro alocamos #i# para obter o nodo #u# cujo bloco contém o #i#-ésimo item da lista. O problema é que queremos inserir #x# no bloco de #u#, mas temos que estar -preparados para o caso em que o bloco de #u# já contém +preparados para o caso em que o bloco de #u# já contenha $#b#+1$ elementos, ou seja, cheio e sem espaço para #x#. Considere que @@ -380,16 +378,16 @@ \subsection{Adicionando um Elemento} \end{tabular} \end{center} \caption[Adição no SEList]{Os três casos que ocorrem durante a adição de um item - #x# no interior de uma #SEList#. (Essa #SEList# tem tamano de bloco $#b#=3$.)} + #x# no interior de uma #SEList#. (Essa #SEList# tem tamanho de bloco $#b#=3$.)} \figlabel{selist-add} \end{figure} \begin{enumerate} -\item Rapidamente (em $r+1\le #b#$ passo) achamos um nodo $#u#_r$ cujo +\item Rapidamente (em $r+1\le #b#$ passos) achamos um nodo $#u#_r$ cujo bloco não está cheio. Nesse caso, realizamos - $r$ deslocamentos de um elemento de um bloco ao próximo -, de forma que o espaço livro em $#u#_r$ torne-se um espaço livre em $#u#_0$. + $r$ deslocamentos de um elemento de um bloco ao próximo, de forma + que o espaço livre em $#u#_r$ torne-se um espaço livre em $#u#_0$. Podemos então inserir #x# no bloco do $#u#_0$. \item Podemos rapidamente (em $r+1\le #b#$ passos) ir ao fim da lista de blocos. @@ -402,20 +400,19 @@ \subsection{Adicionando um Elemento} $#b#+1$ elementos. Nós inserimos um novo bloco $#u#_{#b#}$ no fim dessa sequência e \emph{espalhamos} os $#b#(#b#+1)$ elementos originais para que cada bloco de - $#u#_0,\ldots,#u#_{#b#}$ contenho exatamente -#b# elementos. Agora o bloco de $#u#_0$'s contém somente #b# elementos então ele tem espaço para inserir #x#. + $#u#_0,\ldots,#u#_{#b#}$ contenha exatamente +#b# elementos. Agora o bloco de $#u#_0$ contém somente #b# elementos então ele tem espaço para inserir #x#. \end{enumerate} \codeimport{ods/SEList.add(i,x)} -O tempo de execução da operação - #add(i,x)# depende de quais dos três casos anteriores ocorrem. - Casos~1 e 2 envolvem examinar e deslocar elementos ao longo de até #b# blocos - e levam - $O(#b#)$ de tempo. +O tempo de execução da operação #add(i,x)# depende de +quais dos três casos anteriores ocorrem. + Casos~1 e 2 envolvem examinar e deslocar elementos ao longo + de até #b# blocos e levam $O(#b#)$ de tempo. Caso~3 envolve chamar o método #spread(u)#, que move $#b#(#b#+1)$ elementos e leva $O(#b#^2)$ de tempo. Se ignorarmos o custo do Caso~3 -(que iremos levar em consideração depois com amortização) isso significa +(que iremos levar em consideração depois, com amortização) isso significa que o tempo total de execução para localizar #i# e realizar a inserção de #x# é $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$. @@ -424,7 +421,7 @@ \subsection{Remoção de um Elemento} Remover um elemento de uma #SEList# é similar a adicionar um elemento. Nós primeiro localizamos o nodo #u# que contém o elemento com índice #i#. -Agora, temos estar preparamos para o caso no qual não podemos remover +Agora, temos que estar preparados para o caso no qual não podemos remover um elemento de #u# sem fazer que o bloco de #u# se torne menor que $#b#-1$. Novamente, sejam @@ -432,7 +429,7 @@ \subsection{Remoção de um Elemento} Nós examinamos $#u#_0,#u#_1,#u#_2,\ldots$ para procurar por um nodo do qual podemos emprestar um elemento para fazer o tamanho do bloco de $#u#_0$ pelo menos $#b#-1$. Há outros casos a considerar -(veja \figref{selist-remove}): +(veja a \figref{selist-remove}): \begin{figure} \noindent @@ -443,7 +440,7 @@ \subsection{Remoção de um Elemento} \includegraphics[scale=0.90909]{figs/selist-remove-c}\\ \end{tabular} \end{center} - \caption[Remoção na SEList]{Os três casos que ocorrem durante a remoção de um item #x# no interior de uma #SEList#. (Essa #SEList# tem bloco de tamanho $#b#=3$.)} +\caption[Remoção na SEList]{Os três casos que ocorrem durante a remoção de um item #x# no interior de uma #SEList#. (Essa #SEList# tem bloco de tamanho $#b#=3$.)} \figlabel{selist-remove} \end{figure} @@ -461,7 +458,7 @@ \subsection{Remoção de um Elemento} \item Após #b# passos, não achamos nenhum bloco contendo mais de $#b#-1$ elementos. Nesse caso, $#u#_0,\ldots,#u#_{#b#-1}$ é uma sequência de -#b# blocks que contém $#b#-1$ elementos. Nós \emph{acumulamos} +#b# blocos que contém $#b#-1$ elementos. Nós \emph{acumulamos} esses $#b#(#b#-1)$ elementos em $#u#_0,\ldots,#u#_{#b#-2}$ de forma tal que cada um desses $#b#-1$ blocos contém exatamente #b# elementos e removemos @@ -478,7 +475,7 @@ \subsection{Remoção de um Elemento} \subsection{Análise amortizada do Espalhamento e Acumulação} -A seguir, avaliaremos o csuto dos métodos +A seguir, avaliaremos o custo dos métodos #gather(u)# e #spread(u)# que podem ser executados pelos métodos #add(i,x)# e #remove(i)#. Por questão de completude, eles são mostrados a seguir: @@ -486,9 +483,9 @@ \subsection{Análise amortizada do Espalhamento e Acumulação} \codeimport{ods/SEList.gather(u)} O tempo de execução de cada um desses métodos é dominado pelos dois laços aninhados. -Ambos laços interno e externo excutam no máximo +Ambos laços interno e externo executam no máximo $#b#+1$ vezes, então o tempo total de execução de cada um desses métodos é -$O((#b#+1)^2)=O(#b#^2)$. Porém, o lema a seguir mostra que esses métodos executam em no máximo uma a cada #b# chamadas de #add(i,x)# ou #remove(i)#. +$O((#b#+1)^2)=O(#b#^2)$. Porém, o lema a seguir mostra que esses métodos executam em, no máximo, uma a cada #b# chamadas de #add(i,x)# ou #remove(i)#. \begin{lem}\lemlabel{selist-amortized} Se uma #SEList# é criada e qualquer sequência de @@ -508,18 +505,15 @@ \subsection{Análise amortizada do Espalhamento e Acumulação} ou $#b#+1$ elementos). Qualquer nodo cujo bloco contém #b# elementos é \emph{durável}. Defina o \emph{potencial} de uma #SEList# como o número de nodos frágeis que possui. - Iremos consideram somente a operação - #add(i,x)# + Iremos considerar somente a operação #add(i,x)# e sua relação com o número de chamadas a #spread(u)#. - As análises de - #remove(i)# e #gather(u)# são idênticas. + As análises de #remove(i)# e #gather(u)# são idênticas. - Note que, se Caso~1 ocorra durante - o método #add(i,x)#, então somente um nodo + Note que, se o Caso~1 ocorrer durante o método #add(i,x)#, então somente um nodo $#u#_r$ tem o tamanho de seu bloco alterado. Portanto, no máximo um nodo, $#u#_r$, deixa de ser durável e torna-se frágil. Se o Caso~2 ocorrer, então um novo nodo é criado, e esse nó é frágil, - mas nenhum outro nodo muda de tamanho, então o número de nodos frágeis aumenta em one. Então, nos Caso~1 ou 2 o potencial da SEList aumenta em até uma unidade. + mas nenhum outro nodo muda de tamanho, então o número de nodos frágeis aumenta em um. Então, nos Casos~1 ou 2 o potencial da SEList aumenta em até uma unidade. Finalmente, se Caso~3 ocorrer, é porque $#u#_0,\ldots,#u#_{#b#-1}$ são todos nodos frágeis. @@ -535,35 +529,33 @@ \subsection{Análise amortizada do Espalhamento e Acumulação} $#b#-1$. O potencial (que conta o número de nodos frágeis) nunca é menor que 0. Concluímos que, para toda ocorrência do Caso~3, houveram pelo menos $#b#-1$ ocorrências de um Caso~1 ou um Caso~2. Então, para toda chamada a - #spread(u)# existem pelo menos #b# chamadas a #add(i,x)#. Isso conclui a prova. + #spread(u)#, existem pelo menos #b# chamadas a #add(i,x)#. Isso conclui a prova. \end{proof} \subsection{Resumo} -O seguinte teorema resume o desempenho da estrutura de dados - #SEList#: +O seguinte teorema resume o desempenho da estrutura de dados #SEList#: \begin{thm}\thmlabel{selist} Uma #SEList# implementa a interface #List#. Ignorando o custo de chamadas a #spread(u)# e #gather(u)#, uma #SEList# tamanho de bloco #b# - aceita as operações + possui as operações \begin{itemize} - \item #get(i)# e #set(i,x)# em $O(1+\min\{#i#,#n#-#i#\}/#b#)$ de tempo por operação; e - \item #add(i,x)# e #remove(i)# em $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ de tempo por operação. + \item #get(i)# e #set(i,x)# em tempo $O(1+\min\{#i#,#n#-#i#\}/#b#)$ por operação; e + \item #add(i,x)# e #remove(i)# em tempo $O(#b#+\min\{#i#,#n#-#i#\}/#b#)$ por operação. \end{itemize} - Além disso, iniciando com uma #SEList# vazia, qualquer sequência de + Além disso, iniciando com uma #SEList# vazia, uma sequência de $m$ operações - #add(i,x)# e #remove(i)# resultam em um total de $O(#b#m)$ de tempo gasto - durante todas as chamadas a - #spread(u)# e #gather(u)#. + #add(i,x)# e #remove(i)# resulta em um total de $O(#b#m)$ de tempo gasto + durante todas as chamadas a #spread(u)# e #gather(u)#. O espaço (medido em palavras) - \footnote{Consulte \secref{model} para uma discussão de como a memória é medida. + \footnote{Consulte a \secref{model} para uma discussão de como a memória é medida. } usada por uma #SEList# que guarda #n# elementos é $#n# +O(#b# + #n#/#b#)$. \end{thm} -A #SEList# é uma busca por um balanceamento de custos visando um meio termo entre #ArrayList# e uma #DLList#, onde a mistura de custos entre essas duas estruturas depende do tamanho do bloco #b#. +A #SEList# foi projetada visando um meio termo de custos entre #ArrayList# e uma #DLList#, onde a mistura de custos entre essas duas estruturas depende do tamanho do bloco #b#. Em um extremo $#b#=2$, cada nodo da #SEList# guarda no máximo três valores, o que não é muito diferente em uma #DLList#. No outro extremo, @@ -572,7 +564,7 @@ \subsection{Resumo} \section{Discussão e Exercícios} -Ambas listas simplesmente e duplamente encadeadas são técnicas bem estabelecidas e têm sido usadas em programas por mais de 40 anos. Elas são discutidas, por exemplo, por Knuth \cite[Sections~2.2.3--2.2.5]{k97v1}. Mesmo a estrutura de dados +Ambas as listas simplesmente e duplamente encadeadas são técnicas bem estabelecidas e têm sido usadas em programas por mais de 40 anos. Elas são discutidas, por exemplo, por Knuth \cite[Sections~2.2.3--2.2.5]{k97v1}. Mesmo a estrutura de dados #SEList# parece ser uma variante de estrutura de dados bem conhecida. A #SEList# é às vezes conhecida pelo nome de \emph{unrolled linked list} @@ -593,14 +585,13 @@ \section{Discussão e Exercícios} #u.next# = #u.prev# \verb+^+ #u.nextprev# \enspace . \] (Aqui \verb+^+ computa o ou-exclusivo bit-a-bit de seus dois argumentos.) -Essa técnica complica o código um pouco e não é possível em algumas linguagem como Java e Python, que tem coletores de lixo, porém fornece uma implementação de uma lista duplamente encadeada que requer somente um ponteiro por nodo. +Essa técnica complica o código um pouco e não é possível em algumas linguagem como Java e Python, que têm coletores de lixo, porém fornece uma implementação de uma lista duplamente encadeada que requer somente um ponteiro por nodo. Veja o artigo de Sinha \cite{s04} para uma discussão detalhada dessa XOR-list. \begin{exc} Porque não é possível usar um nodo dummy em uma #SLList# para evitar todos os casos especiais que ocorrem nas operações -#push(x)#, #pop()#, - #add(x)#, e #remove()#? +#push(x)#, #pop()#, #add(x)# e #remove()#? \end{exc} \begin{exc} @@ -611,26 +602,26 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Implementa as operações de uma #List# + Implemente as operações de uma #List# #get(i)#, #set(i,x)#, #add(i,x)# e #remove(i)# em uma #SLList#. Cada uma dessas operações devem rodar - $O(1+#i#)$ de tempo. + tempo $O(1+#i#)$. \end{exc} \begin{exc} Projete e implemente um método para a #SLList# chamado #reverse()# que inverte a ordem de elementos em uma - #SLList#. Esse método deve rodar em $O(#n#)$ de tempo, não deve usar recursão, não deve usar nenhuma estrutura de dados secundária, - , não deve criar nenhum nodo novo. + #SLList#. Esse método deve rodar em tempo $O(#n#)$, não deve usar recursão, não deve usar nenhuma estrutura de dados secundária e + não deve criar nenhum nodo novo. \end{exc} \begin{exc} Projete e implemente o método #checkSize()# para as estruturas #SLList# e #DLList#. Esse método percorre a lista e conta o número de nodos para verificar se - ele é igual ao valor #n# guardados na lista. Esse método - não retorna nada, mas lança uma exceção se o tamanho que elas computam - não são idênticos ao valor de #n#. + ele é igual ao valor #n# guardado na lista. Esse método + não retorna nada, mas lança uma exceção se o tamanho que ele computa + não for idêntico ao valor de #n#. \end{exc} \begin{exc} @@ -647,7 +638,7 @@ \section{Discussão e Exercícios} de #prev# e #next# dos nodos existentes. \begin{exc} -Escreva um método para a #DLList# chamado de #isPalindrome()# que retornas #true# se a lista for um +Escreva um método para a #DLList# chamado de #isPalindrome()# que retorna #true# se a lista for um \emph{palíndrome}, \index{palíndrome}% isto é, o elemento na posição #i# é igual ao elemento na posição @@ -664,17 +655,16 @@ \section{Discussão e Exercícios} \begin{exc}\exclabel{linkedlist-truncate} Escreva um método, #truncate(i)#, que trunca uma #DLList# na posição #i#. -Após executar esse método, o tamanho da lista será #i# e deve conter somente os elementos nos índices - $0,\ldots,#i#-1$. O valor retornado é outra +Após executar esse método, o tamanho da lista será #i# e deve conter somente os elementos nos índices $0,\ldots,#i#-1$. + Esse método retorna outra #DLList# que contém os elementos nos índices - $#i#,\ldots,#n#-1$. Esse método deve rodar em $O(\min\{#i#,#n#-#i#\})$ de - tempo. + $#i#,\ldots,#n#-1$. Esse método deve rodar em tempo $O(\min\{#i#,#n#-#i#\})$. \end{exc} \begin{exc} - Escreva uma método para a + Escreva um método para a #DLList# chamado #absorb(l2)#, que recebe como argumento - uma #DLList#, #l2#, a vazia e coloca seu conteúdo, em ordem, na lista receptora. + uma #DLList#, #l2#, a esvazia e coloca seu conteúdo, em ordem, na lista receptora. Por exemplo, se #l1# contém $a,b,c$ e #l2# contém $d,e,f$, então após chamar #l1.absorb(l2)#, #l1# terá @@ -701,20 +691,19 @@ \section{Discussão e Exercícios} \index{merge-sort}% Este exercício te guia para realizar uma implementação do algoritmo merge-sort para a ordenação de uma - #DLList#, conforme discutido em \secref{merge-sort}. + #DLList#, conforme discutido na \secref{merge-sort}. \javaonly{Na sua implementação, realize comparações entre elementos usando o método #compareTo(x)# de forma que a implementação resultante possa ordenar - - qualquer #DLList# contendo elementos que implementam da interface #Comparable#.} + qualquer #DLList# contendo elementos que implementam a interface #Comparable#.} \begin{enumerate} \item Escreva um método para a #DLList# chamado #takeFirst(l2)#. Esse método recebe o primeiro nodo de #l2# e o combina à lista que efetua o método. Isso é equivalente a #add(size(),l2.remove(0))#, - exceto que não deve criar um novo nodo. + exceto que não deve criar um nodo novo. \item Escreva um método estático para a #DLList# chamado #merge(l1,l2)#, que recebe sua listas ordenadas #l1# e #l2#, faz a junção (em inglês, merge) delas, e retorna uma nova lista ordenada contendo o resultado. - Essa faz com que + Essa junção faz com que #l1# e #l2# sejam esvaziadas no processo. Por exemplo, se #l1# contém $a,c,d$ e #l2# contém @@ -733,16 +722,15 @@ \section{Discussão e Exercícios} \end{exc} Os próximos exercícios são mais avançados e requerem a compreensão -aprofundada do que acontence ao valor mínimo armazenado em uma +aprofundada do que acontece ao valor mínimo armazenado em uma #Stack# ou #Queue# quando itens são adicionados ou removidos. \begin{exc} \index{MinStack@#MinStack#}% - Projeto e implemente uma + Projete e implemente uma estrutura de dados #MinStack# que pode guardar elementos comparáveis - e aceita as operações de stack -#push(x)#, - #pop()#, e #size()#, assim como a operação #min()#, que retorna + e possui as operações de stack +#push(x)#, #pop()# e #size()#, assim como a operação #min()#, que retorna o valor mínimo atualmente guardado na estrutura de dados. Todas as operações devem rodar em tempo constante. \end{exc} @@ -751,7 +739,7 @@ \section{Discussão e Exercícios} \index{MinQueue@#MinQueue#}% Projete e implemente uma estrutura de dados #MinQueue# que pode guardar elementos comparáveis - e aceita as operações de queue + e possui as operações de queue #add(x)#, #remove()# e #size()#, assim como a operação #min()#, que retorna o valor mínimo atualmente armazenado na estrutura de dados. @@ -761,7 +749,7 @@ \section{Discussão e Exercícios} \begin{exc} \index{MinDeque@#MinDeque#}% Projete e implemente uma estrutura de dados - #MinDeque# que pode guardar elementos comparáveis e aceita todas as operações + #MinDeque# que pode guardar elementos comparáveis e possui todas as operações da deque #addFirst(x)#, #addLast(x)#, #removeFirst()#, #removeLast()# e #size()#, e a operação From 52cf2845df66dfcca51438197e4de642d56c1cf3 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 9 Sep 2020 19:18:40 -0300 Subject: [PATCH 43/66] finished revision of portuguese translation of skiplists --- latex/skiplists.tex | 225 +++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 119 deletions(-) diff --git a/latex/skiplists.tex b/latex/skiplists.tex index 6db96df8..9dadc7ed 100644 --- a/latex/skiplists.tex +++ b/latex/skiplists.tex @@ -4,16 +4,15 @@ \chapter{Skiplists} Neste capítulo, discutimos uma elegante estrutura de dados: a skiplist, que tem uma variedade de aplicações. Usando uma skiplist podemos implementar uma -#List# que tem implementações $O(\log n)$ de time para #get(i)#, #set(i,x)#, +#List# que tem implementações $O(\log n)$ de tempo para #get(i)#, #set(i,x)#, #add(i,x)# e #remove(i)#. Podemos também implementar um #SSet# no qual todas as operações rodam em tempo $O(\log #n#)$. %Finally, a skiplist can be used to implement a #Rope# in which all %operations run in $O(\log #n#)$ time. -A eficiência de skiplists está o seu uso de -randomization. -Quando um novo elemento é adicionado a uma skiplist, a skiplists usa um lançamento de uma moeda aleatória para determinar a altura do novo elemento. +A eficiência de skiplist está no seu uso inteligente de decisões aleatórias. +Quando um novo elemento é adicionado a uma skiplist, ela usa lançamentos de uma moeda para determinar a altura do novo elemento. O desempenho das skiplists é expresso em termos da esperança do tempo de execução e do comprimento de caminhos percorridos. -Essa esperança (relativo ao valor esperado ou médio) é feita sobre os lançamentos da moeda aleatória usados pela skiplist. Na implementação, os lançamentos são simulados usando um gerador de números (ou bits) pseudo-aleatórios. +Essa esperança, relativa ao valor esperado ou médio, é feita sobre os lançamentos da moeda usados pela skiplist. Na implementação, os lançamentos são simulados usando um gerador de números (ou bits) pseudo-aleatórios. \section{A Estrutura Básica} @@ -27,43 +26,41 @@ \section{A Estrutura Básica} Os itens em $L_r$ são obtidos por lançamento de uma moeda para cada elemento #x# na $L_{r-1}$ e incluindo #x# na $L_r$ se a moeda sai do lado cara. Esse processo termina quando criamos uma lista $L_r$ que está vazia. -Um exemplo de uma skiplist é mostrado em \figref{skiplist}. +Um exemplo de uma skiplist é mostrado na \figref{skiplist}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist} \end{center} - \caption{Uma skiplist contendo sete elementos.} + \caption{Uma skiplist com sete elementos.} \figlabel{skiplist} \end{figure} Para um elemento #x# em uma skiplist, chamamos de \emph{altura} \index{altura!de uma skiplist}% -de #x# o -maior valor $r$ tal que #x# aparece em $L_r$. Então, por exemplo +de #x# o maior valor $r$ tal que #x# aparece na lista $L_r$. Então, por exemplo, elementos que somente aparecem em $L_0$ tem altura $0$. Se pararmos alguns momentos para pensar sobre isso, notamos que a altura de #x# corresponde ao seguinte experimento: jogar uma moeda repetidamente até que saia coroa. Quantas vezes sai cara? -A resposta, pouco supreendentemente, é que a altura esperada de um nodo é 1. +A resposta, pouco surpreendentemente, é que a altura esperada de um nodo é 1. (Esperamos jogar a moeda duas vezes antes de sair coroa, mas não contamos o último lançamento.) A \emph{altura} de uma skiplist é a altura do seu nodo mais alto. -A cabeça de toda lista é um nodo especial, chamado de +A cabeça da lista toda é um nodo especial, chamado de \emph{sentinela}, \index{nodo sentinela}% -que atua como um nodo dummy para a lista. A propriedade chama de skiplists -é existe um caminho curto, chamado de +que atua como um nodo dummy para a lista. A propriedade chave das skiplists +é que existe um caminho curto, chamado de \emph{caminho de busca}, \index{caminho de busca!em uma skiplist}% do sentinela em -$L_h$ para todo nodo em $L_0$. Como construir um caminho de busca para um nodo #u# é facil (veja \figref{skiplist-searchpath}) +$L_h$ para todo nodo em $L_0$. Como construir um caminho de busca para um nodo #u# é facil (veja a \figref{skiplist-searchpath}) : inicie no canto superior esquerdo da sua skiplist (o sentila em $L_h$) e sempre vá à direita a menos que ultrapasse #u#, nesse caso você deve descer à lista logo abaixo. Mais precisamente, para construir o caminho de busca para o nodo #u# em $L_0$, começamos no sentinela #w# em $L_h$. Depois examinamos #w.next#. -Se -#w.next# contém um item que aparece antes de #u# em $L_0$, então +Se #w.next# contém um item que aparece antes de #u# em $L_0$, então fazemos $#w#=#w.next#$. Caso contrário, seguimos a busca na lista abaixo e buscamos a ocorrência de #w# na lista \begin{figure} @@ -74,7 +71,7 @@ \section{A Estrutura Básica} \figlabel{skiplist-searchpath} \end{figure} -O resultado a seguir, que provaremos em +O resultado a seguir, que provaremos na \secref{skiplist-analysis}, mostra que o caminho de busca é bem curto: @@ -83,8 +80,8 @@ \section{A Estrutura Básica} $L_0$ é no máximo $2\log #n# + O(1) = O(\log #n#)$. \end{lem} -Um modo eficiente em relação ao espaço para implementar uma -skiplist é definir um #Node# #u# consistindo de um valor #x# +Um modo de implementar uma skiplist que economiza memória é +definir um #Node# #u# consistindo de um valor #x# e um array #next# de ponteiros onde #u.next[i]# aponta para o sucessor de #u# na lista $L_{#i#}$. Desse jeito, o valor #x# em um nodo é @@ -98,8 +95,8 @@ \section{A Estrutura Básica} skiplists. Em cada uma dessas aplicações, $L_0$ guarda a estrutura principal (uma lista de elementos or um conjunto ordenado de elementos). A principal diferença entre essas estruturas está em como um caminho -de busca é navegado; em particular, eles diferem em como eles -decidem se um caminho de busca ir para a lista abaixo $L_{r-1}$ ou à direita em $L_r$. +de busca é navegado; em particular, eles se diferem na forma de decidir +se um caminho de busca deve para a lista abaixo $L_{r-1}$ ou à direita em $L_r$. \section{#SkiplistSSet#: Um #SSet# Eficiente} \seclabel{skiplistset} @@ -108,7 +105,7 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} Um #SkiplistSSet# usa uma estrutura de skiplist para implementar a interface #SSet#. Quando usada dessa maneira, a lista $L_0$ guarda os elementos de -#SSet# de modo ordenado. O método #find(x)# funciona seguindo o caminho de busa para o menor valor #y# tal que +#SSet# de modo ordenado. O método #find(x)# funciona seguindo o caminho de busca para o menor valor #y# tal que $#y#\ge#x#$: \codeimport{ods/SkiplistSSet.find(x).findPredNode(x)} @@ -119,7 +116,7 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} Se $#x#>#u.next[r].x#$, então damos um passo à direita em $L_{#r#}$; caso contrário, descemos para a lista $L_{#r#-1}$. -Cada passo (à direita ou embaixo) nessa busca leva apenas um tempo constante, +Cada passo (à direita ou abaixo) nessa busca leva apenas um tempo constante, então, pelo \lemref{skiplist-searchpath}, o tempo esperado de execução de #find(x)# é $O(\log #n#)$. @@ -132,7 +129,7 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} \footnote{Esse método não replica exatamente o experimento de lançamento de moedas pois o valor de #k# será sempre menor que o número de bits em um #int#. -Porém, esse fato terá um impacto negligível a menos que o número de elementos em +Porém, esse fato terá um impacto muito pequeno a menos que o número de elementos em uma estrutura seja muito maior que $2^{32}=4294967296$.} \codeimport{ods/SkiplistSSet.pickHeight()} @@ -144,14 +141,13 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} #pickHeight()#. O jeito mais fácil de fazer isso é usar um array #stack#, que registra os nodos em que o caminho de busca desce de alguma lista - $L_{#r#}$ em $L_{#r#-1}$. + $L_{#r#}$ para $L_{#r#-1}$. Mais precisamente, #stack[r]# é o nodo em $L_{#r#}$ onde o caminho de busca desceu em -$L_{#r#-1}$. Os nodos que modificamos para inserir #x# são +$L_{#r#-1}$. Os nodos que modificamos para inserir #x# são precisamente os nodos $#stack[0]#,\ldots,#stack[k]#$. O código a seguir implementa esse - algoritmo para -#add(x)#: + algoritmo para #add(x)#: \label{pg:skiplist-add} \codeimport{ods/SkiplistSSet.add(x)} @@ -167,7 +163,7 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} não há necessidade para a #stack# registrar o caminho de busca. A remoção pode ser feita conforme vamos seguindo o caminho de busca. -Buscamos por #x# e cada vez que a busca segue para baixo de um novo #u#, +Buscamos por #x# e cada vez que a busca segue abaixo de um novo #u#, verificamos se $#u.next.x#$ é igual a $#x#$ e, caso positivo, tiramos #u# da lista: \codeimport{ods/SkiplistSSet.remove(x)} @@ -176,24 +172,24 @@ \section{#SkiplistSSet#: Um #SSet# Eficiente} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-remove} \end{center} - \caption{Removing the node containing $3$ from a skiplist.} + \caption{Remoção do nodo contendo $3$ de uma skiplist.} \figlabel{skiplist-remove} \end{figure} \subsection{Resumo} -O teorema a seguir resumo o desempenho de skiplists quando usadas para implementar conjuntos ordenados: +O teorema a seguir resume o desempenho de skiplists quando usadas para implementar conjuntos ordenados: \begin{thm}\thmlabel{skiplist} -#SkiplistSSet# implementa a interface #SSet#. Uma #SkiplistSSet# aceita as -operações #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ de tempo esperado por operação. +#SkiplistSSet# implementa a interface #SSet#. Uma #SkiplistSSet# possui as +operações #add(x)#, #remove(x)# e #find(x)# em tempo esperado $O(\log #n#)$ por operação. \end{thm} \section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} \seclabel{skiplistlist} \index{SkiplistList@#SkiplistList#}% -Uma #SkiplistList# implementa a interface #List# usando uma estrutura skiplist. Em uma #SkiplistList#, $L_0$ contém os elementos da lista na ordem que eles aparecem na lista. Como em uma +Uma #SkiplistList# implementa a interface #List# usando uma estrutura skiplist. Em uma #SkiplistList#, $L_0$ contém os elementos da lista na ordem que eles aparecem na lista. Como em uma #SkiplistSSet#, elementos podem ser adicionados, removidos e acessados em $O(\log #n#)$ de tempo. @@ -203,9 +199,7 @@ \section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} Definimos o comprimento de toda aresta em $L_{0}$ como 1. O comprimento de uma aresta, #e#, -em $L_{#r#}$, $#r#>0$, é definido como a soma dos comprimentos das arestas abaixo de #e# em - $L_{#r#-1}$. De maneira equivalente, o comprimento de #e# é o número de arestas em -$L_0$ abaixo de #e#. Veja \figref{skiplist-lengths} para um exemplo +em $L_{#r#}$, $#r#>0$, é definido como a soma dos comprimentos das arestas abaixo de #e# em $L_{#r#-1}$. De maneira equivalente, o comprimento de #e# é o número de arestas em $L_0$ abaixo de #e#. Veja a \figref{skiplist-lengths} para um exemplo de uma skiplist com os comprimentos de suas arestas mostradas. Como as arestas de skiplists são guardadas em arrays, os comprimentos podem ser guardados do mesmo jeito: @@ -222,30 +216,25 @@ \section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} Essa definição de comprimento tem a útil propriedade de que se estivermos atualmente em um nodo que está na posição #j# -em $L_0$ e seguimos uma aresta e de comprimento -$\ell$, então movemos para um nodo cuja posição, em $L_0$, +em $L_0$ e seguimos uma aresta de comprimento +$\ell$, então vamos para um nodo cuja posição, em $L_0$, é $#j#+\ell$. Desse jeito, enquanto seguirmos um caminho de busca, podemos rastrear a posição, #j#, do nodo atual em $L_0$. -Quando em um nodo -, #u#, em $L_{#r#}$, iremos à direita se #j# mais o comprimento da aresta -#u.next[r]# é menor que #i#. Caso contrário, descemos para $L_{#r#-1}$. +Quando em um nodo, #u#, em $L_{#r#}$, iremos à direita se #j# mais o comprimento da aresta #u.next[r]# for menor que #i#. Caso contrário, descemos para $L_{#r#-1}$. \codeimport{ods/SkiplistList.findPred(i)} \codeimport{ods/SkiplistList.get(i).set(i,x)} Uma vez que a parte mais difícil das operações #get(i)# e #set(i,x)# é achar -o -#i#-ésimo nodo em $L_0$, essas operações rodam em -$O(\log #n#)$ de tempo. +o #i#-ésimo nodo em $L_0$, essas operações rodam em tempo +$O(\log #n#)$. -Adicionar um elemento na - #SkiplistList# em uma posição #i# razoavelmente simples. Diferentemente de uma #SkiplistSSet#, temos certeza que um novo nodo será realmente adicionado, então podemos fazer a adição ao mesmo tempo que procuramos pelo lugar de um novo nodo. +Adicionar um elemento na #SkiplistList# em uma posição #i# é razoavelmente simples. Diferentemente de uma #SkiplistSSet#, temos certeza que um novo nodo será realmente adicionado, então podemos fazer a adição ao mesmo tempo que procuramos pelo lugar de um novo nodo. Primeiro pegamos a altura #k# do nodo recentemente inserido #w# e então seguimos o caminho de busca para #i#. -Toda vez que o caminho de busca desce a partir de - +Toda vez que o caminho de busca descer a partir de $L_{#r#}$ com $#r#\le #k#$, inserimos #w# -em $L_{#r#}$. O último cuidade extra necessário é assegurar que os comprimentos das arestas são atualizadas corretamente. Veja \figref{skiplist-addix}. +em $L_{#r#}$. O último cuidado extra necessário é assegurar que os comprimentos das arestas sejam atualizados corretamente. Veja a \figref{skiplist-addix}. \begin{figure} \begin{center} @@ -255,33 +244,33 @@ \section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} \figlabel{skiplist-addix} \end{figure} -Note que, cada vez que o caminho de busca desce em um novo #u# em $L_{#r#}$, -o comprimento da aresta - #u.next[r]# aumenta em um, pois estamos adicionando um elemento abaixo daquela aresta na posição #i#. +Note que, cada vez que o caminho de busca desce em um nodo #u# em $L_{#r#}$, +o comprimento da aresta #u.next[r]# aumenta em um, pois +estamos adicionando um elemento abaixo daquela aresta na posição #i#. -A divisão do nodo #w# entre dois nodos, #u# e #z# funciona como mostrado em \figref{skiplist-lengths-splice}. +A divisão, em inglês \emph{splice, do nodo #w# em dois nodos, #u# e #z# funciona como mostrado na \figref{skiplist-lengths-splice}. Ao seguir o caminho de busca já estamos guardando a posição #j#, de #u# em $L_0$. Portanto, sabemos que o comprimento da aresta -de #u# a #w# é $#i#-#j#$. Também podemos deduzir o comprimento da aresta de #w# a #z# a patir do comprimento $\ell$ de uma aresta de #u# a #z#. +de #u# a #w# é $#i#-#j#$. Também podemos deduzir o comprimento da aresta de #w# a #z# a partir do comprimento $\ell$ de uma aresta de #u# a #z#. Portanto, podemos dividir em #w# e atualizar os comprimentos das arestas em tempo constante. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/skiplist-lengths-splice} \end{center} - \caption[Adição à a SkiplistList]{Atualizar os comprimentos das arestas enquanto dividindo um nodo #w# em uma skiplist.} + \caption[Adição à SkiplistList]{Atualizando os comprimentos das arestas ao dividir um nodo #w# de uma skiplist.} \figlabel{skiplist-lengths-splice} \end{figure} -Isso parece mais complicado do que é, porque o código é bem simples: +Isso parece ser mais complicado do que realmente é, porque o código é bem simples: \codeimport{ods/SkiplistList.add(i,x)} \codeimport{ods/SkiplistList.add(i,w)} Agora a implementação da operação #remove(i)# em uma #SkiplistList# deve ser óbvia. -Seguimos o caminho de pesquisa para o nodo na posição #i#. Cada vez que o caminho de busca desce a partir de um nodo #u# no nível #r# nós decrementamos o comprimento +Seguimos o caminho de pesquisa para o nodo na posição #i#. Cada vez que o caminho de busca desce a partir de um nodo #u# no nível #r# decrementamos o comprimento da aresta que deixa #u# naquele nível. Também verificamos se - #u.next[r]# é o elemento do #i# e, se o for, o removemos da lista naquele nível. Um exemplo é mostrado em \figref{skiplist-removei}. + #u.next[r]# é o elemento do #i# e, se o for, o removemos da lista naquele nível. Um exemplo é mostrado na \figref{skiplist-removei}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/skiplist-removei} @@ -298,8 +287,8 @@ \subsection{Resumo} \begin{thm}\thmlabel{skiplistlist} Uma #SkiplistList# implementa a interface #List#. Uma #SkiplistList# - aceita a oeprações #get(i)#, #set(i,x)#, #add(i,x)#, e - #remove(i)# em $O(\log #n#)$ de tempo esperado por operação. + possui a operações #get(i)#, #set(i,x)#, #add(i,x)# e + #remove(i)# em tempo esperado $O(\log #n#)$ por operação. \end{thm} %\section{Skiplists as Ropes} @@ -308,8 +297,8 @@ \subsection{Resumo} \section{Análise de Skiplists} \seclabel{skiplist-analysis} -Nesta seção, analisaremos a altura esperada, tamanho, e comprimento do caminho -de busca em uma skiplist. Esta seção que conhecimentos básicos de +Nesta seção, analisaremos a altura esperada, o tamanho e o comprimento do caminho +de busca em uma skiplist. Esta seção requer conhecimentos básicos de probabilidades. Várias provas são baseadas na seguinte obervação básica sobre lançamentos de moedas. @@ -322,11 +311,11 @@ \section{Análise de Skiplists} \end{lem} \begin{proof} - Suponha que paramos de lança a moeda na primeira vez que sai cara. + Suponha que paramos de lançar a moeda na primeira vez que sai cara. Defina a variável indicadora \[ I_{i} = \left\{\begin{array}{ll} - 0 & \mbox{se a moeda é lançada menos de $i$ vezes} \\ - 1 & \mbox{se a moeda é lança $i$ vezes ou mais} + 0 & \mbox{se a moeda for lançada menos de $i$ vezes} \\ + 1 & \mbox{se a moeda for lançada $i$ vezes ou mais} \end{array}\right. \] Note que @@ -344,12 +333,11 @@ \section{Análise de Skiplists} \end{align*} \end{proof} -Os próximos dois lemas afirmam que skiplists tem tamanho linear: +Os próximos dois lemas afirmam que skiplists têm tamanho linear: \begin{lem}\lemlabel{skiplist-size1} - O número esperado de nodos em um skiplist contendo - $#n#$ elementos, - sem incluir as ocorrências da sentinela, é + O número esperado de nodos em uma skiplist com + $#n#$ elementos, sem incluir as ocorrências da sentinela, é $2#n#$. \end{lem} @@ -357,13 +345,13 @@ \section{Análise de Skiplists} A probabilidade que qualquer elemento em particular, #x#, seja incluído na lista $L_{#r#}$ é $1/2^{#r#}$, então o número esperado de nodos em $L_{#r#}$ - é $#n#/2^{#r#}$.\footnote{Veja \secref{randomization} para ver como isso é obtido usando variáveis indicadores e linearidade de esperança.} - Portanto, o número esperado total de nodos em todas as listas é + é $#n#/2^{#r#}$.\footnote{Veja a \secref{randomization} para ver como isso é obtido usando variáveis indicadores e linearidade de esperança.} + Portanto, o número total esperado de nodos em todas as listas é \[ \sum_{#r#=0}^\infty #n#/2^{#r#} = #n#(1+1/2+1/4+1/8+\cdots) = 2#n# \enspace . \qedhere \] \end{proof} \begin{lem}\lemlabel{skiplist-height} - A altura esperada de uma skiplist contendo #n# elementos é até + A altura esperada de uma skiplist contendo #n# elementos é de até $\log #n# + 2$. \end{lem} @@ -371,11 +359,11 @@ \section{Análise de Skiplists} Para cada $#r#\in\{1,2,3,\ldots,\infty\}$, defina a variável indicadora aleatória \[ I_{#r#} = \left\{\begin{array}{ll} - 0 & \mbox{se $L_{#r#}$ está vazia } \\ - 1 & \mbox{se $L_{#r#}$ não está vazia} + 0 & \mbox{se $L_{#r#}$ estiver vazia } \\ + 1 & \mbox{se $L_{#r#}$ não estiver vazia} \end{array}\right. \] - A altura, #h# da skiplist é então dada por + A altura #h# da skiplist é então dada por \[ #h# = \sum_{r=1}^\infty I_{#r#} \enspace . \] @@ -404,42 +392,42 @@ \section{Análise de Skiplists} \end{lem} \begin{proof} - Segundo \lemref{skiplist-size1}, o número esperado de nodos, sem incluir o sentinela, + Segundo o \lemref{skiplist-size1}, o número esperado de nodos, sem incluir o sentinela, é $2#n#$. O número de ocorrências do sentinela é igual à altura $#h#$ da skiplist então, segundo - \lemref{skiplist-height}, o número esperado de ocorrências da sentinela é até + o \lemref{skiplist-height}, o número esperado de ocorrências da sentinela é de até $\log #n#+2 = O(\log #n#)$. \end{proof} \begin{lem} -O comprimento esperado de um caminho de busca em uma skiplist é até +O comprimento esperado de um caminho de busca em uma skiplist é de até $2\log #n# + O(1)$. \end{lem} \begin{proof} - O jeito mais fácil de ver issso é considerar o + O jeito mais fácil de ver isso é considerar o \emph{caminho de busca reverso}. Esse caminho inicia-se no predecessor de #x# em - $L_0$. Em qualquer momento, se o caminho pode subir um nível, então ele o faz. + $L_0$. Em qualquer momento, se o caminho puder subir um nível, então ele o faz. Se ele não puder subir um nível, então vai à esquerda. Considerando isso por alguns momentos vemos que o caminho de busca reverso para #x# é idêntico ao caminho de busca para #x#, exceto que é invertido. O número de nodos que o caminho de busca reverso visita em um dado nível #r# é relacionado ao seguinte experimento: lance uma moeda. - Se a moeda cai cara, então suba um nível e pare. Caro contrário, + Se a moeda sair cara, então suba um nível e pare. Caso contrário, vá à esquerda e repita o experimento. O número de lançamentos obtido dessa forma representa o número de passos à esquerda que um caminho de busca invertido faz em um dado nível. \footnote{Note que isso pode superestimar o número de passos à - esquerda pois o experimento deve terminar ou em um lançamento cara ou + esquerda pois o experimento deve terminar em um lançamento cara ou quando o caminho de busca chega no sentinela, aquele que vier primeiro. Isso não é um problema porque o lema está somente apresentando um limitante superior.} - \lemref{coin-tosses} afirma que o número esperado de lançamento de moedas - antes da primeira cada é 1. + O \lemref{coin-tosses} afirma que o número esperado de lançamentos de moedas + antes da primeira cara é 1. Seja - $S_{#r#}$ o número de passos que uma caminho de busca (direta) usa no + $S_{#r#}$ o número de passos que um caminho de busca (direta) usa no nível $#r#$ para ir à direita. Acabamos de mostrar que $\E[S_{#r#}]\le 1$. Além disso, $S_{#r#}\le |L_{#r#}|$, pois não podemos fazer mais passos em @@ -447,8 +435,7 @@ \section{Análise de Skiplists} \[ \E[S_{#r#}] \le \E[|L_{#r#}|] = #n#/2^{#r#} \enspace . \] - Podemos agora terminar como na prova de - \lemref{skiplist-height}. + Podemos agora terminar como na prova do \lemref{skiplist-height}. Seja $S$ o comprimento do caminho de busca para algum nodo #u# em uma skiplist e seja $#h#$ a altura da skiplist. Então \begin{align*} \E[S] @@ -471,7 +458,7 @@ \section{Análise de Skiplists} \begin{thm} Uma skiplist contendo $#n#$ elementos tem tamanho esperado $O(#n#)$ e o comprimento esperado - do caminho de busca para qualquer elemento é até + do caminho de busca para qualquer elemento é de até $2\log #n# + O(1)$. \end{thm} @@ -482,36 +469,36 @@ \section{Análise de Skiplists} \section{Discussão e Exercícios} Skiplists foram inicialmente propostas por \cite{p91} que também apresentou várias aplicações e extensões de skiplists -\cite{p89}. Desde então ela tem sido extensivamente estudada. -Vários pesquisadores tem realizado análises bem precisas do comprimento +\cite{p89}. Desde então, ela tem sido extensivamente estudada. +Vários pesquisadores têm realizado análises bem precisas do comprimento esperado e da variância do comprimento do caminho de busca para o #i#-ésimo elemento em uma skiplist \cite{kp94,kmp95,pmp92}. Versões determinísticas \cite{mps92}, versões tendenciosas \cite{bbg02,esss01}, -e versões auto-ajustáveis \cite{bdl08} de skiplists têm surgido. Implementações de skiplists têm sido escritas para várias linguagens e frameworks e usadas em sistemas de bancos de dados open-source \cite{skipdb,redis}. Uma variante de skiplists é usada nas estruturas do gerenciador de processos do kernel do sistema operacional HP-UDX A variant of skiplists is used in the HP-UX +e versões auto-ajustáveis \cite{bdl08} de skiplists têm surgido. Implementações de skiplists têm sido escritas para várias linguagens e bibliotecas e usadas em sistemas de bancos de dados open-source \cite{skipdb,redis}. Uma variante de skiplist é usada nas estruturas do gerenciador de processos do kernel do sistema operacional HP-UX. \cite{hpux}. \javaonly{Skiplists são até parte da Java 1.6 API \cite{oracle_jdk6}.} \begin{exc} - Desenhe os caminhos de busca para 2.5 e 5.5 na skiplists em + Desenhe os caminhos de busca para 2.5 e 5.5 na skiplist da \figref{skiplist}. \end{exc} \begin{exc} Desenhe a adição de valores 0.5 (com altura de 1) e então 3.5 (com - uma altura de 2) para a skiplist em + uma altura de 2) para a skiplist da \figref{skiplist}. \end{exc} \begin{exc} - Desenhe a remoção de valores 1 e então 3 da skiplist em + Desenhe a remoção de valores 1 e então 3 da skiplist na \figref{skiplist}. \end{exc} \begin{exc} Desenhe a execução de - #remove(2)# na #SkiplistList# em + #remove(2)# na #SkiplistList# na \figref{skiplist-lengths}. \end{exc} @@ -524,14 +511,14 @@ \section{Discussão e Exercícios} \begin{exc}\exclabel{skiplist-changes} Mostre que, durante uma operação - #add(x)# ou #remove(x)#, o número esperado de ponteiros em - uma #SkiplistSet# que é alterado é constante. + #add(x)# ou #remove(x)#, o número esperado de ponteiros alterados em + uma #SkiplistSet# é constante. \end{exc} \begin{exc}\exclabel{skiplist-opt} Suponha que, em vez de promover um elemento de $L_{i-1}$ em $L_i$ usando um lançamento de moeda - , fizermos a promoção com uma probabilidade $p$, $0 < + , fazemos a promoção com uma probabilidade $p$, $0 < p < 1$. \begin{enumerate} \item Mostre que, com essa modificação, o comprimento esperado de um caminho de busca é até $(1/p)\log_{1/p} #n# + O(1)$. @@ -542,10 +529,10 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc}\exclabel{skiplist-opt-2} -O método #find(x)# em uma a #SkiplistSet# às vezes faz - \emph{comparações redundantes}; essas ocorrem quando #x# é comparado ao mesmo valor mais de uma vez. Eles podem ocorrer quando, para algum nodo #u# +O método #find(x)# em uma #SkiplistSet# às vezes faz + \emph{comparações redundantes}; essas ocorrem quando #x# é comparado ao mesmo valor mais de uma vez. Elas podem ocorrer quando, para algum nodo #u# $#u.next[r]# = #u.next[r-1]#$. Mostre quando essas comparações redundantes acontecem e modifique - #find(x)# para que sejam evitadas. Analise o número esperado de comparações feito pelo seu método modificado #find(x)#. + #find(x)# para que sejam evitadas. Analise o número esperado de comparações feitas pelo seu método modificado #find(x)#. \end{exc} \begin{exc} @@ -562,26 +549,26 @@ \section{Discussão e Exercícios} \index{finger}% \index{finger search!in a skiplist}% Um \emph{finger} em uma skiplist é um array que guarda a sequência de nodos em um caminho de busca no qual o caminho de busca desce. - (A variável #stack# no código #add(x)# na página~\pageref{pg:skiplist-add} é um finger; os nodos com sombra em - \figref{skiplist-add} mostra o conteúdo do finger.) Pode-se pensar de um finger como estar apontando o caminho para um nodo na lista mais baixa, $L_0$. + (A variável #stack# no código #add(x)# na página~\pageref{pg:skiplist-add} é um finger; os nodos destacados na + \figref{skiplist-add} mostram o conteúdo do finger.) Pode-se pensar de um finger como uma forma de apontar o caminho para um nodo na lista mais baixa, $L_0$. Uma \emph{busca finger} implementa a operação #find(x)# usando um - finger, ao percorrer a lista usando finger até chegar em um nodo + finger, ao percorrer a lista usando o finger até chegar em um nodo #u# tal que $#u.x# < #x#$ e $#u.next#=#null#$ ou $#u.next.x# > - #x#$ e então fazer uma busca normal por #x# començando de #u#. + #x#$ e então fazer uma busca normal por #x# começando de #u#. É possível provar que o número esperado de passos necessários para uma busca finger é $O(1+\log r)$, onde $r$ é o número de valores em $L_0$ entre #x# e o valor apontado pelo finger. Implemente uma subclasse de #Skiplist# chamada #SkiplistWithFinger# que - implementa operações #find(x)# usando um finger interno. Essa subclasse + implementa a operação #find(x)# usando um finger interno. Essa subclasse guarda um finger, que é então usado para que toda operação #find(x)# seja implementada como uma busca finger. Durante cada operação #find(x)# o finger é atualizado tal que cada operação #find(x)# usa como ponto de partida - , um finger que aponta para o resultado da operação + um finger que aponta para o resultado da operação #find(x)# prévia. \end{exc} @@ -592,14 +579,14 @@ \section{Discussão e Exercícios} e contém somente os elementos nos índices $0,\ldots,#i#-1$. O valor retornado é outra #SkiplistList# que contém os elementos nos índices - $#i#,\ldots,#n#-1$. Esse método deve rodar em - $O(\log #n#)$ de tempo. + $#i#,\ldots,#n#-1$. Esse método deve rodar em tempo + $O(\log #n#)$. \end{exc} \begin{exc} Escreva um método para a #SkiplistList# chamado #absorb(l2)#, que recebe como argumento uma - #SkiplistList#, #l2#, a vazia e coloca seu conteúdo na estrutura receptora. + #SkiplistList#, #l2#, a esvazia e coloca seu conteúdo na estrutura receptora. Por exemplo, se #l1# contém $a,b,c$ e #l2# contém $d,e,f$, então após chamar #l1.absorb(l2)#, #l1# @@ -619,19 +606,19 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Usando uma #SSet# com estrutura básica, projete e implemente uma + Usando uma #SSet# como estrutura básica, projete e implemente uma aplicação que lê um arquivo de texto (potencialmente enorme) e permite você buscar, iterativamente, por qualquer substring contida no texto. Conforme o usuário digita a consulta, uma parte do texto que corresponde à busca (se houver) deve aparecer como resultado. - \noindent Dica 1: toda substring é um prefixo de algum sufixo, então basta guarda todos os sufixos do arquivo de texto. + \noindent Dica 1: toda substring é um prefixo de algum sufixo, então basta guardar todos os sufixos do arquivo de texto. \noindent Dica 2: qualquer sufixo pode ser representado como um único inteiro indicando onde o sufixo começa no texto. \noindent Teste sua aplicação em textos maiores, tais como alguns livros disponibilizados pelo - Projeto Gutenberg \cite{gutenberg}. Se feito corretamente, - a sua aplicação será bem rápida; não deve have atraso considerável + Projeto Gutenberg \cite{gutenberg}. Se feito corretamente, + a sua aplicação será bem rápida; não deve haver atraso considerável entre digitar letras e ver os resultados. \end{exc} @@ -639,11 +626,11 @@ \section{Discussão e Exercícios} \index{skiplist!versus árvore binária de busca}% \index{árvore binária de busca!versus skiplist}% (Este exercício deve ser feito após a leitura sobre árvores binárias de busca - em - \secref{binarysearchtree}.) Compare skiplists com árvores binárias de busca + na + \secref{binarysearchtree}.) Compare as skiplists com árvores binárias de busca das seguintes maneiras: \begin{enumerate} - \item Explique como algumas arestas de uma skiplist leva a uma estrutura que parece uma árvore binária e é similar a uma árvore de busca binária. - \item Skiplists e árvores de busca binária usam aproximadamente o mesmo número de ponteiros (2 por nodo). Contundo, skiplists fazer melhor uso desses ponteiros. Explique porque. + \item Explique como algumas arestas de uma skiplist levam a uma estrutura que parece uma árvore binária e é similar a uma árvore de busca binária. + \item Skiplists e árvores de busca binária usam aproximadamente o mesmo número de ponteiros (2 por nodo). Contudo, skiplists fazem melhor uso desses ponteiros. Explique porque. \end{enumerate} \end{exc} From 273ff4bef218cf414af301fce5f9f5dcf66d7bd0 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 10 Sep 2020 17:32:46 -0300 Subject: [PATCH 44/66] fixing error which broke compilation --- latex/skiplists.tex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/latex/skiplists.tex b/latex/skiplists.tex index 9dadc7ed..f3f557c8 100644 --- a/latex/skiplists.tex +++ b/latex/skiplists.tex @@ -12,7 +12,7 @@ \chapter{Skiplists} A eficiência de skiplist está no seu uso inteligente de decisões aleatórias. Quando um novo elemento é adicionado a uma skiplist, ela usa lançamentos de uma moeda para determinar a altura do novo elemento. O desempenho das skiplists é expresso em termos da esperança do tempo de execução e do comprimento de caminhos percorridos. -Essa esperança, relativa ao valor esperado ou médio, é feita sobre os lançamentos da moeda usados pela skiplist. Na implementação, os lançamentos são simulados usando um gerador de números (ou bits) pseudo-aleatórios. +Essa esperança, relativa ao valor esperado ou médio, é feita sobre os lançamentos da moeda usados pela skiplist. Na implementação, os lançamentos são simulados usando um gerador de números (ou bits) pseudo aleatórios. \section{A Estrutura Básica} @@ -54,8 +54,8 @@ \section{A Estrutura Básica} \emph{caminho de busca}, \index{caminho de busca!em uma skiplist}% do sentinela em -$L_h$ para todo nodo em $L_0$. Como construir um caminho de busca para um nodo #u# é facil (veja a \figref{skiplist-searchpath}) -: inicie no canto superior esquerdo da sua skiplist (o sentila em $L_h$) +$L_h$ para todo nodo em $L_0$. Como construir um caminho de busca para um nodo #u# é fácil (veja a \figref{skiplist-searchpath}) +: inicie no canto superior esquerdo da sua skiplist (o sentinela em $L_h$) e sempre vá à direita a menos que ultrapasse #u#, nesse caso você deve descer à lista logo abaixo. Mais precisamente, para construir o caminho de busca para o nodo #u# em $L_0$, @@ -93,7 +93,7 @@ \section{A Estrutura Básica} As próximas duas seções deste capítulo discutem duas diferentes aplicações de skiplists. Em cada uma dessas aplicações, $L_0$ guarda a estrutura principal -(uma lista de elementos or um conjunto ordenado de elementos). +(uma lista de elementos ou um conjunto ordenado de elementos). A principal diferença entre essas estruturas está em como um caminho de busca é navegado; em particular, eles se diferem na forma de decidir se um caminho de busca deve para a lista abaixo $L_{r-1}$ ou à direita em $L_r$. @@ -248,7 +248,7 @@ \section{#SkiplistList#: Uma #List# com Acesso Aleatório Eficiente} o comprimento da aresta #u.next[r]# aumenta em um, pois estamos adicionando um elemento abaixo daquela aresta na posição #i#. -A divisão, em inglês \emph{splice, do nodo #w# em dois nodos, #u# e #z# funciona como mostrado na \figref{skiplist-lengths-splice}. +A divisão, em inglês \emph{splice}, do nodo #w# em dois nodos, #u# e #z# funciona como mostrado na \figref{skiplist-lengths-splice}. Ao seguir o caminho de busca já estamos guardando a posição #j#, de #u# em $L_0$. Portanto, sabemos que o comprimento da aresta de #u# a #w# é $#i#-#j#$. Também podemos deduzir o comprimento da aresta de #w# a #z# a partir do comprimento $\ell$ de uma aresta de #u# a #z#. @@ -304,7 +304,7 @@ \section{Análise de Skiplists} moedas. \begin{lem}\lemlabel{coin-tosses} - \index{coin toss}% + \index{lançamento de moeda}% Seja $T$ o número de vezes que uma moeda honesta é lançada, incluindo a primeira vez que a moeda saiu com a face cara. Então $\E[T]=2$. @@ -475,7 +475,7 @@ \section{Discussão e Exercícios} #i#-ésimo elemento em uma skiplist \cite{kp94,kmp95,pmp92}. Versões determinísticas \cite{mps92}, versões tendenciosas \cite{bbg02,esss01}, -e versões auto-ajustáveis \cite{bdl08} de skiplists têm surgido. Implementações de skiplists têm sido escritas para várias linguagens e bibliotecas e usadas em sistemas de bancos de dados open-source \cite{skipdb,redis}. Uma variante de skiplist é usada nas estruturas do gerenciador de processos do kernel do sistema operacional HP-UX. +e versões auto ajustáveis \cite{bdl08} de skiplists têm surgido. Implementações de skiplists têm sido escritas para várias linguagens e bibliotecas e usadas em sistemas de bancos de dados open-source \cite{skipdb,redis}. Uma variante de skiplist é usada nas estruturas do gerenciador de processos do kernel do sistema operacional HP-UX. \cite{hpux}. \javaonly{Skiplists são até parte da Java 1.6 API \cite{oracle_jdk6}.} From decb27318e4f9aaa7c1b2ccc92fcbc9eb3ee3a0e Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 10 Sep 2020 18:52:41 -0300 Subject: [PATCH 45/66] Finished revision of translation to portuguese of hashing.tex --- latex/hashing.tex | 446 ++++++++++++++++++++++------------------------ 1 file changed, 209 insertions(+), 237 deletions(-) diff --git a/latex/hashing.tex b/latex/hashing.tex index 26286937..1ed6ba24 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -5,8 +5,7 @@ \chapter{Tabelas Hash } Tabelas hash são um eficiente método de guardar um pequeno número #n# de inteiros provenientes de um grande intervalo $U=\{0,\ldots,2^{#w#}-1\}$. -O termo -tabela hash, ou \emph{hash table}, +O termo tabela hash, ou \emph{hash table}, \index{hash table}% \index{tabela hash}% inclui um grande número de tipos de estruturas de dados. A primeira parte @@ -20,8 +19,8 @@ \chapter{Tabelas Hash } A segunda parte deste capítulo discute como tais códigos hash são gerados. -Alguns desses métodos usados neste capítulo requerem escolhas -aleatórias de inteiros em algum intervalo. Nos trechos de código, +Alguns desses métodos que usados neste capítulo requerem obter +inteiros aleatórios em algum intervalo. Nos trechos de código, alguns desses inteiros ``aleatórios'' são constantes fixas no código. Essas constantes foram obtidas usando bits aleatórios gerados a partir de ruído atmosférico. @@ -33,10 +32,10 @@ \section{#ChainedHashTable#: Hashing com Encadeamento} \index{encadeamento}% \index{hashing com encadeamento}% Uma estrutura de dados -#ChainedHashTable# use \emph{hashing com encadeamento} para +#ChainedHashTable# usa \emph{hashing com encadeamento} para guardar dados como um array #t# de listas. Um inteiro #n# registra o -númer total de itens em todas as listas. -(ver \figref{chainedhashtable}): +número total de itens em todas as listas. +(ver a \figref{chainedhashtable}): \codeimport{ods/ChainedHashTable.t.n} \begin{figure} \begin{center} @@ -51,7 +50,7 @@ \section{#ChainedHashTable#: Hashing com Encadeamento} no intervalo $\{0,\ldots,#t.length#-1\}$. Todos os itens com um valor hash #i# são guardados na lista em -#t[i]#. Para garantir que listas não ficam muito longas, mantermos a invariante +#t[i]#. Para garantir que as listas não ficam muito longas, mantemos a invariante \[ #n# \le #t.length# \] @@ -59,22 +58,20 @@ \section{#ChainedHashTable#: Hashing com Encadeamento} $#n#/#t.length# \le 1$. Para adicionar um elemento #x# à tabela primeiro verificamos se o tamanho de #t# precisar ser aumentado e, se precisar, expandimos #t#. -Com isso resolvido, obtemos a hash de #x# para obter um inteiro #i# no +Com isso resolvido, calculamos a hash de #x# para obter um inteiro #i# no intervalo -$\{0,\ldots,#t.length#-1\}$, e adicionamos #x# à lista +$\{0,\ldots,#t.length#-1\}$ e adicionamos #x# à lista #t[i]#: \codeimport{ods/ChainedHashTable.add(x)} Expandir a tabela, caso necessário, envolve dobrar o comprimento de #t# e reinserir todos os elementos na nova tabela. Essa -estratégia é exatamente a mesmo que aquela usada na implementação do +estratégia é exatamente a mesma que aquela usada na implementação do +#ArrayStack# e o mesmo resultado se aplica. +O custo de expandir somente é visto como constante quando amortizado sobre uma sequência de +inserções (ver~o~\lemref{arraystack-amortized} na página~\pageref{lem:arraystack-amortized}). -#ArrayStack# e o mesmo resultado se aplica -O custo de expandir somente é constante quando amortizado sobre uma sequência de -inserções (ver~\lemref{arraystack-amortized} na págia~\pageref{lem:arraystack-amortized}). - -Além de expandir, o único outro trabalho realizado ao adicionar um novo valor #x# -a uma -#ChainedHashTable# involve adicionar #x# à lista #t[hash(x)]#. +Além da expansão, o único outro trabalho realizado ao adicionar um novo valor #x# +a uma #ChainedHashTable# envolve adicionar #x# à lista #t[hash(x)]#. Para qualquer das implementações de lista descrita nos Capítulos~\ref{chap:arrays} ou \ref{chap:linkedlists}, isso leva somente um tempo constante. @@ -85,18 +82,17 @@ \section{#ChainedHashTable#: Hashing com Encadeamento} Buscar pelo elemento #x# #x# em uma tabela hash é similar. Fazemos uma busca linear -na lista -#t[hash(x)]#: +na lista #t[hash(x)]#: \codeimport{ods/ChainedHashTable.find(x)} Novamente, isso leva tempo proporcional ao comprimento da lista #t[hash(x)]#. - O desempenho de um tabela hash depende criticamente na escolha da função +O desempenho de uma tabela hash depende criticamente na escolha da função hash. Uma boa função hash irá espalhar os elementos de modo uniforme entre as #t.length# listas, tal que o tamanho esperado da lista #t[hash(x)]# é $O(#n#/#t.length)# = O(1)$. Por outro lado, uma função hash ruim irá distribuir todos os valores (incluindo #x#) à mesma posição da tabela. Nesse caso, o tamanho da lista #t[hash(x)]# será #n#. -Na próxima seção descrevemos uma boa função hash. +Na seção a seguir descrevemos uma boa função hash. \subsection{Hashing Multiplicativo} @@ -106,31 +102,29 @@ \subsection{Hashing Multiplicativo} \index{hashing multiplicativo}% Hashing multiplicativo é um método eficiente de gerar valores hash baseado em aritmética modular - (discutido em \secref{arrayqueue}) + (discutido na \secref{arrayqueue}) e divisão inteira. Ele usa o operador $\ddiv$, que calcula a parte inteira de um quociente e descarta o resto. -Formalmente, para quaisquer interios $a\ge 0$ e $b\ge 1$, $a\ddiv b = \lfloor +Formalmente, para quaisquer inteiros $a\ge 0$ e $b\ge 1$, $a\ddiv b = \lfloor a/b\rfloor$. -Em hashing multiplicativo, usamos uma tabela hash de tamanho $2^{#d#}$ para um inteiro -#d# (chamado de \emph{dimensão}). A fórmula para aplicar hashing em um inteiro +Em hashing multiplicativo, usamos uma tabela hash de tamanho $2^{#d#}$ para um inteiro #d# (chamado de \emph{dimensão}). A fórmula para aplicar hashing em um inteiro $#x#\in\{0,\ldots,2^{#w#}-1\}$ é \[ #hash(x)# = ((#z#\cdot#x#) \bmod 2^{#w#}) \ddiv 2^{#w#-#d#} \enspace . \] Aqui, #z# é um inteiro ímpar escolhido aleatoriamente em $\{1,\ldots,2^{#w#}-1\}$. Essa função hash pode ser computada muito eficientemente ao observar que, por padrão, operações em inteiros são -módulo $2^{#w#}$ onde $#w#$ é o número de bits em um inteiro.\footnote{Isso é verdade para a moior parte de linguagens de programação incluindo +módulo $2^{#w#}$ onde $#w#$ é o número de bits em um inteiro.\footnote{Isso é verdade para a maior parte de linguagens de programação incluindo C, C\#, C++, e Java. Exceções notáveis são Python e -Ruby, nos quais o resultado de uma operação inteira de tamanho fixo #w#-bit -que passa do intervalo permitido é convertido a uma variável com tamanho de representação variável.}a (Ver -\figref{multihashing}.) Além disso, divisão inteira por $2^{#w#-#d#}$ +Ruby, nas quais o resultado de uma operação inteira de tamanho fixo #w#-bit +que passa do intervalo permitido é convertido a uma variável com tamanho de representação variável.} (Ver a \figref{multihashing}.) Além disso, +divisão inteira por $2^{#w#-#d#}$ é equivalente a descartar os $#w#-#d#$ bits mais à direita em uma representação binária ( -que é implementado usando o deslocamento de bits $#w#-#d#$ à direita -usando o operador - \javaonly{#>>>#}\cpponly{#>>#}\pcodeonly{#>>#} +o que é implementado usando o deslocamento de bits $#w#-#d#$ à direita +usando o operador \javaonly{#>>>#}\cpponly{#>>#}\pcodeonly{#>>#} ). \notpcode{Dessa forma, o código que implementa a fórmula acima é mais simples que a própria fórmula:} \codeimport{ods/ChainedHashTable.hash(x)} @@ -154,17 +148,16 @@ \subsection{Hashing Multiplicativo} \figlabel{multihashing} \end{figure} -O lema a seguir, cuja prova será feita posteriomente nesta seção, -mostra que o -hashing multiplicativo faz um bom trabalho em evitar colisões: +O lema a seguir, cuja prova será feita posteriormente nesta seção, +mostra que o hashing multiplicativo faz um bom trabalho em evitar colisões: \begin{lem}\lemlabel{universal-hashing} Sejam #x# e #y# dois valores em $\{0,\ldots,2^{#w#}-1\}$ com $#x#\neq #y#$. Então $\Pr\{#hash(x)#=#hash(y)#\} \le 2/2^{#d#}$. \end{lem} -Com -\lemref{universal-hashing}, o desempenho de #remove(x)#, e +Com o +\lemref{universal-hashing}, o desempenho de #remove(x)# e #find(x)# são fáceis de analisar: \begin{lem} @@ -176,8 +169,8 @@ \subsection{Hashing Multiplicativo} \end{lem} \begin{proof} - Seja - $S$ o conjunto de elementos guardado na tabela hash que não são iguais a #x#. Para um elemento + Seja $S$ o conjunto de elementos guardado na tabela hash + que não são iguais a #x#. Para um elemento $#y#\in S$, defina a variável indicadora \[ I_{#y#} = \left\{\begin{array}{ll} 1 & \mbox{se $#hash(x)#=#hash(y)#$} \\ @@ -200,17 +193,17 @@ \subsection{Hashing Multiplicativo} \end{proof} Agora, queremos provar - \lemref{universal-hashing}, mas primeiro precisamos de um resultado - da teoria de números. Na prova a seguir, usamos a notação + o \lemref{universal-hashing}, mas primeiro precisamos de um resultado + da Teoria de Números. Na prova a seguir, usamos a notação $(b_r,\ldots,b_0)_2$ para denotar $\sum_{i=0}^r b_i2^i$, onde cada $b_i$ é um bit, 0 ou 1. Em outras palavras, $(b_r,\ldots,b_0)_2$ é o inteiro cuja representação binária é dada por $b_r,\ldots,b_0$. Usamos - $\star$ para denotar um valor bit desconhecido. + $\star$ para denotar um valor de bit desconhecido. \begin{lem}\lemlabel{hashing-mapping} - Sej $S$ o conjunto de inteiros ímpares em $\{1,\ldots,2^{#w#}-1\}$; seja $q$ + Seja $S$ o conjunto de inteiros ímpares em $\{1,\ldots,2^{#w#}-1\}$; seja $q$ e $i$ quaisquer dois elementos em $S$. Então há exatamente um valor $#z#\in S$ tal que $#z#q\bmod 2^{#w#} = i$. \end{lem} @@ -237,12 +230,12 @@ \subsection{Hashing Multiplicativo} \[ (#z#-#z#')q = k\cdot(1,\underbrace{0,\ldots,0}_{#w#})_2 \enspace , \] - tal que o últimos #w# bits na representação binária de + tal que os últimos #w# bits na representação binária de $(#z#-#z#')q$ são todos 0. Além disso, $k\neq 0$, pois $q\neq 0$ e $#z#-#z#'\neq 0$. Uma vez que $q$ - é ímpar, não há 0's terminando sua representação binária: + é ímpar, não há um 0 terminando sua representação binária: \[ q = (\star,\ldots,\star,1)_2 \enspace . \] @@ -259,26 +252,24 @@ \subsection{Hashing Multiplicativo} \enspace . \] Portanto - $(#z#-#z#')q$ não pode satisfazer \myeqref{factors}, + $(#z#-#z#')q$ não pode satisfazer a \myeqref{factors}, chegando a uma contradição e completando a prova. \end{proof} A utilidade do - \lemref{hashing-mapping} vem da seguine observação: - Se #z# é escolhido uniformemente aleatório de $S$, então #zt# + \lemref{hashing-mapping} vem da seguinte observação: + Se #z# for escolhido de forma uniformemente aleatória de $S$, então #zt# é uniformemente distribuído sobre $S$. Na prova a seguir, ajuda a lembrar da representação binária de #z#, que consiste de -$#w#-1$ bits aleatórios seguindos por um 1. +$#w#-1$ bits aleatórios seguidos por um 1. -\begin{proof}[Prova da \lemref{universal-hashing}] - Primeiro notamos que a condição - $#hash(x)#=#hash(y)#$ equivale à +\begin{proof}[Prova do \lemref{universal-hashing}] + Primeiro notamos que a condição $#hash(x)#=#hash(y)#$ equivale à afirmação os #d# bits de ordem mais alta em ``$#z# #x#\bmod2^{#w#}$ e os #d# bits de ordem mais alta de #z# $#z# #y#\bmod 2^{#w#}$ são os mesmos.'' Uma condição necessária dessa afirmação é que os #d# bits de ordem mais - alta na representação de - $#z#(#x#-#y#)\bmod 2^{#w#}$ + alta na representação de $#z#(#x#-#y#)\bmod 2^{#w#}$ são todos 0 ou todos 1. Isto é, \begin{equation} #z#(#x#-#y#)\bmod 2^{#w#} = @@ -294,7 +285,7 @@ \subsection{Hashing Multiplicativo} \end{equation} quando $#zx#\bmod 2^{#w#} < #zy#\bmod 2^{#w#}$. Portanto, só temos que delimitar a probabilidade de que - $#z#(#x#-#y#)\bmod 2^{#w#}$ pareça \myeqref{all-zeros} ou \myeqref{all-ones}. + $#z#(#x#-#y#)\bmod 2^{#w#}$ pareça a \myeqref{all-zeros} ou \myeqref{all-ones}. Seja $q$ ser um inteiro único tal que $(#x#-#y#)\bmod 2^{#w#}=q2^r$ para algum inteiro $r\ge 0$. Pelo @@ -313,34 +304,34 @@ \subsection{Hashing Multiplicativo} \] Podemos agora terminar a prova: se $r > #w#-#d#$, então os #d# bits de alta ordem de - $#z#(#x#-#y#)\bmod 2^{#w#}$ contém tanto 0 quanto - 1, tal que a probabilidade de que $#z#(#x#-#y#)\bmod 2^{#w#}$ pareça a + $#z#(#x#-#y#)\bmod 2^{#w#}$ contêm tanto 0 quanto + 1, tal que a probabilidade de que $#z#(#x#-#y#)\bmod 2^{#w#}$ sejam \myeqref{all-zeros} ou \myeqref{all-ones} é 0. - Se $#r#=#w#-#d#$, então a probabilidade de parecer a - \myeqref{all-zeros} é 0, mas a probabilidae de parecer a + Se $#r#=#w#-#d#$, então a probabilidade de ocorrer + \myeqref{all-zeros} é 0, mas a probabilidade de ocorrer \myeqref{all-ones} é $1/2^{#d#-1}=2/2^{#d#}$ (como precisamos ter $b_1,\ldots,b_{d-1}=1,\ldots,1$). Se $r < #w#-#d#$, então precisamos ter $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=0,\ldots,0$ ou $b_{#w#-r-1},\ldots,b_{#w#-r-#d#}=1,\ldots,1$. A probabilidade de cada um desses casos é - $1/2^{#d#}$ e eles são mutualmente exclusivos, então a probabilidade de cada deses casos é - $2/2^{#d#}$. Isso completa essa prova. + $1/2^{#d#}$ e eles são mutualmente exclusivos, então a probabilidade de cada desses casos é + $2/2^{#d#}$. Isso completa a prova. \end{proof} \subsection{Resumo} -O teorema a seguir resume o desempenho de uma estrutura de dados +O teorema a seguir resume o desempenho da estrutura de dados #ChainedHashTable#: \begin{thm}\thmlabel{hashtable} Uma #ChainedHashTable# implementa a interface #USet#. Ignorando o custo das chamadas a - #grow()#, uma #ChainedHashTable# aceita as operações #add(x)#, - #remove(x)#, e #find(x)# em $O(1)$ de tempo esperado por operação. + #grow()#, uma #ChainedHashTable# possui as operações #add(x)#, + #remove(x)# e #find(x)# em $O(1)$ de tempo esperado por operação. - Além disso, comelando com uma + Além disso, começando com uma #ChainedHashTable# vazia, qualquer sequência de $m$ operações #add(x)# e #remove(x)# resulta em um total de $O(m)$ de tempo gasto durante todas a chamadas a #grow()#. @@ -353,14 +344,13 @@ \section{#LinearHashTable#: Sondagem Linear} A estrutura de dados #ChainedHashTable# usa um array de listas onde a #i#-ésima lista guarda todos os elementos #x# tais que $#hash(x)#=#i#$. Uma outra opção de projeto, chamado de \emph{endereçamento aberto}, \index{endereçamento aberto}% -é guardar elementos diretamento em um array #t# sendo cad posição #t# para armazenar no máximo um valor. +é guardar elementos diretamente em um array #t# sendo cada posição #t# para armazenar no máximo um valor. Essa abordagem é implementada pela #LinearHashTable# descrita nesta seção. -Em alguns lugares, essa estrutura de dados é descrita como \emph{endereçamento aberta com sondagem linear}. +Em alguns lugares, essa estrutura de dados é descrita como \emph{endereçamento aberto com sondagem linear}. \index{sondagem linear}% A principal ideia por trás da - #LinearHashTable# é que deveríamos, idealmente, guardar o elemento #x# com valor hash #i=hash(x)# na posição da tabela @@ -380,9 +370,8 @@ \section{#LinearHashTable#: Sondagem Linear} Em adição ao contador #n# que registra o número de elementos na #LinearHashTable#, um contador, #q#, registra o número de elementos dos Tipos~1 e 3. -Isto é #q# é igual a #n# mais o número de valores #del# em #t#. Para fazer isso eficientemente, precisamos que #t# seja consideravelmente maior que #q#, tal que haja muitos #null# em #t#. +Isso é, #q# é igual a #n# mais o número de valores #del# em #t#. Para fazer isso eficientemente, precisamos que #t# seja consideravelmente maior que #q#, tal que haja muitos #null# em #t#. As operações em - #LinearHashTable# portanto mantém a invariante $#t.length#\ge 2#q#$. @@ -393,53 +382,49 @@ \section{#LinearHashTable#: Sondagem Linear} $#t.length#=2^#d#$. \codeimport{ods/LinearHashTable.t.n.q.d} -A operação -#find(x)# na #LinearHashTable# é simples. Iniciamos na posição do array #t[i]# -onde +A operação #find(x)# na #LinearHashTable# é simples. +Iniciamos na posição do array #t[i]# onde $#i#=#hash(x)#$ e buscamos as posições #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, e assim segue, -até encontrarmos um índice #i'# tal que ou - #t[i']=x#, ou #t[i']=null#. -No primero caso retornamos - #t[i']#. No outro, quando #t[i']=null# In the latter case, concluímos - que não está contido na tabela hash e retornamos -#null#. +até encontrarmos um índice #i'# tal que + #t[i']=x# ou #t[i']=null#. +No primeiro caso retornamos + #t[i']#. No outro, quando #t[i']=null#, concluímos + que #x# não está contido na tabela hash e retornamos #null#. \codeimport{ods/LinearHashTable.find(x)} A operação #add(x)# também é razoavelmente simples para implementar. Após verificar que #x# não está na tabela (usando #find(x)#), procuramos em #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, -e assim por diante até acharmos um -#null# ou #del# e guarde #x# naquela posição, -incremente #n#, e #q#, se apropriado. +e assim por diante, até acharmos um +#null# ou #del# e guardamos #x# naquela posição, +incrementando #n# e #q#, se apropriado. \codeimport{ods/LinearHashTable.add(x)} -Até agora, a implementação da operação -#remove(x)# deve ser óbvia. +Até agora, a implementação da operação #remove(x)# deve ser óbvia. Buscamos #t[i]#, $#t#[(#i#+1)\bmod #t.length#]$, $#t#[(#i#+2)\bmod #t.length#]$, e assim por diante até acharmos um índice #i'# tal que #t[i']=x# ou #t[i']=null#. No primeiro caso, atribuímos #t[i']=del# e retornamos #true#. No segundo caso, concluímos que #x# não foi armazenado na tabela -(e portanto não pode ser deletado) e retorna #false#. +(e portanto não pode ser deletado) e retornamos #false#. \codeimport{ods/LinearHashTable.remove(x)} A corretude dos métodos -#find(x)#, #add(x)#, e #remove(x)# fácil de verificar embora dependa do uso dos valores #del#. Note que nenhuma dessas operações nunca atribui em posição não-#null# a #null#. +#find(x)#, #add(x)# e #remove(x)# é fácil de verificar embora dependa do uso dos valores #del#. Note que nenhuma dessas operações nunca atribui #null# a uma posição diferente de #null#. Portanto, se alcançarmos um índice #i'# tal que #t[i']=null# isso comprova que o elemento #x# pelo qual buscamos não está guardado na tabela ; #t[i']# sempre foi #null#, então não há porque uma operação prévia #add(x)# -continuaria além do índice #i'#. +continuar além do índice #i'#. O método #resize()# é chamado por #add(x)# quando o número de posições não-#null# -excede $#t.length#/2$ ou por #remove(x)# quando o número de entrada de dados é bem menor que -#t.length/8#. O método #resize()# funciona como os métodos +exceder $#t.length#/2$ ou por #remove(x)# quando o número de entrada de dados +for bem menor que #t.length/8#. O método #resize()# funciona como os métodos #resize()# em outras estruturas de dados baseadas em array. -Achamos o menor inteiro não-negativo #d# tal que -$2^{#d#} -\ge 3#n#$. Realocamos o array #t# tal que tenha tamanho $2^{#d#}$, +Achamos o menor inteiro não negativo #d# tal que +$2^{#d#} \ge 3#n#$. Realocamos o array #t# tal que tenha tamanho $2^{#d#}$, e então inserimos todos os elementos na versão antiga de #t# na nova cópia redimensionada de #t#. Ao fazer isso, reiniciamos #q# igual a #n# pois o novo #t# contém nenhum valor #del#. @@ -447,67 +432,69 @@ \section{#LinearHashTable#: Sondagem Linear} \subsection{Análise da Sondagem Linear} Note que cada operação -#add(x)#, #remove(x)#, ou #find(x)#, termina assim que (ou mesmo antes) +#add(x)#, #remove(x)# ou #find(x)#, termina assim que (ou mesmo antes) encontra a primeira posição #null# em #t#. A intuição por trás da análise da sondagem linear é que, como pelo menos metade dos elementos em #t# são iguais a #null#, uma operação não deve demorar muito para completar pois irá rapidamente achar uma posição com valor #null#. Não devemos usar essa intuição ao pé da letra pois levaria à conclusão (incorreta) de que o número esperado de posições em #t# que foram examinadas por uma operação -é até 2. +é de até 2. Para o resto desta seção, iremos assumir que todos os valores hash são independentemente e uniformemente distribuídos em $\{0,\ldots,#t.length#-1\}$. Essa não é uma premissa realista, mas permitirá uma análise da sondagem linear. -Mais a seguir nesta seção, iremos descrever um método, chamado de hashing de tabulação, que produz uma função hash que é ``boa o suficiente'' para sondagem linear. Também isso assumir que todos os índices das posições de #t# +Mais a seguir, nesta seção, iremos descrever um método, chamado +de hashing de tabulação, que produz uma função hash que é +``boa o suficiente'' para sondagem linear. +Também isso assumir que todos os índices das posições de #t# são obtidos módulo #t.length#, tal que #t[i]# é na verdade um atalho para $#t#[#i#\bmod#t.length#]$. \index{run}% Dizemos que um \emph{agrupamento de comprimento $k$ iniciando em #i#} ocorre quando as posições da tabela -$#t[i]#, #t[i+1]#,\ldots,#t#[#i#+k-1]$ are non-#null# -and $#t#[#i#-1]=#t#[#i#+k]=#null#$. O número de elementos não-#null# de -#t# é exatamente #q# e o método #add(x)# garante que, a qualquer momento, $#q#\le#t.length#/2$. Há #q# elementos $#x#_1,\ldots,#x#_{#q#}$ -que foram inseridos em -#t# desde a última operação #resize()#. +$#t[i]#, #t[i+1]#,\ldots,#t#[#i#+k-1]$ não são #null# +e $#t#[#i#-1]=#t#[#i#+k]=#null#$. O número de elementos não-#null# de +#t# é exatamente #q# e o método #add(x)# garante que, a qualquer momento, +$#q#\le#t.length#/2$. Há #q# elementos $#x#_1,\ldots,#x#_{#q#}$ +que foram inseridos em #t# desde a última operação #resize()#. De acordo com nossa premissa, cada um desses elementos tem valor hash $#hash#(#x#_j)$ que é de uma distribuição uniforme e cada qual é independente do resto. -Nesse cenário podemos prova que principal lema necessário para analisar +Nesse cenário podemos provar o principal lema necessário para analisar a sondagem linear. \begin{lem}\lemlabel{linear-probing} -Fixe um valor $#i#\in\{0,\ldots,#t.length#-1\}$. Então a probabilidade de qua um agrupamento de -comprimento $k$ inicie em #i# é $O(c^k)$ para alguma constante $00$.) Como $\sqrt{e}/{2}< 0.824360636 < 1$, isso completa a prova. \end{proof} -Usar \lemref{linear-probing} para provar limitantes superiores no tempo de execução esperado de -#find(x)#, #add(x)# e #remove(x)# agora é razoavelmente direto. Considere o caso -mais simples, onde executamos -#find(x)# para algum valor #x# nunca foi guardado em #LinearHashTable#. Nesse caso, $#i#=#hash(x)#$ é uma variável aleatória em +Usar o \lemref{linear-probing} para provar limitantes superiores no tempo +de execução esperado de #find(x)#, #add(x)# e #remove(x)# agora é +mais simples. Considere o caso mais simples, onde executamos +#find(x)# para algum valor #x# nunca foi guardado em #LinearHashTable#. +Nesse caso, $#i#=#hash(x)#$ é uma variável aleatória em $\{0,\ldots,#t.length#-1\}$ independente do conteúdo de #t#. Se #i# é parte de um agrupamento de tamanho $k$, então o tempo que leva para executar -a operação -#find(x)# é até $O(1+k)$. Então, o tempo esperado de execução pode ser limitado superiormente por +a operação #find(x)# é até $O(1+k)$. Então, o tempo esperado de execução pode ser limitado superiormente por \[ O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k\Pr\{\text{#i# é parte de um agrupamento de tamanho $k$}\}\right) \enspace . \] @@ -547,7 +534,7 @@ \subsection{Análise da Sondagem Linear} & = O(1) \enspace . \end{align*} O último passo dessa derivação vem do fato que -$\sum_{k=0}^{\infty} k^2\cdot O(c^k)$ é uma séries exponencial decrescente.\footnote{Em muitos livros de cálculo, essa soma passa no teste da taxa: +$\sum_{k=0}^{\infty} k^2\cdot O(c^k)$ é uma série exponencial decrescente.\footnote{Em muitos livros de cálculo, essa soma passa no teste de convergência: existe um inteiro positivo $k_0$ tal que, para todo $k\ge k_0$, $\frac{(k+1)^2c^{k+1}}{k^2c^k} < 1$.} @@ -556,25 +543,22 @@ \subsection{Análise da Sondagem Linear} #LinearHashTable# é $O(1)$. Se ignorarmos o custo da operação -#resize()# , então a análise acima nos dá tudo que precisamos para analisar -o custo de operações em uma -#LinearHashTable#. +#resize()# , então a análise acima nos dá tudo o que precisamos para analisar +o custo de operações em uma #LinearHashTable#. -Primeiramente, a análise de -#find(x)# dada acima aplica à operação +Primeiramente, a análise de #find(x)# dada acima aplica-se à operação #add(x)# quando#x# não está na tabela. Para analisar a operação - #find(x)# quando #x# está na tabela, precisamos somente notar que isso tem o mesmo custo da operação -#add(x)# que anteriormente adicionou + #find(x)# quando #x# está na tabela, precisamos somente notar + que isso tem o mesmo custo da operação #add(x)# que anteriormente adicionou #x# à tabela. Finalmente, o custo de uma operação #remove(x)# -é o mesmo que o custo de uma operação -#find(x)#. +é o mesmo que o custo de uma operação #find(x)#. -Em resumo, se ignorarmo o custo de chamadas a +Em resumo, se ignorarmos o custo de chamadas a #resize()#, todas as operações em uma #LinearHashTable# rodam em $O(1)$ de tempo esperado. Considerar o custo de redimensionamento pode ser feito usando o mesmo tipo de análise amortizada realizada para a estrutura de dados - #ArrayStack# feita em \secref{arraystack}. + #ArrayStack# feita na \secref{arraystack}. \subsection{Resumo} @@ -595,23 +579,21 @@ \subsection{Resumo} $O(m)$ de tempo gasto durante todas as chamadas a #resize()#. \end{thm} -\subsection{Hashing por tabulação } +\subsection{Hashing por tabulação} \seclabel{tabulation} \index{hashing por tabulação}% -Ao analisar a estrutura - #LinearHashTable#, fazemos uma suposição muito forte: +Ao analisarmos a estrutura #LinearHashTable#, fazemos uma suposição muito forte: para qualquer conjunto de elementos $\{#x#_1,\ldots,#x#_#n#\}$ os valores de hash $#hash#($x$_1),\ldots,#hash#(#x#_#n#)$ são independentes e uniformemente distribuídos sobre o conjunto - $\{0,\ldots,#t.length#-1\}$. Um jeito de alcançar isso é guardar em um array gigante #tab# de tamanho + $\{0,\ldots,#t.length#-1\}$. Um jeito de obter isso é guardar em um array gigante #tab# de tamanho $2^{#w#}$, onde cada entrada é um inteiro aleatório #w#-bit, independente de todas as outras posições. Dessa maneira, poderíamos implementar #hash(x)# pela -extração de um inteiro com #d# bits da - #tab[x.hashCode()]#: +extração de um inteiro com #d# bits da #tab[x.hashCode()]#: \codeimport{ods/LinearHashTable.idealHash(x)} -\pcodeonly{Aqui, #>>#, é o operador de \emph{deslocamemto bit-a-bit à direita}, então +\pcodeonly{Aqui, #>>#, é o operador de \emph{deslocamento bit-a-bit à direita}, então #x.hashCode() >> w-d# extrai os #d# bits mais significativos do código hash dos #w# bits do código hash de #x#} Infelizmente, guardar um array de tamanho @@ -619,10 +601,10 @@ \subsection{Hashing por tabulação } A abordagem usada pela \emph{hashing por tabulação} é, em vez disso, tratar inteiros de #w# bits como sendo compostos de $#w#/#r#$ inteiros, cada qual tem somente $#r#$ bits. -Dessa maneira, hashing por tabulalão precisa somente de -$#w#/#r#$ arrays cada qual com tamanho $2^{#r#}$. Todas as entradas nesses arrays sao inteiros aleatórios independentes com #w# bits. +Dessa maneira, hashing por tabulação precisa somente de +$#w#/#r#$ arrays cada qual com tamanho $2^{#r#}$. Todas as entradas nesses arrays são inteiros aleatórios independentes com #w# bits. -Para oter o valor de +Para obter o valor de #hash(x)# dividimos #x.hashCode()# em $#w#/#r#$ inteiros com #r# bits e os usamos como índices desses arrays. Então combinamos todos esses valores com o operador bit-a-bit ou-exclusivo para obter #hash(x)#. @@ -630,7 +612,7 @@ \subsection{Hashing por tabulação } $#w#=32$ e $#r#=4$: \codeimport{ods/LinearHashTable.hash(x)} Nesse caso, - #tab# é um array bi-dimensional com quatro colunas e + #tab# é um array bidimensional com quatro colunas e $2^{32/4}=256$ linhas. \pcodeonly{ Quantidades como @@ -645,32 +627,29 @@ \subsection{Hashing por tabulação } É fácil verificar que, para qualquer #x#, #hash(x)# é uniformemente distribuído sobre $\{0,\ldots,2^{#d#}-1\}$. Com um pouco de trabalho, -é possível verificar que qualquer par de valores tem valores hash independentes. -Isso implica que hashing por tabulação poderia ser usada no lugar -do hashing multiplicativo para a implementação da - #ChainedHashTable#. +é possível verificar que qualquer par de valores tem suas hash independentes. +Isso implica que hashing por tabulação poderia ser usado no lugar +do hashing multiplicativo para a implementação da #ChainedHashTable#. - Entretanto, não é verdade que qualquer conjunto de #n# valores distintos - resulte em #n# valores hash independentes. De qualquer forma, - quando hashing por tabulação é usado, o limitante - \thmref{linear-probing} ainda vale. Referências para isso - são providas no final deste capítulo. +Entretanto, não é verdade que qualquer conjunto de #n# valores distintos +resulte em #n# valores hash independentes. De qualquer forma, +quando hashing por tabulação é usado, o limitante +\thmref{linear-probing} ainda vale. Referências para isso +são providas no final deste capítulo. \section{Códigos Hash} \index{código hash}% -Tabelas hash discutivas na seção anterior, são usadas para associar +Tabelas hash discutidas na seção anterior são usadas para associar dados com chaves inteiras consistindo de #w# bits. Em muitos casos, temos chaves que não são inteiros. Elas podem ser strings, objetos, arrays, ou outras estruturas compostas. Para usar tabelas hash para esses tipos de dados, precisamos mapear esses tipos de dados para códigos hash de #w# bits. -Mapeamento de hash codes devem ter as seguintes propriedades: +Mapeamentos de códigos hash devem ter as seguintes propriedades: \begin{enumerate} \item Se #x# e #y# são iguais, então #x.hashCode()# e #y.hashCode()# são iguais. - \item Se #x# e #y# não são iguais, então a probabilidade que - $#x.hashCode()#=#y.hashCode()#$ deve ser pequena (próxima a - $1/2^{#w#}$). + $#x.hashCode()#=#y.hashCode()#$ deve ser pequena (próxima a $1/2^{#w#}$). \end{enumerate} A primeira propriedade assegura que se guardarmos #x# em um tabela hash @@ -681,12 +660,11 @@ \section{Códigos Hash} tabela hash. \subsection{Códigos hash para Tipos de Dados Primitivos} - \index{código hash!para dados primitivos}% Tipos de dados primitivos pequenos como #char#, #byte#, #int# e #float# são normal fáceis de obter códigos hash. -Esses tipos de dados sempre têm uma representação binária que usualmente consitem de #w# ou menos bits. +Esses tipos de dados sempre têm uma representação binária que usualmente consistem de #w# ou menos bits. \javaonly{(Por exemplo, em Java, #byte# é um tipo de 8 bits e #float# é um tipo de 32 bits.)}\cpponly{(Por exemplo, em C++ #char# é tipicamente de 8 bits e #float# é um tipo de 32 bits.)} Nesses casos, @@ -699,15 +677,14 @@ \subsection{Códigos hash para Tipos de Dados Primitivos} \subsection{Códigos Hash para Objetos Compostos} \seclabel{stringhash} - \index{códigos hash!para objetos compostos}% Para um objeto composto, queremos criar um código hash combinando os códigos hash individuais das partes que constituem o objeto. Isso não é tão fácil quanto parece. -Embora seja possível achar muitos hacks para issso (por exemplo, combinar os códigos hash com operações bitwise ou-exclusivo), muitos desses hacks acabam sendo fáceis de enganar (veja os Exercícios~\ref{exc:hash-hack-first}--\ref{exc:hash-hack-last}). -Porém, se quisermos fazer aritmética com $2#w#$ bits de precisão, então -existem métodos simples e robustos a disposição. +Embora seja possível achar muitos hacks para isso (por exemplo, combinar os códigos hash com operações bitwise ou-exclusivo), muitos desses hacks acabam sendo fáceis de enganar (veja os Exercícios~\ref{exc:hash-hack-first}--\ref{exc:hash-hack-last}). +Porém, se pudermos fazer aritmética com $2#w#$ bits de precisão, então +existem métodos simples e robustos a nossa disposição. Suponha que temos um objeto composto de várias partes $P_0,\ldots,P_{r-1}$ cujos códigos hash são $#x#_0,\ldots,#x#_{r-1}$. Então, podemos escolher inteiros de #w# bits mutuamente independentes @@ -719,16 +696,16 @@ \subsection{Códigos Hash para Objetos Compostos} \ddiv 2^{#w#} \enspace . \] Note que esse código hash tem um passo final (multiplicar por #z# e dividir -por $2^{#w#}$) que usa uma função hash multiplicativo de -\secref{multihash} para obter um resultado intermediária $2#w#$ bits -e reduzir a um resultado final de #w# bits. Aque segue um exemplo desse método -aplica a um único objeto composto de três partes - #x0#, #x1#, and #x2#: +por $2^{#w#}$) que usa uma função hash multiplicativo da \secref{multihash} para obter um resultado intermediário $2#w#$ bits +e reduzir a um resultado final de #w# bits. Aqui segue um exemplo desse método +aplicado a um único objeto composto de três partes + #x0#, #x1# e #x2#: \javaimport{junk/Point3D.x0.hashCode()} \cppimport{ods/Point3D.hashCode()} \pcodeimport{ods/Point3d.hashCode()} + O teorema a seguir mostra que, em adição a ser de simples implementação, esse -método é comprovadamento bom: +método é comprovadamente bom: \begin{thm}\thmlabel{multihash} Sejam $#x#_0,\ldots,#x#_{r-1}$ e $#y#_0,\ldots,#y#_{r-1}$ sequências de inteiros de #w# bits em $\{0,\ldots,2^{#w#}-1\}$ e assuma $#x#_i \neq #y#_i$ para pelo menos um índice $i\in\{0,\ldots,r-1\}$. Então @@ -739,14 +716,14 @@ \subsection{Códigos Hash para Objetos Compostos} \end{thm} \begin{proof} - Iremos a princípio ignorar o passo final de hashing multiplicativo + Iremos, a princípio, ignorar o passo final de hashing multiplicativo e ver como aquele passo contribui depois. Defina: \[ h'(#x#_0,\ldots,#x#_{r-1}) = \left(\sum_{j=0}^{r-1} #z#_j #x#_j\right)\bmod 2^{2#w#} \enspace . \] Assuma que $h'(#x#_0,\ldots,#x#_{r-1}) = h'(#y#_0,\ldots,#y#_{r-1})$. - Podemos rescrever isso como: + Podemos reescrever isso como: \begin{equation} \eqlabel{bighash-x} #z#_i(#x#_i-#y#_i) \bmod 2^{2#w#} = t \end{equation} @@ -756,7 +733,7 @@ \subsection{Códigos Hash para Objetos Compostos} \] Se assumirmos, sem perda de generalidade, que $#x#_i> #y#_i$, então - \myeqref{bighash-x} torna-se + a \myeqref{bighash-x} torna-se \begin{equation} #z#_i(#x#_i-#y#_i) = t \eqlabel{bighash-xx} \enspace , \end{equation} @@ -766,18 +743,17 @@ \subsection{Códigos Hash para Objetos Compostos} $2^{2#w#}-2^{#w#+1}+1 < 2^{2#w#}-1$. Por suposição, - $#x#_i-#y#_i\neq 0$, então \myeqref{bighash-xx} tem no máximo uma solução + $#x#_i-#y#_i\neq 0$, então a \myeqref{bighash-xx} tem no máximo uma solução em $#z#_i$. Portanto, como $#z#_i$ e $t$ são independentes ($#z#_0,\ldots,#z#_{r-1}$ são mutuamente independentes), a probabilidade de selecionarmos $#z#_i$ tal que $h'(#x#_0,\ldots,#x#_{r-1})=h'(#y#_0,\ldots,#y#_{r-1})$ é no máximo $1/2^{#w#}$. - O passo final da função hash é aplicar o - hashing multiplicativo + O passo final da função hash é aplicar o hashing multiplicativo para reduzir nosso resultado intermediário de $2#w#$ bits $h'(#x#_0,\ldots,#x#_{r-1})$ a um - resultado final com #w# bits $h(#x#_0,\ldots,#x#_{r-1})$. Usando \thmref{multihash}, + resultado final com #w# bits $h(#x#_0,\ldots,#x#_{r-1})$. Usando o \thmref{multihash}, se $h'(#x#_0,\ldots,#x#_{r-1})\neq h'(#y#_0,\ldots,#y#_{r-1})$, então $\Pr\{h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1})\} \le 2/2^{#w#}$. @@ -796,7 +772,6 @@ \subsection{Códigos Hash para Objetos Compostos} \end{align*} \end{proof} - \index{código hash!para strings}% \index{código hash!para arrays}% \subsection{Códigos Hash para Arrays e Strings} @@ -806,24 +781,24 @@ \subsection{Códigos Hash para Arrays e Strings} objetos que tem um número variável de componentes, pois querer um inteiro aleatório de #w# bits $#z#_i$ para cada componente. -Poderíamos usar uma sequência pseudoaleatória para gerar quantos -$#z#_i$ fossem necessários, então então os $#z#_i$ não seriam mutuamente independentes e se torna difícil provar que os números pseudoaleatórios não interagem mal com a função hash que estamos usando. +Poderíamos usar uma sequência pseudo aleatória para gerar quantos +$#z#_i$ fossem necessários, mas então os $#z#_i$ não seriam mutuamente independentes e se torna difícil provar que os números pseudo aleatórios não interagem mal com a função hash que estamos usando. Em particular, os valores de $t$ e -$#z#_i$ na prova de \thmref{multihash} não seriam mais independentes. +$#z#_i$ na prova do \thmref{multihash} não seriam mais independentes. \index{prime field}% \index{corpo primal}% -Uma abordagem mais rigorosa é obter nossos códigos hash com base em polinômios sobre corpos primais; esse são basicamente polinômios comumns que são avaliados em módulo algum número primo, #p#. Esse método é baseado no seguinte teorema, que +Uma abordagem mais rigorosa é obter nossos códigos hash com base em polinômios sobre corpos primais; esse são basicamente polinômios comuns que são avaliados em módulo algum número primo, #p#. Esse método é baseado no seguinte teorema, que diz que polinômios sobre corpos primais se comportam como polinômios comuns: \begin{thm}\thmlabel{prime-polynomial} - Seja $#p#$ um número primo, e seja $f(#z#) = #x#_0#z#^0 + #x#_1#z#^1 + + Seja $#p#$ um número primo e seja $f(#z#) = #x#_0#z#^0 + #x#_1#z#^1 + \cdots + #x#_{r-1}#z#^{r-1}$ um polinômio não trivial com coeficientes $#x#_i\in\{0,\ldots,#p#-1\}$. Então a equação $f(#z#)\bmod #p# = 0$ tem até $r-1$ soluções para $#z#\in\{0,\ldots,p-1\}$. \end{thm} -Para usar \thmref{prime-polynomial}, aplicamos uma função hash em uma sequência de inteiros +Para usar o \thmref{prime-polynomial}, aplicamos uma função hash em uma sequência de inteiros $#x#_0,\ldots,#x#_{r-1}$ cada qual com $#x#_i\in \{0,\ldots,#p#-2\}$ usando um inteiro aleatório $#z#\in\{0,\ldots,#p#-1\}$ via a fórmula @@ -839,7 +814,7 @@ \subsection{Códigos Hash para Arrays e Strings} $\{0,\ldots,#p#-2\}$). Podemos pensar que $#p#-1$ atua como um marcador de fim de sequência. -O teorema a seguir, que considera o caso de duas sequência de mesmo +O teorema a seguir, que considera o caso de duas sequências de mesmo tamanho, mostra que essa função hash produz bons resultados para uma pequena quantidade de randomização necessária para escolher #z#: @@ -862,35 +837,33 @@ \subsection{Códigos Hash para Arrays e Strings} (#x#_0-#y#_0)#z#^0+\cdots+(#x#_{r-1}-#y#_{r-1})#z#^{r-1} \right)\bmod #p# = 0. \end{equation} -Como $#x#_#i#\neq #y#_#i#$, esse polinômio é não-trivial. Portanto, usando , +Como $#x#_#i#\neq #y#_#i#$, esse polinômio é não-trivial. Portanto, usando o \thmref{prime-polynomial}, ele tem no máximo $r-1$ soluções em #z#. A probabilidade que escolhemos #z# para seja uma dessas soluções é portanto - no máximo - $(r-1)/#p#$. + no máximo $(r-1)/#p#$. \end{proof} -Note que essa função hash também lida com o caso em que duas sequências tem comprimentos distintos, mesmo quando uma das sequências é um prefixo de outra. +Note que essa função hash também considera o caso em que duas sequências tem comprimentos distintos, mesmo quando uma das sequências é um prefixo de outra. Isso porque essa função efetivamente obtém o hash da sequência infinita \[ #x#_0,\ldots,#x#_{r-1}, #p#-1,0,0,\ldots \enspace . \] -Isso garante que se temos duas sequências de comprimento +Isso garante que, se temos duas sequências de comprimento $r$ e $r'$ com $r > r'$, então essas duas sequências diferem no índice $i=r$. -Nesse caso, - \myeqref{strhash-eqlen} torna-se +Nesse caso, a \myeqref{strhash-eqlen} torna-se \[ \left( \sum_{i=0}^{i=r'-1}(#x#_i-#y#_i)#z#^i + (#x#_{r'} - #p# + 1)#z#^{r'} +\sum_{i=r'+1}^{i=r-1}#x#_i#z#^i + (#p#-1)#z#^{r} \right)\bmod #p# = 0 \enspace , \] -que, segundo \thmref{prime-polynomial}, tem até $r$ soluções em $#z#$. -Isso combinado com +que, segundo o \thmref{prime-polynomial}, tem até $r$ soluções em $#z#$. +Isso combinado com o \thmref{stringhash-eqlen} é suficiente para provar o seguinte teorema mais geral: \begin{thm}\thmlabel{stringhash} -Seja $#p#>2^{#w#}+1$ um primo, usando duas sequências distintas de #w#bits $#x#_0,\ldots,#x#_{r-1}$ e +Seja $#p#>2^{#w#}+1$ um número primo, usando duas sequências distintas de #w# bits $#x#_0,\ldots,#x#_{r-1}$ e $#y#_0,\ldots,#y#_{r'-1}$ em $\{0,\ldots,2^{#w#}-1\}$. Então \[ @@ -905,51 +878,55 @@ \subsection{Códigos Hash para Arrays e Strings} \cppimport{ods/GeomVector.hashCode()} \pcodeimport{ods/GeomVector.hashCode()} -O código precedendo sacrifica alguma probabilidade de colisão por conveniência +O código precedente sacrifica alguma probabilidade de colisão por conveniência na implementação. Em particular, ele aplica a função hash multiplicativa de -\secref{multihash}, com $#d#=31$ para reduzir #x[i].hashCode()# a um valor de 31 bits. Isso é feito dessa forma para que adições e multiplicações +\secref{multihash}, com $#d#=31$ para reduzir #x[i].hashCode()# a um valor de 31 bits. +Isso é feito dessa para que adições e multiplicações feitas módulo o primo $#p#=2^{32}-5$ possam ser realizadas usando aritmética 63 bits sem sinal. -Então a probabilidade de duas diferentes sequências, a mais longa com comprimento #r#, terem o mesmo código hash é até +Então a probabilidade de duas diferentes sequências, a mais longa com comprimento #r#, +terem o mesmo código hash é até \[ 2/2^{31} + r/(2^{32}-5) \] em vez da -$r/(2^{32}-5)$ especificada em \thmref{stringhash}. +$r/(2^{32}-5)$ especificada no \thmref{stringhash}. \section{Discussão e Exercícios} -Tabelas hash e códigos hash representam um campo de pesquisa muito ativo que é brevemente pincelado neste capítulo. +Tabelas hash e códigos hash representam um campo de pesquisa muito ativo +que é brevemente pincelado neste capítulo. A online \emph{Bibliography on Hashing} \cite{hashing} \index{Bibliography on Hashing}% -contém aproximadamente 2000 referencias. +contém aproximadamente 2000 referências. Existe uma grande variedade de implementações de diferentes tabelas. -Aquela descrita em +Aquela descrita na \secref{hashtable} é conhecida como \emph{hashing with chaining} ou \emph{hashing com encadeamento} \index{hashing with chaining}% \index{hashing com encadeamento} -(cada posição do array contém uma lista, por vezes encadeada, (#List#) de elementos). Hashing com encadeamento tem suas origens em um +(cada posição do array contém uma lista, por vezes encadeada, (#List#) de elementos). +Hashing com encadeamento tem suas origens em um memorando interno da IBM escrito por - H. P. Luhn que data de Janeiro de 1953. Esse memorando também parece - ser uma das primeiras referências a listas ligadas. +H. P. Luhn que data de Janeiro de 1953. Esse memorando também parece +ser uma das primeiras referências a listas ligadas. \index{open addressing}% \index{endereçamento aberto}% Uma alternativa a hashing com encadeamento é aquela que usa esquemas -de - \emph{open -addressing}, ou , \emph{endereçamento aberto}, onde todos os dados são armazenados diretamente em um array. +de \emph{open addressing}, ou , \emph{endereçamento aberto}, onde todos +os dados são armazenados diretamente em um array. Esses esquemas incluem a estrutura - #LinearHashTable# de + #LinearHashTable# da \secref{linearhashtable}. Essa ideia também foi proposta independentemente por um -grupo na IBM na década de 1950. Esquemas de endereçamento aberto devem tratar do problema de resolução de colisões: +grupo na IBM na década de 1950. Esquemas de endereçamento aberto devem +tratar do problema de resolução de colisões: \emph{resolução de colisões}: \index{resolução de colisões}% o caso em que dois valores hash mapeiam para a mesma posição do array. -Dintintas estratégias existem para resolução de colisões; essas provêem +Distintas estratégias existem para resolução de colisões; essas provêem diferentes garantias de desempenho e frequentemente -requerem funções hash mais sofisticadas que aquelas descritas aqui. +requerem funções hash mais sofisticadas do que aquelas descritas aqui. Outra categoria de implementações de tabelas hash são os famosos métodos de \emph{hashing perfeito}. @@ -960,7 +937,7 @@ \section{Discussão e Exercícios} \emph{funções hash perfeitas} \index{funções hash perfeitas}% \index{função hash!perfeita}% -para os dadoss; existem funções que mapeiam cada dado da uma única +para os dados; existem funções que mapeiam cada dado de uma única posição do array. Para dados que mudam com o tempo, métodos de hashing perfeito incluem \emph{tabelas hash de dois níveis FKS} @@ -971,16 +948,16 @@ \section{Discussão e Exercícios} \index{hashing cuckoo }% \index{tabela hash!cuckoo}% -As funções has apresentadas neste capítulo estão provavelmente entre +As funções hash apresentadas neste capítulo estão provavelmente entre os métodos práticos atualmente conhecidos que funcionam bem para qualquer conjunto de dados. Outros métodos bons datam do trabalho pioneiro -de -Carter e Wegman que criaram o conceito de \emph{hashing universal} +de Carter e Wegman que criaram o conceito de \emph{hashing universal} \index{hashing universal }% \index{universal!hashing}% e descreveram várias funções hash para diferentes cenários \cite{cw79}. Hashing por tabulação, descrita na \secref{tabulation}, foi proposta por Carter -e Wegman \cite{cw79}, mas sua análise, quando aplicada a sondagem linear (e vários outros esquemas de tabela hash) +e Wegman \cite{cw79}, mas sua análise, quando aplicada a sondagem linear +(e vários outros esquemas de tabela hash) é creditada a P\v{a}tra\c{s}cu e Thorup \cite{pt12}. @@ -988,13 +965,13 @@ \section{Discussão e Exercícios} \emph{hashing multiplicativo} \index{hashing multiplicativo}% \index{hashing!multiplicativo}% -é muito antiga e parece ser parte do folklore de hashing +é muito antiga e parece ser parte do folclore de hashing \cite[Section~6.4]{k97v3}. Porém, a ideia de escolher um #z# sendo um número aleatório \emph{ímpar}, -e a análise em \secref{multihash} é de Dietzfelbinger \etal\ +e a análise na \secref{multihash} é de Dietzfelbinger \etal\ \cite{dhkp97}. Essa versão de hashing multiplicativo é uma das mais simples, mas sua probabilidade de colisão de -$2/2^{#d#}$ é um fator de 2 maior que se poderia esperar com uma função +$2/2^{#d#}$ é um fator de 2 maior do que se poderia esperar com uma função aleatória de $2^{#w#}\to 2^{#d#}$. O método de \emph{hashing multiplica e soma} @@ -1023,28 +1000,25 @@ \section{Discussão e Exercícios} $1/2^{#w#}$. Isso pode ser reduzido a um código hash de #w# bits usando hashing multiplicativo (ou multiplica e soma). Esse método é rápido porque requer somente - $r/2$ multiplicações de $2#w#$ bits enquanto o método descrito em + $r/2$ multiplicações de $2#w#$ bits enquanto o método descrito na \secref{stringhash} usa $r$ multiplicações. (As operações $\bmod$ ocorrem implicitamente usando aritmética de #w# e $2#w#$ bits para as adições e multiplicações, respectivamente.) -O método de - \secref{polyhash} de usar polinômios em corpos primais para fazer hash de arrays de tamanho variável e strings é atribuído a +O método da \secref{polyhash} de usar polinômios em corpos primais para fazer hash de arrays de tamanho variável e strings é atribuído a Dietzfelbinger \etal\ \cite{dgmp92}. Devido ao uso do operador $\bmod$ que usa uma instrução de máquina de alto custo, não é, infelizmente, muito rápida. Algumas variantes desse método escolhem o primo #p# para ser da forma $2^{#w#}-1$, que nesse caso o operador $\bmod$ pode ser substituído pelas operações de -adição (#+#) e and bit a bit (#&#) \cite[Section~3.6]{k97v2}. +adição (#+#) e AND bit a bit (#&#) \cite[Section~3.6]{k97v2}. Outra opção é aplicar um dos métodos rápidos para strings de tamanho fixo para blocos de tamanho $c$ para alguma constante $c>1$ e então aplicar o -método de corpo primal à sequência resultante de - $\lceil r/c\rceil$ -códigos hash. +método de corpo primal à sequência resultante de $\lceil r/c\rceil$ códigos hash. \begin{exc} Uma certa universidade atribui a cada um de seus estudantes números na primeira vez que eles registram-se para qualquer disciplina. - Esses números são inteiros sequencias que iniciam-se em 0 muitos + Esses números são sequências de inteiros que iniciaram em 0 muitos anos atrás e agora estão na casa dos milhões. Suponha que temos uma turma de cem alunos do primeiro ano que queremos atribuí-los código hash baseados em seus números de estudantes. @@ -1053,29 +1027,27 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Considere o esquema de hashing em \secref{multihash}, e suponha + Considere o esquema de hashing na \secref{multihash} e suponha $#n#=2^{#d#}$ e $#d#\le #w#/2$. \begin{enumerate} - \item Mostre que, para qualquer escolha de multiplicador #z#, existe #n# valores que tem o mesmo código hash. + \item Mostre que, para qualquer escolha de multiplicador #z#, existe #n# valores que têm o mesmo código hash. (Dica: isso é fácil e não precisa usar teoria dos números.) - \item Dado um multiplicador #z#, descreva #n# valores que tem o mesmo código hash. (Dica: isso é mais difícil e requer um pouco de teoria dos números.) + \item Dado um multiplicador #z#, descreva #n# valores que têm o mesmo código hash. (Dica: isso é mais difícil e requer um pouco de teoria dos números.) \end{enumerate} \end{exc} \begin{exc} - Prove que o limitante $2/2^{#d#}$ em \lemref{universal-hashing} é o + Prove que o limitante $2/2^{#d#}$ no \lemref{universal-hashing} é o melhor limitante possível ao mostrar que, se $x=2^{#w#-#d#-2}$ e $#y#=3#x#$, então $\Pr\{#hash(x)#=#hash(y)#\}=2/2^{#d#}$. (Dica: verifique as representações binárias de - $#zx#$ e $#z#3#x#$ e o uso o fato de que + $#zx#$ e $#z#3#x#$ e use o fato de que $#z#3#x# = #z#x#+2#z#x#$.) \end{exc} \begin{exc}\exclabel{linear-probing} - Prove \lemref{linear-probing} usando a versão completa da aproximação de Stirling - dada em -\secref{factorials}. + Prove o \lemref{linear-probing} usando a versão completa da aproximação de Stirling dada na \secref{factorials}. \end{exc} \begin{exc} @@ -1111,7 +1083,7 @@ \section{Discussão e Exercícios} Suponha que você tem um objeto composto de dois inteiros de #w# bits, #x# e #y#. Suponha que o código hash para o seu objeto é definido por alguma função hash determinística $h(#x#,#y#)$ que produz um único inteiro de #w# bits. - Prove que existe um grande conjunto de objetos que tem o mesmo código hash. + Prove que existe um grande conjunto de objetos que têm o mesmo código hash. \end{exc} \begin{exc} @@ -1144,5 +1116,5 @@ \section{Discussão e Exercícios} fazê-los ao inspecionar o código da implementação, ou você pode ter que escrever algum código que fazer tentativas de inserções e buscas, medindo o tempo que leva para adicionar e achar valores. (Isso pode ser, tem sido, usado para efetuar ataques de negação de serviço em servidores web \cite{cw03}.) - \index{ataque de complexidade algoritmica}% + \index{ataque de complexidade algoritmico}% \end{exc} From 33b5ccf7f687fe87f0c250b6467c60668bebb2b7 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 10 Sep 2020 19:10:15 -0300 Subject: [PATCH 46/66] fixed minor typos and revised translation of linkedlists to portuguese --- latex/arrays.tex | 8 ++++---- latex/intro.tex | 29 ++++++++++++++--------------- latex/linkedlists.tex | 14 +++++++------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index cca794e6..2ebe4d3d 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -226,7 +226,7 @@ \subsection{Resumo} \item #get(i)# e #set(i,x)# em tempo $O(1)$ por operação; e \item #add(i,x)# e #remove(i)# em tempo $O(1+#n#-#i#)$ por operação. \end{itemize} - Além disso, ao comerçarmos com um + Além disso, ao começarmos com um #ArrayStack# vazio e realizarmos uma sequência de $m$ operações #add(i,x)# e #remove(i)# resulta em um total de $O(m)$ tempo gasto durante todas as chamadas a #resize()#. @@ -633,7 +633,7 @@ \subsection{Balanceamento} Iremos realizar a nossa análise usando uma técnica conhecida como o \emph{método do potencial}. \index{potencial}% - \index{método do potential}% + \index{método do potencial}% Definimos o \emph{potencial}, $\Phi$, do #DualArrayDeque# como a diferença dos tamanhos entre #front# e #back#: \[ \Phi = |#front.size()# - #back.size()#| \enspace . \] @@ -921,7 +921,7 @@ \subsection{Resumo} \subsection{Computando Raízes Quadradas} -\index{raizes quadradas}% +\index{raízes quadradas}% Um leitor que tenha tido alguma exposição a modelos de computação pode notar que a #RootishArrayStack#, conforme descrita anteriormente, não se encaixa no modelo de computação normal word-RAM @@ -1081,7 +1081,7 @@ \section{Discussão e Exercícios} #get(i,x)# e #set(i,x)# em tempo constante e #add(i,x)# e #remove(i)# em $O(\sqrt{#n#})$ de tempo. Esses tempos de execução são similares ao que pode ser conseguido com uma implementação mais cuidadosa de uma -#RootishArrayStack# discutida em \excref{rootisharraystack-fast}. +#RootishArrayStack# discutida no \excref{rootisharraystack-fast}. \javaonly{ \begin{exc} diff --git a/latex/intro.tex b/latex/intro.tex index 82d4461a..7ef8d0fd 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -76,7 +76,7 @@ \section{A Necessidade de Eficiência} Por exemplo, podemos manter um conjunto ordenado com um bilhão de itens e inspecionar no máximo 60 itens para qualquer operação. No nosso computador de um bilhão de instruções por segundo, essas operações levam $0.00000006$ segundos cada. -O resto deste capítulo revisa brevemente alguns dos principais conceitos usados ao longo do resto do livro. \secref{interfaces} descreve as interfaces implementadas por todas as estruturas de dados descritas neste livro e devem ser consideradas como leitura obrigatória. +O resto deste capítulo revisa brevemente alguns dos principais conceitos usados ao longo do resto do livro. A \secref{interfaces} descreve as interfaces implementadas por todas as estruturas de dados descritas neste livro e devem ser consideradas como leitura obrigatória. O restante das seções discute: @@ -105,7 +105,7 @@ \section{Interfaces} A \emph{implementação} de uma estrutura de dados, por outro lado, inclui a representação interna da estrutura de dados assim como as definições dos algoritmos que implementam as operações aceitas pela estrutura de dados. Então, pode haver muitas implementações de uma dada interface. -Por exemplo, em \chapref{arrays}, veremos implementações da interface #List# usando arrays e em \chapref{linkedlists} veremos implementações da interface #List# usando estruturas de dados baseadas em ponteiros. Ambas implementam a mesma interface, #List#, mas de modos distintos. +Por exemplo, no \chapref{arrays}, veremos implementações da interface #List# usando arrays e no \chapref{linkedlists} veremos implementações da interface #List# usando estruturas de dados baseadas em ponteiros. Ambas implementam a mesma interface, #List#, mas de modos distintos. \subsection{As Interfaces #Queue#, #Stack# e #Deque#} @@ -164,7 +164,7 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} \index{queue!LIFO}% \index{stack}% \index{pilha}% -, ilustrada em \figref{stack}. Em uma \emph{Queue LIFO}, +, ilustrada na \figref{stack}. Em uma \emph{Queue LIFO}, o elemento mais recentemente adicionado é o próximo a ser removido. Isso é melhor visualizado em termos de uma pilha de pratos; pratos são posicionados no topo da pilha a também removidos do topo. Essa estrutura @@ -196,7 +196,7 @@ \subsection{A Interface #List#: Sequências Lineares} as interfaces #Queue# FIFO, #Stack#, ou #Deque#. Isso porque essas interfaces são englobadas pela interface #List#. Uma #List#, \index{List@#List#}% -ilustrada em \figref{list}, representa uma +ilustrada na \figref{list}, representa uma sequência, $#x#_0,\ldots,#x#_{#n#-1}$, de valores. A interface #List# inclui as operações a seguir: \begin{enumerate} @@ -279,7 +279,7 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} \index{SSet@#SSet#}% A interface #SSet# representa um conjunto ordenado de elementos. -Um #SSet# guarda elementos provientes de alguma ordem total, tal que quaisquer dois elementos #x# e #y# podem ser comparados. Em código, isso será feito com um método chamado #compare(x,y)# no qual +Um #SSet# guarda elementos provenientes de alguma ordem total, tal que quaisquer dois elementos #x# e #y# podem ser comparados. Em código, isso será feito com um método chamado #compare(x,y)# no qual \[ #compare(x,y)# \begin{cases} @@ -305,7 +305,7 @@ \subsection{A Interface #SSet#: Sorted Sets---Conjuntos Ordenados} Ela difere de um modo fundamental de #USet.find(x)# pois retorna um resultado mesmo quando não há elemento igual a #x# no conjunto. A distinção entre a operação #find(x)# de #USet# e de #SSet# é muito importante e frequentemente esquecida ou não percebida. A funcionalidade extra provida por um #SSet# frequentemente vem com um preço que inclui tanto tempo de execução mais alto quanto uma complexidade de implementação maior. -Por exemplo, a maior parte das implementações #SSet# discutidas neste livro tem operações #find(x)# com tempo de execução que são logaritmicas no tamanho do conjunto. +Por exemplo, a maior parte das implementações #SSet# discutidas neste livro tem operações #find(x)# com tempo de execução que são logarítmicas no tamanho do conjunto. Por outro lado, a implementação de um #USet# com uma #ChainedHashTable# no \chapref{hashing} tem uma operação #find(x)# que roda em tempo esperado constante. Ao escolher quais dessas estruturas usar, deve-se sempre usar um #USet# a menos que a funcionalidade extra oferecida por um #SSet# seja verdadeiramente necessária. @@ -492,7 +492,7 @@ \subsection{Notação assintótica} Esses atalhos servem principalmente para evitar frases estranhas e tornar mais fácil o uso de notação assintótica em manipulações sequenciais de equações. -A exemplo particulamente estranho disso ocorre quando escrevemos afirmações tipo +A exemplo particularmente estranho disso ocorre quando escrevemos afirmações tipo \[ T(n) = 2\log n + O(1) \enspace . \] @@ -549,10 +549,10 @@ \subsection{Notação assintótica} e que não pode não haver um ganhador em todas as situações. Um algoritmo pode ser mais rápido em uma máquina enquanto o outro em uma máquina diferente. Porém, se os dois algoritmos têm tempos de execução big-Oh distintos, então teremos certeza que aquele com menor função Big-Oh será mais rápido \emph{para valores #n# grandes o suficiente}. -Um exemplo de como a notação big-Oh nos permite comparar duas diferentes funções é mostrado em \figref{intro-asymptotics}, que compara a taxa de crescimento +Um exemplo de como a notação big-Oh nos permite comparar duas diferentes funções é mostrado na \figref{intro-asymptotics}, que compara a taxa de crescimento de $f_1(#n#)=15#n#$ versus $f_2(n)=2#n#\log#n#$. Hipoteticamente, $f_1(n)$ seria o tempo de execução de um complicado algoritmo de tempo linear enquanto $f_2(n)$ é o tempo de execução de um algoritmo bem mais simples baseado no paradigma de divisão e conquista. -Isso exemplica essa situação, +Isso exemplifica essa situação, embora $f_1(#n#)$ seja maior que $f_2(n)$ para valores baixos de #n#, o oposto é verdade para valores mais altos de #n#. Eventualmente $f_1(#n#)$ ganha e por uma margem crescente. @@ -733,7 +733,7 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} \index{tempo de execução no pior caso}% Esse é o tipo mais forte de garantia de tempo de execução. Se uma operação de uma estrutura de dados tem tempo - de execução no pior-caso de + de execução no pior caso de $f(#n#)$, então uma dessas operações \emph{nunca} leva mais tempo que $f(#n#)$. @@ -775,7 +775,7 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} \paragraph{Pior caso versus custo esperado:} \index{custo esperado}% A seguir, considere o caso de um seguro de incêndio na nossa casa de \$120\,000. -Por analisar centenas de milhares de casos, companias de seguro têm determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de +Por analisar centenas de milhares de casos, seguradoras têm determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de \$10 por mês. Esse é uma valor bem baixo, pois a maior parte das casa nunca pegam fogo, algumas poucas tem incêndios pequenos que causam algum dano e um número minúsculo de casas queimam até as cinzas. Baseada nessa informação, a seguradora cobra \$15 mensais para a contratação de um seguro. @@ -863,7 +863,7 @@ \section{Trechos de Código} . Entretanto, para fazer o livro acessível para leitores não familiares com todas as construções e \emph{keywords} definidas pela linguagem \lang, os trechos de código foram simplificados. Por exemplo, um leitor não irá encontrar keywords como #public#, #protected#, #private# ou #static#. O leitor também não encontrará discussão aprofundada sobre hierarquia de classes. -Quais interfaces uma determinada classes implementa ou qual classes ela extende, se relevante para a discussão, deve estar claro do texto do contexto em questão. +Quais interfaces uma determinada classes implementa ou qual classes ela estende, se relevante para a discussão, deve estar claro do texto do contexto em questão. Essas convenções deve fazer os trechos de código mais fáceis de entender por qualquer um com background linguagens que variantes da linha ALGOL, incluindo B, C, C++, C\#, Objective-C, D, Java, JavaScript e assim por diante. Leitores em busca de detalhes das implementações são encorajados a olhar no código fonte da linguagem \lang\ que acompanha este livro. @@ -957,8 +957,7 @@ \section{Lista de Estruturas de Dados} \section{Discussão e Exercícios} As interfaces -#List#, #USet# e #SSet# descritas em -\secref{interfaces} são influenciadas pela Java Collections Framework +#List#, #USet# e #SSet# descritas na \secref{interfaces} são influenciadas pela Java Collections Framework \cite{oracle_collections}. \index{Java Collections Framework}% Elas são essencialmente versões simplificadas das interfaces @@ -1005,7 +1004,7 @@ \section{Discussão e Exercícios} \item Leia a entrada uma linha por vez e então imprima as linhas pares (começando com a primeira linha, linha 0) seguidas das linhas ímpares. -\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimí-las. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser impressa, mas em ordem aleatória. +\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimir. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser impressa, mas em ordem aleatória. \end{enumerate} \end{exc} diff --git a/latex/linkedlists.tex b/latex/linkedlists.tex index e36eb49c..5aa91441 100644 --- a/latex/linkedlists.tex +++ b/latex/linkedlists.tex @@ -59,7 +59,7 @@ \section{#SLList#: Uma Lista Simplesmente Ligada} \codeimport{ods/SLList.push(x)} A operação #pop()#, após verificar que a - #SLList# não estsá vazia, remove a cabeça fazendo + #SLList# não está vazia, remove a cabeça fazendo $#head=head.next#$ e também decrementando #n#. \codeimport{ods/SLList.pop()} @@ -256,7 +256,7 @@ \section{#SEList#: Uma Lista Ligada Eficiente no Espaço} \index{deque!bounded}% \index{deque!delimitado}% derivado da estrutura #ArrayDeque# -descrita em \secref{arraydeque}. A #BDeque# difere do #ArrayDeque# +descrita na \secref{arraydeque}. A #BDeque# difere do #ArrayDeque# em um detalhe: quando uma nova #BDeque# é criada, o tamanho do array de apoio #a# é fixo em #b+1# e nunca expande ou escolhe. A propriedade mais importante de um #BDeque# é que permite a adição ou remoção @@ -294,7 +294,7 @@ \subsection{Requisitos de Espaço} $O(#b#+#n#/#b#)$. Ao escolher um valor de #b# dentro de um fator constante de $\sqrt{#n#}$, fazemos o custo de espaço extra de uma SEList se aproximar -ao limitante inferior $\sqrt{#n#}$ descrito em \secref{rootishspaceusage}. +ao limitante inferior $\sqrt{#n#}$ descrito na \secref{rootishspaceusage}. \subsection{Procurando Elementos} @@ -366,7 +366,7 @@ \subsection{Adicionando um Elemento} $#u#_0,#u#_1,#u#_2,\ldots$ denotam #u#, #u.next#, #u.next.next# e assim por diante. Exploramos $#u#_0,#u#_1,#u#_2,\ldots$ procurando por um nodo que provê espaço para #x#. Três casos podem ocorrer durante -nossa exploração (ver \figref{selist-add}): +nossa exploração (ver a \figref{selist-add}): \begin{figure} \noindent @@ -639,8 +639,8 @@ \section{Discussão e Exercícios} \begin{exc} Escreva um método para a #DLList# chamado de #isPalindrome()# que retorna #true# se a lista for um - \emph{palíndrome}, - \index{palíndrome}% + \emph{palíndromo}, + \index{palíndromo}% isto é, o elemento na posição #i# é igual ao elemento na posição $#n#-i-1$ para todo $i\in\{0,\ldots,#n#-1\}$. O seu código deve rodar em $O(#n#)$ de tempo. @@ -764,7 +764,7 @@ \section{Discussão e Exercícios} Prove que, se uma #SEList# é usada como uma #Stack# (tal que as únicas modificações à #SEList# são feitas usando $#push(x)#\equiv - #add(size(),x)#$ and $#pop()#\equiv #remove(size()-1)#$), então essas + #add(size(),x)#$ e $#pop()#\equiv #remove(size()-1)#$), então essas operações rodam em tempo constante amortizado, independentemente do valor de #b#. \end{exc} From 651e1a29ca3c2c9868a6fcdcbba9ccdc1f3c6f79 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Thu, 10 Sep 2020 19:20:00 -0300 Subject: [PATCH 47/66] fixed minor typos in portuguese translation of binarytrees --- latex/binarytrees.tex | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/latex/binarytrees.tex b/latex/binarytrees.tex index 4821f4c6..b2d175ff 100644 --- a/latex/binarytrees.tex +++ b/latex/binarytrees.tex @@ -74,7 +74,7 @@ \chapter{Árvores Binárias} Um nodo #u# é uma \emph{folha} se não tem filhos. -Ás vezes pensamos de árvores sendo extendidas com \emph{nodos externos}. +Ás vezes pensamos de árvores sendo estendidas com \emph{nodos externos}. Qualquer nodo que não tem filho à esquerda tem um nodo externo como filho à esquerda e, de modo similar, um nodo que não tem filho à direita tem um nodo externo como filho à direita (ver \figref{binary-tree}.b). É fácil verificar, por indução, que uma árvore binária @@ -88,7 +88,7 @@ \section{#BinaryTree#: Uma Árvore Binária Básica} (no máximo três) vizinhos de #u#\notpcode{:}\pcodeonly{.} \javaimport{ods/BinaryTree.BTNode Date: Fri, 11 Sep 2020 14:38:22 -0300 Subject: [PATCH 48/66] revised translation of binarytrees to portugues --- latex/binarytrees.tex | 90 +++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/latex/binarytrees.tex b/latex/binarytrees.tex index b2d175ff..7821a20f 100644 --- a/latex/binarytrees.tex +++ b/latex/binarytrees.tex @@ -1,17 +1,17 @@ \chapter{Árvores Binárias} \chaplabel{binarytrees} -Este capítulo apresenta uma das estruturas mais fundamentais em ciência -da computação: árvores binárias. O uso da palavra +Este capítulo apresenta uma das estruturas mais fundamentais na Ciência +da Computação: as árvores binárias. O uso da palavra \emph{árvore} \index{árvore}% \index{árvore!binária}% \index{árvore binária}% -vem do fato que, quando as desenhamos, ela frequentemente lembra árvores +vem do fato que, quando as desenhamos, elas frequentemente lembram árvores encontradas em uma floresta. Existem muitos jeitos de definir árvores binárias. Matematicamente, uma \emph{árvore binária} é um grafo finito, conectado, não direcionado, sem ciclos e sem nenhum vértice de grau maior que três. -Para a maioria das aplicações em Ciência da Computação árvores binárias são \emph{enraizadas}: +Para a maioria das aplicações em Ciência da Computação, árvores binárias são \emph{enraizadas}: \index{árvore!enraizada}% \index{árvore enraizada}% Um nodo especial #r# de grau até dois é chamado de \emph{raiz} da árvore. @@ -34,7 +34,7 @@ \chapter{Árvores Binárias} Em desenhos, árvores binárias são tipicamente desenhadas de ponta-cabeça, com a raiz no topo do desenho e os filhos à esquerda e direita respectivamente dados pelas posições à esquerda e direita no desenho -(\figref{bintree-orientation}). Por exemplo, \figref{binary-tree}.a mostra +(\figref{bintree-orientation}). Por exemplo, a \figref{binary-tree}.a mostra uma árvore binária com nove nodos. \begin{figure} @@ -54,7 +54,7 @@ \chapter{Árvores Binárias} (a) & (b) \end{tabular} \end{center} - \caption{Uma árvore binária com (a)~nove nodos reais e (b)~dez nodos externos.} + \caption{Uma árvore binária com (a)~nove nodos reais (ou internos) e (b)~dez nodos externos.} \figlabel{binary-tree} \end{figure} @@ -71,13 +71,12 @@ \chapter{Árvores Binárias} longo de #u# e um de seus descendentes. A \emph{altura} de \index{altura!de uma árvore}% uma árvore é a altura de sua raiz. -Um nodo #u# é uma \emph{folha} -se não tem filhos. +Um nodo #u# é uma \emph{folha} se não tem filhos. -Ás vezes pensamos de árvores sendo estendidas com \emph{nodos externos}. +Ás vezes consideramos que árvores possuem \emph{nodos externos}. Qualquer nodo que não tem filho à esquerda tem um nodo externo como filho à -esquerda e, de modo similar, um nodo que não tem filho à direita tem um nodo externo como filho à direita (ver -\figref{binary-tree}.b). É fácil verificar, por indução, que uma árvore binária +esquerda e, de modo similar, um nodo que não tem filho à direita tem um nodo externo como filho à direita (ver a \figref{binary-tree}.b). +É fácil verificar, por indução, que uma árvore binária com $#n#\ge 1$ nodos reais tem $#n#+1$ nodos externos. \section{#BinaryTree#: Uma Árvore Binária Básica} @@ -104,10 +103,10 @@ \section{#BinaryTree#: Uma Árvore Binária Básica} \subsection{Algoritmos recursivos} \index{algoritmo recursivo}% -Usando algoritmos recursivos facilita a computação de fatos sobre árvores -binárias. Por exemplo, para computar o tamanho de (número de nodos em) -uma árvore binária enraizada no nodo #u#, nós recursivamente computamos -os tamanhos de duas subárvores enraizadas aos filhos de #u#, somar esses tamanhos, e adicionar um: +Usar algoritmos recursivos facilita a computação de fatos sobre árvores +binárias. Por exemplo, para computar o tamanho de (número de nodos) +uma árvore binária enraizada no nodo #u#, recursivamente computamos +os tamanhos de duas subárvores enraizadas aos filhos de #u#, somar esses tamanhos, e somar um: \codeimport{ods/BinaryTree.size(u)} @@ -127,22 +126,22 @@ \subsection{Percorrendo Árvores Binárias} da árvore binária na mesma ordem que o código a seguir: \codeimport{ods/BinaryTree.traverse(u)} -Usando recursão desse jeito resulta em um código bem curto e simples mas +Usar recursão desse jeito resulta em um código bem curto e simples mas pode também ser problemático. A profundidade máxima da recursão é dada pela profundidade máxima de um nodo na árvore binária, i.e., a altura da árvore. Se a altura da árvore é grande, então essa recursão pode muito bem usar -mais espaço de pilha de memória que está disponível, causando um erro. +mais espaço de pilha de memória do que está disponível, causando um erro. Para percorrer uma árvore binária sem recursão, você pode usar um algoritmo que usa a informação de onde veio para determinar para onde irá a seguir. -Veja \figref{bintree-traverse}. +Veja a \figref{bintree-traverse}. Ao chegarmos no nodo #u# a partir de #u.parent# então o próximo passo -a tomar é visitar #u.left#. Se chegarmos em #u# vindos de #u.right#, então +é visitar #u.left#. Se chegarmos em #u# vindo de #u.right#, então terminamos a visita da subárvore de #u# e retornamos a #u.parent#. O código a seguir implementa essa ideia, incluindo o tratamento dos -casos onde #u.left#, #u.right# ou #u.parent# seja #nil#: +casos onde #u.left#, #u.right# ou #u.parent# sejam #nil#: \codeimport{ods/BinaryTree.traverse2()} \begin{figure} @@ -152,11 +151,11 @@ \subsection{Percorrendo Árvores Binárias} \includegraphics[scale=0.90909]{figs/bintree-3} \end{tabular} \end{center} - \caption[Percorrendo uma BinaryTree]{Os três casos que ocorrem no nodo #u# ao percorrer uma árvore binária não recursiva, o resultante caminho na árvore.} + \caption[Percorrendo uma BinaryTree]{Os três casos que ocorrem no nodo #u# ao percorrer uma árvore binária não recursiva e o caminho resultante na árvore.} \figlabel{bintree-traverse} \end{figure} -Os mesmo fatos que podem ser computados com algoritmos recursivos também podem +Os mesmos fatos que podem ser computados com algoritmos recursivos também podem ser obtidos dessa maneira, sem recursão. Por exemplo, para computar o tamanho de uma árvore mantemos um contador #n# e o incrementamos toda vez que visitarmos um nodo pela primeira vez: \codeimport{ods/BinaryTree.size2()} @@ -291,7 +290,7 @@ \subsection{Remoção} Melhor ainda, se #u# tiver um único filho, então podemos destacar #u# da árvore fazendo -#u.parent# adotar o filho de #u# (veja +#u.parent# adotar o filho de #u# (veja a \figref{bst-splice}): \codeimport{ods/BinarySearchTree.splice(u)} @@ -303,14 +302,14 @@ \subsection{Remoção} \figlabel{bst-splice} \end{figure} -A situação complicada quando #u# tem dois filhos. Nesse caso, +A situação complica quando #u# tem dois filhos. Nesse caso, o mais simples a fazer é achar um nodo #w#, que seja menor que -os dois filho e tal que #w.x# possa substituir #u.x#. +os dois filhos e tal que #w.x# possa substituir #u.x#. Para manter a propriedade da árvore de busca binária, o valor #w.x# deveria ser próximo ao valor de #u.x#. Por exemplo, escolher um #w# tal que #w.x# é o menor valor maior que #u.x# funcionaria. Achar o nodo #w# é fácil; é o menor valor na subárvore enraizada em #u.right#. Esse nodo pode ser facilmente removido porque não tem filho à esquerda -(veja \figref{bst-remove}). +(veja a \figref{bst-remove}). \javaimport{ods/BinarySearchTree.remove(u)} \cppimport{ods/BinarySearchTree.remove(u)} \pcodeimport{ods/BinarySearchTree.remove_node(u)} @@ -328,7 +327,7 @@ \subsection{Remoção} \subsection{Resumo} -A operações +As operações #find(x)#, #add(x)# e #remove(x)# em uma #BinarySearchTree# envolvem seguir um caminho da raiz da árvore a algum nodo. Sem saber mais sobre o formato da árvore é difícil dizer algo sobre o comprimento desse caminho, exceto que é menor que #n#, o número de nodos na árvore. @@ -337,26 +336,25 @@ \subsection{Resumo} \begin{thm}\thmlabel{bst} #BinarySearchTree# implementa a interface #SSet# interface e aceita - as operações - #add(x)#, #remove(x)# + as operações #add(x)#, #remove(x)# e #find(x)# em $O(#n#)$ de tempo por operação. \end{thm} -\thmref{bst} tem desempenho ruim em comparação a \thmref{skiplist}, que mostra que +O \thmref{bst} demonstra um desempenho ruim em comparação ao \thmref{skiplist}, que mostra que uma estrutura #SkiplistSSet# implementa a interface #SSet# com $O(\log #n#)$ de tempo esperado por operação. O problema com a estrutura -#BinarySearchTree# é que pode ser tornar \emph{desbalanceada}. -Em vez de parecer como uma árvore em - \figref{bst}, ela pode parecer como uma longa cadeias de +#BinarySearchTree# é que pode se tornar \emph{desbalanceada}. +Em vez de parecer como uma árvore na \figref{bst}, ela pode parecer +como uma longa cadeia de #n# nodos, todos exceto o último com exatamente um filho. -Existe várias maneiras de evitar árvores binárias desbalanceadas, todas elas +Existem várias maneiras de evitar árvores binárias desbalanceadas, todas elas levam a estruturas de dados que tem - operações que executam com $O(\log -#n#)$ de tempo. No \chapref{rbs} mostramos como operações com $O(\log #n#)$ de tempo + operações que executam com $O(\log #n#)$ de tempo. +No \chapref{rbs} mostramos como operações com $O(\log #n#)$ de tempo \emph{esperado} podem ser obtidas com randomização. No \chapref{scapegoat} mostramos como operações que executam em $O(\log #n#)$ de tempo \emph{amortizado} podem ser obtidas com operações de reconstrução parcial. -No \chapref{redblack} mostramos como operações que executam em $O(\log #n#)$ de tempo no \emph{pior caso} pode ser obtidas ao simular uma árvore que não é binária: uma árvore cujos nodos podem ter até quatro filhos. +No \chapref{redblack} mostramos como operações que executam em $O(\log #n#)$ de tempo no \emph{pior caso} podem ser obtidas ao simular uma árvore que não é binária: uma árvore cujos nodos podem ter até quatro filhos. \section{Discussão e Exercícios} @@ -369,14 +367,15 @@ \section{Discussão e Exercícios} Em séculos mais recentes, árvores binárias também tem sido usadas para modelar espécies de árvores \index{espécies de árvores}% -em biologia, onde as folhas da árvore representam espécie existente e nodos internos +em biologia, onde as folhas da árvore representam uma espécie existente e nodos internos de uma árvore representam \emph{eventos de especiação} -\index{evento de especiação} no qual duas populações de uma única espécie evoluem em duas espécies separadas. +\index{evento de especiação} no qual duas populações de uma única espécie evoluem em duas espécies distintas. Árvores binárias de busca parecem terem sido descobertas independentemente por vários grupos na década de 1950 -\cite[Section~6.2.2]{k97v3}. Referências adicionais a tipos específicos de árvores binárias de busca são fornecidas nos capítulos a seguir. +\cite[Section~6.2.2]{k97v3}. Referências adicionais a tipos específicos +de árvores binárias de busca são fornecidas nos capítulos a seguir. Ao implementar uma árvore binária desde o início, várias decisões de projeto devem ser feitas. Uma delas é se nodos devem guardar um ponteiro para o seu pai. @@ -385,7 +384,8 @@ \section{Discussão e Exercícios} Alguns outros métodos (como inserir ou remover em alguns tipos de árvores binárias de busca balanceadas) também podem ficar mais complicados sem o uso de ponteiros para nodo-pai. -Outra decisão de projeto refere-se a como guardar os ponteiros pai, filho à esquerda, filho à direita em um nodo. Na implementação dada aqui, esses +Outra decisão de projeto refere-se a como guardar os ponteiros pai, filho à esquerda e +filho à direita em um nodo. Na implementação dada aqui, esses ponteiros são guardados como variáveis separadas. Outra opção é guardá-los em um array #p# de tamanho 3, tal que #u.p[0]# é o filho à esquerda de #u#, @@ -426,8 +426,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Escreva um método não recursivo - #height2(u)# que computa a altura do nodo + Escreva um método não recursivo #height2(u)# que computa a altura do nodo #u# em uma #BinaryTree#. \end{exc} @@ -482,7 +481,7 @@ \section{Discussão e Exercícios} Crie uma subclasse de #BinaryTree# cujos nodos tem campos para guardar numerações pré/in/pós-ordem. Escreva métodos recursivos - #preOrderNumber()#, #inOrderNumber()# e #postOrderNumbers()# que atribue + #preOrderNumber()#, #inOrderNumber()# e #postOrderNumbers()# que atribui esses números corretamente. Cada um desses métodos deve rodar em $O(#n#)$ de tempo. \end{exc} @@ -525,8 +524,7 @@ \section{Discussão e Exercícios} Implemente um método na #BinarySearchTree# chamado de #getLE(x)# que retorna uma lista de todos os itens na árvore que são menores que ou iguais a #x#. O tempo de execução do seu método deve ser - $O(#n#'+#h#)$ - onde $#n#'$ é o número de itens menores que ou iguais a #x# e #h# + $O(#n#'+#h#)$ onde $#n#'$ é o número de itens menores que ou iguais a #x# e #h# é a altura da árvore. \end{exc} From 9a46d9af391848ab4f23bb864ee5f5ac9374a6a3 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 11 Sep 2020 17:04:44 -0300 Subject: [PATCH 49/66] finished revision of portuguese translation for rbs --- latex/rbs.tex | 274 ++++++++++++++++++++++++++------------------------ 1 file changed, 145 insertions(+), 129 deletions(-) diff --git a/latex/rbs.tex b/latex/rbs.tex index 278edfef..3eb0d366 100644 --- a/latex/rbs.tex +++ b/latex/rbs.tex @@ -8,8 +8,8 @@ \chapter{Árvores Binárias de Busca Aleatórias} \section{Árvores Binárias de Busca Aleatórias} \seclabel{rbst} -Considere as duas árvores binárias de busca mostradas em \figref{rbs-lvc}, cada qual com -$#n#=15$ nodos. A árvore na esquerda é uma lista e a outra é uma árvore binária de busca perfeitament balanceada. A altura da árvore na esquerda é +Considere as duas árvores binárias de busca mostradas na \figref{rbs-lvc}, cada uma com +$#n#=15$ nodos. A árvore na esquerda é uma lista e a outra é uma árvore binária de busca perfeitamente balanceada. A altura da árvore na esquerda é $#n#-1=14$ e da direita é três. \begin{figure} @@ -23,9 +23,9 @@ \section{Árvores Binárias de Busca Aleatórias} \figlabel{rbs-lvc} \end{figure} -Imagine como essas duas árvores podem ter sido construídas. Aquela na esquerda ocorre se iniciamos com uma -#BinarySearchTree# vazia e adicionando a -sequência +Imagine como essas duas árvores podem ter sido construídas. +Aquela na esquerda ocorre se iniciamos com uma #BinarySearchTree# +vazia e adicionando a sequência \[ \langle 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14 \rangle \enspace . \] @@ -43,13 +43,13 @@ \section{Árvores Binárias de Busca Aleatórias} \langle 7,3,1,11,5,0,2,4,6,9,13,8,10,12,14 \rangle \enspace . \] De fato, há - $21,964,800$ sequências que gerariam a árvore na direita e somente uma gera a àrvore na esquerda. + $21,964,800$ sequências que gerariam a árvore na direita e somente uma gera a árvore na esquerda. O exemplo acima passa uma ideia de que, se escolhermos uma permutação - aleatória de -$0,\ldots,14$, e a adicionarmos em uma árvore binária de busca, então teremos uma chance maior de obter uma árvore muito balanceada (o lado direito de -\figref{rbs-lvc}) que temos de obter uma árvore muito desbalanceada -(o lado esquerdo de \figref{rbs-lvc}). + aleatória de $0,\ldots,14$, e a adicionarmos em uma árvore binária de + busca, então teremos uma chance maior de obter uma árvore muito + balanceada (o lado direito da \figref{rbs-lvc}) que temos de obter uma + árvore muito desbalanceada (o lado esquerdo da \figref{rbs-lvc}). Podemos formalizar essa noção ao estudar árvores binárias de busca aleatórias. Uma \emph{árvore binária de busca aleatória} @@ -62,19 +62,19 @@ \section{Árvores Binárias de Busca Aleatórias} \index{permutação!aleatória}% \index{permutação aleatória}% que cada uma das possíveis $#n#!$ permutações (reordenações) de $0,\ldots,#n#-1$ -é igualmente provável, tal que a probabilidade de obter qualqueer permutação em particular é +é igualmente provável, tal que a probabilidade de obter uma permutação em particular é $1/#n#!$. -Note que os valore $0,\ldots,#n#-1$ poderiam ser substituídos por qualquer conjunto ordenado de #n# elmento sem alterar a propriedade das árvores binárias de busca aleatória. -O elemento - $#x#\in\{0,\ldots,#n#-1\}$ está simplesmente representando o elemento de ranking (posição) #x# em um conjunto ordenado de tamanho #n#. +Note que os valores $0,\ldots,#n#-1$ poderiam ser substituídos por qualquer conjunto ordenado de #n# elementos sem alterar a propriedade das árvores binárias de busca aleatória. +O elemento $#x#\in\{0,\ldots,#n#-1\}$ está simplesmente +representando o elemento de ranking (posição) #x# em um conjunto +ordenado de tamanho #n#. -Antes de apresentarmos nosso principal resultado sobre árvores binárias de busca aleatórias, precisamos fazer uma curta discussão sobre um tipo de número que +Antes de apresentarmos o nosso principal resultado sobre árvores binárias de busca aleatórias, precisamos fazer uma curta discussão sobre um tipo de número que aparece frequentemente ao estudar estruturas randomizadas. Para um inteiro não-negativo $k$, o $k$-ésimo \emph{número harmônico}, \index{número harmônico}% -\index{H@$H_k$ (númro harmônico)}% -denotado por -$H_k$, é definido como +\index{H@$H_k$ (número harmônico)}% +denotado por $H_k$, é definido como \[ H_k = 1 + 1/2 + 1/3 + \cdots + 1/k \enspace . \] @@ -89,7 +89,7 @@ \section{Árvores Binárias de Busca Aleatórias} pois a integral $\hint = \ln k$. Tendo em mente que uma integral pode ser interpretada como a área entre uma curva e o eixo $x$, o valor de $H_k$ pode ser limitado inferiormente pela integral $\hint$ e limitado superiormente por -$1+ \hint$. (Veja \figref{harmonic-integral} para uma explicação gráfica.) +$1+ \hint$. (Veja a \figref{harmonic-integral} para uma explicação gráfica.) \begin{figure} \begin{center} @@ -110,7 +110,7 @@ \section{Árvores Binárias de Busca Aleatórias} \item Para qualquer $#x#\in\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho de busca por #x# é $H_{#x#+1} + H_{#n#-#x#} - O(1)$.\footnote{As expressões $#x#+1$ e $#n#-#x#$ podem ser interpretadas respectivamente - como o número de elementos na árvore menos que ou igual a #x# + como o número de elementos na árvore menor que ou igual a #x# e o número de elementos na árvore maiores que ou iguais a #x#.} \item Para qualquer $#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho de busca para #x# é @@ -119,18 +119,18 @@ \section{Árvores Binárias de Busca Aleatórias} \end{enumerate} \end{lem} -Provaremos -\lemref{rbs} na seção a seguire. Por enquanto, considere -o que as duas partes de - \lemref{rbs} nos diz. A primeira parte nos diz que se buscarmos por um elemento em uma árvore de tamanho #n#, então o comprimento esperado do caminho de busca é no máximo $2\ln n + O(1)$. +Provaremos o \lemref{rbs} na seção a seguir. Por enquanto, considere +o que as duas partes do \lemref{rbs} nos diz. A primeira parte nos +diz que se buscarmos por um elemento em uma árvore de tamanho #n#, +então o comprimento esperado do caminho de busca é no máximo $2\ln n + O(1)$. A segunda parte no diz sobre a busca de um valor não guardado na árvore. Ao compararmos duas partes do lema, vemos que buscar algo que está na -árvore é apenas pouco mais rápido do que algo que não está. +árvore é apenas um pouco mais rápido do que algo que não está. -\subsection{Prova de \lemref{rbs}} +\subsection{Prova do \lemref{rbs}} -A observação chave necessária para provar +A observação chave necessária para provar o \lemref{rbs} é a seguinte: o caminho de busca por um valor #x# no intervalo aberto $(-1,#n#)$ em uma árvore binária de busca aleatória @@ -140,14 +140,15 @@ \subsection{Prova de \lemref{rbs}} aparece antes de qualquer $\{i+1,i+2,\ldots,\lfloor#x#\rfloor\}$. Para ver isso, -To see this, observe \figref{rbst-records} e note que até que algum valor em -$\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ seja adicionado, os caminhos de busca para cada valor no intervalo aberto +observe a \figref{rbst-records} e note que até que algum valor em +$\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ seja adicionado, os caminhos de busca +para cada valor no intervalo aberto $(i-1,\lfloor#x#\rfloor+1)$ são idênticos. (Lembre-se que para dois valores terem caminhos distintos, precisa haver algum elemento na árvore que compara de modo diferente entre eles.) Seja $j$ o primeiro elemento em $\{i,i+1,\ldots,\lfloor#x#\rfloor\}$ para aparecer na permutação aleatória. -Note que $j$ é agora e sempre estará no caminho de busca por #x#. +Note que $j$ está e sempre estará no caminho de busca por #x#. Se $j\neq i$ então o nodo $#u#_j$ contendo $j$ é criado antes do nodo $#u#_i$ que contém $i$. Depois, quando $i$ for adicionado, ele será adicionado na subárvore enraizada em $#u#_j#.left#$, pois $i#x#$, $i$ aparece no caminho de busca para #x# se e somente se - $i$ aparece antes de $\{\lceil#x#\rceil, + $i$ aparecer antes de $\{\lceil#x#\rceil, \lceil#x#\rceil+1,\ldots,i-1\}$ na permutação aleatória usada para criar $T$. Note que, se iniciarmos com uma permutação aleatória de @@ -189,19 +190,19 @@ \subsection{Prova de \lemref{rbs}} \end{array}\right . \enspace . \] -A partir dessa observação, a prova de - \lemref{rbs} +A partir dessa observação, a prova do \lemref{rbs} envolve alguns cálculos simples com números harmônicos: -\begin{proof}[Prova de \lemref{rbs}] +\begin{proof}[Prova do \lemref{rbs}] Seja $I_i$ seja uma variável indicadora aleatória que é igual a um quando $i$ -aparece no caminho de busca por #x# e zero caso contrário. Então, o comprimento +aparecer no caminho de busca por #x# e zero caso contrário. Então, o comprimento do caminho de busca é dado por \[ \sum_{i\in\{0,\ldots,#n#-1\}\setminus\{#x#\}} I_i \] - então, se $#x#\in\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho de busca é dado por (veja \figref{rbst-probs}.a) + então, se $#x#\in\{0,\ldots,#n#-1\}$, o comprimento esperado do caminho + de busca é dado por (veja a \figref{rbst-probs}.a) \begin{align*} \E\left[\sum_{i=0}^{#x#-1} I_i + \sum_{i=#x#+1}^{#n#-1} I_i\right] & = \sum_{i=0}^{#x#-1} \E\left[I_i\right] @@ -215,7 +216,7 @@ \subsection{Prova de \lemref{rbs}} & = H_{#x#+1} + H_{#n#-#x#} - 2 \enspace . \end{align*} Os cálculos correspondentes para um valor buscado -$#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$ são quase idênticos (veja +$#x#\in(-1,n)\setminus\{0,\ldots,#n#-1\}$ são quase idênticos (veja a \figref{rbst-probs}.b). \end{proof} @@ -226,27 +227,25 @@ \subsection{Prova de \lemref{rbs}} \includegraphics[width=\ScaleIfNeeded]{figs/rbst-probs-b} \\ (b) \\[2ex] \end{tabular} \end{center} - \caption[As probabilidade de um elemento estar em um caminho de busca]{As probabilidade de um elemento estar no caminho de busca por #x# quando + \caption[As probabilidades de um elemento estar em um caminho de busca]{As probabilidades de um elemento estar no caminho de busca por #x# quando (a)~#x# é um inteiro e (b)~quando #x# não é um inteiro. } \figlabel{rbst-probs} \end{figure} \subsection{Resumo} -O teorema a seguir resumo o desempenho de uma árvore binária de busca aleatória: +O teorema a seguir resume o desempenho de uma árvore binária de busca aleatória: \begin{thm}\thmlabel{rbs} Uma árvore binária de busca aleatória pode ser construída em $O(#n#\log #n#)$ de tempo. Em uma árvore binária de busca aleatória, a operação -#find(x)# leva $O(\log -#n#)$ de tempo esperado. +#find(x)# leva $O(\log #n#)$ de tempo esperado. \end{thm} -Devemos enfatizar novamente que o valor esperado em - \thmref{rbs} é em respeito à permutação aleatória usada em respeito - à permutação aleatória usada para criar a árvore binária de busca aleatória. -Em particular, não depende em uma escolha aleatória de #x#; isso é verdade para +Devemos enfatizar novamente que o valor esperado no \thmref{rbs} é em respeito à permutação aleatória usada +para criar a árvore binária de busca aleatória. +Em especial, não depende de uma escolha aleatória de #x#; isso é verdade para todo valor de #x#. \section{#Treap#: Uma Árvore Binária de Busca Aleatória} @@ -254,11 +253,12 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \index{Treap@#Treap#}% O problema com uma árvore binária de busca aleatória é, claramente, que -elas não são dinâmica. Elas não aceitam as operações +ela não é dinâmica. Ela não aceita as operações #add(x)# ou #remove(x)# necessárias para implementar a interface #SSet#. Nesta seção descrevemos uma estrutura de dados chamada de #Treap# que usa o \lemref{rbs} para implementar -a interface #SSet#.\footnote{O nome #Treap# vem do fato que essa estrutura de dados é simultâneamente uma árvore binária de busca +a interface #SSet#.\footnote{O nome #Treap# vem do fato que essa estrutura +de dados é simultâneamente uma árvore binária de busca (do inglês, binary search \textbf{tr}ee) (\secref{binarysearchtree}) e uma h\textbf{eap} (\chapref{heaps}).} @@ -273,7 +273,7 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \index{propriedade raiz}% \end{itemize} Em outras palavras, cada nodo tem uma prioridade menor que aquelas de seus filhos. -Um exemplo é mostrado em \figref{treap}. +Um exemplo é mostrado na \figref{treap}. \begin{figure} \begin{center} @@ -291,12 +291,12 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} Um fato importante sobre os valores de prioridades em uma #Treap# é que ele são únicos e atribuídos aleatoriamente. -Por causa disso, existem duas formas equivalentes que podem pensar sobre uma #Treap#. Conforme definido acima, uma #Treap# obedece as propriedades das árvores binárias de buca e das heaps. +Por causa disso, existem duas formas equivalentes que podem pensar sobre uma #Treap#. Conforme definido acima, uma #Treap# obedece as propriedades das árvores binárias de busca e das heaps. Alternativamente, -podemos que uma #Treap# como uma +podemos usar uma #Treap# como uma #BinarySearchTree# cujos nodos são adicionados em ordem crescente de prioridade. Por exemplo, a #Treap# -em \figref{treap} pode ser obtida ao adicionar a sequência $(#x#,#p#)$ +na \figref{treap} pode ser obtida ao adicionar a sequência $(#x#,#p#)$ \[ \langle (3,1), (1,6), (0,9), (5,11), (4,14), (9,17), (7,22), (6,42), (8,49), (2,99) @@ -308,14 +308,14 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \[ \langle 3, 1, 0, 5, 9, 4, 7, 6, 8, 2 \rangle \] ----e inserí-las à uma #BinarySearchTree#. +---e inseri-las à uma #BinarySearchTree#. Mas isso significa que a forma de uma treap é idêntica àquela de uma árvore binária de busca binária. Em particular, se substituímos cad chave #x# por seu rank, \footnote{O rank de um elemento #x# em um conjunto $S$ de elementos é o número de elementos -em $S$ que são menores que #x#.} então \lemref{rbs} aplica-se. +em $S$ que são menores que #x#.} então o \lemref{rbs} aplica-se. -Reafirmando +Reafirmando o \lemref{rbs} em termos de #Treap#s, temos: \begin{lem}\lemlabel{rbs-treap} Em uma #Treap# que guarda um conjunto $S$ de #n# chaves, as seguintes afirmações valem: @@ -326,14 +326,14 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \end{enumerate} Aqui, $r(#x#)$ denota o rank #x# no conjunto $S\cup\{#x#\}$. \end{lem} -Novamente, enfatizamos que o valor esperado em -\lemref{rbs-treap} é obtido sobre escolhas aleatórias das prioridade para cada nodo. Isso não requer quaisquer premissas sobre a aleatoriedade nas chaves. +Novamente, enfatizamos que o valor esperado no \lemref{rbs-treap} é obtido sobre escolhas aleatórias das prioridades para cada nodo. Isso não requer quaisquer premissas sobre a aleatoriedade nas chaves. -\lemref{rbs-treap} nos diz que #Treap#s podem implementar a operação #find(x)# +O \lemref{rbs-treap} nos diz que #Treap#s podem implementar a operação #find(x)# eficientemente. Contudo, o benefício real de uma #Treap# é que ela pode implementar as operações - #add(x)# e #delete(x)#. Para fazer isso, ela precisa realizar rotações para manter a propriedade de heaps. -Veja \figref{rotations}. + #add(x)# e #delete(x)#. Para fazer isso, ela precisa realizar rotações + para manter a propriedade das heaps. +Veja a \figref{rotations}. Uma \emph{rotação} \index{rotação}% em uma árvore binária de busca é uma modificação local que pega um pai #u# @@ -353,18 +353,17 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} O código que implementa isso tem que lidar com essas duas possibilidades e ser cuidadoso com um caso especial (quando #u# for a raiz), então o código real é um pouco mais longo que - \figref{rotations} daria a entender: + a \figref{rotations} daria a entender: \codeimport{ods/BinarySearchTree.rotateLeft(u).rotateRight(u)} \label{page:rotations} -Em termos da estrutura de dados #Treap#, a propriedade mais importante de uma rotação é que a profundidade de #w# é reduzido em 1 enquanto a profundidade de +Em termos da estrutura de dados #Treap#, a propriedade mais importante de uma rotação é que a profundidade de #w# é reduzida em 1 enquanto a profundidade de #w# aumenta em 1. Usando rotações, podemos implementar a operação #add(x)# da forma a seguir: criamos um nodo #u#, atribuímos #u.x = x# e pegamos um valor aleatório para #u.p#. Depois adicionamos #u# usando o algoritmo #add(x)# usual para -uma -#BinarySearchTree#, então #u# é agora uma folha da #Treap#. +uma #BinarySearchTree#, então #u# é agora uma folha da #Treap#. Nesse ponto, nossa #Treap# satisfaz a propriedade das árvores binárias de busca, mas não necessariamente a propriedade das heaps. @@ -378,7 +377,7 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} $#u.parent.p# < #u.p#$. \codeimport{ods/Treap.add(x).bubbleUp(u)} Um exemplo de uma operação -#add(x)# é mostrado em \figref{treap-add}. +#add(x)# é mostrado na \figref{treap-add}. \begin{figure} \begin{center} @@ -386,32 +385,32 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \includegraphics[width=\ScaleIfNeeded]{figs/treap-insert-b} \\ \includegraphics[width=\ScaleIfNeeded]{figs/treap-insert-c} \\ \end{center} - \caption[Adiciionando a uma Treap]{Adicionando o valor 1.5 na #Treap# em \figref{treap}.} + \caption[Adicionando a uma Treap]{Adicionando o valor 1.5 na #Treap# na \figref{treap}.} \figlabel{treap-add} \end{figure} O tempo de execução da operação #add(x)# é dado pelo tempo que leva para seguir o caminho de busca para #x# mais o número de rotações realizadas para mover o novo nodo #u# subindo a árvore até sua posição correta na #Treap#. -De acordo com \lemref{rbs-treap}, o comprimento esperado do caminho de busca é no máximo $2\ln #n#+O(1)$. Além disso, cada rotação reduz a profundidade de #u#. -Isso para se #u# se torna raiz, então o número esperado de rotações não pode exceder o comprimento esperado do caminho de busca. Portanto o tempo esperado de execução da operação #add(x)# em uma #Treap# é $O(\log #n#)$. (\excref{treap-rotates} pede que se mostre que o número esperado de rotações realizadas durante uma adição é na verdade somente $O(1)$.) +De acordo com o \lemref{rbs-treap}, o comprimento esperado do caminho de busca é no máximo $2\ln #n#+O(1)$. Além disso, cada rotação reduz a profundidade de #u#. +Isso para se #u# se torna raiz, então o número esperado de rotações não pode exceder o comprimento esperado do caminho de busca. Portanto o tempo esperado de execução da operação #add(x)# em uma #Treap# é $O(\log #n#)$. (o \excref{treap-rotates} pede que se mostre que o número esperado de rotações realizadas durante uma adição é na verdade somente $O(1)$.) A operação #remove(x)# em uma #Treap# é o oposto da operação #add(x)#. Buscamos pelo nodo #u#, contendo #x#, então realizamos rotações para mover #u# abaixo na árvore até que se torne uma folha e então removemos #u# da #Treap#. Note que, para mover #u# árvore abaixo, podemos fazer -uma rotação à esquerda ou direita em #u#, que subtituir #u# com #u.right# +uma rotação à esquerda ou direita em #u#, que substitui #u# com #u.right# ou #u.left#, respectivamente. A escolha é feita de acordo com a primeira das seguintes regras que for adequada: \begin{enumerate} \item Se #u.left# e #u.right# forem ambos #null#, então #u# é uma folha e nenhuma rotação é realizada. -\item Se #u.left# (ou #u.right#) for #null#, então realizar uma rotação à direita (ou à esquerda, respectivamente) em #u#. -\item Se $#u.left.p# < #u.right.p#$ (ou $#u.left.p# > #u.right.p#)$, então realizar uma rotação à direita (ou rotação à esquerda, respectivamente) em #u#. +\item Se #u.left# (ou #u.right#) for #null#, então realizamos uma rotação à direita (ou à esquerda, respectivamente) em #u#. +\item Se $#u.left.p# < #u.right.p#$ (ou $#u.left.p# > #u.right.p#)$, então realizamos uma rotação à direita (ou rotação à esquerda, respectivamente) em #u#. \end{enumerate} Essas três regras asseguram que #Treap# não se torne desconectada e que a propriedade das heaps é restabelecida uma vez que #u# seja removida. \codeimport{ods/Treap.remove(x).trickleDown(u)} -Um exemplo da operação #remove(x)# é mostrado em \figref{treap-remove}. +Um exemplo da operação #remove(x)# é mostrado na \figref{treap-remove}. \begin{figure} \begin{center} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-a} \\ @@ -419,7 +418,7 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-c} \\ \includegraphics[height=\QuarterHeightScaleIfNeeded]{figs/treap-delete-d} \end{center} - \caption[Removendo de uma treap]{Removendo o valor 9 da #Treap# em \figref{treap}.} + \caption[Removendo de uma treap]{Removendo o valor 9 da #Treap# na \figref{treap}.} \figlabel{treap-remove} \end{figure} @@ -427,11 +426,11 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} #remove(x)# é notar que essa operação inverte a operação #add(x)#. Em particular, se fossemos reinserir #x# usando a mesmo prioridade #u.p#, então a operação #add(x)# faria exatamente o mesmo número de rotações -e restabeceria a #Treap# para exatamente o mesmo estado que estava antes +e reestabeleceria a #Treap# para exatamente o mesmo estado que estava antes que a operação #remove(x)# ocorreu. -(Lendo de cima para baixo, \figref{treap-remove} ilustra a adição do valor $9$ em uma #Treap#.) -Isso significa que o tempo de execução esperado de #remove(x)# em uma #Treap# de tamanho #n# é proporcional ao tempo esperado de execução da operação #add(x)# em um #Treap# de tamanho $#n#-1$. Concluímos que o tempo esperado de execução de #remove(x)# é $O(\log #n#)$. +(Lendo de cima para baixo, a \figref{treap-remove} ilustra a adição do valor $9$ em uma #Treap#.) +Isso significa que o tempo de execução esperado de #remove(x)# em uma #Treap# de tamanho #n# é proporcional ao tempo esperado de execução da operação #add(x)# em uma #Treap# de tamanho $#n#-1$. Concluímos que o tempo esperado de execução de #remove(x)# é $O(\log #n#)$. \subsection{Resumo} @@ -439,15 +438,17 @@ \subsection{Resumo} \begin{thm} Uma #Treap# implementa a interface #SSet#. Uma #Treap# executa -as operações #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ -de tempo esperado por operação. +as operações #add(x)#, #remove(x)# e #find(x)# em tempo esperado $O(\log #n#)$ +por operação. \end{thm} Vale a pena comparar a estrutura de dados -#Treap# à estrutura de dados #SkiplistSSet# -. Ambas implementam operações #SSet# em $O(\log #n#)$ de tempo esperado por operação. -Na duas estruturas de dados, #add(x)# e #remove(x)# envolvem uma busca e então um número constante de mudanças de ponteiros -(veja \excref{treap-rotates} a seguir). Então, para essas estruturas, o comprimento esperado do caminho de busca é um valor crítico na avaliação de seus desempenhos. +#Treap# à estrutura de dados #SkiplistSSet#. Ambas implementam +operações #SSet# em $O(\log #n#)$ de tempo esperado por operação. +Na duas estruturas de dados, #add(x)# e #remove(x)# envolvem uma busca +e então um número constante de mudanças de ponteiros +(veja a \excref{treap-rotates} a seguir). Então, para essas estruturas, +o comprimento esperado do caminho de busca é um valor crítico na avaliação de seus desempenhos. Em uma #SkiplistSSet#, o comprimento esperado de um caminho de busca é \[ 2\log #n# + O(1) \enspace , @@ -459,7 +460,7 @@ \subsection{Resumo} Então, os caminhos de busca em um #Treap# são consideravelmente mais curtos e isso traduz em operações mais rápidas em #Treap#s que #Skiplist#s. -\excref{skiplist-opt} em \chapref{skiplists} mostra como o comprimento +O \excref{skiplist-opt} no \chapref{skiplists} mostra como o comprimento esperado do caminho de busca em uma #Skiplist# pode ser reduzido a \[ @@ -473,10 +474,10 @@ \subsection{Resumo} \section{Discussão e Exercícios} -Árvores binárias de busca aleatórias têm sido estudados extensivamente. +Árvores binárias de busca aleatórias têm sido estudadas extensivamente. Devroye -\cite{d88} provou \lemref{rbs} e outros resultados relacionados. -Há resultados bem mais precisos na literatura também, o mais impressionant do qual é +\cite{d88} provou o \lemref{rbs} e outros resultados relacionados. +Há resultados bem mais precisos na literatura também, o mais impressionante é de Reed \cite{r03}, que mostra que a altura esperada de uma árvore binária de busca aleatória é \[ @@ -494,11 +495,11 @@ \section{Discussão e Exercícios} Uma possibilidade de otimização relacionada ao uso de memória da estrutura de dados #Treap# é a eliminação do armazenamento explícito -da prioridade #p# em cada nodo. Em vez idsso, a prioridade de um nodo #u# +da prioridade #p# em cada nodo. Em vez disso, a prioridade de um nodo #u# é computado usando o hashing do endereço de #u# em memória \javaonly{ (no Java 32-bits, isso é equivalente a fazer o hashing de -#u.hashCode()#)}. Embora muitas funções has provavelmente funcionam bem para isso na prática, para as partes importantes da prova -de \lemref{rbs} continuarem a serem válidas, a função hash deve ser randomizada e ter +#u.hashCode()#)}. Embora muitas funções hash provavelmente funcionam bem para isso na prática, para que as partes importantes da prova +do \lemref{rbs} continuarem a serem válidas, a função hash deve ser randomizada e ter a \emph{propriedade de independência em relação ao mínimo}: \index{independência em relação ao mínimo}% Para quaisquer valores distintos @@ -509,30 +510,32 @@ \section{Discussão e Exercícios} \] para alguma constante $c$. Uma classe de funções hash desse tipo que é fácil de implementar e -razoavelmente rápida é \emph{hashing por tabulação} +razoavelmente rápida é o \emph{hashing por tabulação} (\secref{tabulation}). \index{hashing por tabulação}% -\index{hashing!tabulaçãofilme star wars luke nasceu}% +\index{hashing!tabulação}% Outra variante -#Treap# que não guarda prioridade em cada nodo é a árvore binária de busca randomizada. +#Treap# que não guarda a prioridade em cada nodo é a árvore binária de busca randomizada. \index{árvore binária de busca randomizada}% \index{árvore binária de busca!randomizada}% de Mart\'\i nez e Roura \cite{mr98}. -Nessa variante, todo nodo #u# guarda o tamanho #u.size# da subárvore enraizada em #u#. Ambos algoritmos - #add(x)# e #remove(x)# algorithms são randomizados -. O algoritmo para adicionar #x# à subárvore enraizada em #u# +Nessa variante, todo nodo #u# guarda o tamanho #u.size# da subárvore enraizada em #u#. +Ambos algoritmos + #add(x)# e #remove(x)# são randomizados. + O algoritmo para adicionar #x# à subárvore enraizada em #u# faz o seguinte: \begin{enumerate} - \item Com probabilidade $1/(#size(u)#+1)$, %TODO + \item Com probabilidade $1/(#size(u)#+1)$, o valor #x# é adicionado normalmente, como uma folha, e rotações são então feitas para trazer #x# à raiz dessa subárvore. \item Caso contrário, (com probabilidade $1-1/(#size(u)#+1)$), o valor #x# - é recursivamente adicionado em uma das duas subárvores enraizadas em #u.left# + é recursivamente adicionado em uma das duas subárvores enraizadas em #u.left# ou #u.right#, conforme apropriado. \end{enumerate} O primeiro caso corresponde a uma operação #add(x)# em uma #Treap# onde o nodo -de #x# recebe uma prioridade aleatória que é menor que qualquer uma das prioridades na subárvore de #u# e esse caso ocorre com exatamente a mesma probabilidade. +de #x# recebe uma prioridade aleatória que é menor que qualquer uma das prioridades +na subárvore de #u# e esse caso ocorre com exatamente a mesma probabilidade. A remoção de um valor #x# de uma árvore binária de busca randomizada é similar ao processo de remoção de uma #Treap#. Achamos o nodo #u# que contém #x# e então @@ -540,32 +543,35 @@ \section{Discussão e Exercícios} ou direita é randomizada. \begin{enumerate} \item Com probabilidade #u.left.size/(u.size-1)#, fazemos uma rotação à direita em #u#, fazendo #u.left# a raiz da subárvore que anteriormente estava enraizada em #u#. - \item Com probabilidade #u.right.size/(u.size-1)#, realizamos uma rotação à esquerda em #u#, fazendo #u.right# a raiz da subárvore que estava anteriormente - enraizada em #u#. + \item Com probabilidade #u.right.size/(u.size-1)#, realizamos uma rotação + à esquerda em #u#, fazendo #u.right# a raiz da subárvore que estava + anteriormente enraizada em #u#. \end{enumerate} Novamente, podemos facilmente verificar que essas são exatamente as mesmas -probabilidades que o algoritmo de remoção em uma #Treap# terá para fazer rotação à esquerda ou direita de #u#. +probabilidades que o algoritmo de remoção em uma #Treap# terá para +fazer rotação à esquerda ou direita de #u#. -Árvores binárias de busca randomizada tem a desvantagem, em relação às treaps, +Árvores binárias de busca randomizada têm a desvantagem, em relação às treaps, de que ao adicionar ou remover elementos elas farão muitas escolhas aleatórias e precisam manter os tamanhos das subárvores. Uma vantagem de árvores binárias de busca randomizada em relação às treaps é que -os tamanhos de subárvores pode servir para outra utilidade: prover acesso por rank -em tempo esperado $O(\log #n#)$ (veja \excref{treap-get}). -Em comparação, as prioridades aleatórias guardadas em nodos de uma treap somente são úteis para manter a treap balanceada. +os tamanhos de subárvores pode servir para outro uso: prover acesso por rank +em tempo esperado $O(\log #n#)$ (veja o \excref{treap-get}). +Em comparação, as prioridades aleatórias guardadas em nodos de uma +treap somente são úteis para manter a treap balanceada. \begin{exc} - Simule a adição de 4.5 (com prioridade 7) e então 7.5 (com prioridade 20) na #Treap# em \figref{treap}. + Simule a adição de 4.5 (com prioridade 7) e então 7.5 (com prioridade 20) na #Treap# da \figref{treap}. \end{exc} \begin{exc} - Simule a remoção do elemento 5 e então do elemento 7 na #Treap# em + Simule a remoção do elemento 5 e então do elemento 7 na #Treap# da \figref{treap}. \end{exc} \begin{exc} Prove que existem $21,964,800$ sequências que geram a árvore - do lado direito de + do lado direito da \figref{rbs-lvc}. (Dica: obtenha uma fórmula recursiva para o número de sequências que geram uma árvore binária de altura $h$ e use essa fórmula para $h=3$.) @@ -580,8 +586,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc}\exclabel{treap-rotates} - Use ambas partes de - \lemref{rbs-treap} para provar que o número esperado de rotações + Use ambas partes do \lemref{rbs-treap} para provar que o número esperado de rotações realizadas por uma operação #add(x)# (e portanto uma operação #remove(x)#) é $O(1)$. \end{exc} @@ -598,15 +603,17 @@ \section{Discussão e Exercícios} enraizada em #u#. \begin{enumerate} \item Mostre que, se fizermos uma rotação à esquerda ou direita em #u#, então - essas duas quantidade podem ser atualizadas em tempo constante para todos os nodos afetados pela rotação. - \item Explique porque o mesmo resultado não é possível se tentarmos também guardar a profundidade #u.depth# de cada nodo #u#. + essas duas quantidades podem ser atualizadas em tempo constante + para todos os nodos afetados pela rotação. + \item Explique porque o mesmo resultado não é possível se tentarmos + também guardar a profundidade #u.depth# de cada nodo #u#. \end{enumerate} \end{exc} \begin{exc} Projete e implemente um algoritmo que constrói uma #Treap# a partir de um array ordenado #a# de #n# elementos. Esse método deve rodar em - $O(#n#)$ de tempo no pios caso e deve construir uma #Treap# que é + $O(#n#)$ de tempo no pior caso e deve construir uma #Treap# que é indistinguível de uma em que os elementos de #a# foram adicionados um por vez usando o método #add(x)#. \end{exc} @@ -618,36 +625,42 @@ \section{Discussão e Exercícios} fazer buscas em uma #Treap# dado um ponteiro que está próximo ao nodo que estamos procurando. \begin{enumerate} - \item Projete e implemente uma #Treap# em que cada nodo registra os valores mínimos e máximos em sua subárvore. - \item Usando essa informação extrea, adicione um método #fingerFind(x,u)# + \item Projete e implemente uma #Treap# em que cada nodo registra os + valores mínimos e máximos em sua subárvore. + \item Usando essa informação extra, adicione um método #fingerFind(x,u)# que executa a operação - #find(x)# com a ajudar de um ponteiro para o nodo #u# (que + #find(x)# com a ajuda de um ponteiro para o nodo #u# (que espera-se que não esteja distante do nodo que contém #x#). Essa operação deve iniciar em #u# subir na árvore até que alcance um nodo #w# tal que $#w.min#\le #x#\le #w.max#$. A partir desse ponto, deve-se realizar uma busca padrão por #x# partindo de #w#. - (É possível mostrar que - #fingerFind(x,u)# leva - $O(1+\log r)$ de tempo, onde $r$ é o número de elementos em uma treap cujo valor está entre #x# e #u.x#.) - \item Estenda sua implementação em uma versão de treap que inicia todas as operações #find(x)# a partir do nodo mais recentemente encontrado por #find(x)#. + (É possível mostrar que #fingerFind(x,u)# leva + $O(1+\log r)$ de tempo, onde $r$ é o número de elementos em uma treap + cujo valor está entre #x# e #u.x#.) + \item Estenda sua implementação em uma versão de treap que + inicia todas as operações #find(x)# a partir do nodo mais + recentemente encontrado por #find(x)#. \end{enumerate} \end{exc} \begin{exc}\exclabel{treap-get} Projete e implemente uma versão de #Treap# que inclui uma operação #get(i)# - que retorna a chave com rank #i# na #Treap#. (Dica: faça que cada nodo #u# registre o tamanho da subárvore enraizada em #u#.) Hint: + que retorna a chave com rank #i# na #Treap#. (Dica: faça que cada + nodo #u# registre o tamanho da subárvore enraizada em #u#.) \end{exc} \begin{exc} \index{TreapList@#TreapList#}% - Codifique uma #TreapList#, uma implementação de uma interface #List# na forma de uma treap. Cada nodo na treap deve guardar um item da lista e uma - travessia em-ordem na treap encontra os itens na mesma ordem que ocorrem na lista. + Codifique uma #TreapList#, uma implementação de uma interface #List# na forma de uma treap. + Cada nodo na treap deve guardar um item da lista e uma + travessia em-ordem na treap encontra os itens na mesma + ordem que ocorrem na lista. Todas as operações da #List#, #get(i)#, #set(i,x)#, - #add(i,x)# e #remove(i)# rodar em tempo esperado $O(\log #n#)$. + #add(i,x)# e #remove(i)# devem rodar em tempo esperado $O(\log #n#)$. \end{exc} \begin{exc}\exclabel{treap-split} @@ -656,21 +669,24 @@ \section{Discussão e Exercícios} #x# e retorna uma segunda #Treap# que contém todos os valores removidos. \noindent Exemplo: o código #t2 = t.split(x)# remove de #t# todos os - valores maiores que - #x# e retorna uma nova #Treap# #t2# contendo todos esses valores. - A operação #split(x)# devem rodar em tempo esperado $O(\log #n#)$. + valores maiores que #x# e retorna uma nova #Treap# #t2# contendo todos esses valores. + A operação #split(x)# deve rodar em tempo esperado $O(\log #n#)$. - \noindent Aviso: Para essa modificação funcionar adequadamente e ainda possibilitar o método #size()# rodar em tempo constante, é necessário implementar as modificações em \excref{treap-get}. + \noindent Aviso: Para essa modificação funcionar adequadamente e ainda + possibilitar o método #size()# rodar em tempo constante, é necessário + implementar as modificações no \excref{treap-get}. \end{exc} \begin{exc}\exclabel{treap-join} Projete e implemente uma versão de uma #Treap# que aceita a operação #absorb(t2)#, que pode ser pensada como o inverso da operação #split(x)#. Essa operação remove todos os valores da #Treap# #t2# e os adiciona ao receptor. - Essa operação pressupõe que o menor valor em #t2# é maior que o maior valor no receptor. A operação #absorb(t2)# deve rodar em tempo esperado + Essa operação pressupõe que o menor valor em #t2# é maior que o + maior valor no receptor. A operação #absorb(t2)# deve rodar em tempo esperado $O(\log #n#)$. \end{exc} \begin{exc} - Implemente a árvore binária de busca randomizada de Martinez conforme discutido nesta seção. Compare o desempenho da sua implementação com a implementação da #Treap#. + Implemente a árvore binária de busca randomizada de Martinez conforme discutido nesta seção. + Compare o desempenho da sua implementação com a implementação da #Treap#. \end{exc} From b0067c720130811ef74c5fb5d9b48edc4d8b761d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 11 Sep 2020 17:38:30 -0300 Subject: [PATCH 50/66] finished revision of portuguese translation of scapegoat --- latex/scapegoat.tex | 158 +++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 83 deletions(-) diff --git a/latex/scapegoat.tex b/latex/scapegoat.tex index 279ac882..6586c94c 100644 --- a/latex/scapegoat.tex +++ b/latex/scapegoat.tex @@ -2,9 +2,9 @@ \chapter{Árvores Scapegoat} \chaplabel{scapegoat} Neste capítulo, estudamos uma estrutura de dados de árvore binária de busca, a -#ScapegoatTree#. Essa estrutura é baseada na ideia popular que +#ScapegoatTree#. Essa estrutura é baseada na ideia popular de que quando algo sai errado, a primeira coisa que pessoas tendem a fazer -é achar alguém para por a culpa (o bode expiatório, em inglês \emph{scapegoat}). +é achar alguém para culpar (o bode expiatório, em inglês \emph{scapegoat}). \index{scapegoat}% \index{bode expiatório}% Uma vez que culpa está estabelecida, podemos deixar que o bode expiatório resolva o problema. @@ -19,33 +19,33 @@ \chapter{Árvores Scapegoat} Se fazemos $#m#=#a.length#/2$, -então o elemento #a[m]# torna-se raiz da nova subárvore, +então o elemento #a[m]# torna-se a raiz da nova subárvore, $#a#[0],\ldots,#a#[#m#-1]$ é armazenada recursivamente na subárvore à esquerda e $#a#[#m#+1],\ldots,#a#[#a.length#-1]$ é armazenada na subárvore à direita. \codeimport{ods/ScapegoatTree.rebuild(u).packIntoArray(u,a,i).buildBalanced(a,i,ns)} Uma chamada a -#rebuild(u)# leva $O(#size(u)#)$ de tempo. A subárvore resultante altura mínima; +#rebuild(u)# leva tempo $O(#size(u)#)$. A subárvore resultante tem altura mínima; não há árvore de menor altura com #size(u)# nodos. \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parcial} \seclabel{scapegoattree} \index{ScapegoatTree@#ScapegoatTree#}% -Uma #ScapegoatTree# é uma #BinarySearchTree# que além de registrar o númer #n# de nodos na árvore também mantém um contador #q# que mantém um limitante superior no número de nodos. +Uma #ScapegoatTree# é uma #BinarySearchTree# que além de registrar o número #n# de nodos na árvore também mantém um contador #q# que mantém um limitante superior no número de nodos. \codeimport{ods/ScapegoatTree.q} Durante todo seu uso, #n# e #q# seguem as seguintes desigualdades: \[ #q#/2 \le #n# \le #q# \enspace . \] Além disso, uma -#ScapegoatTree# tem altura logaritmica; e a altura da árvore scapegoat não excede: +#ScapegoatTree# tem altura logarítmica; e a altura da árvore scapegoat não excede: \begin{equation} \log_{3/2} #q# \le \log_{3/2} 2#n# < \log_{3/2} #n# + 2\enspace . \eqlabel{scapegoat-height} \end{equation} Mesmo com essa restrição, uma - #ScapegoatTree# pode parecer surpreendetemente desbalanceada. A árvore em \figref{scapegoat-example} tem $#q#=#n#=10$ e altura $5<\log_{3/2}10 \approx 5.679$. + #ScapegoatTree# pode parecer surpreendentemente desbalanceada. A árvore na \figref{scapegoat-example} tem $#q#=#n#=10$ e altura $5<\log_{3/2}10 \approx 5.679$. \begin{figure} \begin{center} @@ -58,8 +58,7 @@ \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parci A implementação da operação #find(x)# em uma #ScapegoatTree# é feita usando o algoritmo padrão para buscas em uma #BinarySearchTree# -(veja \secref{binarysearchtree}). Isso leva tempo proporcional à altura da árvore que, de acordo com - \myeqref{scapegoat-height} é $O(\log #n#)$. +(veja a \secref{binarysearchtree}). Isso leva tempo proporcional à altura da árvore que, de acordo com a \myeqref{scapegoat-height}, é $O(\log #n#)$. Para implementar a operação #add(x)#, primeiramente incrementamos #n# e #q# e então usamos o algoritmo usual @@ -71,10 +70,10 @@ \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parci Infelizmente, em algumas vezes pode acontecer que $#depth(u)# > \log_{3/2} -#q#$. Nesse caso, precisamos reduzir a altura. +#q#$. Nesse caso, precisamos reduzir essa altura. Isso não é nada demais; há somente um nodo, #u#, cuja profundidade excede $\log_{3/2} -#q#$. Para arrumar #u#, saimos de #u# para subir na árvore em busca de um \emph{bode expiatório} #w#. Esse bode expiatório é um nodo muito desbalanceado. +#q#$. Para arrumar #u#, saímos de #u# para subir na árvore em busca de um \emph{bode expiatório} #w#. Esse bode expiatório é um nodo muito desbalanceado. Ele tem a seguinte propriedade \begin{equation} \frac{#size(w.child)#}{#size(w)#} > \frac{2}{3} \enspace , @@ -88,9 +87,9 @@ \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parci destruir a subárvore enraizada em #w# e reconstruí-la em uma árvore binária de busca perfeitamente balanceada. Sabemos -de \myeqref{scapegoat} que mesmo antes da adição de #u#, a subárvore +da \myeqref{scapegoat} que mesmo antes da adição de #u#, a subárvore de #w# não era uma árvore binária completa. -Portanto, quando reconstruirmos #w#, a altura decresce por pelo menos 1 tal que +Portanto, ao reconstruirmos #w#, a altura decresce por pelo menos 1 para que a altura de #ScapegoatTree# seja novamente até $\log_{3/2}#q#$. @@ -103,7 +102,7 @@ \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parci \includegraphics[scale=0.90909]{figs/scapegoat-insert-4} \end{tabular} \end{center} - \caption[Adicionando à árvore scapegoat]{Inserindo 3.5 em uma #ScapegoatTree# aumenta sua altura para 6, o que viola \myeqref{scapegoat-height} pois $6 > \log_{3/2} 11 \approx 5.914$. Um bode expiatório é achado no nodo contendo 5.} + \caption[Adicionando à árvore scapegoat]{Inserindo 3.5 em uma #ScapegoatTree# aumenta sua altura para 6, o que viola a \myeqref{scapegoat-height} pois $6 > \log_{3/2} 11 \approx 5.914$. Um bode expiatório é achado no nodo contendo 5.} \end{figure} Se ignorarmos o custo de achar o bode expiatório #w# e reconstruirmos a subárvore enraizada em #w#, então o tempo de execução de #add(x)# é @@ -112,33 +111,30 @@ \section{#ScapegoatTree#: Uma Árvore Binária de Busca com Reconstrução Parci Iremos considerar o custo de encontrar o bode expiatório e de reconstrução usando análise amortizada na seção a seguir. -A implementação de #remove(x)# em uma -#ScapegoatTree# é muito simples. +A implementação de #remove(x)# em uma #ScapegoatTree# é muito simples. Buscamos por #x# e o removemos usando o algoritmo usual para remover um nodo de uma #BinarySearchTree#. (Note que isso nunca aumenta a altura da árvore.) A seguir, decrementamos #n#, mas não alteramos #q#. Finalmente, verificamos se $#q# > 2#n#$ e, caso positivo, então \emph{reconstruímos a árvore inteira} -em uma árvore binária perfeitamente balanceada em atribuímos +em uma árvore binária perfeitamente balanceada e atribuímos $#q#=#n#$. \codeimport{ods/ScapegoatTree.remove(x)} Novamente, se ignorarmos o custo de recontrução, o tempo de execução da operação -#remove(x)# é proporcional à altura da árvore que é +#remove(x)# é proporcional à altura da árvore, que é $O(\log #n#)$. \subsection{Análise de Corretude e Tempo de Execução} -Nesta seção, analizaremos a corretude e o tempo amortizado de operações -em uma - #ScapegoatTree#. Primeiro provamos a corretude ao mostrar que, quando a operação #add(x)# resulta em um nodo que viola a -Condition \myeqref{scapegoat-height}, então sempre podemos achar um bode expiatório: +Nesta seção, analisaremos a corretude e o tempo amortizado de operações +em uma #ScapegoatTree#. Primeiro provamos a corretude ao mostrar que, quando a operação #add(x)# resulta em um nodo que viola a +condição da \myeqref{scapegoat-height}, então sempre podemos achar um bode expiatório: \begin{lem} Seja #u# um nodo de profundidade $h>\log_{3/2} #q#$ em uma #ScapegoatTree#. - Então existe um nodo - $#w#$ no caminho de #u# à raiz tal que + Então existe um nodo $#w#$ no caminho de #u# à raiz tal que \[ \frac{#size(w)#}{#size(parent(w))#} > 2/3 \enspace . \] @@ -149,7 +145,7 @@ \subsection{Análise de Corretude e Tempo de Execução} \[ \frac{#size(w)#}{#size(parent(w))#} \le 2/3 \enspace . \] - para todos os nodos #w# no caminho de #u# a raiz. Denote o caminho + para todos os nodos #w# no caminho de #u# à raiz. Denote o caminho da raiz a #u# como $#r#=#u#_0,\ldots,#u#_h=#u#$. Então, temos $#size(u#_0#)#=#n#$, $#size(u#_1#)#\le\frac{2}{3}#n#$, @@ -168,7 +164,7 @@ \subsection{Análise de Corretude e Tempo de Execução} \] \end{proof} -A seguirm analizamos as partes do tempo de execuão que ainda não foram +A seguir analisamos as partes do tempo de execução que ainda não foram levadas em conta. Existem duas partes: o custo das chamadas a #size(u)# ao buscar por nodos bodes expiatórios e o custo de chamadas a @@ -176,16 +172,18 @@ \subsection{Análise de Corretude e Tempo de Execução} O custo das chamadas a #size(u)# pode ser relacionado ao custo de chamadas a #rebuild(w)# da seguinte forma: \begin{lem} - Durante uma chamada a -#add(x)# em uma #ScapegoatTree#, o custo de encontrar o bode expiatório #w# e reconstruir a subárvore enraizada em #w# é $O(#size(w)#)$. +Durante uma chamada a +#add(x)# em uma #ScapegoatTree#, o custo de encontrar o bode expiatório #w# +e reconstruir a subárvore enraizada em #w# é $O(#size(w)#)$. \end{lem} \begin{proof} O custo de reconstruir o nodo bode expiatório #w# uma vez que o achamos é -$O(#size(w)#)$. Ao busca o nodo bode expiatório, chamamos #size(u)# em uma +$O(#size(w)#)$. Ao buscar o nodo bode expiatório, chamamos #size(u)# em uma sequência de nodos $#u#_0,\ldots,#u#_k$ até que encontramos um bode expiatório -$#u#_k=#w#$. Porém, como $#u#_k$ é o primeiro nodo nessa sequência que é um bode expiatório, sabemos que +$#u#_k=#w#$. Porém, como $#u#_k$ é o primeiro nodo nessa sequência que é + um bode expiatório, sabemos que \[ #size(u#_{i}#)# < \frac{2}{3}#size(u#_{i+1}#)# \] @@ -206,7 +204,7 @@ \subsection{Análise de Corretude e Tempo de Execução} \right)\right) \\ &=& O(#size(u#_k#)#) = O(#size(w)#) \enspace , \end{eqnarray*} - onde a última linha segue do fato que a soma é uma séries geométrica decrescente. + onde a última linha segue do fato que a soma é uma série geométrica decrescente. \end{proof} Somente falta provar um limitante superior no custo de todas as chamadas a @@ -214,9 +212,8 @@ \subsection{Análise de Corretude e Tempo de Execução} \begin{lem}\lemlabel{scapegoat-amortized} Iniciando com uma - #ScapegoatTree# vazia com qualquer sequência de $m$ operações #add(x)# - e #remove(x)# causam até $O(m\log m)$ de tempo para ser usado por operações - #rebuild(u)#. + #ScapegoatTree# vazia, uma sequência de $m$ operações #add(x)# + e #remove(x)# faz com que as operações #rebuild(u) usem tempo #$O(m\log m)$. \end{lem} \begin{proof} @@ -226,14 +223,16 @@ \subsection{Análise de Corretude e Tempo de Execução} pode pagar por alguma constante, $c$, unidades de tempo gastos na reconstrução. O esquema resulta provê um total de - $O(m\log m)$ créditos e toda chamada de #rebuild(u)# é paga com esses créditos guardados em #u#. + $O(m\log m)$ créditos e toda chamada de #rebuild(u)# é paga com esses + créditos guardados em #u#. Durante uma inserção ou deleção, cedemos um crédito a cada nodo no caminho ao nodo inserido, ou removido, #u#. Dessa maneira gastamos até $\log_{3/2}#q#\le \log_{3/2}m$ créditos por operação. - Duração a remoção também guardamos um crédito adicional de reserva. + Durante a remoção também guardamos um crédito adicional de reserva. Então, no total cedemos até - $O(m\log m)$ créditos. Tudo o que resta é mostrar que esses créditos são suficientes para pagar por todas as chamadas a #rebuild(u)#. + $O(m\log m)$ créditos. Tudo o que resta é mostrar que esses créditos + são suficientes para pagar por todas as chamadas a #rebuild(u)#. Se chamarmos #rebuild(u)# durante uma inserção, é porque #u# é um bode expiatório. Suponha, sem perda de generalidade, que @@ -253,7 +252,7 @@ \subsection{Análise de Corretude e Tempo de Execução} #size(u.left)# - #size(u.right)# > \frac{1}{2}#size(u.left)# > \frac{1}{3}#size(u)# \enspace . \] - Agor, a última vez que uma subárvore contendo #u# foi reconstruída (ou quando #u# + Agora, a última vez que uma subárvore contendo #u# foi reconstruída (ou quando #u# foi inserido, se uma subárvore contendo #u# nunca foi reconstruída), temos \[ #size(u.left)# - #size(u.right)# \le 1 \enspace . @@ -266,15 +265,15 @@ \subsection{Análise de Corretude e Tempo de Execução} \] e há portanto pelo menos essa quantidade de créditos guardados em #u# que estão disponíveis para pagar pelo - $O(#size(u)#)$ de time que leva para chamar + $O(#size(u)#)$ de tempo que leva para chamar #rebuild(u)#. - Se chamarmos + Se chamamos #rebuild(u)# durante uma remoção, é porque $#q# > 2#n#$. Nesse caso, temos $#q#-#n#> #n#$ créditos guardados em uma reserva e os usamos para pagar pelo - $O(#n#)$ de tempo que leva para reconstrução a raiz. Isso completa a prova. + $O(#n#)$ de tempo que leva para reconstruir a raiz. Isso completa a prova. \end{proof} \subsection{Resumo} @@ -284,21 +283,21 @@ \subsection{Resumo} \begin{thm}\thmlabel{scapegoat} Uma #ScapegoatTree# implementa a interface #SSet#. Ignorando o custo de operações - #rebuild(u)#, uma #ScapegoatTree# aceita as operações - #add(x)#, #remove(x)# e #find(x)# em $O(\log #n#)$ de tempo por operação. + #rebuild(u)#, uma #ScapegoatTree# possui as operações + #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log #n#)$ por operação. Além disso, iniciando com uma - #ScapegoatTree# vazia, qualquer sequência de $m$ operações - #add(x)# e #remove(x)# resulta em um tempo total $O(m\log m)$ - gasto em todas as chamadas a #rebuild(u)#. + #ScapegoatTree# vazia, uma sequência de $m$ operações + #add(x)# e #remove(x)# resulta em um tempo total $O(m\log m)$ + gasto nas chamadas a #rebuild(u)#. \end{thm} \section{Discussão e Exercícios} O termo \emph{scapegoat tree} é atribuído a Galperin e Rivest \cite{gr93}, - que defime e analizam essas árvores. Porém, a mesma estrutura foi - descuberta anteriormente por + que definiram e analizaram essas árvores. Porém, a mesma estrutura foi + descoberta anteriormente por \cite{a89,a99}, que as chamou de \emph{árvores balanceadas gerais (originalmente, em inglês, general balanced trees)} \index{general balanced tree}% @@ -306,28 +305,28 @@ \section{Discussão e Exercícios} pois elas podem ter qualquer forma desde que a altura seja pequena. Experimentos com a implementação - #ScapegoatTree# irá revelar que + #ScapegoatTree# mostram que ela costuma ser consideravelmente mais lenta que as outras implementações de #SSet# neste livro. - Isso pode ser surpreendente pois a altura é limitada por + Isso pode ser surpreendente pois a altura é limitada a \[ \log_{3/2}#q# \approx 1.709\log #n# + O(1) \] -é melhor que o comprimento esperado de um caminho de busca em uma +que é melhor que o comprimento esperado de um caminho de busca em uma #Skiplist# e não muito longe de um em uma #Treap#. A implementação pode ser otimizada ao armazenar os tamanhos das subárvores explicitamente em cada nodo ou reutilizando tamanhos de subárvores previamente -comptuados (Exercícios~\ref{exc:scapegoat-quicksize} +computados (Exercícios~\ref{exc:scapegoat-quicksize} e \ref{exc:scapegoat-explicitsize}). Mesmo com essas otimizações, sempre haverão sequências de operações #add(x)# e #delete(x)# para as quais uma #ScapegoatTree# leva mais tempo que outras implementações da #SSet#. -Essa diferença em despempenho é devido ao fato que, diferentemente de +Essa diferença em desempenho é devido ao fato que, diferentemente de outras implementações #SSet# discutidas neste livro, uma #ScapegoatTree# pode gastar muito tempo se reestruturando. - \excref{scapegoat-nlogn} pede que você prova que existem sequências de #n# operações em que uma -#ScapegoatTree# irá gastar +O \excref{scapegoat-nlogn} pede que você prove que existem sequências de #n# +operações em que uma #ScapegoatTree# irá gastar $#n#\log #n#$ de tempo em chamadas a #rebuild(u)#. Isso contrasta a outras implementações #SSet# discutidas neste livro, que fazem somente $O(#n#)$ mudanças estruturais durante uma sequência de @@ -335,27 +334,25 @@ \section{Discussão e Exercícios} uma #ScapegoatTree# faz sua restruturação usando chamadas a #rebuild(u)# \cite{d90}. -Embora seu desempenho relativamente ruim, há aplicações em que uma +Apesar de seu desempenho relativamente ruim, há aplicações em que uma #ScapegoatTree# pode ser a escolha certa. -Isso ocorreria quando há dados adicionais associados a nodos que não +Isso ocorre quando há dados adicionais associados a nodos que não podem ser atualizados em tempo constante quando uma rotação é realizada mas que podem ser atualizados em uma operação #rebuild(u)#. Nesses casos, a #ScapegoatTree# e outras estruturas similares baseadas em reconstrução parcial podem -funcionar bem. Um exemplo de tal aplicação é esboçado em -\excref{list-order-maintenance}. +funcionar bem. Um exemplo de tal aplicação é esboçado no \excref{list-order-maintenance}. \begin{exc} Simule a adição dos valores 1.5 e depois 1.6 na - #ScapegoatTree# em \figref{scapegoat-example}. + #ScapegoatTree# na \figref{scapegoat-example}. \end{exc} \begin{exc} Ilustre o que acontece quando a sequência $1,5,2,4,3$ é adicionada a uma - #ScapegoatTree# vazia, e mostre onde os créditos descritos na prova - de - \lemref{scapegoat-amortized} vão, e como eles são usados durante + #ScapegoatTree# vazia e mostre onde os créditos descritos na prova + do \lemref{scapegoat-amortized} vão e como eles são usados durante essa sequência de adições. \end{exc} @@ -367,8 +364,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - A - #ScapegoatTree#, conforme descrita neste capítulo, garante que o + A #ScapegoatTree#, conforme descrita neste capítulo, garante que o comprimento do caminho de busca não excede $\log_{3/2}#q#$. \begin{enumerate} @@ -376,18 +372,17 @@ \section{Discussão e Exercícios} #ScapegoatTree# onde o comprimento do caminho de busca que não excede $\log_{#b#} #q#$, onde #b# é um parâmetro com $1<#b#<2$. \item O que sua análise e experimentos dizem sobre o custo amortizado de - #find(x)#, #add(x)# e #remove(x)# como uma função de - #n# e #b#? + #find(x)#, #add(x)# e #remove(x)# como uma função de #n# e #b#? \end{enumerate} \end{exc} \begin{exc}\exclabel{scapegoat-quicksize} -Modifique o método #add(x)# da #ScapegoatTree# tal que não gaste -qualquer tempo recomputando os tamanhos das subárvores que foram +Modifique o método #add(x)# da #ScapegoatTree# para que não gaste +tempo recomputando os tamanhos das subárvores que foram anteriormente computados. Isso é possível porque quando o método quer computar #size(w)#, ele já computou #size(w.left)# ou #size(w.right)#. Compare o desempenho de sua - implementação modificada com a implementação dada aqui. + implementação modificada com a implementação fornecida neste livro. \end{exc} \begin{exc}\exclabel{scapegoat-explicitsize} @@ -399,7 +394,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Reimplemente o método #rebuild(u)# discutido no começo desse capítulo tal que não requer o uso de um array para guardar os nodos da subárve sendo reconstruída. + Reimplemente o método #rebuild(u)# discutido no começo desse capítulo para que não necessite do uso de um array para guardar os nodos da subárvore sendo reconstruída. Em vez disso, deve-se usar recursão primeiro para conectar os nodos em uma lista ligada e então converter essa lista ligada em uma árvore binária perfeitamente balanceada. (Existem implementações recursivas muito elegantes de ambos os passos.) \end{exc} @@ -425,27 +420,25 @@ \section{Discussão e Exercícios} a variável #u.t# é decrementada. Quando $#u.t#=0$ a subárvore inteira enraizada em #u# é reconstruída em uma árvore binária de busca perfeitamente balanceada. - Quando um nodo #u# estiver envolvido em uma operação de reconstrução (porque #u# foi reconstruída ou um dos ancestrais de #u# foi reconstruído) #u.t# é reiniciado a + Quando um nodo #u# estiver envolvido em uma operação de reconstrução (porque #u# foi reconstruída ou um dos ancestrais de #u# foi reconstruído) #u.t# é reiniciada a $#size(u)#/3$. Sua análise deve mostrar que as operações em uma #CountdownTree# roda em tempo amortizado - $O(\log #n#)$. (Dica: primeiro mostre que cada nodo #u# satisfaz alguma versão de uma invariante de balanceamento.) + $O(\log #n#)$. (Dica: primeiro mostre que cada nodo #u# satisfaz + alguma versão de uma invariante de balanceamento.) \end{exc} \begin{exc} \index{DynamiteTree@#DynamiteTree#}% - Analise e implemente uma - #DynamiteTree#. Em uma #DynamiteTree# cada nodo + Analise e implemente uma #DynamiteTree#. Em uma #DynamiteTree# cada nodo #u# registra o tamanho da subárvore enraizada em #u# em uma variável #u.size#. As operações #add(x)# e #remove(x)# são exatamente as mesmas que em uma - #BinarySearchTree# padrão exceto que, sempre que uma dessas operações afetam uma subárvore de um nodo #u#, esse nodo \emph{explode} com probabilidade + #BinarySearchTree# padrão exceto que, sempre que uma dessas operações afeta uma subárvore de um nodo #u#, esse nodo \emph{explode} com probabilidade $1/#u.size#$. Quando #u# explode, sua subárvore inteira é reconstruída em uma árvore binária de busca perfeitamente balanceada. - A sua análise deve mostrar que operações em uma - #DynamiteTree# roda em tempo esperado - $O(\log #n#)$. + #DynamiteTree# rodam em tempo esperado $O(\log #n#)$. \end{exc} @@ -455,13 +448,12 @@ \section{Discussão e Exercícios} #Sequence# que mantém uma sequência (lista) de elementos. Ela suporta as seguintes operações: \begin{itemize} - \item #addAfter(e)#: adicionamos um novo elemento após o elemento #e# na sequência. Retorna o elemento adicionado. (Se #e# é null, - o novo elemento é adicionado no início da sequência.) + \item #addAfter(e)#: adiciona um novo elemento após o elemento #e# na sequência. Retorna o elemento adicionado. (Se #e# for null, o novo elemento é adicionado no início da sequência.) \item #remove(e)#: Remove #e# da sequência. \item #testBefore(e1,e2)#: retorna #true# se e somente se #e1# vem antes de #e2# na sequência. \end{itemize} - As primeiras duas operações devem rodas em tempo amortizado + As primeiras duas operações devem rodar em tempo amortizado $O(\log #n#)$. A terceira operação deve rodar em tempo constante. A estrutura de dados @@ -469,6 +461,6 @@ \section{Discussão e Exercícios} Para implementar #testBefore(e1,e2)# em tempo constante, cada elemento #e# é marcado com um inteiro que codifica o caminho da raiz até #e#. Dessa forma, - #testBefore(e1,e2)# pode ser implementado pela comparação das marcações de #e1# e #e2#. + #testBefore(e1,e2)# pode ser implementada pela comparação das marcações de #e1# e #e2#. \end{exc} From 3b4c9c7389d5fcb19a938eaae32dccdb94c05352 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 11 Sep 2020 17:43:25 -0300 Subject: [PATCH 51/66] fixing typos in redblack (portuguese translation) --- latex/redblack.tex | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/latex/redblack.tex b/latex/redblack.tex index c8c974ac..a7731a9b 100644 --- a/latex/redblack.tex +++ b/latex/redblack.tex @@ -21,7 +21,7 @@ \chapter{Árvores Rubro-Negras} \end{enumerate} As duas primeiras propriedades põem árvores rubro-negras a frente de skiplists, treaps e árvores scapegoat. -Skiplists e treaps dependem de randomization e seus tempos de execução $O(\log #n#)$ +Skiplists e treaps dependem de randomização e seus tempos de execução $O(\log #n#)$ somente são esperados. Árvores scapegoat tem limitante garantido na altura, mas #add(x)# e #remove(x)# somente rodam em tempo amortizado $O(\log #n#)$. A terceira propriedade é apenas a cereja do bolo. Ela nos diz que o tempo necessário para adicionar ou remover um elemento #x# é minúsculo em relação ao tempo que leva para achar #x#. @@ -91,11 +91,11 @@ \subsection{Adição de uma Folha} #w#' não tem pai, então recursivamente tornamos #w#' um filho do pai de #w#. Novamente, isso pode fazer com que o pai de #w# -tenha muitos filhos e, dessa forma, teremos que repartí-los. +tenha muitos filhos e, dessa forma, teremos que reparti-los. Esse processo segue repetidamente até alcançar um nodo que tem menos que quatro filhos, ou até repartimos a raiz #w# em dois nodos #r# e #r'#. No último caso, fazemos uma nova raiz que tem #r# e #r'# como filhos. Isso simultaneamente aumenta a - profundidade de todas as folhas e assim mantém propridade da + profundidade de todas as folhas e assim mantém propriedade da altura. \begin{figure} @@ -154,7 +154,7 @@ \subsection{Remoção de um Folha} é deixada com somente um filho, então removemos a raiz e fazemos seu filho a nova raiz. -Novamente, isso simultaneamente reduz a altua de toda folha e portanto +Novamente, isso simultaneamente reduz a altura de toda folha e portanto mantém a propriedade de altura. Como a altura da árvore não passa de $\log #n#$, o processo de remover uma folha acaba após no máximo $\log #n#$ passos. @@ -174,7 +174,7 @@ \section{#RedBlackTree#: Uma Árvore 2-4 Simulada} \cppimport{ods/RedBlackTree.RedBlackNode.red.black} Antes e depois de qualquer operação em uma árvore rubro-negra, as -seguinter duas propriedades satisfeitas. Cada propriedade é definida em +seguintes duas propriedades satisfeitas. Cada propriedade é definida em termos das cores vermelha e preta e em termos dos valores numéricos 0 e 1. \begin{prp}[black-height] @@ -211,7 +211,7 @@ \section{#RedBlackTree#: Uma Árvore 2-4 Simulada} \subsection{Árvores Rubro-Negras e Árvores 2-4} -À primeira vista, pode parecer supreendente que uma árvore rubro-negra possa +À primeira vista, pode parecer surpreendente que uma árvore rubro-negra possa ser atualizada eficientemente para manter as propriedades de altura preta e de nenhuma aresta vermelha e parece estranho considerar essas propriedades como úteis. Entretanto, árvores rubro-negras foram projetada para ser uma simulação eficiente das árvores 2-4 na forma de árvores binárias. @@ -258,7 +258,7 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} $#n#\ge 1$. Isso a propriedade mais importante das árvores rubro-negras: \begin{lem} -The altura de uma árvore rubro-negra com #n# nodos é no máximo $2\log #n#$. +A altura de uma árvore rubro-negra com #n# nodos é no máximo $2\log #n#$. \end{lem} Agora que vimos a relação entre as árvores 2-4 e as @@ -290,14 +290,14 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} \figlabel{rb-split} \end{figure} -De modo similar, implemtar +De modo similar, implementar #remove(x)# exige um método de unir dois nodos e empresta um filho de um irmão. -A união de dois nodos é o inverse da repartição (mostrado na +A união de dois nodos é o inverso da repartição (mostrado na \figref{rb-split}), e envolve pintar dois irmãos vermelhos de preto e pintar o pai (que é vermelho) de preto. Emprestar um irmão é o procedimento mais complicado e envolve ambas as rotações e pintar os nodos. Obviamente, durante tudo isso devemos ainda manter a propriedade de nenhuma aresta -vermelha e a propriedade da altura preta. Enquanto não é mais supreendente +vermelha e a propriedade da altura preta. Enquanto não é mais surpreendente que isso pode ser feito, existe um grande número de casos que devem ser considerados se tentarmos fazer uma simulação direta de uma árvore 2-4 com uma árvore rubro-negra. @@ -336,9 +336,9 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} Um nodo de grau quatro se torna um nodo preto com dois filhos vermelhos. Antes de descrevermos a implementação de - #add(x)# e #remove(x)# em detalhes, primeiro apresentamos alguma subrotinas + #add(x)# e #remove(x)# em detalhes, primeiro apresentamos alguma sub-rotinas simples usadas por esses métodos que estão ilustradas na - \figref{redblack-flippullpush}. As duas primeiras subrotinas são para manipular + \figref{redblack-flippullpush}. As duas primeiras sub-rotinas são para manipular as cores e presentar a propriedade da altura preta. O método #pushBlack(u)# recebe como entrada um nodo preto #u# @@ -374,7 +374,7 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} \subsection{Adição} Para implementar -#add(x)# em uma #RedBlackTree#, fazems uma inserção de um +#add(x)# em uma #RedBlackTree#, fazemos uma inserção de um #BinarySearchTree# padrão para adicionar uma nova folha #u#, com $#u.x#=#x#$ e atribuímos $#u.colour#=#red#$. Note que isso não muda a altura preta de nenhum nodo e, portanto, não viola a propriedade de altura preta. Ela pode, entretanto, violar a propriedade de pender à esquerda (se #u# é o filho @@ -483,7 +483,7 @@ \subsection{Remoção} Nós realizamos um right-flip em #w# e então procedemos à próxima iteração. Note que essa ação faz com que o pai de #w# viole a propriedade de pender à esquerda e a -profundidade de #u# aumentar. Entretando, isso também implica que a próxima iteração será no Caso~3 com #w# pintado de vermelho. Ao examinar o Caso~3 a seguir, veremos que o processo irá parar na próxima iteração. +profundidade de #u# aumentar. Entretanto, isso também implica que a próxima iteração será no Caso~3 com #w# pintado de vermelho. Ao examinar o Caso~3 a seguir, veremos que o processo irá parar na próxima iteração. \codeimport{ods/RedBlackTree.removeFixupCase1(u)} \noindent @@ -534,13 +534,13 @@ \subsection{Remoção} Se o filho à esquerda de #v#'s for preto, então #v# viola a propriedade de pender à esquerda e a restauramos com uma chamada a #flipLeft(v)#. Então retornamos o nodo #v# tal que a próxima iteração de -#removeFixup(u)# então constinua com +#removeFixup(u)# então continua com $#u#=#v#$. \codeimport{ods/RedBlackTree.removeFixupCase3(u)}. Cada iteração de #removeFixup(u)# leva tempo constante. Casos~2 e 3 - ou terminam ou movem #u# mais próximo à raiz da árvore. Caso~0 (one #u# é + ou terminam ou movem #u# mais próximo à raiz da árvore. Caso~0 (o #u# é a raiz) sempre termina e Caso~1 levam imediatamente ao Caso~3, que também termina. Como a altura da árvore é no máximo $2\log @@ -612,7 +612,7 @@ \section{Resumo} uma operação de empréstimo aumentando seu potencial total em até um. Para resumir, cada união e repartição faz que o potencial caia por ao menos dois. -Ignorando uniões e repartições, cada adição ou remoção faz com que o potencial aumentar em até três e o potencial sempre é não-negativo. +Ignorando uniões e repartições, cada adição ou remoção faz com que o potencial aumentar em até três e o potencial sempre é não negativo. Portanto, o número de repartições e uniões causado por $m$ adições ou remoções em uma árvore inicialmente vazia é até $3m/2$. @@ -630,7 +630,7 @@ \section{Discussão e Exercícios} árvores rubro-negras mas tem a restrições adicional de que todo nodo tem no máximo um filho vermelho. Isso implica que essas árvores simulam árvores 2-3 em vez de árvores 2-4. Ela são significantemente mais simples que a estrutura #RedBlackTree# apresentada neste capítulo. -Sedgewick \cite{s08} descreve duas versões de árvores rubro-negras pendentes à esquerda. Elas usam recursão juntamente com uma simulação top-down de repartição e união em árvore 2-4. A combinação dessas duas técnicas tornam seus códigos particulamente curtos e elegantes. +Sedgewick \cite{s08} descreve duas versões de árvores rubro-negras pendentes à esquerda. Elas usam recursão juntamente com uma simulação top-down de repartição e união em árvore 2-4. A combinação dessas duas técnicas tornam seus códigos particularmente curtos e elegantes. Uma estrutura de dados relacionada e mais antigos é a \emph{árvore AVL} \cite{avl62}. @@ -729,7 +729,7 @@ \section{Discussão e Exercícios} \begin{exc} Suponha que você é dado uma árvore binária de busca com #n# nodos e uma altura de até - $2\log #n#-2$. É sempre possível colorir os nodos vermelhos e pretos tal que a árcore satisfaz as propriedades da altura preta e de que não há nenhuma aresta vermelha? Se sim, também é possível fazê-la satisfazer a propriedade de pender à esquerda? + $2\log #n#-2$. É sempre possível colorir os nodos vermelhos e pretos tal que a árvore satisfaz as propriedades da altura preta e de que não há nenhuma aresta vermelha? Se sim, também é possível fazê-la satisfazer a propriedade de pender à esquerda? \end{exc} \begin{exc}\exclabel{redblack-merge} @@ -741,7 +741,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Extenda sua solução para \excref{redblack-merge} para o caso onde + Estenda sua solução para \excref{redblack-merge} para o caso onde as duas árvores $T_1$ e $T_2$ têm diferentes alturas pretas, $h_1\neq h_2$. O tempo de execução deve ser From 0fa89c83f1b491bf1c6b33654e369a48ec2f5cb2 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Fri, 11 Sep 2020 22:49:38 -0300 Subject: [PATCH 52/66] finished revision of portuguese version of redblack --- latex/redblack.tex | 253 +++++++++++++++++++++++---------------------- 1 file changed, 129 insertions(+), 124 deletions(-) diff --git a/latex/redblack.tex b/latex/redblack.tex index a7731a9b..51297b94 100644 --- a/latex/redblack.tex +++ b/latex/redblack.tex @@ -1,3 +1,5 @@ +%%merge = união +%%split = repartição \chapter{Árvores Rubro-Negras} \chaplabel{redblack} @@ -7,11 +9,12 @@ \chapter{Árvores Rubro-Negras} \index{árvore rubro-negra}% Neste capítulo, apresentamos as árvores rubro-negras (em inglês, red-black trees), uma versão de árvores binárias de busca com altura logarítmica. -Árvores rubros-negras são as estruturas de dados mais amplamente usadas. +Árvores rubro-negras são as estruturas de dados mais amplamente usadas. Elas aparecerem como estrutura de busca primária em muitas implementações, incluindo a Java Collections Framework e várias implementações da -C++ Standard Template Library. Elas também são usadas dentro do kernel Linux. Existem várias razões para a popularidade das árvores rubro-negras: +C++ Standard Template Library. Elas também são usadas dentro do kernel Linux. +Existem várias razões para a popularidade das árvores rubro-negras: \begin{enumerate} \item Uma árvore rubro-negra com #n# valores tem altura no máximo $2\log #n#$. \item As operações #add(x)# e #remove(x)# em uma árvore rubro-negra rodam em tempo @@ -22,20 +25,18 @@ \chapter{Árvores Rubro-Negras} As duas primeiras propriedades põem árvores rubro-negras a frente de skiplists, treaps e árvores scapegoat. Skiplists e treaps dependem de randomização e seus tempos de execução $O(\log #n#)$ -somente são esperados. Árvores scapegoat tem limitante garantido na altura, mas #add(x)# e #remove(x)# somente rodam em -tempo amortizado $O(\log -#n#)$. A terceira propriedade é apenas a cereja do bolo. Ela nos diz que o tempo necessário para adicionar ou remover um elemento #x# é minúsculo em relação ao tempo que leva para achar #x#. +somente são esperados. Árvores scapegoat têm um limitante garantido na altura, mas #add(x)# e #remove(x)# rodam em tempo amortizado $O(\log #n#)$. +A terceira propriedade é apenas a cereja do bolo. Ela nos diz que o tempo necessário para adicionar ou remover um elemento #x# é minúsculo em relação ao tempo que leva para achar #x#. \footnote{Note que skiplists e -treaps também tem essas propriedade de forma esperada. Veja -Exercícios~\ref{exc:skiplist-changes} e \ref{exc:treap-rotates}.} +treaps também têm essas propriedades de forma esperada. Veja os +exercícios~\ref{exc:skiplist-changes} e \ref{exc:treap-rotates}.} -Entretanto, as boas propriedades de árvores rubro-negras têm um preço: complexidade de implementação. Manter um limitante de -$2\log #n#$ na altura não é fácil. +Entretanto, as boas propriedades de árvores rubro-negras têm um preço: complexidade de implementação. Manter um limitante de $2\log #n#$ na altura não é fácil. Isso exige uma análise cuidadosa de vários casos. Precisamos assegurar que a implementação siga exatamente os passos corretos em cada caso. Uma rotação mal posicionada ou um alteração de cor errada causa um bug que pode ser muito difícil de entender e resolver. -Em vez de pular diretamente à implementação de árvores rubro-negras, +Em vez de pular diretamente à implementação da árvore rubro-negra, primeiro construímos uma base por meio de uma estrutura de dados diretamente relacionada: árvore 2-4. Isso fornecerá entendimento de como árvores rubro-negras foram descobertas e porque é possível manter essa estrutura eficientemente. @@ -44,7 +45,7 @@ \section{Árvore 2-4} Uma árvore 2-4 é uma árvore enraizada com as seguintes propriedades: \begin{prp}[height] - Todas as folhas tem a mesma profundidade. + Todas as folhas têm a mesma profundidade. \end{prp} \begin{prp}[degree] Todo nodo interno tem 2,3 ou 4 filhos. @@ -64,8 +65,7 @@ \section{Árvore 2-4} \begin{proof} O limitante inferior de 2 no número de filhos de um nodo interno - implica que, se - altura de uma árvore 2-4 é $h$, então ela tem pelo menos + implica que, se altura de uma árvore 2-4 for $h$, então ela tem pelo menos $2^h$ folhas. Em outras palavras, \[ #n# \ge 2^h \enspace . @@ -79,7 +79,7 @@ \subsection{Adição de uma Folha} Adicionar uma folha a uma árvore 2-4 é fácil (veja a \figref{twofour-add}). Se queremos adicionar uma folha -#u# com filha de um nodo #w# no penúltimo nível, +#u# coma filha de um nodo #w# no penúltimo nível, então simplesmente fazemos #u# um filho de #w#. Isso certamente mantém a propriedade da altura, mas pode violar a propriedade do grau; se #w# @@ -87,16 +87,13 @@ \subsection{Adição de uma Folha} Neste caso, \emph{repartimos} \index{repartição}% #w# em dois nodos, #w# e #w#', com dois e três filhos, respectivamente. -Mas agora -#w#' não tem pai, -então recursivamente tornamos +Mas agora #w#' não tem pai, então recursivamente tornamos #w#' um filho do pai de #w#. Novamente, isso pode fazer com que o pai de #w# tenha muitos filhos e, dessa forma, teremos que reparti-los. Esse processo segue repetidamente até alcançar um nodo que tem menos que quatro filhos, ou até repartimos a raiz #w# em dois nodos #r# e #r'#. No último caso, fazemos uma nova raiz que tem #r# e #r'# como filhos. -Isso simultaneamente aumenta a - profundidade de todas as folhas e assim mantém propriedade da -altura. +Isso simultaneamente aumenta a profundidade de todas as folhas e +assim mantém propriedade da altura. \begin{figure} \begin{center} @@ -114,14 +111,14 @@ \subsection{Adição de uma Folha} processo de adicionar uma folha termina após no máximo $\log #n#$ passos. -\subsection{Remoção de um Folha} +\subsection{Remoção de uma Folha} Remoção de uma folha de uma árvore 2-4 é um pouco mais complicada (veja a \figref{twofour-remove}). Para remover uma folha #u# de seu pai #w#, então simplesmente a removemos. Se #w# tivesse somente dois filhos antes à remoção de #u#, -então #w# é deixamos com somente um filho o que viola a propriedade de grau. +então #w# é deixado com somente um filho, o que viola a propriedade de grau. \begin{figure} \begin{center} @@ -135,7 +132,7 @@ \subsection{Remoção de um Folha} \end{center} \caption[Remoção de uma folha de uma árvore 2-4]{Remoção de uma folha de uma árvore 2-4. Esse processo segue até a raiz pois cada - ancestral de #u# e seus irmãos tem somente dois filhos.} + ancestral de #u# e seus irmãos têm somente dois filhos.} \figlabel{twofour-remove} \end{figure} @@ -145,7 +142,7 @@ \subsection{Remoção de um Folha} e o transferimos para #w#. Agora #w# tem dois filhos e #w'# tem dois ou três filhos e terminamos o processo. -Por outro lado, se #w'# tem somente dois filhos, então fazemos uma fusão +Por outro lado, se #w'# tem somente dois filhos, então fazemos uma fusão (em inglês, \emph{merge}) \index{fusão}% de #w# e #w'# em um único nodo, #w#, que tem três filhos. A seguir, recursivamente removemos #w'# do pai de #w'#. Esse processo @@ -154,7 +151,7 @@ \subsection{Remoção de um Folha} é deixada com somente um filho, então removemos a raiz e fazemos seu filho a nova raiz. -Novamente, isso simultaneamente reduz a altura de toda folha e portanto +Novamente, isso simultaneamente reduz a altura de todas as folhas e portanto mantém a propriedade de altura. Como a altura da árvore não passa de $\log #n#$, o processo de remover uma folha acaba após no máximo $\log #n#$ passos. @@ -174,7 +171,7 @@ \section{#RedBlackTree#: Uma Árvore 2-4 Simulada} \cppimport{ods/RedBlackTree.RedBlackNode.red.black} Antes e depois de qualquer operação em uma árvore rubro-negra, as -seguintes duas propriedades satisfeitas. Cada propriedade é definida em +seguintes duas propriedades são satisfeitas. Cada propriedade é definida em termos das cores vermelha e preta e em termos dos valores numéricos 0 e 1. \begin{prp}[black-height] @@ -192,13 +189,11 @@ \section{#RedBlackTree#: Uma Árvore 2-4 Simulada} sem violar nenhuma dessas duas propriedades, então iremos assumir que a raiz é preta e os algoritmos para atualizar uma árvore rubro-negra irão manter isso. -Outro truque que simplifica -rubro-negras +Outro truque que simplifica na implementação é tratar os nodos externos (representados por #nil#) como nodos pretos. Dessa forma, todo nodo real, #u#, de uma árvore rubro-negra tem exatamente dois filhos, cada qual com uma cor bem definida. Um exemplo de uma -árvore rubro-negra é mostrado na -\figref{redblack-example}. +árvore rubro-negra é mostrado na \figref{redblack-example}. \begin{figure} \begin{center} @@ -214,10 +209,13 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} À primeira vista, pode parecer surpreendente que uma árvore rubro-negra possa ser atualizada eficientemente para manter as propriedades de altura preta e de nenhuma aresta vermelha e parece estranho considerar essas propriedades como úteis. -Entretanto, árvores rubro-negras foram projetada para ser uma simulação eficiente das árvores 2-4 na forma de árvores binárias. +Entretanto, árvores rubro-negras foram projetadas para serem uma simulação +eficiente das árvores 2-4 na forma de árvores binárias. Veja a \figref{twofour-redblack}. -Considere qualquer árvore rubro-negra, $T$, com #n# nodos e realize a seguinte transformação: remova cada nodo vermelho #u# e conecte os dois filhos de #u# diretamente ao pai (preto) de #u#. +Considere qualquer árvore rubro-negra, $T$, com #n# nodos e realize a seguinte +transformação: remova cada nodo vermelho #u# e conecte os dois filhos +de #u# diretamente ao pai (preto) de #u#. Após essa transformação, a árvore resultante $T'$ possui somente nodos pretos. \begin{figure} \begin{center} @@ -238,8 +236,7 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} Um nodo preto que começou com dois filhos vermelhos terá quatro filhos após essa transformação. Além disso, a propriedade de altura preta garante que todo caminho da raiz até a folha em $T'$ tem o mesmo comprimento. -Em outras palavras, $T'$ é uma -árvore 2-4! +Em outras palavras, $T'$ é uma árvore 2-4! A árvore 2-4 $T'$ tem $#n#+1$ folhas que correspondem a $#n#+1$ nodos externos da árvore rubro-negra. Portanto, essa árvore @@ -247,7 +244,7 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} $\log (#n#+1)$. Agora, todo caminho da raiz para uma folha na árvore 2-4 corresponde a um caminho da raiz da árvore rubro-negra $T$ a um nodo externo. -O primeiro e último nodo nesse caminho são pretos e no máximo de cada dois +O primeiro e último nodo nesse caminho são pretos e no máximo um de cada dois nodos internos é vermelho, então esse caminho tem no máximo $\log(#n#+1)$ nodos pretos e no máximo $\log(#n#+1)-1$ nodos vermelhos. Portanto, o caminho mais longo da raiz para qualquer nodo \emph{interno} em $T$ é no máximo @@ -255,17 +252,16 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} 2\log(#n#+1) -2 \le 2\log #n# \enspace , \] para todo -$#n#\ge 1$. Isso a propriedade mais importante das +$#n#\ge 1$. Isso é a propriedade mais importante das árvores rubro-negras: \begin{lem} A altura de uma árvore rubro-negra com #n# nodos é no máximo $2\log #n#$. \end{lem} -Agora que vimos a relação entre as árvores 2-4 e as +Agora que estudamos a relação entre as árvores 2-4 e as árvores rubro-negras, não é difícil de acreditar que podemos manter eficientemente uma árvore rubro-negra ao adicionar e remover elementos. -Vimos que a adição de elementos em uma - #BinarySearchTree# +Vimos que a adição de elementos em uma #BinarySearchTree# pode ser feita pela adição de uma nova folha. Portanto, para implementar #add(x)# em uma árvore rubro-negra precisamos de um método de simular a repartição de um nodo com cinco filhos em uma @@ -273,8 +269,7 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} um nodo preto que tem dois filhos vermelhos, um dos quais também tem um filho vermelho. Podemos ``reparticionar'' esse nodo pintando ele de vermelho e pintando seus dois filhos de preto. -Um exemplo disso é mostrado na - \figref{rb-split}. +Um exemplo disso é mostrado na \figref{rb-split}. \begin{figure} \begin{center} @@ -285,15 +280,16 @@ \subsection{Árvores Rubro-Negras e Árvores 2-4} \end{tabular} \end{center} \caption[Simulando uma árvore 2-4]{Simulando uma operação de reparticionamento de uma árvore 2-4 durante a adição em uma árvore rubro-negra. (Isso simula - a adição da árvore 2-4 mostrada em - \figref{twofour-add}.)} + a adição da árvore 2-4 mostrada na \figref{twofour-add}.)} \figlabel{rb-split} \end{figure} De modo similar, implementar -#remove(x)# exige um método de unir dois nodos e empresta um filho de um irmão. +#remove(x)# exige um método de unir dois nodos e emprestar um filho de um irmão. A união de dois nodos é o inverso da repartição (mostrado na -\figref{rb-split}), e envolve pintar dois irmãos vermelhos de preto e pintar o pai (que é vermelho) de preto. Emprestar um irmão é o procedimento mais complicado +\figref{rb-split}), e envolve pintar dois irmãos vermelhos de +preto e pintar o pai (que é vermelho) de preto. Emprestar um +irmão é o procedimento mais complicado e envolve ambas as rotações e pintar os nodos. Obviamente, durante tudo isso devemos ainda manter a propriedade de nenhuma aresta @@ -316,7 +312,7 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} Aqui, implementamos uma estrutura de dados que chamamos de #RedBlackTree#. \index{RedBlackTree@#RedBlackTree#}% -Essa estrutura implementar uma variante de árvore rubro-negra que satisfaz +Essa estrutura implementa uma variante de árvore rubro-negra que satisfaz uma propriedade adicional: \begin{prp}[left-leaning]\prplabel{left-leaning}\prplabel{redblack-last} \index{propriedade de pender à esquerda}% @@ -340,11 +336,9 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} simples usadas por esses métodos que estão ilustradas na \figref{redblack-flippullpush}. As duas primeiras sub-rotinas são para manipular as cores e presentar a propriedade da altura preta. - O método -#pushBlack(u)# recebe como entrada um nodo preto #u# + O método #pushBlack(u)# recebe como entrada um nodo preto #u# que tem dois filhos vermelhos e pinta #u# de vermelho e seus dois filhos de preto. -O método -#pullBlack(u)# inverte essa operação +O método #pullBlack(u)# inverte essa operação \codeimport{ods/RedBlackTree.pushBlack(u).pullBlack(u)} \begin{figure} @@ -358,7 +352,7 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} O método #flipLeft(u)# troca as cores de #u# e #u.right# e então realiza uma rotação à esquerda em #u#. -Esse método inverte as cores desses dois nodo assim como seu relacionamento +Esse método inverte as cores desses dois nodos assim como seu relacionamento pai-filho: \codeimport{ods/RedBlackTree.flipLeft(u)} A operação #flipLeft(u)# é especialmente útil ao restaurar a @@ -367,8 +361,8 @@ \subsection{Árvores Rubro-Negras Pendentes à Esquerda} Nesse caso especial, temos a certeza de que essa operação preserva as propriedades de altura preta e de nenhuma aresta vermelha. A operação -#flipRight(u)# é simétrica -#flipLeft(u)#, quando os papéis de esquerda e direita são invertidos. +#flipRight(u)# é simétrica à +#flipLeft(u)#, quando os papéis de esquerda e direita estão invertidos. \codeimport{ods/RedBlackTree.flipRight(u)} \subsection{Adição} @@ -382,12 +376,12 @@ \subsection{Adição} Para restaurar essas propriedades, chamamos o método #addFixup(u)#. \codeimport{ods/RedBlackTree.add(x)} -Ilustrados na \figref{rb-addfix}, o método #addFixup(u)# recebe como entrada +Ilustrado na \figref{rb-addfix}, o método #addFixup(u)# recebe como entrada um nodo #u# cuja cor é vermelha e que pode violar a propriedade de nenhuma -aresta vermelhe e/ou a propriedade pendente à esquerda. +aresta vermelha e/ou a propriedade pendente à esquerda. A discussão a seguir é provavelmente impossível de acompanhar sem -observa a \figref{rb-addfix} ou recriá-la em um papel. -Certamente, o leitor deve estudar essa figura antes de continuar este capítulo. +observar a \figref{rb-addfix} ou recriá-la em um papel. +Certamente, o leitor deve estudar essa figura antes de continuar a ler este capítulo. \begin{figure} \begin{center} @@ -397,8 +391,8 @@ \subsection{Adição} \figlabel{rb-addfix} \end{figure} -Se #u# é a raiz da árvore, então podemos pintar #u# de preto para reestabelecer as duas propriedades. Se -o irmão de #u# também for vermelho, então o pai de #u# precisa ser preto, de forma que as propriedades de pender à esquerda e nenhuma aresta vermelha valham. +Se #u# for a raiz da árvore, então podemos pintar #u# de preto para reestabelecer as duas propriedades. Se +o irmão de #u# também for vermelho, então o pai de #u# precisa ser preto, de forma que as propriedades de pender à esquerda e nenhuma aresta vermelha continuem valendo. Caso contrário, primeiro determinamos se o pai de #u#, que é #w#, viola a propriedade de pender à esquerda e, se esse for o caso, realizamos uma operação @@ -443,7 +437,7 @@ \subsection{Remoção} ao adicionar #w.colour# a #u.colour#. Isso causa dois outros problemas: (1)~se #u# e #w# iniciaram-se pretos, então $#u.colour#+#w.colour#=2$ (preto duplo), que é uma cor inválida. -Se #w# era vermelho, então é substituído por um nodo preto #u#, +Se #w# era vermelho, então é substituído por um nodo preto #u#, o que pode violar a propriedade de pender à esquerda em $#u.parent#$. Esse dois problemas podem ser resolvidos com uma chamada ao método #removeFixup(u)#. @@ -457,8 +451,8 @@ \subsection{Remoção} refere-se à raiz da subárvore que foi alterada. A raiz dessa subárvore pode ter mudado de cor. Em particular, pode ter ido de vermelho para preto, então o método -#removeFixup(u)# terminar ao verificar se -o pai de #u# viola a propriedade de pender à esquerda e, caso positivo, corrigindo esse problema. +#removeFixup(u)# termina ao verificar se +o pai de #u# viola a propriedade de pender à esquerda e, caso positivo, corrige esse problema. \codeimport{ods/RedBlackTree.removeFixup(u)} O método @@ -479,10 +473,10 @@ \subsection{Remoção} Caso 0: #u# é a raiz. Esse é o caso mais fácil de tratar. Repintamos #u# em preto (isso não viola nenhuma das propriedades da árvore rubro-negra). \noindent -Caso 1: o irmão de #u#, #v#, é vermelho. Nesse caso, o irmão de #u# é filho à esquerda de seu pai, #w# (de acordo com a propriedade de pender à esquerda). +Caso 1: o irmão de #u#, #v#, é vermelho. Nesse caso, o irmão de #u# é filho à esquerda de seu pai, #w# (de acordo com a propriedade de pender à esquerda). Nós realizamos um right-flip em #w# e então procedemos à próxima iteração. Note que essa ação -faz com que o pai de #w# viole a propriedade de pender à esquerda e a +faz com que o pai de #w# viole a propriedade de pender à esquerda e faz a profundidade de #u# aumentar. Entretanto, isso também implica que a próxima iteração será no Caso~3 com #w# pintado de vermelho. Ao examinar o Caso~3 a seguir, veremos que o processo irá parar na próxima iteração. \codeimport{ods/RedBlackTree.removeFixupCase1(u)} @@ -494,17 +488,15 @@ \subsection{Remoção} Nesse ponto, #w# não satisfaz a propriedade de pender à esquerda, então chamamos #flipLeft(w)# para arrumar isso. Além disso, #w# é vermelho e #v# é a raiz da subárvore com que começamos. -Precisamos verificar se #w# faz a propriedade de não haver nenhuma aresta vermelha ser violada. Fazer isso inspecionando o filho à direita de #w#, #q#. +Precisamos verificar se #w# faz a propriedade de não haver nenhuma aresta vermelha ser violada. Fazemos isso inspecionando o filho à direita de #w#, #q#. Se #q# é preto, então #w# satisfaz a propriedade de nenhuma aresta vermelha e podemos continuar a próxima iteração com $#u#=#v#$. Caso contrário (#q# é vermelho) então as duas propriedades de não haver nenhuma aresta vermelha e de pender à esquerda são violadas em #q# e #w#, respectivamente. A propriedade de pender à esquerda é restaurada com uma chamada a -#rotateLeft(w)#, mas a propriedade de não haver nenhuma aresta vermelha continua sendo violada. Nesse ponto, #q# é o filho à esquerda de #v#, #w# é o filho à esquerda de #q#, #q# e #w# são vermelhos e #v# é preto ou duplo preto. Uma -#flipRight(v)# torna #q# o pai de -#v# e de #w#. -Seguindo isso por um -#pushBlack(q)# faz #v# +#rotateLeft(w)#, mas a propriedade de não haver nenhuma aresta vermelha continua sendo violada. Nesse ponto, #q# é o filho à esquerda de #v#, #w# é o filho à esquerda de #q#, #q# e #w# são vermelhos e #v# é preto ou duplo preto. +Uma #flipRight(v)# torna #q# o pai de #v# e de #w#. +Em seguida, um #pushBlack(q)# faz #v# e #w# pretos e devolve a cor de #q# de volta à cor original de #w#. Nesse ponto, o nodo duplo preto foi eliminado e as propriedades @@ -517,24 +509,30 @@ \subsection{Remoção} \noindent Caso 3: o irmão de #u# é preto e #u# é o filho à direita de seu pai, #w#. Esse caso é simétrico ao Caso~2 e é tratado quase da mesma forma. -As únicas diferenças vêm do fato que a propriedade de pender à esquerda é assimétrica e, portanto, requer tratamento diferenciado. +As únicas diferenças vêm do fato que a propriedade de +pender à esquerda é assimétrica e, portanto, requer tratamento diferenciado. Como antes, começamos com uma chamada a #pullBlack(w)#, que torna #v# vermelho e #u# preto. Uma chamada a -#flipRight(w)# promove #v# à raiz da subárvore. Nesse momento, #w# é vermelho e existem duas formas de tratamento dependente da cor do filho à esquerda de #w#, #q#. +#flipRight(w)# promove #v# à raiz da subárvore. Nesse momento, +#w# é vermelho e existem duas formas de tratamento +dependendo da cor do filho à esquerda de #w#, #q#. -Se #q# é vermelho, então o código termina exatamente da mesma maneira que o Caso~2, -mas é ainda mais simples pois não há perigo de #v# não satisfazer a propriedade de pender à esquerda. +Se #q# for vermelho, então o código termina exatamente da mesma maneira que o Caso~2, +mas é ainda mais simples pois não há perigo de #v# não +satisfazer a propriedade de pender à esquerda. O caso mais complicado ocorre quando #q# é preto. Nesse caso, examinamos a cor do filho à esquerda de #v#. Se for vermelho, então #v# tem -dois filhos vermelhos e seu extra preto pode ser empurrado abaixo com uma +dois filhos vermelhos e seu preto extra pode ser empurrado abaixo com uma chamada a #pushBlack(v)#. Nesse ponto, #v# agora tem a cor original de #w# e terminamos o processo. +%% -Se o filho à esquerda de #v#'s for preto, então #v# viola a propriedade de pender à esquerda e a restauramos com uma chamada a -#flipLeft(v)#. Então retornamos o nodo #v# tal que a próxima iteração de -#removeFixup(u)# então continua com +Se o filho à esquerda de #v#'s for preto, então #v# viola a propriedade +de pender à esquerda e a restauramos com uma chamada a +#flipLeft(v)#. Então retornamos o nodo #v# para que a próxima iteração de +#removeFixup(u)# então continue com $#u#=#v#$. \codeimport{ods/RedBlackTree.removeFixupCase3(u)}. @@ -544,10 +542,9 @@ \subsection{Remoção} a raiz) sempre termina e Caso~1 levam imediatamente ao Caso~3, que também termina. Como a altura da árvore é no máximo $2\log -#n#$, concluimos o processo em até $O(\log #n#)$ iterações de +#n#$, concluímos o processo em até $O(\log #n#)$ iterações de #removeFixup(u)#, então #removeFixup(u)# roda em tempo $O(\log #n#)$. - \section{Resumo} \seclabel{redblack-summary} @@ -555,37 +552,32 @@ \section{Resumo} #RedBlackTree#: \begin{thm} - Uma #RedBlackTree# implementa a interface #SSet# interface e aceita as - operações - #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log + Uma #RedBlackTree# implementa a interface #SSet# e possui as + operações #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log #n#)$ no pior caso, por operação. \end{thm} -Não incluído no teorema anterior é o extra bônus: +O bônus extra a seguir não está incluído no teorema anterior: \begin{thm}\thmlabel{redblack-amortized} Começando com uma #RedBlackTree# vazia, qualquer sequência de $m$ operações #add(x)# e #remove(x)# resulta em um total de tempo $O(m)$ - durante todas as chamadas - #addFixup(u)# e #removeFixup(u)#. + durante todas as chamadas #addFixup(u)# e #removeFixup(u)#. \end{thm} -Apenas rascunhamos uma prova do -\thmref{redblack-amortized}. Ao comparar +Apenas rascunhamos uma prova do \thmref{redblack-amortized}. Ao comparar #addFixup(u)# e #removeFixup(u)# com os algoritmos para adicionar ou remover uma folha em uma árvore 2-4, podemos nos convencer que essa propriedade é herdada de uma árvore 2-4. Em particular, se podemos mostrar que o tempo total gasto com repartições de nodos, uniões e empréstimos em uma árvore 2-4 é $O(m)$ então isso implica no \thmref{redblack-amortized}. - A prova desse teorema para - a árvore 2-4 usa o método potencial + A prova desse teorema para a árvore 2-4 usa o método potencial \index{método potencial}% -de análise amortizada.\footnote{Veja as prova de -\lemref{dualarraydeque-amortized} e \lemref{selist-amortized} para +de análise amortizada.\footnote{Veja as prova do +\lemref{dualarraydeque-amortized} e do \lemref{selist-amortized} para %%%% outras aplicações do método potencial.} Defina o potencial de um nodo -interno #u# em uma -árvore 2-4 como +interno #u# em uma árvore 2-4 como \[ \Phi(#u#) = \begin{cases} @@ -597,11 +589,10 @@ \section{Resumo} e o potencial de uma árvore 2-4 como a soma dos potenciais de seus nodos. Quando uma repartição ocorre, é porque um nodo com quatro filhos - de torna dois nodos, com dois e três filhos. Isso significa que o potencial geral cai em + se torna dois nodos, com dois e três filhos. Isso significa que o potencial geral cai em $3-1-0 = 2$. Quando uma união ocorre, dois modos que tinham dois filhos são substituídos por um nodo com três filhos. O resultado é uma queda em potencial -de - $2-0=2$. Portanto, para todas repartição ou união, o potencial diminui em dois. +de $2-0=2$. Portanto, para todas repartição ou união, o potencial diminui em dois. A seguir note que se ignorarmos a repartição e união de nodos, há somente um número constante de nodo cujo número de filhos é alterado pela adição @@ -612,27 +603,35 @@ \section{Resumo} uma operação de empréstimo aumentando seu potencial total em até um. Para resumir, cada união e repartição faz que o potencial caia por ao menos dois. -Ignorando uniões e repartições, cada adição ou remoção faz com que o potencial aumentar em até três e o potencial sempre é não negativo. -Portanto, o número de repartições e uniões causado por $m$ +Ignorando uniões e repartições, cada adição ou remoção faz com que o +potencial aumente em até três e o potencial sempre é não negativo. +Portanto, o número de repartições e uniões causados por $m$ adições ou remoções em uma árvore inicialmente vazia é até $3m/2$. -\thmref{redblack-amortized} é uma consequência dessa análise e a correspondência entre árvores 2-4 e árvores rubro-negras. +O \thmref{redblack-amortized} é uma consequência dessa análise e da +correspondência entre árvores 2-4 e árvores rubro-negras. \section{Discussão e Exercícios} -Árvores rubro-negra foi inicialmente desenvolvidas por Guibas e Sedgewick \cite{gs78}. -Embora sua implementação de alta complexidade, ela é encontrada em algumas +A árvore rubro-negra foi inicialmente desenvolvida por Guibas e Sedgewick \cite{gs78}. +Embora sua implementação seja de alta complexidade, ela é encontrada em algumas das bibliotecas e aplicações mais utilizadas. A maior parte dos livros didáticos de algoritmos e estruturas de dados discutem alguma variante de árvores rubro-negras. -Andersson \cite{a93} descreve uma versão pendente à esquerda de árvores balanceadas que é similar às -árvores rubro-negras mas tem a restrições adicional de que todo nodo tem no máximo um filho vermelho. Isso implica que essas árvores simulam árvores 2-3 em vez de árvores 2-4. Ela são significantemente mais simples que a estrutura +Andersson \cite{a93} descreve uma versão pendente à esquerda de árvores +balanceadas que é similar às árvores rubro-negras mas tem a +restrição adicional de que todo nodo tem no máximo um filho vermelho. +Isso implica que essas árvores simulam árvores 2-3 em vez de árvores 2-4. +Ela são significantemente mais simples que a estrutura #RedBlackTree# apresentada neste capítulo. -Sedgewick \cite{s08} descreve duas versões de árvores rubro-negras pendentes à esquerda. Elas usam recursão juntamente com uma simulação top-down de repartição e união em árvore 2-4. A combinação dessas duas técnicas tornam seus códigos particularmente curtos e elegantes. +Sedgewick \cite{s08} descreve duas versões de árvores rubro-negras +pendentes à esquerda. Elas usam recursão juntamente com uma simulação +\emph{top-down} de repartição e união em árvore 2-4. A combinação dessas +duas técnicas tornam seus códigos especialmente curtos e elegantes. -Uma estrutura de dados relacionada e mais antigos é a \emph{árvore AVL} +Uma estrutura de dados relacionada e mais antiga é a \emph{árvore AVL} \cite{avl62}. \index{árvore AVL}% Árvores AVL são \emph{balanceadas na altura}: @@ -666,13 +665,15 @@ \section{Discussão e Exercícios} \begin{center} \includegraphics[scale=0.90909]{figs/avl-rebalance} \end{center} - \caption{Rebalanceamento em um árvore AVL. No máximo duas rotações são necessárias para converter um nodo cujas subárvores tem altura de $h$ e $h+2$ em um nodo cujas subárvores tem altura de até $h+1$} + \caption{Rebalanceamento em uma árvore AVL. No máximo duas rotações são necessárias para converter um nodo cujas subárvores têm alturas de $h$ e $h+2$ em um nodo cujas subárvores têm alturas de até $h+1$} \figlabel{avl-rebalance} \end{figure} As variantes de árvores rubro-negras de Andersson e de Sedgewick e as árvores AVL são mais simples de implementar que a estrutura #RedBlackTree# -aqui definida. Infelizmente nenhuma delas pode garantir que o tempo amortizado gasto rebalanceando é $O(1)$ por atualização. Em particular não há teorema análogo +aqui definida. Infelizmente nenhuma delas pode garantir que o tempo +amortizado gasto rebalanceando é $O(1)$ por atualização. +Em especial não há teorema análogo ao \thmref{redblack-amortized} para essas estruturas. \begin{figure} @@ -682,17 +683,17 @@ \section{Discussão e Exercícios} \end{figure} \begin{exc} - Desenhe a árvore 2-4 que corresponde à #RedBlackTree# em + Desenhe a árvore 2-4 que corresponde à #RedBlackTree# na \figref{redblack-example2}. \end{exc} \begin{exc} Desenhe a adição de 13, então 3.5 e depois 3.3 na #RedBlackTree# - em \figref{redblack-example2}. + na \figref{redblack-example2}. \end{exc} \begin{exc} - Desenhe a remoção de 11, então 9 e depois 5 na #RedBlackTree# em + Desenhe a remoção de 11, então 9 e depois 5 na #RedBlackTree# na \figref{redblack-example2}. \end{exc} @@ -713,9 +714,8 @@ \section{Discussão e Exercícios} \begin{exc} Porque o método #remove(x)# na implementação da #RedBlackTree# realiza - a atribuição - #u.parent=w.parent#? Isso não seria feito pela chamada - #splice(w)#? +a atribuição #u.parent=w.parent#? Isso não seria feito pela chamada +#splice(w)#? \end{exc} \begin{exc} @@ -728,12 +728,15 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Suponha que você é dado uma árvore binária de busca com #n# nodos e uma altura de até - $2\log #n#-2$. É sempre possível colorir os nodos vermelhos e pretos tal que a árvore satisfaz as propriedades da altura preta e de que não há nenhuma aresta vermelha? Se sim, também é possível fazê-la satisfazer a propriedade de pender à esquerda? +Suponha que você tem uma árvore binária de busca com #n# nodos e uma altura de até +$2\log #n#-2$. É sempre possível colorir os nodos vermelhos e pretos +tal que a árvore satisfaz as propriedades da altura preta e de que não +há nenhuma aresta vermelha? Se sim, também é possível fazê-la satisfazer +a propriedade de pender à esquerda? \end{exc} \begin{exc}\exclabel{redblack-merge} - Suponha que você tem duas árvores rubro-negras $T_1$ e $T_2$ que tem a + Suponha que você tem duas árvores rubro-negras $T_1$ e $T_2$ que têm a mesma altura preta , $h$, e tal que a maior chave em $T_1$ é menor que a menor chave em $T_2$. Mostre como juntar $T_1$ e $T_2$ em uma única árvore rubro-negra em @@ -752,19 +755,21 @@ \section{Discussão e Exercícios} Prove que, durante uma operação #add(x)#, uma árvore AVL deve realizar até uma operação de rebalanceamento (que envolve até duas rotações; veja a \figref{avl-rebalance}). Dê um exemplo de uma árvore AVL e uma operação - #remove(x)# nessa árvore que requer na ordem de $\log + #remove(x)# nessa árvore que requer na ordem de $\log #n#$ operações de rebalanceamento. \end{exc} \begin{exc} - Implemente uma classe #AVLTree# que implementa árvores AVL conforme descrito acima. Compare seu desempenho à implementação da - #RedBlackTree# . Qual implementação tem uma operação #find(x)# mais rápida? + Implemente uma classe #AVLTree# que implementa árvores AVL conforme descrito acima. + Compare seu desempenho à implementação da #RedBlackTree#. + Qual implementação tem a operação #find(x)# mais rápida? \end{exc} \begin{exc} - Projete e implemente uma séries de experimentos que comparam o desempenho de + Projete e implemente uma série de experimentos que comparam o desempenho de #find(x)#, #add(x)# e #remove(x)# para as implementações #SSet#: #SkiplistSSet#, - #ScapegoatTree#, #Treap# e #RedBlackTree#. Inclua diferentes cenários de teste, incluindo casos onde os dados são aleatórios, previamente ordenados, são removidos em ordem aleatória, são removidos em ordem crescente e assim por diante. + #ScapegoatTree#, #Treap# e #RedBlackTree#. Inclua diferentes cenários de teste, + incluindo casos onde os dados são aleatórios, previamente ordenados, + são removidos em ordem aleatória, são removidos em ordem crescente + e assim por diante. \end{exc} -%%merge = união -%%split = repartição From 86c6a52230263d685841fe709305c321dae60d81 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sat, 12 Sep 2020 13:29:32 -0300 Subject: [PATCH 53/66] finished revision of portuguese translation of heaps --- latex/heaps.tex | 173 ++++++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 79 deletions(-) diff --git a/latex/heaps.tex b/latex/heaps.tex index 6c437f7f..7c6fd030 100644 --- a/latex/heaps.tex +++ b/latex/heaps.tex @@ -1,34 +1,38 @@ \chapter{Heaps} \chaplabel{heaps} -Neste capítulo, nós discutimos duas implementações da estrutura de dados estremamente útil #Queue# de prioridades. Essas duas estruturas são +Neste capítulo, nós discutiremos duas implementações da estrutura de dados +extremamente útil #Queue# de prioridades. Essas duas estruturas são um tipo especial de árvore binária chamada de \emph{heap}, \index{heap}% \index{heap binária}% \index{heap!binária}% -que significa ``uma pilha desorganizada.'' Isso é em contraste a árvores binárias de busca que podem ser pensadas com pilhas altamente organizadas. +que significa ``uma pilha desorganizada.'' Isso contrasta com árvores binárias +de busca que podem ser pensadas como pilhas altamente organizadas. A primeira implementação da heap usa um array que simula uma árvore binária completa. Essa implementação muito rápida é a base de um dos algoritmos mais rápido de ordenação, chamado de heapsort (veja a \secref{heapsort}). A segunda implementação é baseada em árvores binárias mais flexíveis. -Ela aceita a operação -#meld(h)# que permite que a fila de prioridades absorva os elementos a uma segunda fila de prioridades $h$. +Ela possui a operação +#meld(h)# que permite que a fila de prioridades absorva os elementos +de outra fila de prioridades $h$. \section{#BinaryHeap#: Uma Árvore Binária Implícita} \seclabel{binaryheap} \index{BinaryHeap@#BinaryHeap#}% Nossa primeira implementação de uma - #Queue# (de prioridades) é baseada em uma técnica criada há quatrocentos anos atrás. O \emph{método de Eytzinger} + #Queue# (de prioridades) é baseada em uma técnica criada há quatrocentos anos atrás. + O \emph{método de Eytzinger} \index{método de Eytzinger}% nos permite representar uma árvore binária completa como um array -listando os nodos da árvore em ordem de largura (veja a - \secref{bintree:traversal}). +listando os nodos da árvore em ordem de largura (veja a \secref{bintree:traversal}). Dessa forma, a raiz é guardada na posição 0, o filho à esquerda da raiz é - guardada na posição 1, o filho à direita da raiz na posição 2, o filho à esquerda do filho à esquerda da raiz é guardado na posição 3 e assim por diante. -Veja a -\figref{eytzinger}. + guardada na posição 1, o filho à direita da raiz na posição 2, + o filho à esquerda do filho à esquerda da raiz é guardado na + posição 3 e assim por diante. +Veja a \figref{eytzinger}. \begin{figure} \begin{center} @@ -57,12 +61,13 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} Em uma #BinaryHeap#, os #n# elementos são guardados em um array #a#: \codeimport{ods/BinaryHeap.a.n} -Implementar a operação #add(x)# razoavelmente simples. +Implementar a operação #add(x)# é razoavelmente simples. Assim como todas as estruturas baseadas em array, primeiro verificamos se -#a# está cheio (verificando se - $#a.length#=#n#$) e, caso positivo, expandimos #a#. +#a# está cheio (verificando se $#a.length#=#n#$) e, caso positivo, expandimos #a#. A seguir, posicionamos #x# na posição -#a[n]# e incrementamos #n#. Nesse ponto, resta garantirmos que mantemos a propriedade heap. Fazemos isso repetidamente trocando #x# com seu pai, #x#, até não seja mais menor que seu pai. +#a[n]# e incrementamos #n#. Nesse ponto, resta garantirmos que +mantemos a propriedade heap. Fazemos isso repetidamente trocando #x# com seu pai, +#x#, até não seja mais menor que seu pai. Veja a \figref{heap-insert}. \codeimport{ods/BinaryHeap.add(x).bubbleUp(i)} @@ -78,14 +83,16 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} \end{figure} A implementação da -operação #remove()#, que remove o menor valor da heap é um pouco mais complicado. Sabemos onde o menor valor está (na raiz) mas precisamos substituí-lo após sua remoção e garantir a manutenção da propriedade heap. +operação #remove()#, que remove o menor valor da heap é um pouco mais complicada. +Sabemos onde o menor valor está (na raiz) mas precisamos substituí-lo +após sua remoção e garantir a manutenção da propriedade heap. O modo mais fácil de fazer isso é substituir a raiz com o valor - #a[n-1]#, remover esse valor e decrementar #n#. delete + #a[n-1]#, remover esse valor e decrementar #n#. Infelizmente, o novo elemento na raiz agora provavelmente não é o menor elemento, então ele precisa ser movido para baixo na árvore. Fazemos isso repetidamente, comparando esse elemento aos seus dois filhos. - Se for menor dos três, então paramos. Caso contrário, trocamos esse elemento + Se ele for o menor dos três, então paramos. Caso contrário, trocamos esse elemento com o menor de seus dois filhos e continuamos. \codeimport{ods/BinaryHeap.remove().trickleDown(i)} @@ -109,7 +116,7 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} Felizmente, essa árvore binária está \emph{completa} \index{árvore binária!completa}% \index{árvore binária completa}% -; todo nível exceto o último têm o maior número possível de nodos. +; todo nível exceto o último tem o maior número possível de nodos. Portanto, se a altura dessa árvore é $h$, então ele tem pelo menos $2^h$ nodos. Se outra forma podemos afirmar \[ @@ -119,8 +126,7 @@ \section{#BinaryHeap#: Uma Árvore Binária Implícita} \[ h \le \log #n# \enspace . \] -Portanto, as operações - #add(x)# e #remove()# rodam em $O(\log #n#)$ de tempo. +Portanto, as operações #add(x)# e #remove()# rodam em $O(\log #n#)$ de tempo. \subsection{Resumo} @@ -128,11 +134,10 @@ \subsection{Resumo} \begin{thm}\thmlabel{binaryheap} Uma #BinaryHeap# implementa a interface #Queue# de prioridades. - Ignorando o custo de chamadas ao método #resize()#, uma #BinaryHeap# aceita - as operações - #add(x)# e #remove()# em $O(\log #n#)$ de tempo por operação. + Ignorando o custo de chamadas ao método #resize()#, uma #BinaryHeap# possui + as operações #add(x)# e #remove()# em $O(\log #n#)$ de tempo por operação. Além disso, começando com uma - #BinaryHeap# vazia, qualquer sequência de $m$ operações + #BinaryHeap# vazia, uma sequência de $m$ operações #add(x)# e #remove()# resulta em um total de $O(m)$ de tempo gasto durante todas as chamadas a #resize()#. \end{thm} @@ -144,11 +149,15 @@ \section{#MeldableHeap#: Uma Heap Randomizada Combinável} Nesta seção, descrevemos a #MeldableHeap#, uma implementação da #Queue# com prioridades na qual a estrutura básica também é uma árvore binária do tipo heap ordenada. Entretanto, diferentemente de uma #BinaryHeap# na qual a estrutura básica é completamente definida pelo número de elementos, não há restrições na forma da árvore binária que implementa uma #MeldableHeap#, uma heap combinável; qualquer forma vale. -As operações #add(x)# e #remove()# em uma #MeldableHeap# são implementada -em termos da operação - #merge(h1,h2)#. Essa operação recebe dois nodos de heap #h1# e #h2# os junta, retornando um nodo de heap que é a raiz de uma heap que contém todos os elementos em uma subárvore enraizada em #h1# e todos elementos em uma subárvore enraizada em #h2#. +As operações #add(x)# e #remove()# em uma #MeldableHeap# são implementadas +em termos da operação #merge(h1,h2)#. Essa operação recebe dois +nodos de heap #h1# e #h2# os junta, retornando um nodo de heap que +é a raiz de uma heap que contém todos os elementos em uma subárvore +enraizada em #h1# e todos elementos em uma subárvore enraizada em #h2#. -O lado bom de uma operação #merge(h1,h2)# é que ela pode ser definida recursivamente. Veja a \figref{meldable-merge}. Se #h1# ou #h2# +O lado bom de uma operação #merge(h1,h2)# é que ela pode +ser definida recursivamente. Veja a \figref{meldable-merge}. +Se #h1# ou #h2# forem #nil#, então estaremos combinando com um conjunto vazio, então retornamos #h1# ou #h2#, respectivamente. Por outro lado, assumimos @@ -158,19 +167,19 @@ \section{#MeldableHeap#: Uma Heap Randomizada Combinável} recursivamente combinar #h2# com #h1.left# ou #h1.right#, conforme desejarmos. É nisso que a randomização entra e lançamos uma moeda para decidir -se combinamos #h2# com -#h1.left# ou #h1.right#: +se combinamos #h2# com #h1.left# ou #h1.right#: \codeimport{ods/MeldableHeap.merge(h1,h2)} \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/meldable-merge}} - \caption[A combinação em uma MeldableHeap]{A combinação de #h1# e #h2# é feita pela combinação de #h2# com a subárvore + \caption[A combinação em uma MeldableHeap]{A combinação, operação \emph{merge}, de #h1# e #h2# é feita pela combinação de #h2# com a subárvore #h1.left# ou a subárvore #h1.right#.} \figlabel{meldable-merge} \end{figure} Na próxima seção, veremos que - #merge(h1,h2)# roda em tempo esperado $O(\log #n#)$, onde #n# é o número total + #merge(h1,h2)# roda em tempo esperado $O(\log #n#)$, + onde #n# é o número total de elementos em #h1# e #h2#. Com acesso à operação @@ -179,27 +188,29 @@ \section{#MeldableHeap#: Uma Heap Randomizada Combinável} \codeimport{ods/MeldableHeap.add(x)} Isso leva $O(\log (#n#+1)) = O(\log #n#)$ de tempo esperado. -A operação #remove()# também é fácil. O nodo que queremos remover é a raiz, então simplesmente combinamos seus seu filhos e fazemos o resultado ser a raiz: +A operação #remove()# também é fácil. O nodo que queremos remover é a +raiz, então simplesmente combinamos seus seu filhos e fazemos o resultado ser a raiz: \codeimport{ods/MeldableHeap.remove()} -Novamente, isso leva -$O(\log #n#)$ de tempo esperado. +Novamente, isso leva $O(\log #n#)$ de tempo esperado. -Adicionalmente, uma -#MeldableHeap# pode implementar muitas outras operações em +Adicionalmente, uma #MeldableHeap# pode implementar muitas +outras operações em $O(\log #n#)$ de tempo esperado, incluindo: \begin{itemize} \item #remove(u)#: remove o nodo #u# (e sua chave #u.x#) da heap. \item #absorb(h)#: adiciona todos os elementos de uma #MeldableHeap# #h# para essa heap, esvaziando #h# no processo. \end{itemize} -Cada uma dessas operações pode ser implementada usando um número constante de operações #merge(h1,h2)# que leva $O(\log #n#)$ de tempo experado. +Cada uma dessas operações pode ser implementada usando um número constante de operações #merge(h1,h2)# que leva $O(\log #n#)$ de tempo esperado. -\subsection{Análise de #merge(h1,h2)#} +\subsection{Análise da Operação #merge(h1,h2)#} A análise de -#merge(h1,h2)# é baseada na análise da uma caminhada aleatória em uma árvore binária. Uma \emph{caminhada aleatória} em uma árvore binária inicia-se +#merge(h1,h2)# é baseada na análise da uma caminhada aleatória em uma árvore binária. +Uma \emph{caminhada aleatória} em uma árvore binária inicia-se na raiz da árvore. A cada passo na caminhada aleatória, uma moeda é lançada e, -dependendo do resultado desse lançamento, a caminhada prossegur ao filho à esquerda -ou à direita do nodo atual. Essa caminhada termina quando sai da árvore (o nodo atual torna-se #nil#). +dependendo do resultado desse lançamento, a caminhada prossegue ao filho à esquerda +ou à direita do nodo atual. Essa caminhada termina quando sai da árvore +(o nodo atual torna-se #nil#). O lema a seguir é de certa forma impressionante porque não depende da forma da árvore binária: @@ -210,17 +221,19 @@ \subsection{Análise de #merge(h1,h2)#} \begin{proof} A prova é por indução em #n#. No caso base, $#n#=0$ e a caminhada tem comprimento -$0=\log (#n#+1)$. Suponha que o resultado é verdadeiro para todos os inteiros não negativos $#n#'< #n#$. +$0=\log (#n#+1)$. Suponha que o resultado é verdadeiro para todos + os inteiros não negativos $#n#'< #n#$. Considere que $#n#_1$ denota o tamanho da subárvore à esquerda da raiz tal que -$#n#_2=#n#-#n#_1-1$ seja o tamanho da subárvore à direita da raiz. Iniciando na raiz, a caminhada faz um passo e então continua em uma subárvores de tamanho +$#n#_2=#n#-#n#_1-1$ seja o tamanho da subárvore à direita da raiz. + Iniciando na raiz, a caminhada faz um passo e então continua em uma subárvore de tamanho $#n#_1$ ou $#n#_2$. Pela nossa hipótese indutiva, o comprimento esperado de uma caminhada é então \[ \E[W] = 1 + \frac{1}{2}\log (#n#_1+1) + \frac{1}{2}\log (#n#_2+1) \enspace , \] pois $#n#_1$ e $#n#_2$ são menores que $#n#$. Como $\log$ é uma função côncava, -$\E[W]$ é maximizado quando $#n#_1=#n#_2=(#n#-1)/2$. +$\E[W]$ é maximizada quando $#n#_1=#n#_2=(#n#-1)/2$. %To maximize this, %over all choices of $#n#_1\in[0,#n#-1]$, we take the derivative and obtain %\[ @@ -239,31 +252,33 @@ \subsection{Análise de #merge(h1,h2)#} \end{proof} Fazemos uma rápida digressão para notar que, para leitores que sabem um -pouco sobre teoria da informação, a prova de +pouco sobre teoria da informação, a prova do \lemref{tree-random-walk} pode ser escrita em termos da entropia. -\begin{proof}[Prova baseada em Teoria da Informação da \lemref{tree-random-walk}] - Seja -$d_i$ a profundidade do $i$-ésimo nodo externo e lembre-se que uma árvore binária com #n# nodos tem #n+1# nodos externos. A probabilidade da caminhada aleatória alcançar o $i$-ésimo nodo externo é exatamente +\begin{proof}[Prova baseada em Teoria da Informação do \lemref{tree-random-walk}] + Seja $d_i$ a profundidade do $i$-ésimo nodo externo e + lembre-se que uma árvore binária com #n# nodos tem #n+1# nodos + externos. A probabilidade da caminhada aleatória alcançar o + $i$-ésimo nodo externo é exatamente $p_i=1/2^{d_i}$, então o comprimento esperado da caminhada aleatória é dado por \[ H=\sum_{i=0}^{#n#} p_id_i =\sum_{i=0}^{#n#} p_i\log\left(2^{d_i}\right) = \sum_{i=0}^{#n#}p_i\log({1/p_i}) \] -O lado direito dessa equação é facilmente reconhecível como a entropia da uma distribuição de probabilidade sobre +O lado direito dessa equação é facilmente reconhecível como a + entropia da uma distribuição de probabilidade sobre $#n#+1$ elementos. Um fato básico sobre a entropia de uma distribuição sobre $#n#+1$ elementos é que ela não passa de $\log(#n#+1)$, o que prova o lema. \end{proof} -Com esse resultado em caminhadas aleatórias, podemos facilmente provar que o tempo -de execução da operação -#merge(h1,h2)# é $O(\log #n#)$. +Com esse resultado sobre caminhadas aleatórias, podemos facilmente provar que o tempo +de execução da operação #merge(h1,h2)# é $O(\log #n#)$. \begin{lem} - Senda #h1# e #h2# as raízes de duas heaps contendo $#n#_1$ + Sendo #h1# e #h2# as raízes de duas heaps contendo $#n#_1$ e $#n#_2$ nodos, respectivamente, então o tempo esperado da execução de - #merge(h1,h2)# é até $O(\log #n#)$, onde $#n#=#n#_1+#n#_2$. + #merge(h1,h2)# é $O(\log #n#)$, onde $#n#=#n#_1+#n#_2$. \end{lem} \begin{proof} @@ -280,13 +295,11 @@ \subsection{Análise de #merge(h1,h2)#} \subsection{Resumo} -O teorema a seguir resume o desempenho de uma - #MeldableHeap#: +O teorema a seguir resume o desempenho de uma #MeldableHeap#: \begin{thm}\thmlabel{meldableheap} Uma #MeldableHeap# implementa a interface de uma #Queue# com prioridades. - Uma - #MeldableHeap# aceita as operações #add(x)# e #remove()# em + Uma #MeldableHeap# possui as operações #add(x)# e #remove()# em $O(\log #n#)$ de tempo esperado por operação. \end{thm} @@ -296,7 +309,8 @@ \section{Discussão e Exercícios} parece ter sido proposta pela primeira vez por Eytzinger \cite{e1590}. Ele usou essa representação contendo árvores genealógicas \index{árvores genealógicas}% -de famílias nobres. A estrutura de dados #BinaryHeap# descrita aqui foi descoberta por Williams \cite{w64}. +de famílias nobres. A estrutura de dados #BinaryHeap# descrita +aqui foi descoberta por Williams \cite{w64}. A estrutura de dados randomizada #MeldableHeap# descrita aqui parece ter sido originalmente proposta por @@ -314,27 +328,27 @@ \section{Discussão e Exercícios} heaps de pareamento (em inglês, \emph{pairing heaps}) \cite{fsst86},\ \index{pairing heap}% \index{heap!pairing}% -e heap inclinadas (em inglês, \emph{skew heaps}) \cite{st83}, +e \emph{skew heaps} \cite{st83}, \index{skew heap}% -\index{heap inclinada}% -\index{heap!inclinada}% embora nenhuma dessas seja tão simples quanto a estrutura #MeldableHeap#. Algumas dessas estruturas também aceitam uma operação chamada de #decreaseKey(u,y)#, na qual o valor no nodo #u# é reduziado a #y#. \index{decreaseKey@#decreaseKey(u,y)#}% -(É uma pré-condição que $#y#\le#u.x#$.) Na maior parte desses estruturas, essa operação pode ser rodar em +(É uma pré-condição que $#y#\le#u.x#$.) +Na maior parte desses estruturas, essa operação pode rodar em $O(\log #n#)$ de tempo ao remover o nodo #u# e inserir #y#. Porém, algumas dessas estruturas podem implementar #decreaseKey(u,y)# mais eficientemente. Em especial, #decreaseKey(u,y)# leva $O(1)$ de tempo amortizado em uma heap de Fibonacci e $O(\log\log #n#)$ -de tempo amortizado em uma versão especial de \emph{pairing heaps}\cite{e09}. +de tempo amortizado em uma versão especial das \emph{pairing heaps}\cite{e09}. Essa operação -#decreaseKey(u,y)# mais eficiente tem aplicações em acelerar vários algoritmos de grafos, incluindo o importante algoritmo de encontrar menores caminhos de Dijkstra \cite{ft87}. +#decreaseKey(u,y)# mais eficiente pode ser aplicada para acelerar vários +algoritmos de grafos, incluindo o importante algoritmo de +encontrar menores caminhos de Dijkstra \cite{ft87}. \begin{exc} - Ilustre a adição dos valores 7 e então 3 à - #BinaryHeap# mostrada no final da + Ilustre a adição dos valores 7 e então 3 à #BinaryHeap# mostrada no final da \figref{heap-insert}. \end{exc} @@ -346,28 +360,28 @@ \section{Discussão e Exercícios} \begin{exc} Implemente o método #remove(i)# que remove o valor guardado em #a[i]# em uma #BinaryHeap#. Esse método devem rodar em tempo $O(\log #n#)$. - A seguirm explique porque esse método provavelmente não é útil. + A seguir explique porque esse método provavelmente não é útil. \end{exc} \begin{exc}\exclabel{general-eytzinger} \index{árvore!$d$-ária}% - Uma - árvore $d$-ária é uma generalização de uma árvore binária em que cada nodo interno tem - $d$ filhos. Usando o método de Eytzinger também é possível representar árvores $d$-árias usando arrays. Trabalhe as equações de forma que, dado um índice #i#, determine o pai de #i# e os seus $d$ filhos nessa representação. + Uma árvore $d$-ária é uma generalização de uma árvore binária + em que cada nodo interno tem $d$ filhos. Usando o método de Eytzinger + também é possível representar árvores $d$-árias usando arrays. + Trabalhe as equações de forma que, dado um índice #i#, determine + o pai de #i# e os seus $d$ filhos nessa representação. \end{exc} \begin{exc} \index{DaryHeap@#DaryHeap#}% - Usando o que você aprendeu em - \excref{general-eytzinger}, projete e implemente uma + Usando o que você aprendeu no \excref{general-eytzinger}, + projete e implemente uma \emph{#DaryHeap#}, a generalização $d$-ária de uma #BinaryHeap#. Analise os tempos de execução das operações em uma #DaryHeap# e teste o desempenho da sua implementação comparando-a com a implementação da #BinaryHeap# dada neste capítulo. \end{exc} - - \begin{exc} Ilustre a adição dos valores 17 e então 82 na #MeldableHeap# #h1# mostrada em \figref{meldable-merge}. Use uma moeda @@ -391,10 +405,11 @@ \section{Discussão e Exercícios} \begin{exc} Mostre como achar o $k$-ésimo menor valor em uma - #BinaryHeap# ou - #MeldableHeap# em tempo $O(k\log k)$. (Dica: usar uma outra heap pode ajudar.) + #BinaryHeap# ou #MeldableHeap# em tempo $O(k\log k)$. (Dica: usar uma outra heap pode ajudar.) \end{exc} \begin{exc} - Suponha que você recebe #k# listas ordenadas, de um comprimento total de #n#. Usando uma heap, mostre como combinar essas listas em uma única lista ordenada em tempo $O(n\log k)$ . (Dica: iniciar com o caso $k=2$ pode ser instrutivo.) + Suponha que você recebe #k# listas ordenadas, de um comprimento total de #n#. + Usando uma heap, mostre como combinar essas listas em uma única lista + ordenada em tempo $O(n\log k)$. (Dica: iniciar com o caso $k=2$ pode ser instrutivo.) \end{exc} From e63c459ef3dcebc5fc5969b41eed5f55d513b650 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sat, 12 Sep 2020 13:36:55 -0300 Subject: [PATCH 54/66] Fixed minor tyopos on sorting --- latex/sorting.tex | 70 ++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/latex/sorting.tex b/latex/sorting.tex index 0833fe9d..1c2f7eda 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -1,5 +1,6 @@ \chapter{Algoritmos de Ordenação} \chaplabel{sorting} +%%% TODO verificar consistencia dos nomes dos algoritmos de ordenação: quicksort, heap sort, mergesort.. Este capítulo discute algoritmos para ordenação de um conjunto de #n# itens. Esse pode ser um tópico estranho para um livro em estruturas de dados, @@ -55,7 +56,7 @@ \section{Ordenação Baseada em Comparação} .} Esses algoritmos dependem de qual tipo de dados está sendo ordenado; a única operação que eles fazem nos dados é comparações usando o método -#compare(a,b)#. Lembre-se, de \secref{sset}, +#compare(a,b)#. Lembre-se, do \secref{sset}, que #compare(a,b)# returna um valor negativo se $#a#<#b#$, um valor positivo se $#a#>#b#$ e zero se $#a#=#b#$. @@ -75,7 +76,7 @@ \subsection{Merge-Sort} \javaimport{ods/Algorithms.mergeSort(a,c)} \cppimport{ods/Algorithms.mergeSort(a)} \pcodeimport{ods/Algorithms.mergeSort(a)} -Um exemplo é mostrado em \figref{merge-sort}. +Um exemplo é mostrado na \figref{merge-sort}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/mergesort} @@ -84,7 +85,7 @@ \subsection{Merge-Sort} \figlabel{merge-sort} \end{figure} -Comparada à ordaneção, fazer a junção (merge) de dois arrays ordenados #a0# +Comparada à ordenação, fazer a combinação (merge) de dois arrays ordenados #a0# e #a1# é razoavelmente simples. Adicionamos elemento a #a# um por vez. Se #a0# ou #a1# estão vazios, então adicionamos os próximos elementos do outro array com elementos. @@ -169,7 +170,7 @@ \subsection{Merge-Sort} \begin{equation} \log(x+1/2) + \log(x-1/2) \le 2\log(x) \enspace , \eqlabel{log-ineq-b} \end{equation} -para todo $x\ge 1/2$. A desigualdade~\myeqref{log-ineq-a} vem do fato que $\log(x)+1 = \log(2x)$ enquanto \myeqref{log-ineq-b} segue do fato que $\log$ é uma função côncava. Com essas ferramentas em mãos, para #n# ímpar, +para todo $x\ge 1/2$. A desigualdade~\myeqref{log-ineq-a} vem do fato que $\log(x)+1 = \log(2x)$ enquanto a \myeqref{log-ineq-b} segue do fato que $\log$ é uma função côncava. Com essas ferramentas em mãos, para #n# ímpar, \begin{align*} C(#n#) &\le #n#-1 + C(\lceil #n#/2 \rceil) + C(\lfloor #n#/2 \rfloor) \\ @@ -197,8 +198,7 @@ \subsection{Quicksort} \index{elemento pivot}% #x#, de #a#; particionar #a# em um conjunto de elementos menores que #x#, um conjunto de elementos igual a #x# e um conjunto de elementos maiores que #x#; e, finalmente, ordenar recursivamente o primeiro e o terceiro conjunto dessa partição. -Um exemplo é mostrado no -\figref{quicksort}. +Um exemplo é mostrado na \figref{quicksort}. \javaimport{ods/Algorithms.quickSort(a,c).quickSort(a,i,n,c)} \cppimport{ods/Algorithms.quickSort(a).quickSort(a,i,n)} \pcodeimport{ods/Algorithms.quickSort(a).quickSort(a,i,n)} @@ -234,8 +234,7 @@ \subsection{Quicksort} #j# não é incrementado pois o novo elemento na posição #j# ainda não foi processado. O quicksort é muito ligado às árvores binárias aleatórias de busca -estudadas em -\secref{rbst}. De fato, se a entrada ao quicksort +estudadas na \secref{rbst}. De fato, se a entrada ao quicksort consiste de #n# elementos distintos, então a árvore de recursão do quicksort é uma árvore binária de busca aleatória @@ -250,11 +249,11 @@ \subsection{Quicksort} do array e elementos maiores no final do array. O quicksort então recursivamente ordena o começo do array e o final do array, enquanto a árvore binária de busca aleatória recursivamente insere os -elementos menores na subáres à esquerda da raiz e os maiores elementos +elementos menores na subárvores à esquerda da raiz e os maiores elementos na subárvore à direita da raiz. -Ess correspondência entre as árvores binárias de busca aleatórias e o -quicksort significa que podemos traduzir +Essa correspondência entre as árvores binárias de busca aleatórias e o +quicksort significa que podemos traduzir o \lemref{rbs} para uma afirmação sobre o quicksort: \begin{lem}\lemlabel{quicksort} @@ -274,7 +273,7 @@ \subsection{Quicksort} \begin{proof} Seja $T$ o número de comparações realizadas pelo quicksort ao ordenar -#n# elementos distintos. Usando \lemref{quicksort} e a linearidade da esperança, temos: +#n# elementos distintos. Usando o \lemref{quicksort} e a linearidade da esperança, temos: \begin{align*} \E[T] &= \sum_{i=0}^{#n#-1}(H_{#i#+1}+H_{#n#-#i#}) \\ &= 2\sum_{i=1}^{#n#}H_i \\ @@ -283,7 +282,7 @@ \subsection{Quicksort} \end{align*} \end{proof} -\thmref{quicksort} descreve o caso em que os elementos sendo ordenados são todos distintos. +O \thmref{quicksort} descreve o caso em que os elementos sendo ordenados são todos distintos. Guando o array de entrada, #a#, contém elementos duplicados o tempo esperado do quicksort não é pior e pode ser ainda melhor; toda vez que um elemento duplicado #x# é escolhido como um pivot, @@ -304,19 +303,19 @@ \subsection{Heap sort} \index{heap-sort}% O algoritmo \emph{heap sort} é outro algoritmo de ordenação que funciona \emph{in place}. -O Heap sort usa as heaps binárias discutidas em \secref{binaryheap}. +O Heap sort usa as heaps binárias discutidas na \secref{binaryheap}. Lembre-se que a estrutura de dados #BinaryHeap# representa uma heap usando um único array. O algoritmo heap sort converte o array de entrada #a# em uma heap e então repetidamente extrai o valor mínimo. Mais especificamente, uma heap guarda #n# elementos em um array, #a#, em -em posições do arrau +em posições do array $#a[0]#,\ldots,#a[n-1]#$ com o menor valor guardado na raiz, #a[0]#. Após transformar #a# em uma #BinaryHeap#, o algoritmo heap sort -algorithm repetidamente troca #a[0]# e #a[n-1]#, decrementa #n#, e +repetidamente troca #a[0]# e #a[n-1]#, decrementa #n#, e chama #trickleDown(0)# tal que $#a[0]#,\ldots,#a[n-2]#$ volte a ser uma representação válida de uma heap. Quando esse processo termina (porque $#n#=0$) os elementos de #a# são guardados em ordem decrescente, @@ -324,7 +323,7 @@ \subsection{Heap sort} \footnote{O algoritmo poderia como opção redefinir a função #compare(x,y)# para que o heap sort guarde os elementos diretamente na ordem crescente.} -\figref{heapsort} mostra um exemplo da execução do #heapSort(a,c)#. +A \figref{heapsort} mostra um exemplo da execução do #heapSort(a,c)#. \begin{figure} \begin{center} @@ -381,7 +380,7 @@ \subsection{Heap sort} A penúltima igualdade pode ser obtida ao reconhecer que a soma $\sum_{i=1}^{\infty} i/2^{i}$ é igual, pela definição de valos esperado, ao número esperado de lançamentos de uma moeda, incluindo a primeira vez, cair -do lado cara e aplicar \lemref{coin-tosses}. +do lado cara e aplicar o \lemref{coin-tosses}. O teorema a seguir descreve o desempenho de #heapSort(a,c)#. \begin{thm} @@ -419,7 +418,7 @@ \subsection{Um Limitante Inferior para Ordenação} Estudamos até agora três algoritmos de ordenação baseados em comparação em funcionam em $O(#n#\log #n#)$ de tempo. Nesse momento, podemos nos perguntar se existem algoritmos mais rápidos. A resposta a essa questão é não. Se as únicas operações permitidas nos elementos de #a# são comparações, então nenhum algoritmo pode evitar fazer aproximadamente -$#n#\log #n#$ comparações. Isso não é difícil provar, mas requer um pouc +$#n#\log #n#$ comparações. Isso não é difícil provar, mas requer um pouco de imaginação. No final das contas, isso vêm do fato que \[ \log(#n#!) @@ -427,7 +426,7 @@ \subsection{Um Limitante Inferior para Ordenação} = #n#\log #n# - O(#n#) \enspace . \] -(Fazer a prova desse fato é sugerida como \excref{log-factorial}.) +(Fazer a prova desse fato é sugerida como o \excref{log-factorial}.) Iniciaremos ao focar nossa atenção em algoritmos determinísticos como merge sort e heap sort e em um valor fixo de #n#. Imagine @@ -461,8 +460,7 @@ \subsection{Um Limitante Inferior para Ordenação} #a[w.p[0]]#<#a[w.p[1]]#<\cdots<#a[w.p[n-1]]# \enspace . \] Um exemplo de uma árvore de comparações para um array de tamanho #n=3# -é mostrado em -\figref{comparison-tree}. +é mostrado na \figref{comparison-tree}. \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/comparison-tree} @@ -481,8 +479,7 @@ \subsection{Um Limitante Inferior para Ordenação} senão, então há duas permutações distintas que levam à mesma folha; portanto, o algoritmo não ordena corretamente pelo uma dessas permutações. -Por exemplo, a árvore de comparações em -\figref{comparison-tree-2} tem somente +Por exemplo, a árvore de comparações na \figref{comparison-tree-2} tem somente $4< 3!=6$ folhas. Ao inspecionar essa árvore, vemos que os dois arrays de entrada $3,1,2$ e $3,2,1$ ambos levam à folha mais a direita. Na entrada @@ -557,7 +554,7 @@ \subsection{Um Limitante Inferior para Ordenação} $\{1,\ldots,#n#\}$, então isso é equivalente a selecionar uma folha aleatória #w# dentre as $#n#!$ folhas de $\mathcal{T}(\hat{b})$. -\excref{randomized-lower-bound} pede que você prove que, se selecionarmos +O \excref{randomized-lower-bound} pede que você prove que, se selecionarmos uma folha aleatória de qualquer árvore binária com $k$ folhas, então a profundidade esperada daquela folha é pelo menos $\log k$. Portanto, o número esperado de comparações realizado pelo algoritmo (determinístico) @@ -575,7 +572,7 @@ \subsection{Um Limitante Inferior para Ordenação} \section{Counting Sort e Radix Sort} -Nesta seção estudamos dois algoritmos de ordeanção que não são +Nesta seção estudamos dois algoritmos de ordenação que não são baseados em comparação. Especializados para ordenar valores inteiros baixos, esses algoritmos se esquivam dos limites @@ -607,7 +604,7 @@ \subsection{Counting Sort} vai ser do tipo #c[0]# ocorrências de 0, seguida de #c[1]# ocorrências de 1, seguida de #c[2]# ocorrências de 2,\ldots, seguida de #c[k-1]# ocorrências de #k-1#. -O código que faz isso é bem elegante e sua execução é ilustrada em +O código que faz isso é bem elegante e sua execução é ilustrada na \figref{countingsort}: \codeimport{ods/Algorithms.countingSort(a,k)} @@ -674,8 +671,7 @@ \subsection{Radix sort} (Nesse código, a expressão #(a[i]>>d*p)&((1< Date: Sun, 13 Sep 2020 12:40:08 -0300 Subject: [PATCH 55/66] finished revision of sorting chapter in portuguese --- latex/sorting.tex | 331 ++++++++++++++++++++++++---------------------- 1 file changed, 171 insertions(+), 160 deletions(-) diff --git a/latex/sorting.tex b/latex/sorting.tex index 1c2f7eda..9d875526 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -1,17 +1,18 @@ \chapter{Algoritmos de Ordenação} \chaplabel{sorting} %%% TODO verificar consistencia dos nomes dos algoritmos de ordenação: quicksort, heap sort, mergesort.. +% quicksort, mergesort, heapsort -Este capítulo discute algoritmos para ordenação de um conjunto de #n# itens. -Esse pode ser um tópico estranho para um livro em estruturas de dados, +Este capítulo apresenta algoritmos para ordenação de um conjunto de #n# itens. +Esse pode ser um tópico estranho para um livro sobre estruturas de dados, mas existem várias boas razões para incluí-lo aqui. -A razão mais óbvia é que dois desses algoritmos de ordenação (quicksort e heap-sort) +A razão mais óbvia é que dois desses algoritmos de ordenação (quicksort e heapsort) são intimamente relacionados com duas das estruturas de dados que são estudadas neste livro (árvores binárias de busca aleatórias e heaps). A primeira parte deste capítulo discute algoritmos que ordenam usando somente comparações e apresenta três algoritmos que rodam em $O(#n#\log #n#)$ de tempo. -Acontece que, esses três algoritmos são assintoticamente ótimos; +Acontece que esses três algoritmos são assintoticamente ótimos; nenhum algoritmo que usa somente comparações pode evitar fazer aproximadamente $#n#\log #n#$ comparações no pior caso e mesmo no caso médio. @@ -20,9 +21,9 @@ \chapter{Algoritmos de Ordenação} também podem ser usadas para obter um algoritmo de ordenação $O(#n#\log #n#)$. -Por exemplo, podemos ordenar #n# itens ao fazer #n# +Por exemplo, podemos ordenar #n# itens fazendo #n# operações #add(x)# seguidas de - #n# operações #remove()# em uma #BinaryHeap# ou #MeldableHeap#. + #n# operações #remove()# em uma #BinaryHeap# ou em uma #MeldableHeap#. Alternativamente, podemos usar #n# operações #add(x)# em qualquer estrutura de dados de árvore de busca binária e então realizar uma travessia em ordem @@ -35,7 +36,7 @@ \chapter{Algoritmos de Ordenação} A segunda parte deste capítulo mostra que, se for possível usar outras operações além de comparações, então tudo é possível. Realmente, ao usar indexação por arrays, é possível ordenar um conjunto de #n# inteiros -no intervalo $\{0,\ldots,#n#^c-1\}$ usando somente $O(c#n#)$ de time. +no intervalo $\{0,\ldots,#n#^c-1\}$ usando somente $O(c#n#)$ de tempo. \section{Ordenação Baseada em Comparação} @@ -43,9 +44,9 @@ \section{Ordenação Baseada em Comparação} \index{ordenação baseada em comparação}% \index{sorting algorithm!comparison-based}% \index{algoritmo de ordenação!baseado em comparação}% -Nesta seção, apresentamos três algoritmos: merge-sort, quicksort e heap-sort. +Nesta seção, apresentamos três algoritmos: mergesort, quicksort e heapsort. Cada um desses algoritmos recebe um array de entrada #a# -e ordena os elementos de #a# em ordem não-decrescente em +e ordena os elementos de #a# em ordem não decrescente em $O(#n#\log #n#)$ de tempo (esperado). Esses algoritmos são todos \emph{baseados em comparação}. \javaonly{O segundo argumento, #c#, é um #Comparator# @@ -56,23 +57,22 @@ \section{Ordenação Baseada em Comparação} .} Esses algoritmos dependem de qual tipo de dados está sendo ordenado; a única operação que eles fazem nos dados é comparações usando o método -#compare(a,b)#. Lembre-se, do \secref{sset}, -que #compare(a,b)# returna um valor negativo se $#a#<#b#$, um valor positivo +#compare(a,b)#. Lembre-se, da \secref{sset}, +que #compare(a,b)# retorna um valor negativo se $#a#<#b#$, um valor positivo se $#a#>#b#$ e zero se $#a#=#b#$. -\subsection{Merge-Sort} +\subsection{Mergesort} \seclabel{merge-sort} -\index{merge-sort}% -O algoritmo \emph{merge-sort} é um clássico exemplo do paradigma de divisão e conquista (recursiva): -\index{divide-and-conquer}% +\index{mergesort}% +O algoritmo \emph{mergesort} é um clássico exemplo do paradigma de divisão e conquista (recursiva): \index{divisão e conquista}% Se o tamanho de -#a# for até 1, então #a# está ordenado então não fazemos nada. +#a# for até 1, então #a# está ordenado e não fazemos nada. Caso contrário, dividimos #a# em duas metades, $#a0#=#a[0]#,\ldots,#a[n/2-1]#$ e $#a1#=#a[n/2]#,\ldots,#a[n-1]#$. -Nós recusivamente ordemos #a0# e #a1# e então fazermos o merge dos (agora -ordenados) #a0# e #a1# para ordenar nos array #a# completo: +Nós recursivamente ordenamos #a0# e #a1# e então fazermos o merge (em português, a combinação) dos, agora +ordenados, #a0# e #a1# para ordenar o array #a# completo: \javaimport{ods/Algorithms.mergeSort(a,c)} \cppimport{ods/Algorithms.mergeSort(a)} \pcodeimport{ods/Algorithms.mergeSort(a)} @@ -85,30 +85,30 @@ \subsection{Merge-Sort} \figlabel{merge-sort} \end{figure} -Comparada à ordenação, fazer a combinação (merge) de dois arrays ordenados #a0# -e #a1# é razoavelmente simples. Adicionamos elemento a #a# um por vez. +Comparada à ordenação, fazer a combinação (\emph{merge}) de dois arrays ordenados #a0# +e #a1# é razoavelmente simples. Adicionamos elementos a #a# um por vez. Se #a0# ou #a1# estão vazios, então adicionamos os próximos elementos do outro array com elementos. -Por outro lado, pegamos o mínimo entre o próximo elemento em #a0# e -o próximo elemento em #a1# e adicioná-los a #a#: +Caso contrário, pegamos o mínimo entre o próximo elemento em #a0# e +o próximo elemento em #a1# e o adicionamos a #a#: \javaimport{ods/Algorithms.merge(a0,a1,a,c)} \cppimport{ods/Algorithms.merge(a0,a1,a)} \pcodeimport{ods/Algorithms.merge(a0,a1,a)} Note que o algoritmo -#merge(a0,a1,a,c)# realiza em até $#n#-1$ +#merge(a0,a1,a,c)# realiza até $#n#-1$ comparações antes de acabarem os elementos em #a0# ou #a1#. -Para entender o tempo de execução de merge-sort, é mais fácil pensar +Para entender o tempo de execução do mergesort, é mais fácil pensar em termos de sua árvore de recursão. -Considere por agora que #n# seja uma potência de dois, tal que - $#n#=2^{\log #n#}$ e $\log #n#$ é um inteiro. -Veja \figref{mergesort-recursion}. Merge-sort torna-se o problema de ordenar -#n# elementos em dois problemas, cada qual ordenando $#n#/2$ elementos. -Esse dois subproblemas se tornam em dois problemas cada, a um total de +Considere por agora que #n# seja uma potência de dois, para que + $#n#=2^{\log #n#}$ e $\log #n#$ seja um inteiro. +Veja a \figref{mergesort-recursion}. Mergesort torna-se o problema de ordenar +#n# elementos em dois subproblemas, cada qual ordenando $#n#/2$ elementos. +Esse dois subproblemas se tornam em dois subproblemas cada, totalizando quatro subproblemas, cada um de tamanho - $#n#/4$. Esses quatro subproblemas se torna oito + $#n#/4$. Esses quatro subproblemas se tornam oito subproblemas, cada um de tamanho $#n#/8$, assim por diante. -Na base nesse processo, +Na base desse processo, $#n#/2$ subproblemas, cada um de tamanho dois, são convertidos em #n# problemas, cada de tamanho um. Para cada subproblema de tamanho $#n#/2^{i}$, o tempo gasto fazendo merge e copiando dados é @@ -118,7 +118,7 @@ \subsection{Merge-Sort} \[ 2^i\times O(#n#/2^i) = O(#n#) \enspace . \] -Portanto a tempo total usado pelo merge-sort é +Portanto a tempo total usado pelo mergesort é \[ \sum_{i=0}^{\log #n#} O(#n#) = O(#n#\log #n#) \enspace . \] @@ -126,32 +126,30 @@ \subsection{Merge-Sort} \begin{figure} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/mergesort-recursion} - \caption{A árvore de recusão do merge-sort.} + \caption{A árvore de recursão do mergesort.} \figlabel{mergesort-recursion} \end{center} \end{figure} -A prova do teorma a seguir é baseada na análise discutida anteriormente, -mas tem que ser um pouco cuidadosa para considerar os casos onde #n# -não é uma potência de 2. +A prova do teorema a seguir é baseada na análise discutida anteriormente, +mas tem que ser um pouco cuidadosa para considerar os casos em que #n# +não sejam uma potência de 2. \begin{thm} -O algoritmo \javaonly{#mergeSort(a,c)#}\cpponly{mergeSort(a)}\pcodeonly{merge\_sort(a)} roda em $O(#n#\log #n#)$ de tempo e faz até - $#n#\log #n#$ comparações. +O algoritmo \javaonly{#mergeSort(a,c)#}\cpponly{mergeSort(a)}\pcodeonly{merge\_sort(a)} roda em $O(#n#\log #n#)$ de tempo e faz até $#n#\log #n#$ comparações. \end{thm} \begin{proof} Essa prova é por indução em $#n#$. O caso base, em que $#n#=1$, é trivial; - ao ser apresentado com um array de tamano de 0 ou 1 o algoritmo + ao ser apresentado com um array de tamanho 0 ou 1, o algoritmo retorna sem fazer nenhuma comparação. - Fazer o merge de duas listas ordenadas de tamanho total $#n#$ requer no máximo $#n#-1$ comparações. Seja $C(#n#)$ o número máximo de comparações realizadas por -#mergeSort(a,c)# em um array #a# de tamanho #n#. Se $#n#$ é par, então -aplicamos a hipótese indutiva para os dois subproblemas e obter +#mergeSort(a,c)# em um array #a# de tamanho #n#. Se $#n#$ for par, então +aplicamos a hipótese indutiva para os dois subproblemas e obtemos \begin{align*} C(#n#) &\le #n#-1 + 2C(#n#/2) \\ @@ -162,7 +160,7 @@ \subsection{Merge-Sort} \end{align*} O caso em que $#n#$ é ímpar é ligeiramente mais complicado. Para esse caso, - usamos duas desigualdade que são de fácil verificação: + usamos duas desigualdades que são de fácil verificação: \begin{equation} \log(x+1) \le \log(x) + 1 \enspace , \eqlabel{log-ineq-a} \end{equation} @@ -170,7 +168,8 @@ \subsection{Merge-Sort} \begin{equation} \log(x+1/2) + \log(x-1/2) \le 2\log(x) \enspace , \eqlabel{log-ineq-b} \end{equation} -para todo $x\ge 1/2$. A desigualdade~\myeqref{log-ineq-a} vem do fato que $\log(x)+1 = \log(2x)$ enquanto a \myeqref{log-ineq-b} segue do fato que $\log$ é uma função côncava. Com essas ferramentas em mãos, para #n# ímpar, +para todo $x\ge 1/2$. A desigualdade~\myeqref{log-ineq-a} vem do fato que $\log(x)+1 = \log(2x)$ enquanto a \myeqref{log-ineq-b} segue do fato que $\log$ é uma função côncava. + Com essas ferramentas em mãos, para #n# ímpar, \begin{align*} C(#n#) &\le #n#-1 + C(\lceil #n#/2 \rceil) + C(\lfloor #n#/2 \rfloor) \\ @@ -191,12 +190,13 @@ \subsection{Quicksort} \index{quicksort}% O algoritmo \emph{quicksort} é outro exemplo clássico do paradigma de divisão e conquista. -Diferentemente do merge-sort, que faz merging após resolver os dois subproblemas, +Diferentemente do mergesort, que faz uma combinação após resolver os dois subproblemas, o quicksort faz esse trabalho logo de uma vez. Quicksort é simples de descrever: escolher um elemento aleatório chamado de \emph{pivot}, \index{elemento pivot}% -#x#, de #a#; particionar #a# em um conjunto de elementos menores que #x#, um conjunto de elementos igual a #x# e um conjunto de elementos maiores que #x#; +#x#, de #a#; particionar #a# em um conjunto de elementos menores que #x#, +um conjunto de elementos igual a #x# e um conjunto de elementos maiores que #x#; e, finalmente, ordenar recursivamente o primeiro e o terceiro conjunto dessa partição. Um exemplo é mostrado na \figref{quicksort}. \javaimport{ods/Algorithms.quickSort(a,c).quickSort(a,i,n,c)} @@ -226,17 +226,18 @@ \subsection{Quicksort} \end{cases} \] Esse particionamento, que é feito pelo laço -#while# no código, funciona iterativamente aumento #p# e decrementando #q# enquanto mantém a primeira e últma dessas condições. +#while# no código, funciona iterativamente aumentando #p# e +decrementando #q# enquanto mantém a primeira e última dessas condições. A cada passo, o elemento na posição -#j# é ou movido para a frente, à esquerda do onde está, ou movido para trás. +#j# é movido para a frente, à esquerda do onde está ou movido para trás. Nos dois primeiros casos, #j# é incrementado, enquanto no último caso #j# não é incrementado pois o novo elemento na posição #j# ainda não foi processado. -O quicksort é muito ligado às árvores binárias aleatórias de busca +O quicksort é profundamente ligado às árvores binárias aleatórias de busca estudadas na \secref{rbst}. De fato, se a entrada ao quicksort -consiste de #n# elementos distintos, então a árvore de recursão do quicksort -é uma árvore binária de busca aleatória +consistir de #n# elementos distintos, então a árvore de recursão do quicksort +será uma árvore binária de busca aleatória. Para ver isso, lembre-se que ao construir uma árvore binária de busca aleatória a primeira coisa a fazer é escolher um elemento aleatório #x# e @@ -245,7 +246,7 @@ \subsection{Quicksort} e elementos maiores à direita. No quicksort, escolhemos um elemento aleatório #x# e imediatamente -comparamos todo o array a #x#, colocando os elementos menores no começo +comparamos todo o array a #x#, colocando os elementos menores que ele no começo do array e elementos maiores no final do array. O quicksort então recursivamente ordena o começo do array e o final do array, enquanto a árvore binária de busca aleatória recursivamente insere os @@ -254,15 +255,15 @@ \subsection{Quicksort} Essa correspondência entre as árvores binárias de busca aleatórias e o quicksort significa que podemos traduzir o -\lemref{rbs} para uma afirmação sobre o quicksort: +\lemref{rbs} em uma afirmação sobre o quicksort: \begin{lem}\lemlabel{quicksort} Quando o quicksort é chamado para ordenar um array contendo os inteiros - $0,\ldots,#n#-1$, o número esperado de vezes que o elemento #i# é comparado - ao elemento pivot é até $H_{#i#+1} + H_{#n#-#i#}$. + $0,\ldots,#n#-1$, o número esperado de comparações entre o elemento #i# + e o pivot é até $H_{#i#+1} + H_{#n#-#i#}$. \end{lem} -A soma rápida dos números harmônicos nos dá o seguinte teorema +Uma soma aproximada dos números harmônicos nos dá o seguinte teorema sobre o tempo de execução do quicksort: \begin{thm}\thmlabel{quicksort-i} @@ -272,7 +273,7 @@ \subsection{Quicksort} \end{thm} \begin{proof} -Seja $T$ o número de comparações realizadas pelo quicksort ao ordenar +Seja $T$ o número de comparações realizadas pelo quicksort para ordenar #n# elementos distintos. Usando o \lemref{quicksort} e a linearidade da esperança, temos: \begin{align*} \E[T] &= \sum_{i=0}^{#n#-1}(H_{#i#+1}+H_{#n#-#i#}) \\ @@ -282,46 +283,45 @@ \subsection{Quicksort} \end{align*} \end{proof} -O \thmref{quicksort} descreve o caso em que os elementos sendo ordenados são todos distintos. -Guando o array de entrada, #a#, contém elementos duplicados +O \thmref{quicksort} descreve o caso em que os elementos +sendo ordenados são todos distintos. +Quando o array de entrada, #a#, contém elementos duplicados o tempo esperado do quicksort não é pior e pode ser ainda melhor; toda vez que um elemento duplicado #x# é escolhido como um pivot, -todas as ocorrências de #x# são agrupadas e não participam +todas as ocorrências de #x# estão agrupadas e não participam de nenhum dos dois subproblemas que se seguem. \begin{thm}\thmlabel{quicksort} O método \javaonly{#quickSort(a,c)#} \cpponly{#quickSort(a)#} \pcodeonly{#quickSort(a,c)#} roda em tempo esperado de $O(#n#\log #n#)$ - e o número esperado de comparações que - realiza é em até - $2#n#\ln #n# +O(#n#)$. + e o número esperado de comparações que ele + realiza é $2#n#\ln #n# +O(#n#)$. \end{thm} -\subsection{Heap sort} +\subsection{Heapsort} \seclabel{heapsort} \index{heap-sort}% - O algoritmo \emph{heap sort} é outro algoritmo de ordenação que funciona \emph{in place}. + O algoritmo \emph{heapsort} é outro algoritmo de ordenação que funciona \emph{in place}. -O Heap sort usa as heaps binárias discutidas na \secref{binaryheap}. +O heapsort usa as heaps binárias discutidas na \secref{binaryheap}. Lembre-se que a estrutura de dados #BinaryHeap# representa uma heap usando um único array. -O algoritmo heap sort converte o array de entrada #a# +O algoritmo heapsort converte o array de entrada #a# em uma heap e então repetidamente extrai o valor mínimo. Mais especificamente, uma heap guarda #n# elementos em um array, #a#, em em posições do array $#a[0]#,\ldots,#a[n-1]#$ com o menor valor guardado na raiz, #a[0]#. -Após transformar #a# em uma - #BinaryHeap#, o algoritmo heap sort -repetidamente troca #a[0]# e #a[n-1]#, decrementa #n#, e -chama #trickleDown(0)# tal que $#a[0]#,\ldots,#a[n-2]#$ volte a ser +Após transformar #a# em uma #BinaryHeap#, o algoritmo heapsort +repetidamente troca #a[0]# e #a[n-1]#, decrementa #n# e +chama #trickleDown(0)# para que $#a[0]#,\ldots,#a[n-2]#$ volte a ser uma representação válida de uma heap. Quando esse processo termina (porque $#n#=0$) os elementos de #a# são guardados em ordem decrescente, então #a# é invertido para obter a forma ordenada final. \footnote{O algoritmo poderia como opção redefinir a função -#compare(x,y)# para que o heap sort guarde os elementos +#compare(x,y)# para que o heapsort guarde os elementos diretamente na ordem crescente.} A \figref{heapsort} mostra um exemplo da execução do #heapSort(a,c)#. @@ -329,8 +329,8 @@ \subsection{Heap sort} \begin{center} \includegraphics[scale=0.90909]{figs/heapsort} \end{center} - \caption[Heap sort]{Uma etapa da execução do #heapSort(a,c)#. - A parte sombreada do array já está ordenada. A parte não sombreada é uma + \caption[Heapsort]{Uma etapa da execução do #heapSort(a,c)#. + A parte destacada do array já está ordenada. A parte não destacada é uma #BinaryHeap#. Durante a próxima iteração, o elemento $5$ será colocado na posição $8$ do array.} \figlabel{heapsort} @@ -340,15 +340,17 @@ \subsection{Heap sort} \cppimport{ods/BinaryHeap.sort(b)} \pcodeimport{ods/Algorithms.heapSort(a)} -Uma subrotina chave no heap sort é construtor que transforma um +Uma sub-rotina chave do heapsort é construtor que transforma um array desordenado #a# em uma heap. Seria fácil fazer isso usando $O(#n#\log#n#)$ de tempo ao chamar repetidamente o método da #BinaryHeap# -#add(x)#, mas podemos faer melhor ao usar um algoritmo bottom-up, que atua de baixo para cima no processo, ou seja, dos subarrays menores até os maiores. +#add(x)#, mas podemos fazer melhor ao usar um algoritmo +bottom-up, que atua de baixo para cima no processo, ou seja, +dos subarrays menores até os maiores. Lembre-se que, em uma heap binária, os filhos de #a[i]# são guardados nas posições #a[2i+1]# e #a[2i+2]#. Isso implica que os elementos -$#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ não tem filhos. +$#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ não têm filhos. Em outras palavras, cada um dos elementos em $#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ é uma subheap de tamanho 1. Agora, trabalhando de trás para frente, podemos chamar @@ -356,17 +358,17 @@ \subsection{Heap sort} $#i#\in\{\lfloor #n#/2\rfloor-1,\ldots,0\}$. Isso funciona porque ao chamarmos #trickleDown(i)#, cada um dos dois filhos de #a[i]# - são raizes de uma subheap, então chamar + são raízes de uma subheap, então chamar #trickleDown(i)# torna #a[i]# a raiz de sua própria subheap. \javaimport{ods/BinaryHeap.BinaryHeap(a,c)} \cppimport{ods/BinaryHeap.BinaryHeap(b)} Um fato interessante sobre essa estratégia bottom-up é que ela é mais -eficiente que chamar - #add(x)# #n# vezes. Para ver isso, note que para -$#n#/2$ elementos, não é necessária nenhum operação, para $#n#/4$ elementos, chamamos +eficiente que chamar #add(x)# #n# vezes. Para ver isso, note que para +$#n#/2$ elementos, não é necessária nenhuma operação, para $#n#/4$ elementos, chamamos #trickleDown(i)# em uma subheap com raiz em #a[i]# e cuja altura é 1, para -$#n#/8$ elementos, chamamos #trickleDown(i)# em uma subheap cuja altura é dois, e assim segue. +$#n#/8$ elementos, chamamos #trickleDown(i)# em uma subheap cuja +altura é dois e assim segue. Como o trabalho feito por #trickleDown(i)# é proporcional à altura da subheap enraizada em #a[i]#, isso significa que o trabalho total realizado é até @@ -378,11 +380,11 @@ \subsection{Heap sort} \] A penúltima igualdade pode ser obtida ao reconhecer que a soma -$\sum_{i=1}^{\infty} i/2^{i}$ é igual, pela definição de valos esperado, -ao número esperado de lançamentos de uma moeda, incluindo a primeira vez, cair +$\sum_{i=1}^{\infty} i/2^{i}$ é igual, pela definição de valor esperado, +ao número esperado de lançamentos de uma moeda, incluindo no primeiro lançamento, cair do lado cara e aplicar o \lemref{coin-tosses}. -O teorema a seguir descreve o desempenho de #heapSort(a,c)#. +O teorema a seguir descreve o desempenho do #heapSort(a,c)#. \begin{thm} O método #heapSort(a,c)# roda em $O(#n#\log #n#)$ de tempo e realiza no máximo @@ -401,19 +403,17 @@ \subsection{Heap sort} Passo~2 faz #n# chamadas a #trickleDown(0)#. A #i#-ésima chamada opera em uma heap de tamanho $#n#-i$ e faz até -$2\log(#n#-i)$ comparações. Somando os custos do Passo~2 sobre os possíveis valores de $i$ resulta em +$2\log(#n#-i)$ comparações. Somando os custos do passo~2 sobre os possíveis valores de $i$ resulta em \[ \sum_{i=0}^{#n#-i} 2\log(#n#-i) \le \sum_{i=0}^{#n#-i} 2\log #n# = 2#n#\log #n# \] -Somando o número de comparações realizadas em cada um dos três passos completa a prova. +A soma do número de comparações realizadas em cada um dos três passos completa a prova. \end{proof} \subsection{Um Limitante Inferior para Ordenação} \index{limitante inferior} -\index{lower-bound}% -\index{sorting lower-bound}% \index{limitante inferior para ordenação} Estudamos até agora três algoritmos de ordenação baseados em comparação em funcionam em $O(#n#\log #n#)$ de tempo. Nesse momento, podemos nos perguntar se existem algoritmos mais rápidos. A resposta a essa questão é não. Se as únicas operações permitidas nos elementos @@ -426,10 +426,10 @@ \subsection{Um Limitante Inferior para Ordenação} = #n#\log #n# - O(#n#) \enspace . \] -(Fazer a prova desse fato é sugerida como o \excref{log-factorial}.) +(Fazer a prova desse fato é sugerido como o \excref{log-factorial}.) Iniciaremos ao focar nossa atenção em algoritmos determinísticos como -merge sort e heap sort e em um valor fixo de #n#. Imagine +mergesort e heapsort e em um valor fixo de #n#. Imagine que tal algoritmo está sendo usado para ordenar #n# elementos distintos. A chave para provar o limitante inferior é observar que, para um algoritmo determinístico com valor fixo de #n#, o primeiro par de elementos que @@ -441,20 +441,19 @@ \subsection{Um Limitante Inferior para Ordenação} Uma vez que todos os elementos de entrada são distintos, essa primeira comparação tem somente duas possíveis saídas. A segunda comparação -feita pelo algoritmo depende na saída da primeira comparação. -A terceira comparação depende nos resultados das primeiras duas e assim segue. +feita pelo algoritmo depende da saída da primeira comparação. +A terceira comparação depende dos resultados das primeiras duas e assim segue. Desse jeito, qualquer algoritmo determinístico de ordenação baseado em comparações pode ser visto como uma \emph{árvore de comparações}. \index{árvore de comparações}% -Cada nodo interno, #u#, dessa árvore é marcada com um par de índices #u.i# e -#u.j#. Se -$#a[u.i]#<#a[u.j]#$ o algoritmo segue para a subárvore à esquerda. -caso contrária ela vai para a subárvore à direita. +Cada nodo interno, #u#, dessa árvore é marcado com um par de índices #u.i# e +#u.j#. Se $#a[u.i]#<#a[u.j]#$, o algoritmo segue para a subárvore à esquerda. +caso contrário ela vai para a subárvore à direita. Cada folha #w# dessa árvore é marcada com uma permutação $#w.p[0]#,\ldots,#w.p[n-1]#$ de $0,\ldots,#n#-1$. Essa permutação representa aquilo que é necessário para ordenar #a# -se a árvore de comparação atinge esse folha. +se a árvore de comparação atingir essa folha. Isso é, \[ #a[w.p[0]]#<#a[w.p[1]]#<\cdots<#a[w.p[n-1]]# \enspace . @@ -472,16 +471,16 @@ \subsection{Um Limitante Inferior para Ordenação} A árvore de comparações para um algoritmo de ordenação nos diz tudo sobre o algoritmo. Ela nos diz exatamente a sequência de comparações que será realizada para qualquer array de entrada, #a#, tendo #n# elementos distintos -e nos diz como o algoritmo irá reordenar #a# para organizá-lo. +e nos diz como o algoritmo irá permutar #a# para organizá-lo. -Consequentemente, a árvore de comparação deve ter pelo menos +Consequentemente, a árvore de comparações deve ter pelo menos $#n#!$ folhas; senão, então há duas permutações distintas que levam à mesma folha; portanto, o algoritmo não ordena corretamente pelo uma dessas permutações. Por exemplo, a árvore de comparações na \figref{comparison-tree-2} tem somente $4< 3!=6$ folhas. Ao inspecionar essa árvore, vemos que os dois arrays de entrada -$3,1,2$ e $3,2,1$ ambos levam à folha mais a direita. +$3,1,2$ e $3,2,1$ levam à folha mais a direita. Na entrada $3,1,2$ essa folha corretamente produz $#a[1]#=1,#a[2]#=2,#a[0]#=3$. Entretanto, na entrada @@ -511,7 +510,7 @@ \subsection{Um Limitante Inferior para Ordenação} pelo menos $#n#!$ folhas. Uma prova indutiva fácil mostra que qualquer árvore binária com $k$ folhas tem altura de pelo menos $\log k$. - Portante, a árvore de comparações para + Portanto, a árvore de comparações para $\mathcal{A}$ tem uma folha, #w#, com profundidade de pelo menos $\log(#n#!)$ e existe um array de entradas #a# que leva a essa folha. O array de entrada #a# é uma entrada para qual @@ -520,7 +519,7 @@ \subsection{Um Limitante Inferior para Ordenação} O \thmref{deterministic-sorting-lower-bound} trata de algoritmos determinísticos como -merge-sort e heap-sort, mas não nos diz nada sobre algoritmos randomizados +mergesort e heapsort, mas não nos diz nada sobre algoritmos randomizados como o quicksort. Poderia um algoritmo randomizado usar um número de comparações menor que o limitante inferior $\log(#n#!)$? @@ -622,11 +621,12 @@ \subsection{Counting Sort} ser computados em $O(#n#)$ de tempo com um único #for#. Nesse ponto, poderíamos usar #c# para preencher o array de saída diretamente. -Porém isso não funcionaria se os elementos de #a# tem dados associados. +Porém isso não funcionaria se os elementos de #a# tiverem dados associados. Portanto, gastamos um pouco de esforço extra para copiar os elementos de #a# em #b#. -O próximo laço #for#, que leva $O(#k#)$ de tempo, computa uma soma acumulativa dos contadores tal que +O próximo laço #for#, que leva $O(#k#)$ de tempo, computa uma soma acumulativa +dos contadores tal que #c[i]# se torna o número de elementos em #a# que são menores que ou iguais a #i#. Em particular, para todo $#i#\in\{0,\ldots,#k#-1\}$, o array de saída, #b#, terá @@ -638,8 +638,7 @@ \subsection{Counting Sort} #a[i]=j# é colocado na posição #b[c[j]-1]# e o valor #c[j]# é decrementado. \begin{thm} - O método - #countingSort(a,k)# pode ordenar um array #a# contendo #n# + O método #countingSort(a,k)# ordena um array #a# contendo #n# inteiros no conjunto $\{0,\ldots,#k#-1\}$ em $O(#n#+#k#)$ de tempo. \end{thm} @@ -662,8 +661,7 @@ \subsection{Radix sort} de #w#-bits usando $#w#/#d#$ iterações do counting sort para ordenar esses inteiros considerando somente #d# bits por iteração. \footnote{Assumimos que #d# divide #w#, caso contrário podemos sempre aumentar -#w# para -$#d#\lceil +#w# para $#d#\lceil #w#/#d#\rceil$.} Mais precisamente, o radix sort primeiro ordena os inteiros usando seus #d# bits menos significativos e então seus próximos #d# bits significativos e assim por diante até, na última iteração, os inteiros estão ordenados pelos seus #d# bits mais significativos. @@ -683,138 +681,150 @@ \subsection{Radix sort} Esse algoritmo memorável ordena corretamente porque o counting sort é um algoritmo de ordenação estável. -Se $#x# < #y#$ são dois elementos de #a#, e o bit mais significativo em que #x# difere de #y# tem índice $r$, então #x# será posicionado antes de #y# durante a +Se $#x# < #y#$ são dois elementos de #a#, e o bit mais significativo +em que #x# difere de #y# tem índice $r$, então #x# será posicionado +antes de #y# durante a iteração $\lfloor r/#d#\rfloor$ e as iterações seguintes não alterarão a ordem relativa entre #x# e #y#. -Radix sort usar #w/d# iterações de counting sort. Cada iteração -requer -$O(#n#+2^{#d#})$ de tempo. Portanto, o desempenho do radix sort é dado pelo seguinte teorema. +O radix sort usa #w/d# iterações do algoritmo counting sort. Cada iteração +requer $O(#n#+2^{#d#})$ de tempo. Portanto, o desempenho do +radix sort é dado pelo seguinte teorema. \begin{thm}\thmlabel{radix-sort} Para qualquer inteiro $#d#>0$, o método #radixSort(a,k)# pode ordenar um array #a# contendo #n# inteiros de #w#-bits em $O((#w#/#d#)(#n#+2^{#d#}))$ de tempo. \end{thm} -Alternativamente, se os elementos do array estarem no intervalo -$\{0,\ldots,#n#^c-1\}$, e usarmos $#d#=\lceil\log#n#\rceil$ obtemos -a seguinte versão do - \thmref{radix-sort}. +Alternativamente, se os elementos do array estiverem no intervalo +$\{0,\ldots,#n#^c-1\}$ e usarmos $#d#=\lceil\log#n#\rceil$, então obtemos +a seguinte versão do \thmref{radix-sort}. \begin{cor}\corlabel{radix-sort-poly} O método #radixSort(a,k)# pode ordenar um array #a# contendo #n# valores inteiros no intervalo $\{0,\ldots,#n#^c-1\}$ em $O(c#n#)$ de tempo. \end{cor} \section{Discussão e Exercícios} -Ordenação é \emph{o} problema fundamental em Ciência da Computação e tem uma longa história. +Ordenação é \emph{o} principal problema fundamental em Ciência da Computação +e tem uma longa história. Knuth \cite{k97v3} atribui o algoritmo merge sort a -von~Neumann (1945). Quicksort foi proposto por Hoare \cite{h61}. -O algoritmo heap sort original é de Williams \cite{w64}, mas a +von~Neumann (1945). O quicksort foi proposto por Hoare \cite{h61}. +O algoritmo heapsort original é de Williams \cite{w64}, mas a versão apresentada aqui (na qual a heap é construída bottom-up em -$O(#n#)$ de tempo) foi proposta por Floyd \cite{f64}. Limitantes inferiores para ordenação baseada em comparações parecem ser folclore. +$O(#n#)$ de tempo) foi proposta por Floyd \cite{f64}. Limitantes +inferiores para ordenação baseada em comparações parecem ser folclore. A tabela a seguir resume o desempenho desses algoritmos baseados em comparações: \begin{center} \begin{tabular}{|l|r@{}l@{ }l|l|} \hline & \multicolumn{3}{c|}{comparações} & in place \\ \hline - Merge sort & $#n#\log #n#$ & & pior caso& Não \\ - Quicksort & $1.38#n#\log #n#$ & ${}+ O(#n#)$ & esperado & Sim\\ - Heap sort & $2#n#\log #n#$ & ${}+ O(#n#)$ & pior caso & Sim \\ \hline + mergesort & $#n#\log #n#$ & & pior caso& Não \\ + quicksort & $1.38#n#\log #n#$ & ${}+ O(#n#)$ & esperado & Sim\\ + heapsort & $2#n#\log #n#$ & ${}+ O(#n#)$ & pior caso & Sim \\ \hline \end{tabular} \end{center} Cada um desses algoritmos baseados em comparação tem suas vantagens e desvantagens. -Merge sort faz o menor número de comparações e não precisa de randomização. +O mergesort faz o menor número de comparações e não precisa de randomização. Infelizmente, ele usa um array auxiliar durante sua fase de merge. Alocar esse array pode ser caro e é um ponto de falha se a memória estiver escassa. -Quicksort funciona \emph{in place} +O quicksort funciona \emph{in place} \index{algoritmo in place}% -e é o segundo melhor em termos do número de comparações, mas é randomizado então esse tempo de execução nem sempre é garantido. -Heap sort faz mais comparações mas não usa memória extra e é determinístico. -Existe um cenário em que o merge sort é o vencedor: ao ordenar uma lista ligada. -Nesse caso, o array auxiliar extra não é necessário; duas listas ligadas ordenadas são facilmente juntadas em uma única lista ligada ordenada usando manipulação de ponteiros (ver -\excref{list-merge-sort}). - -Os algoritmos counting sort e radix sort aqui foram apresentador por +e é o segundo melhor em termos do número de comparações, mas é +randomizado então esse tempo de execução nem sempre é garantido. +Heapsort faz mais comparações mas não usa memória extra e é determinístico. +Existe um cenário em que o mergesort é o vencedor: ao ordenar uma lista ligada. +Nesse caso, o array auxiliar extra não é necessário; duas listas +ligadas ordenadas são facilmente juntadas em uma única lista ligada +ordenada usando manipulação de ponteiros (veja o \excref{list-merge-sort}). + +Os algoritmos counting sort e radix sort aqui descritos foram propostos por Seward \cite[Section~2.4.6]{s54}. Porém, variantes do radix sort -têm sido usados desde a década de 1920 para ordenar cartões perfurados usando máquinas de ordenação. -Essas máquinas podem ordenar uma pilha de cartões em duas pilhas usando a existência (ou não) de um furo em uma posição específica no cartão. +têm sido usadas desde a década de 1920 para ordenar cartões +perfurados usando máquinas de ordenação. +Essas máquinas podem ordenar uma pilha de cartões em duas pilhas +usando a existência (ou não) de um furo em uma posição específica no cartão. Ao repetir esse processo para diferentes posições de furos resulta em uma implementação do radix sort. Finalmente, notamos que counting sort e radix sort podem ser usados -para ordenar outros tipos de números além dos inteiros não negativos. +para ordenar outros tipos de números além de inteiros não negativos. Modificações do counting sort podem ordenar inteiros em qualquer intervalo -$\{a,\ldots,b\}$, em $O(#n#+b-a)$ de tempo. De modo similar, radix sort pode ordenar inteiros no mesmo intervalo em +$\{a,\ldots,b\}$, em $O(#n#+b-a)$ de tempo. De modo similar, radix sort +pode ordenar inteiros no mesmo intervalo em $O(#n#(\log_{#n#}(b-a))$ de tempo. Finalmente, esses dois algoritmos também podem ser usados para ordenar números em ponto flutuante no formato IEEE 754. -Isso porque o formato IEEE 754 é projetado para permitir a comparação de dois números ponto flutuantes ao comparar seus valores como se estivessem em uma representação binária de inteiros com bit de sinal. +Isso porque o formato IEEE 754 é projetado para permitir a +comparação de dois números ponto flutuantes ao comparar seus +valores como se estivessem em uma representação binária de inteiros com bit de sinal. \cite{ieee754}. \begin{exc} - Ilustre a execução do merge sort e do heap sort em um array de entrada - contendo $1,7,4,6,2,8,3,5$. Dê um exemplo simples de uma possível execução de quicksort no mesmo array. + Ilustre a execução do mergesort e do heapsort em um array de entrada + contendo $1,7,4,6,2,8,3,5$. Dê um exemplo simples de uma possível + execução do quicksort no mesmo array. \end{exc} \begin{exc}\exclabel{list-merge-sort} - Implementar uma versão do algoritmo merge sort que ordena uma - #DLList# sem usar um array auxiliar (Ver o \excref{dllist-sort}). + Implemente uma versão do algoritmo mergesort que ordena uma + #DLList# sem usar um array auxiliar (Veja o \excref{dllist-sort}). \end{exc} \begin{exc} Algumas implementações do - #quickSort(a,i,n,c)# sempre usa #a[i]# como um pivot. + #quickSort(a,i,n,c)# usam #a[i]# como um pivot. Dê um exemplo de um array de entrada de tamanho #n# no qual tal implementação faria $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} Algumas implementações de - #quickSort(a,i,n,c)# sempre usa #a[i+n/2]# como um pivot. + #quickSort(a,i,n,c)# usam #a[i+n/2]# como um pivot. Dê um exemplo de um array de entrada de tamanho #n# no qual tal implementação faria $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} Mostre que, para qualquer implementação de - #quickSort(a,i,n,c)# que escolha um pivot deterministicamente, sem primeiro - olhar os valores em + #quickSort(a,i,n,c)# que escolha um pivot deterministicamente, + sem primeiro olhar os valores em $#a[i]#,\ldots,#a[i+n-1]#$, existe um array de entrada de tamanho #n# - que faz que essa implementação realize - $\binom{#n#}{2}$ comparações. + que faz que essa implementação realize $\binom{#n#}{2}$ comparações. \end{exc} \begin{exc} - Projete um - #Comparator#, #c#, que você poderia passar como um argumento ao + Projete um #Comparator#, #c#, que você poderia passar como um argumento ao #quickSort(a,i,n,c)# e que faria o quicksort usar - $\binom{#n#}{2}$ comparações. (Dica: O seu comparador não precisa olhar nos valores sendo comparados.) + $\binom{#n#}{2}$ comparações. (Dica: O seu comparador não precisa + olhar nos valores sendo comparados.) \end{exc} \begin{exc} - Analise o número esperado de comparações feitos pelo quicksort - um pouco mais cuidadosamente que a prova - do \thmref{quicksort}. Em particular, mostre que o número esperado - de comparações é $2#n#H_#n# -#n# + H_#n#$. +Analise o número esperado de comparações feitos pelo quicksort +um pouco mais cuidadosamente que a prova +do \thmref{quicksort}. Em particular, mostre que o número esperado +de comparações é $2#n#H_#n# -#n# + H_#n#$. \end{exc} \begin{exc} - Descreva um array de entrada que causa o heap sort realizar pelo menos + Descreva um array de entrada que faz o heapsort realizar pelo menos $2#n#\log #n#-O(#n#)$ comparações. Justifique sua resposta. \end{exc} \javaonly{ \begin{exc} - A implementação do heap sort descrita aqui ordena os elementos + A implementação do heapsort aqui descrita ordena os elementos em ordem reversa e então inverte o array. Esse último passo poderia ser evitar ao definir um novo #Comparator# que nega os resultados - da entrada #Comparator#, #c#. Explique porque isso não seria seria uma boa otimização. (Dica: considere quantas negações seriam necessárias para fazer + da entrada #Comparator#, #c#. Explique porque isso não + seria seria uma boa otimização. (Dica: considere quantas + negações seriam necessárias para fazer em relação a quanto tempo levar a inverter o array.) \end{exc} } \begin{exc} - Achar outro par de permutações de + Ache outro par de permutações de $1,2,3$ que não são corretamente ordenados pela árvore de comparações na \figref{comparison-tree-2}. \end{exc} @@ -828,7 +838,8 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc}\exclabel{randomized-lower-bound} - Prove que, se escolhermos uma folha aleatória de uma árvore binária com $k$ folhas, então a altura esperada dessa folha é pelo menos $\log k$. + Prove que, se escolhermos uma folha aleatória de uma árvore + binária com $k$ folhas, então a altura esperada dessa folha é, pelo menos, $\log k$. % (Hint: Use induction along with the inequality $(k_1/k)\log k_1 + % (k_2/k)\log k_2) \ge \log k-1$, when $k_1+k_2=k$.) \end{exc} From c46a7e8da7d394c372f43d5131f921ae3a03104b Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sun, 13 Sep 2020 18:16:16 -0300 Subject: [PATCH 56/66] finished revision of portuguese translation of graphs --- latex/graphs.tex | 248 +++++++++++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 114 deletions(-) diff --git a/latex/graphs.tex b/latex/graphs.tex index 866e91b5..f7c71ed8 100644 --- a/latex/graphs.tex +++ b/latex/graphs.tex @@ -47,9 +47,13 @@ \chapter{Grafos} \figlabel{graph} \end{figure} -Devido à sua habilidade de modelar tantos fenômenos, grafos tem um número enorme de aplicações. Existem muitos exemplos óbvios. Redes de computadores podem -ser modeladas como grafos, com vértices correspondentes a computadores e arestas correspondentes a links (direcionados) de comunicação entre computadores. -Ruas de cidades podem ser modeladas como grafos, com vértices representando cruzamentos e arestas representando a ligação entre dois cruzamentos consecutivos. +Devido à sua habilidade de modelar tantos fenômenos, grafos têm +um enorme número de aplicações. Existem muitos exemplos óbvios. +Redes de computadores podem ser modeladas como grafos, com vértices +correspondentes a computadores e arestas correspondentes a links +(direcionados) de comunicação entre computadores. +Ruas de cidades podem ser modeladas como grafos, com vértices +representando cruzamentos e arestas representando ruas. Exemplos menos óbvios ocorrem assim que percebemos que grafos podem modelar qualquer relações que ocorrem em pares de elementos de um conjunto. @@ -64,7 +68,7 @@ \chapter{Grafos} de $G$ e #m# para denotar o número de arestas de $G$. Isto é, $#n#=|V|$ e $#m#=|E|$. Além disso, assumiremos que $V=\{0,\ldots,#n#-1\}$. -Outros dados que gostariamos de associar com os elementos de $V$ +Outros dados que gostaríamos de associar com os elementos de $V$ podem ser guardados em um array de tamanho $#n#$. Algumas operações típicas realizadas em grafos são: @@ -78,12 +82,14 @@ \chapter{Grafos} $(#j#,#i#)\in E$ \end{itemize} -Note que essas oepraçoes não são terrivelmente difíceis de implementar eficientemente. +Note que essas operações não são terrivelmente difíceis de implementar eficientemente. Por exemplo, as três primeiras operações podem ser implementadas diretamente -usando um #USet#, de forma que podem ser implementadas em tempo constante esperado usando tabelas hash discutidas no \chapref{hashing}. -As duas últimas operações podem ser implementadas em tempo constante guardando, para cada vértice, uma lista de seu vértices adjacentes. +usando um #USet#, de forma que podem ser implementadas em tempo esperado constante +usando tabelas hash discutidas no \chapref{hashing}. +As duas últimas operações podem ser implementadas em tempo constante +guardando, para cada vértice, uma lista de seu vértices adjacentes. -Entretanto, diferentes aplicações de grafos tem diferentes exigências de desempenho +Entretanto, diferentes aplicações de grafos têm diferentes exigências de desempenho para essas operações e, idealmente, podemos usar a implementação mais simples que satisfaz todos os requisitos da aplicação. Devido essa razão, nós discutimos duas grandes categorias de representações @@ -110,7 +116,7 @@ \section{#AdjacencyMatrix#: Representando um Grafo com um Matriz} Nessa representação, as operações #addEdge(i,j)#, -#removeEdge(i,j)#, e #hasEdge(i,j)# somente atribuir e ler a entrada +#removeEdge(i,j)# e #hasEdge(i,j)# somente atribuem e lêem a entrada da matriz #a[i][j]#: \codeimport{ods/AdjacencyMatrix.addEdge(i,j).removeEdge(i,j).hasEdge(i,j)} @@ -135,44 +141,42 @@ \section{#AdjacencyMatrix#: Representando um Grafo com um Matriz} 11&0&0&0&0&0&0&0&1&0&0&1 &0\\ \end{tabular} \end{center} - \caption{Um grafo e seu matriz de adjacências.} + \caption{Um grafo e sua matriz de adjacências.} \figlabel{graph-adj} \end{figure} A matriz de adjacências tem desempenho ruim com as operações #outEdges(i)# e #inEdges(i)#. Para implementá-las, precisamos varrer todas as #n# entradas na correspondente linha ou coluna de #a# e reunir todos os índices #j# -onde #a[i][j]#, respectivamente #a[j][i]#, seja true. +onde #a[i][j]#, respectivamente #a[j][i]#, seja verdadeiro. \javaimport{ods/AdjacencyMatrix.outEdges(i).inEdges(i)} \cppimport{ods/AdjacencyMatrix.outEdges(i,edges).inEdges(i,edges)} -Essas operações claramente levam -$O(#n#)$ de tempo por operação. +Essas operações claramente levam $O(#n#)$ de tempo por operação. Outra desvantagem da matriz de adjacências é o seu tamanho. Ela guarda uma matriz booleana de tamanho $#n#\times #n#$, o que exige o uso de pelo menos $#n#^2$ bits de memória. A implementação aqui usa uma matriz de valores \javaonly{#boolean#}\cpponly{#bool#} tal que ela na verdade usa na ordem de -of $#n#^2$ bytes de memória. Uma implementação mais cuidadosa, que empacota #w# valores booleanos em cada palavra de memória, poderia reduzir esse uso de espaço a -$O(#n#^2/#w#)$ palavras de memória. +$#n#^2$ bytes de memória. Uma implementação mais cuidadosa, que empacota +#w# valores booleanos em cada palavra de memória, poderia reduzir esse +uso de espaço a $O(#n#^2/#w#)$ palavras de memória. \begin{thm} A estrutura de dados #AdjacencyMatrix# implementa a interface #Graph#. -Uma #AdjacencyMatrix# aceita as operações +Uma #AdjacencyMatrix# possui as operações \begin{itemize} \item #addEdge(i,j)#, #removeEdge(i,j)# e #hasEdge(i,j)# em tempo constante por operação; e \item #inEdges(i)# e #outEdges(i)# em $O(#n#)$ tempo por operações. \end{itemize} -O espaço usado por uma -#AdjacencyMatrix# é $O(#n#^2)$. +O espaço usado por uma #AdjacencyMatrix# é $O(#n#^2)$. \end{thm} Apesar de alto uso de memória e desempenho ruim das operações #inEdges(i)# e #outEdges(i)#, uma #AdjacencyMatrix# pode ainda ser útil para algumas aplicações. Em especial, quando o grafo $G$ é \emph{denso}, -ele tem quase - $#n#^2$ arestas, então um uso de memória de $#n#^2$ pode +ele tem quase $#n#^2$ arestas, então um uso de memória de $#n#^2$ pode ser aceitável. A estrutura de dados #AdjacencyMatrix# também é frequentemente usada @@ -187,8 +191,7 @@ \section{#AdjacencyMatrix#: Representando um Grafo com um Matriz} #a^2[i][j]# = \sum_{k=0}^{#n#-1} #a[i][k]#\cdot #a[k][j]# \enspace . \] Interpretando essa soma em termos do grafo $G$, essa fórmula conta o -número de vértices, -$#k#$, tal que $G$ contém as arestas #(i,k)# +número de vértices, $#k#$, tal que $G$ contém as arestas #(i,k)# e #(k,j)#. Isto é, isso conta o número de caminhos de $#i#$ a $#j#$ (por meio de vértices intermediários, $#k#$) cujo comprimento é exatamente dois. Essa observação é a fundação de um algoritmo que computa os caminhos @@ -200,22 +203,20 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \seclabel{adjacency-list} \index{listas de adjacências}% -Representações de grafos com \emph{listas de adjacências} é uma abordagem centrada nos vértices. +Representações de grafos usando \emph{listas de adjacências} é uma abordagem centrada nos vértices. Existem muitas implementações possíveis de listas de adjacências. Nesta seção, apresentamos uma simples. No final da seção, discutimos diferentes possibilidades. Em uma representação de listas de adjacências, -o grafo -$G=(V,E)$ é representado como um array #adj# de listas. A lista +o grafo $G=(V,E)$ é representado como um array #adj# de listas. A lista #adj[i]# contém uma lista de vértices adjacentes ao vértice #i#. Isto é, ela contém todo índice #j# tal que $#(i,j)#\in E$. \codeimport{ods/AdjacencyLists.adj.n.AdjacencyLists(n0)} (Uma exemplo é mostrado na \figref{graph-adjlist}.) -Nessa implementação em especial, nós representamos cada lista em #adj# como +Nessa implementação em especial, representamos cada lista em #adj# como \javaonly{uma}\cpponly{uma -subclasse de} #ArrayStack#, porque gostariamos acesso pela posição em tempo constante. -Especificamente, poderíamos implementar -#adj# como uma #DLList#. - +subclasse de} #ArrayStack#, porque gostaríamos acesso pela posição +em tempo constante. +Especificamente, poderíamos implementar #adj# como uma #DLList#. \begin{figure} \begin{center} @@ -243,8 +244,7 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \codeimport{ods/AdjacencyLists.removeEdge(i,j)} Isso leva $O(\deg(#i#))$ de tempo, onde $\deg(#i#)$ (o \emph{grau} \index{grau}% -de -$#i#$) conta o número de arestas em $E$ que tem $#i#$ como origem. +de $#i#$) conta o número de arestas em $E$ que têm $#i#$ como origem. A operação #hasEdge(i,j)# é similar: ela busca ao longo da lista @@ -261,19 +261,20 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \cppimport{ods/AdjacencyLists.outEdges(i,edges)} \javaonly{Isso claramente leva tempo constante.}\cpponly{Isso leva $O(\deg(#i#))$ de tempo.} -A operação -#inEdges(i)# é muito mais trabalhosa. Ela varre todo vértice $j$ verificando se a aresta #(i,j)# existe e, caso positivo, adicionad #j# a uma lista de saída: +A operação #inEdges(i)# é muito mais trabalhosa. Ela varre +todo vértice $j$ verificando se a aresta #(i,j)# existe e, +caso positivo, adiciona #j# a uma lista de saída: \pcodeimport{ods/AdjacencyLists.inEdges(i)} \javaimport{ods/AdjacencyLists.inEdges(i)} \cppimport{ods/AdjacencyLists.inEdges(i,edges)} -Essa operação é muito lenta. Ela verifica toda lista de adjacência de todos os vértices, então leva -$O(#n# + #m#)$ de tempo. +Essa operação é muito lenta. Ela verifica toda lista de adjacência de todos +os vértices, então leva $O(#n# + #m#)$ de tempo. O teorema a seguir resume o desempenho das estruturas de dados anteriormente descrita: \begin{thm} A estrutura de dados #AdjacencyLists# implementa a interface #Graph#. -Uma #AdjacencyLists# aceita as operações +Uma #AdjacencyLists# possui as operações \begin{itemize} \item #addEdge(i,j)# em tempo constante por operação; \item #removeEdge(i,j)# e #hasEdge(i,j)# em tempo $O(\deg(#i#))$ por operação; @@ -284,7 +285,7 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} O espaço usado por uma #AdjacencyLists# é $O(#n#+#m#)$. \end{thm} -Conforme mencionado anteriormente, existem muitas diferentes escolhas que +Conforme mencionado anteriormente, existem muitas escolhas que podem ser feitas ao implementar um grafo como listas de adjacências. Algumas escolhas incluem: \begin{itemize} @@ -300,7 +301,11 @@ \section{#AdjacencyLists#: Um Grafo como uma Coleção de Listas} \section{Travessia em Grafos} -Nesta seção, apresentamos dois algoritmos para explorar um grafo, iniciando em um de seus vértices #i# e encontrando todos os vértices que são alcançáveis a partir de #i#. Esses dois algoritmos são mais adequados para grafos representação usando listas de adjacências. Portanto, ao analisar esses algoritmos iremos supor que a representação é uma +Nesta seção, apresentamos dois algoritmos para explorar um grafo, +iniciando em um de seus vértices #i# e encontrando todos os vértices +que são alcançáveis a partir de #i#. Esses dois algoritmos são mais +adequados para grafos representação usando listas de adjacências. +Portanto, ao analisar esses algoritmos iremos supor que a representação é uma #AdjacencyLists#. \subsection{Busca em Largura} @@ -309,17 +314,22 @@ \subsection{Busca em Largura} \index{busca em largura}% O algoritmo de \emph{busca em largura} inicia-se em um vértice #i# e visita primeiramente os vizinhos de #i# e, então, os vizinhos dos vizinhos de #i# e depois os vizinhos dos vizinhos dos vizinhos de #i# e assim por diante. -Esse algoritmo é uma generalização do algoritmo de travessia em largura para árvores binárias (\secref{bintree:traversal}), e é muito similar a ele; +Esse algoritmo é uma generalização do algoritmo de travessia em +largura para árvores binárias (\secref{bintree:traversal}), e é muito similar a ele; utiliza-se uma queue, #q#, que inicialmente contém somente #i#. -Então repetidamente extrai um elemento de #q# e adiciona seus vizinhos a #q#, dado que esses vizinhos nunca estiveram em #q# antes. -A maior diferença entre o algoritmo de busca em largura para grafos e o de árvores é que o algoritmo para grafos tem que garantir que não vai adicionar o mesmo vértice a #q# mais de uma vez. - -Isso é feito usando um array booleano auxiliar, #seen# (vistos, em português), que guarda quais vértices foram descubertos até o momento. +Então repetidamente extrai um elemento de #q# e adiciona seus +vizinhos a #q#, dado que esses vizinhos nunca estiveram em #q# antes. +A maior diferença entre o algoritmo de busca em largura para grafos +e o de árvores é que o algoritmo para grafos tem que garantir +que não vai adicionar o mesmo vértice a #q# mais de uma vez. + +Isso é feito usando um array booleano auxiliar, #seen# (vistos, em português), +que guarda quais vértices foram descobertos até o momento. \codeimport{ods/Algorithms.bfs(g,r)} Um exemplo de execução #bfs(g,0)# no grafo da \figref{graph} é mostrado na \figref{graph-bfs}. Execuções diferentes são possíveis, dependendo da ordem das listas de adjacências; a -\figref{graph-bfs} usa as listas de adjacências em \figref{graph-adjlist}. +\figref{graph-bfs} usa as listas de adjacências da \figref{graph-adjlist}. \begin{figure} \begin{center} @@ -329,13 +339,13 @@ \subsection{Busca em Largura} \figlabel{graph-bfs} \end{figure} -Analisar o tempo de execução da rotina -#bfs(g,i)# razoavelmente fácil. -O uso do array -#seen# assegura que nenhum vértice é adicionado a #q# mais de uma vez. Adicionar (e depois remover) cada vértice de #q# leva tempo constante por vértice para um total de tempo +Analisar o tempo de execução da rotina #bfs(g,i)# é razoavelmente fácil. +O uso do array #seen# assegura que nenhum vértice é +adicionado a #q# mais de uma vez. Adicionar (e depois remover) +cada vértice de #q# leva tempo constante por vértice, levando a um total de tempo $O(#n#)$. Como cada vértice é processado pelo laço interno no máximo uma vez, -cada lista de adjacência é processadas no máximo uma vez, então cada aresta de +cada lista de adjacência é processada no máximo uma vez, então cada aresta de $G$ é processada somente uma vez. Esse processamento, que é feito pelo laço interno, leva tempo constante por iteração, totalizando em $O(#m#)$ de tempo. Portanto, o algoritmo por inteiro roda em @@ -344,38 +354,44 @@ \subsection{Busca em Largura} O teorema a seguir resume o desempenho do algoritmo #bfs(g,r)#. \begin{thm}\thmlabel{bfs-graph} - O algoritmo #bfs(g,r)# roda, para um #Graph# #g# implementado usando - a estrutura de dados #AdjacencyLists#, em tempo - $O(#n#+#m#)$. + O algoritmo #bfs(g,r)# roda, em um #Graph# #g# implementado usando + a estrutura de dados #AdjacencyLists#, em tempo $O(#n#+#m#)$. \end{thm} -Uma travessia em largura tem algumas propriedades especiais. Chamar -#bfs(g,r)# irá eventualmente enfileirar (eventualmente desenfileirar) todo vértice -#j# tal que existe um caminho direto de #r# para #j#. -Além disso, os vértices na uma distância 0 a partir de #r# (o próprio #r#) irá entrar em #q# antes dos vértices com distância 1, que entrarão em #q# antes dos vértices com distância 2 e assim por diante. Portanto, - -o método #bfs(g,r)# visita vértices em ordem crescente de distância de #r# e vértices que não podem ser alcançados a partir de #r# nunca serão visitados de nenhuma forma. +Uma travessia em largura tem algumas propriedades especiais. +Chamar #bfs(g,r)# irá eventualmente enfileirar +(eventualmente desenfileirar) todo vértice +#j#, tal que existe um caminho direto de #r# para #j#. +Além disso, os vértices com distância 0 a partir de #r# (o próprio #r#) irá entrar em #q# antes dos vértices com distância 1, que entrarão em #q# antes dos vértices com distância 2 e assim por diante. Portanto, +o método #bfs(g,r)# visita vértices em ordem crescente de distância de #r# e os vértices que não podem ser alcançados a partir de #r# nunca serão visitados. Uma aplicação especialmente útil do algoritmo de busca em largura é, portanto, na computação dos menores caminhos. Para computar o menor caminho de #r# a todo outro vértice, usamos uma variante de -#bfs(g,r)# que usa uma array auxiliar #p# de comprimento #n#. +#bfs(g,r)# que usa um array auxiliar #p# de comprimento #n#. Quando um novo vértice #j# é adicionado a #q#, fazemos #p[j] =i#. Dessa forma, #p[j]# se torna o penúltimo nodo no caminho mais curto de #r# a #j#. Ao repetir isso fazendo -#p[p[j]#, #p[p[p[j]]]#, e assim por diante podemos reconstruir o caminho mais curto (invertido) de #r# a #j#. +#p[p[j]]#, #p[p[p[j]]]# e assim por diante podemos reconstruir +o caminho mais curto (invertido) de #r# a #j#. \subsection{Busca em Profundidade} O algoritmo de \emph{busca em profundidade} \index{busca em profundidade}% é similar ao algoritmo padrão para percorrer árvores binárias; -ele primeiro completamente explora uma subárvore antes de retornar ao nodo atual e então explora outra subárvore. Outra forma de pensar na busca em profundidade é dizer que é similar à busca em largura exceto que ele usa uma pilha em vez de uma fila. +ele primeiro explora completamente uma subárvore antes de retornar +ao nodo atual e então explora outra subárvore. Outra forma de +pensar na busca em profundidade é dizer que é similar +à busca em largura exceto que ele usa uma pilha em vez de uma fila. Durante a execução do algoritmo de busca em profundidade, cada vértice #i# é atribuído a uma cor #c[i]#: #white# se nunca encontramos o vértice antes -#grey# se estamos visitando o vértice, e #black# se terminamos de visitar o vértice. -O jeito mais fácil de pensar da busca em profundidade é na forma de um algoritmo recursivo. Ele inicia com uma visita a #r#. Ao visitar um vértice #i#, primeiro -marcamos #i# como #cinza#. Depois, varremos a lista de adjência de #i# e recursivamente visitamos qualquer vértice branco que não achamos nessa lista. +#grey# se estamos visitando o vértice e #black# se terminamos de visitar o vértice. +O jeito mais fácil de pensar da busca em profundidade é +na forma de um algoritmo recursivo. Ele inicia com uma visita a #r#. +Ao visitar um vértice #i#, primeiro +marcamos #i# como #cinza#. Depois, varremos a lista de adjacência +de #i# e recursivamente visitamos qualquer vértice branco que não achamos nessa lista. Finalmente, terminamos o processamento de #i# e então pintamos #i# de preto e retornamos. \codeimport{ods/Algorithms.dfs(g,r).dfs(g,i,c)} @@ -389,50 +405,52 @@ \subsection{Busca em Profundidade} \figlabel{graph-dfs} \end{figure} -Embora busca em profundidade possa ser melhor pensada como um algoritmo recursivo, +Embora a busca em profundidade possa ser melhor pensada como um algoritmo recursivo, usar recursão não é a melhor forma de implementá-la. -Realmente, o código dado acima irá falhar para muitos grafos grandes causando +Realmente, o código dado acima irá falhar para grafos grandes causando uma estouro da pilha de memória, um \emph{stack overflow}. -Uma implementação alternativa troca a pilha de recusão por uma pilha explícita #s#. +Uma implementação alternativa troca a pilha de recursão +por uma pilha explícita #s#. A implementação a seguir faz exatamente isso: \codeimport{ods/Algorithms.dfs2(g,r)} No código anterior, quando o vértice a seguir #i# é processado, #i# é pintado -de -#grey# e então substituído na pilha com seus vértices adjacentes. +de #grey# e então substituído na pilha com seus vértices adjacentes. Durante a próxima iteração, um desses vértices será visitado. -Pouco supreendentemente, os tempos de execução de +Pouco surpreendentemente, os tempos de execução de #dfs(g,r)# e #dfs2(g,r)# são os mesmos que os de #bfs(g,r)#: \begin{thm}\thmlabel{dfs-graph} -Ao procesar um #Graph# g que é implementado usando a estrutura de dados +Ao processar um #Graph# g que é implementado usando a estrutura de dados #AdjacencyLists#, os algoritmos #dfs(g,r)# e #dfs2(g,r)# rodam em tempo $O(#n#+#m#)$. \end{thm} -Assim como com o algoritmo de busca em largura, existe uma árvore associada com cada +De modo similar ao algoritmo de busca em largura, existe uma árvore associada com cada execução da busca em profundidade. Quando um nodo $#i#\neq #r#$ vai de #white# a #grey#, é porque #dfs(g,i,c)# foi chamado recursivamente ao processar algum nodo #i'#. (No caso do algoritmo #dfs2(g,r)#, #i# é um dos nodos que substituem #i'# na pilha.) Se pensarmos de #i'# como o pai de #i#, então obtemos uma árvore enraizada em #r#. Na \figref{graph-dfs}, essa árvore é um caminho do vértice 0 ao vértice 11. -vertex 0 to vertex 11. Uma propriedade importante do algoritmo de busca em profundidade é a seguinte: suponha que quando um nodo #i# é pintado de #grey#, existe um caminho de #i# a algum outro nodo #j# que usa somente vértices brancos. Então #j# -será pintado primeiro de #grey# e depois de #preto# antes que #i# seja pintado de #black#. -(Isso pode ser provado por contradição, ao considerar qualquer caminho $P$ de #i# -para #j#.) +será pintado primeiro de #grey# e depois de #preto# antes +que #i# seja pintado de #black#. +(Isso pode ser provado por contradição, ao considerar +qualquer caminho $P$ de #i# para #j#.) Uma aplicação dessa propriedade é a detecção de ciclos. \index{detecção de ciclos}% Veja a -\figref{dfs-cycle}. Considere algum ciclo $C$, que pode ser alcançado a partir de #r#. Seja #i# o primeiro nodo de $C$ que está em #grey# e seja +\figref{dfs-cycle}. Considere algum ciclo $C$, que pode ser +alcançado a partir de #r#. Seja #i# o primeiro nodo de $C$ +que é pintado de #grey# e seja #j# o nodo que precede #i# no ciclo $C$. -Então, pela propriedade acima, #j# irá ser colorido #grey# e a aresta #(j,i)# -será considerada pelo algoritmo enquanto #i# ainda é #grey#. Portanto, +Então, pela propriedade acima, #j# será pintado de #grey# e a aresta #(j,i)# +será considerada pelo algoritmo enquanto #i# ainda for #grey#. Portanto, o algoritmo pode concluir que existe um caminho $P$ partindo de #i# a #j# na árvore de busca em profundidade e que a aresta #(j,i)# existe. Portanto, $P$ também é um ciclo. @@ -441,7 +459,10 @@ \subsection{Busca em Profundidade} \begin{center} \includegraphics[scale=0.90909]{figs/dfs-cycle} \end{center} - \caption[Detecção de ciclos]{O algoritmo de busca em profundidade pode ser usado para detectar ciclos em $G$. O nodo #j# é pintado de #grey# enquanto #i# ainda for #grey#. Isso implica que existe um caminho $P$ de $i$ a $j$ na árvore de busca em profundidade e a aresta + \caption[Detecção de ciclos]{O algoritmo de busca em profundidade pode + ser usado para detectar ciclos em $G$. O nodo #j# está #grey# + enquanto #i# ainda for #grey#. Isso implica que existe um caminho $P$ + de $i$ a $j$ na árvore de busca em profundidade e a aresta #(j,i)# implica que $P$ também é um ciclo.} \figlabel{dfs-cycle} \end{figure} @@ -449,29 +470,34 @@ \subsection{Busca em Profundidade} \section{Discussão e Exercícios} Os tempos de execução dos algoritmos da busca em profundidade e da busca em largura -são de certa forma exagerados pelos Teoremas~ -Teoremas~\ref{thm:bfs-graph} e -\ref{thm:dfs-graph}. Defina $#n#_{#r#}$ como sendo o número de vértices #i# de $G$ para os quais existem um caminho de #r# a #i#. -Defina $#m#_#r#$ como o número de arestas que tem esses vértices como suas origens. +são de certa forma exagerados pelos Teoremas~\ref{thm:bfs-graph} e +\ref{thm:dfs-graph}. Defina $#n#_{#r#}$ como sendo o número de +vértices #i# de $G$ para os quais existem um caminho de #r# a #i#. +Defina $#m#_#r#$ como o número de arestas que têm esses +vértices como suas origens. Então o teorema a seguir é mais preciso na descrição dos tempos de execução dos algoritmos de busca em largura e em profundidade. -(Essa teorema mais preciso do tempo de execução é útil em algumas das aplicações de algoritmos dos exercícios.) +(Esse teorema mais preciso do tempo de execução é útil em algumas +das aplicações descritas nos exercícios deste capítulo.) \begin{thm}\thmlabel{graph-traversal} Ao processar um #Graph#, #g#, que é implementado usando a estrutura de dados #AdjacencyLists#, os algoritmos #bfs(g,r)#, #dfs(g,r)# e #dfs2(g,r)# - rodam em $O(#n#_{#r#}+#m#_{#r#})$ de tempo. + rodam em tempo $O(#n#_{#r#}+#m#_{#r#})$. \end{thm} -Busca em largura parece ter sido descuberta independentemente por -Moore \cite{m59} e Lee \cite{l61} nos contextos de exploração de labirintos e roteamente de circuitos, respectivamente. +Busca em largura parece ter sido descoberta independentemente por +Moore \cite{m59} e Lee \cite{l61} nos contextos de exploração de +labirintos e roteamento de circuitos, respectivamente. Representações de grafos baseadas em listas de adjacências foram propostas -por -Hopcroft e Tarjan \cite{ht73} como uma alternativa à (então mais comum) representação baseada em matriz de adjacências. -Essa representação, assim como busca em profundidade, desempenhou um papel importante no famoso algoritmo de teste de planaridade de Hopcroft-Tarjan +por Hopcroft e Tarjan \cite{ht73} como uma alternativa à +(então mais comum) representação baseada em matriz de adjacências. +Essa representação, assim como busca em profundidade, desempenhou +um papel importante no famoso algoritmo de teste de planaridade de Hopcroft-Tarjan \index{teste de planaridade}% -que pode determinar, em tempo $O(#n#)$ se um grafo pode ser desenhado no plano de forma que nenhum par de arestas se cruzem \cite{ht74}. +que pode determinar, em tempo $O(#n#)$ se um grafo pode ser +desenhado no plano de forma que nenhum par de arestas se cruzem \cite{ht74}. Nos exercícios a seguir, um grafo não direcionado é um em que, para todo #i# e #j#, a aresta @@ -482,8 +508,7 @@ \section{Discussão e Exercícios} \begin{exc} Desenhe uma representação baseada em uma lista de adjacências e uma - baseada em matriz de adjacências do grafo na -\figref{graph-example2}. + baseada em matriz de adjacências do grafo na \figref{graph-example2}. \end{exc} \begin{figure} @@ -507,15 +532,15 @@ \section{Discussão e Exercícios} \item Desenhe a representação baseada em matriz de incidência do grafo na \figref{graph-example2}. \item Projete, analise e implemente uma representação baseada em matriz de incidência de um grafo. Analise o custo de espaço e o custo de tempo das operações - #addEdge(i,j)#, #removeEdge(i,j)#, #hasEdge(i,j)#, #inEdges(i)#, + #addEdge(i,j)#, #removeEdge(i,j)#, #hasEdge(i,j)#, #inEdges(i)# e #outEdges(i)#. \end{enumerate} \end{exc} \begin{exc} Desenhe uma execução do algoritmos - #bfs(G,0)# e #dfs(G,0)# no grafo $#G#$, - in \figref{graph-example2}. + #bfs(G,0)# e #dfs(G,0)# no grafo $#G#$ + da \figref{graph-example2}. \end{exc} \begin{exc} @@ -524,13 +549,12 @@ \section{Discussão e Exercícios} Seja um grafo não direcionado $G$. Dizemos que $G$ é \emph{conectado} se, para todo par de vértices #i# e #j# em $G$, existe um caminho de $#i#$ a $#j#$ (como $G$ não é direcionado, também existe um caminho de #j# a #i#). - Mostre como testar se $G$ é conectado em tempo - $O(#n#+#m#)$. + Mostre como testar se $G$ é conectado em tempo $O(#n#+#m#)$. \end{exc} \begin{exc} \index{componentes conectados}% - Seja $G$ um grafo não direcionado. \emph{Marcações de componentes conectados} (em inglês, \emph{connected-component labelling}) de $G$ particiona os vértices de $G$ em conjuntos maximais, cada qual forma um subgrafo conectado. Mostre como computar as marcações de componentes conectos de $G$ em + Seja $G$ um grafo não direcionado. \emph{Marcações de componentes conectados} (em inglês, \emph{connected-component labelling}) de $G$ particiona os vértices de $G$ em conjuntos maximais, cada qual forma um subgrafo conectado. Mostre como computar as marcações de componentes conectados de $G$ em $O(#n#+#m#)$ de tempo. \end{exc} @@ -538,15 +562,14 @@ \section{Discussão e Exercícios} \index{floresta geradora}% Seja $G$ um grafo não direcionado. Uma \emph{floresta geradora} de $G$ é uma coleção de árvores, uma por componente, cujas arestas são arestas de $G$ e cujos vértices contêm todos os vértices de $G$. Mostre como computar - uma floresta geradora de $G$ em - $O(#n#+#m#)$ de tempo. + uma floresta geradora de $G$ em $O(#n#+#m#)$ de tempo. \end{exc} \begin{exc} \index{grafo fortemente contectado}% \index{grafo!fortemente conectado}% Dizemos que um grafo $G$ é \emph{fortemente conectado} se, para todo - par de vértices #i# e #j# em $G$, existe um caminho de + par de vértices #i# e #j# em $G$, existir um caminho de $#i#$ para $#j#$. Mostre como testar se $G$ é fortemente conectado em $O(#n#+#m#)$ de tempo. \end{exc} @@ -554,13 +577,13 @@ \section{Discussão e Exercícios} \begin{exc} Dado um grafo $G=(V,E)$ e algum vértice especial $#r#\in V$, mostre como computar o comprimento do caminho mais curto a partir de - $#r#$ para #i# para todo vértice - $#i#\in V$. + $#r#$ para #i# para todo vértice $#i#\in V$. \end{exc} \begin{exc} - Ache um exemplo (simples) em que o código #dfs(g,r)# visita os nodos de um grafo em uma ordem que é diferente daquela do código - do algoritmo #dfs2(g,r)#. + Ache um exemplo (simples) em que o código #dfs(g,r)# visita os + nodos de um grafo em uma ordem que é diferente daquela + do código do algoritmo #dfs2(g,r)#. Escreva uma versão de #dfs2(g,r)# que sempre visita nodos exatamente na mesma ordem que #dfs(g,r)#. (Dica: simplesmente inicie verificando a execução de cada algoritmo em algum grafo onde #r# é a fonte de mais de 1 aresta.) @@ -573,11 +596,8 @@ \section{Discussão e Exercícios} $#n#-1$ arestas e a origem de nenhuma aresta. \footnote{Uma universal sink, #v#, é também às vezes chamada de \emph{celebridade}: Todos na sala reconhecem #v#, mas #v# não reconhece ninguém na sala.} - Projete e implemente um algoritmo que esta se um grafo $G$, representado por - uma - #AdjacencyMatrix#, tem uma universal sink. O seu algoritmo deve rodar em - $O(#n#)$ de tempo. + Projete e implemente um algoritmo que testa se um grafo $G$, representado por + uma #AdjacencyMatrix#, tem uma \emph{universal sink}. + O seu algoritmo deve rodar em $O(#n#)$ de tempo. \end{exc} - - From af3cb3bb01871b9cc02407f6d59c7a1fcd8dcc7d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 14 Sep 2020 09:05:10 -0300 Subject: [PATCH 57/66] finished revision of portuguese version of integers.tex --- latex/integers.tex | 252 ++++++++++++++++++++++----------------------- 1 file changed, 123 insertions(+), 129 deletions(-) diff --git a/latex/integers.tex b/latex/integers.tex index 0e4cb1e9..694c0a54 100644 --- a/latex/integers.tex +++ b/latex/integers.tex @@ -13,7 +13,7 @@ \chapter{Estruturas de Dados para Inteiros} #SSet# em tempo $O(#w#)$. Isso não é muito impressionante, pois qualquer subconjunto de $\{0,\ldots,2^{#w#}-1\}$ tem tamanho $#n#\le 2^{#w#}$, tal que -$\log #n# \le #w#$. As outras implementações de #SSet# discutidas neste livro tem operações que rodam em +$\log #n# \le #w#$. Outras implementações de #SSet# discutidas neste livro têm operações que rodam em $O(\log #n#)$ de tempo sendo tão rápidas quanto uma #BinaryTrie#. @@ -27,9 +27,8 @@ \chapter{Estruturas de Dados para Inteiros} #YFastTrie#, usa uma #XFastTrie# para guardar somente uma amostra de aproximadamente um a cada $#w#$ elementos e guarda os elementos restantes em uma estrutura padrão #SSet#. -Esse truque reuz o tempo de execução de #add(x)# e #remove(x)# a -$O(\log #w#)$ e reduz o espaço a -$O(#n#)$. +Esse truque reduz o tempo de execução de #add(x)# e #remove(x)# a +$O(\log #w#)$ e reduz o espaço a $O(#n#)$. As implementações usadas como exemplos neste capítulo podem guardar qualquer tipo de dado, desde que um inteiro possa ser associado a ele. Nos trechos @@ -43,7 +42,10 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} \index{BinaryTrie@#BinaryTrie#}% Uma #BinaryTrie# codifica um conjunto de inteiros com #w# bits em uma árvore binária. -Todas as folhas em uma árvore tem profundidade #w# e cada inteiro é codificado como um caminho da raiz para uma folha. O caminho para o inteiro #x# vai para a esquerda no nível #i# se o #i#-ésimo bit mais significativo de #x# for um 0 e vai para a direita se for um 1. +Todas as folhas em uma árvore têm profundidade #w# e cada inteiro é +codificado como um caminho da raiz para uma folha. O caminho para o +inteiro #x# vai para a esquerda no nível #i# se o #i#-ésimo bit +mais significativo de #x# for um 0 e vai para a direita se for um 1. A \figref{binarytrie-ex} mostra um exemplo para o caso $#w#=4$, no qual a trie guarda inteiros 3(0011), 9(1001), 12(1100) e 13(1101). @@ -65,17 +67,18 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} Para uma folha em uma trie binária, #u.child[0]# (#prev#) é o nodo que antecede #u# na lista e #u.child[1]# (#next#) é o nodo que sucede #u# na lista. - Um nodo especial, #dummy#, é usado tanto antes do primeiro nodo quanto depois do último nodo na lista (veja a \secref{dllist}). -\cpponly{Nos trechos de código, #u.child[0]#, #u.left#, e #u.prev# referem-se ao mesmo campo no nodo #u#, assim como #u.child[1]#, #u.right# e #u.next#.} + Um nodo especial, #dummy#, é usado tanto antes do primeiro nodo + quanto depois do último nodo na lista (veja a \secref{dllist}). +\cpponly{Nos trechos de código, #u.child[0]#, #u.left# e #u.prev# +referem-se ao mesmo campo no nodo #u#, assim como #u.child[1]#, #u.right# e #u.next#.} Cada nodo, #u#, também possui um ponteiro adicional #u.jump#. Se o filho à esquerda da #u# não existe, então #u.jump# aponta para a menor folha na subárvore de #u#. Se o filho à direita de #u# não existe então #u.jump# aponta - para o maior folha na subárvore de #u#. Um exemplo de uma -#BinaryTrie#, mostrando ponteiros #jump# a lista duplamente ligada na folhas -é mostrado na -\figref{binarytrie-ex2}. + para a maior folha na subárvore de #u#. Um exemplo de uma +#BinaryTrie#, mostrando ponteiros #jump# e a lista duplamente ligada na folhas +é mostrado na \figref{binarytrie-ex2}. \begin{figure} \begin{center} @@ -93,8 +96,11 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} folha, então achamos #x#. Se alcançarmos um nodo #u# de onde não podemos prosseguir (porque #u# está sem um filho), então seguimos #u.jump#, que nos leva à menor folha maior que #x# ou à maior folha menor que #x#. Qual desses dois casos -ocorrerá depende em se #u# não tem seu filho à esquerda ou direita, respectivamente. No caso em que #u# não tem seu filho à esquerda, achamos o nodo que queríamos. No caso em que #u# não tem seu filho à direita, podemos usar a lista ligada para alcançar o nodo que queremos. Cada um desses casos é ilustrado na -\figref{binarytrie-find}. +ocorrerá depende em se #u# não tem seu filho à esquerda ou direita, +respectivamente. No caso em que #u# não tem seu filho à esquerda, +achamos o nodo que queríamos. No caso em que #u# não tem seu filho +à direita, podemos usar a lista ligada para alcançar o nodo que +queremos. Cada um desses casos é ilustrado na \figref{binarytrie-find}. \codeimport{ods/BinaryTrie.find(x)} \begin{figure} \begin{center} @@ -103,7 +109,8 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} \caption[Caminhos de busca em uma BinaryTrie]{Os caminhos usados por #find(5)# e #find(8)#.} \figlabel{binarytrie-find} \end{figure} -O tempo de execução do método #find(x)# é dominado pelo tempo que ele leva para seguir um caminho da raiz para a folha, portanto ele roda em tempo +O tempo de execução do método #find(x)# é dominado pelo tempo que +ele leva para seguir um caminho da raiz para a folha, portanto ele roda em tempo $O(#w#)$. A operação #add(x)# em uma #BinaryTrie# também é relativamente simples mas tem muita coisa a fazer: @@ -111,7 +118,7 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} \item Ela segue o caminho de busca por #x# até alcançar um nodo #u# onde não poderá mais prosseguir. \item Ela cria o restante do caminho de busca de #u# a uma folha que contém #x#. \item Ela adiciona o nodo #u'# contendo #x# para a lista ligada de folhas - (ela tem acesso ao predecessor #pred#, de #u'# na lista ligada do ponteiro #jump# do último nodo #u# encontrado durante passo~1.) + (ela tem acesso ao predecessor #pred#, de #u'# na lista ligada do ponteiro #jump# do último nodo #u# encontrado durante o passo~1.) \item Ela retrocede no caminho de busca por #x# ajustando os ponteiros #jump# nos nodos cujos ponteiros #jump# devem agora apontar para #x#. \end{enumerate} @@ -120,82 +127,79 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/binarytrie-add} \end{center} - \caption[Adicionando a uma BinaryTrie]{Adicionando os valores 2 e 15 na #BinaryTrie# + \caption[Adicionando a uma BinaryTrie]{Adicionando os valores 2 e 15 na #BinaryTrie# da \figref{binarytrie-ex2}.} \figlabel{binarytrie-add} \end{figure} \codeimport{ods/BinaryTrie.add(x)} -Esse método realiza uma descida pelo caminho de busca por #x# e uma subida em seguida. Cada passo dessas caminhadas leva tempo constante, então o método roda -em tempo $O(#w#)$. +Esse método realiza uma descida pelo caminho de busca por #x# +e uma subida em seguida. Cada passo dessas caminhadas leva +tempo constante, então o método roda em tempo $O(#w#)$. -A operação #remove(x)# desfaz o trabalho de - #add(x)#. Assim como #add(x)#, +A operação #remove(x)# desfaz o trabalho de #add(x)#. Assim como #add(x)#, ela tem muito a fazer: \begin{enumerate} \item Ela segue o caminho de busca por #x# até alcançar a folha #u# contendo #x#. - containing #x#. \item Ela remove #u# da lista duplamente ligada. - - \item Ela remove #u# e então retrocede no caminho de busca de #x# removendo nodos até alcançar um nodo #v# que tem um filho que não está no caminho de busca de #x#. - \item Ela sabe de #v# à raiz atualizando qualquer ponteiros #jump# que apontam para #u#. + \item Ela remove #u# e então retrocede no caminho de busca de + #x# removendo nodos até alcançar um nodo #v# que tem um filho + que não está no caminho de busca de #x#. + \item Ela sobe de #v# à raiz atualizando os ponteiros #jump# que apontam para #u#. \end{enumerate} -Uma remoção é ilustrada na - \figref{binarytrie-remove}. +Uma remoção é ilustrada na \figref{binarytrie-remove}. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/binarytrie-remove} \end{center} - \caption[Remoção de uma BinaryTrie]{Remoção do valor 9 da #BinaryTrie# em + \caption[Remoção de uma BinaryTrie]{Remoção do valor 9 da #BinaryTrie# na \figref{binarytrie-ex2}.} \figlabel{binarytrie-remove} \end{figure} \codeimport{ods/BinaryTrie.remove(x)} \begin{thm} -Uma #BinaryTrie# implementa a interface #SSet# para inteiro de #w# bits. - Uma -#BinaryTrie# aceita as operações #add(x)#, #remove(x)# e #find(x)# -em tempo de -$O(#w#)$ por operação. O espaço usado por uma #BinaryTrie# que guarda #n# valores -em $O(#n#\cdot#w#)$. +Uma #BinaryTrie# implementa a interface #SSet# para inteiros de #w# bits. +Uma #BinaryTrie# possu as operações #add(x)#, #remove(x)# e #find(x)# +em tempo de $O(#w#)$ por operação. O espaço usado por uma +#BinaryTrie# que guarda #n# valores em $O(#n#\cdot#w#)$. \end{thm} -\section{#XFastTrie#: Buscando em Tempo Duplamente Logaritmico} +\section{#XFastTrie#: Buscando em Tempo Duplamente Logarítmico} \seclabel{xfast} \index{XFastTrie@#XFastTrie#}% -O desempenho da estrutura #BinaryTrie# não é muito impressionante. O número de elemento #n# guardado na estrutura é no máximo - $2^{#w#}$, então +O desempenho da estrutura #BinaryTrie# não é muito impressionante. O número de elementos #n# guardados na estrutura é no máximo $2^{#w#}$, então $\log #n#\le #w#$. Em outras palavras, qualquer estrutura #SSet# baseada em comparações descrita em outras partes deste livro são pelo menos tão eficientes quanto uma #BinaryTrie# e não se restringem a somente guardar inteiros. -A seguir descrevemos a -#XFastTrie#, que é somente uma #BinaryTrie# com -#w+1# tabelas hash --- uma para cada nível da trie. Essas tabelas hash são usadas para acelerar a operação -#find(x)# para $O(\log #w#)$. -Lembre-se que a operação -#find(x)# em uma #BinaryTrie# está quase terminada ao alcançarmos um nodo #u# onde o caminho de busca para #x# tenta prosseguir para #u.right# (ou #u.left#) mas #u# não tem filho à direita (respectivamente, esquerda). Neste ponto, a busca usa -#u.jump# para pular para uma folha -#v# da #BinaryTrie# e retorna #v# ou seu sucessor na lista ligada de folhas. Uma +A seguir descrevemos a #XFastTrie#, que é somente uma #BinaryTrie# com +#w+1# tabelas hash --- uma para cada nível da trie. Essas tabelas hash são usadas para acelerar a operação #find(x)# para $O(\log #w#)$. +Lembre-se que a operação #find(x)# em uma #BinaryTrie# está quase +terminada ao alcançarmos um nodo #u# onde o caminho de busca para #x# +tenta prosseguir para #u.right# (ou #u.left#) mas #u# não tem filho +à direita (respectivamente, esquerda). Neste ponto, a busca usa +#u.jump# para pular para uma folha #v# da #BinaryTrie# e retorna +#v# ou seu sucessor na lista ligada de folhas. Uma #XFastTrie# acelera o processo de busca usando a busca binária \index{busca binária}% -nos nívels da trie para achar o nodo #u#. +nos níveis da trie para achar o nodo #u#. Para usar busca binária, precisamos de um modo para determinar se o nodo #u# que estamos buscando está acima de dado nível #i# ou se #u# está nesse nível ou abaixo. Essa informação é dada pelos #i# bits de maior ordem na representação binária de #x#; esses bits determinam o caminho de busca que #x# percorre da raiz ao nível #i#. -Para um exemplo, veja a -\figref{xfast-path}; nessa figura o último nodo #u#, no +Para um exemplo, veja a \figref{xfast-path}; nessa figura o último nodo #u#, no caminho de busca por 14 (cuja representação binária é 1110) é o nodo marcado -com -$11{\star\star}$ no nível 2 porque não há nodos marcados com -$111{\star}$ no nível 3. Portanto, podemos marcar cada nodo no nível #i# com um inteiro com #i# bits. -Então, o nodo #u# que estamos buscando seria no nível #i# ou abaixo se, e somente se, existir um nodo no nível #i# cuja marcação corresponde aos #i# bits de mais alta ordem de #x#. +com $11{\star\star}$ no nível 2 porque não há nodos marcados com +$111{\star}$ no nível 3. Portanto, podemos marcar cada nodo no nível #i# +com um inteiro com #i# bits. +Então, o nodo #u# que estamos buscando seria no nível #i# ou abaixo se, +e somente se, existir um nodo no nível #i# cuja marcação corresponde aos #i# bits +de mais alta ordem de #x#. \begin{figure} \begin{center} \includegraphics[scale=0.90909]{figs/xfast-path} \end{center} - \caption{Como não há nodo marcado com $111\star$, o caminho de busca por + \caption{Como não há nenhum nodo marcado com $111\star$, o caminho de busca por 14 (1110) termina no nodo marcado com $11{\star\star}$ .} \figlabel{xfast-path} \end{figure} @@ -204,32 +208,31 @@ \section{#XFastTrie#: Buscando em Tempo Duplamente Logaritmico} #XFastTrie#, guardamos para cada $#i#\in\{0,\ldots,#w#\}$, todos os nodos no nível #i# em um #USet# #t[i]# que é implementado como uma tabela hash -(\chapref{hashing}). Usando esse #USet# nos permite checar em tempo constante se existe um nodo no nível #i# cuja marcação corresponde aos #i# bits de ordem mais alta de #x#. Podemos encontrar esse nodo usando +(\chapref{hashing}). Usar esse #USet# nos permite verificar em tempo +constante se existe um nodo no nível #i# cuja marcação corresponde aos +#i# bits de ordem mais alta de #x#. Podemos encontrar esse nodo usando \javaonly{#t[i].find(x>>>(w-i))#}% \cpponly{#t[i].find(x>>(w-i))#}% \pcodeonly{#t[i].find(x>>(w-i))#}% As tabelas hash $#t[0]#,\ldots,#t[w]#$ nos permitem usar busca binária para achar #u#. -Inicialmente, sabemos que -#u# está em algum nível #i# com +Inicialmente, sabemos que #u# está em algum nível #i# com $0\le #i#< #w#+1$. Portanto inicializamos $#l#=0$ e $#h#=#w#+1$ -e repetidamente olhamos na tabela hash - #t[i]#, onde $#i#=\lfloor +e repetidamente olhamos na tabela hash #t[i]#, onde $#i#=\lfloor (#l+h#)/2\rfloor$. Se $#t[i]#$ contém um nodo cuja marcação corresponde -aos #i# bits de maior ordem de #x# então fazemos a atribuição #l=i# (#u# está no mesmo nível ou abaixo de +aos #i# bits de maior ordem de #x# então fazemos a atribuição +#l=i# (#u# está no mesmo nível ou abaixo de #i#); caso contrário fazemos #h=i# (#u# está acima do nível #i#). Esse processo termina quando $#h-l#\le 1$ e neste caso determinamos que #u# está no nível #l#. -Então completamos a operação #find(x)# usando -#u.jump# +Então completamos a operação #find(x)# usando #u.jump# e a lista duplamente ligada de folhas. \codeimport{ods/XFastTrie.find(x)} Cada iteração do laço #while# no método anteriormente descrito -reduz #h-l# por aproximadamente um fator de dois, então esse laço encontra #u# -após -$O(\log #w#)$ +reduz #h-l# por aproximadamente um fator de dois, então esse +laço encontra #u# após $O(\log #w#)$ iterações. Cada iteração realiza uma quantidade constante de trabalho e uma operação #find(x)# em uma #USet#, que leva uma quantidade de tempo esperado constante. O restante da operação leva somente tempo constante, então o método #find(x)# @@ -242,67 +245,64 @@ \section{#XFastTrie#: Buscando em Tempo Duplamente Logaritmico} #add(x)#, quando um novo nodo é criado no nível #i#, esse nodo é adicionado a #t[i]#. Durante a operação #remove(x)#, quando um nodo é removido do nível #i#, esse nodo é removido de - #t[i]#. Como adição e remoção de uma tabela hash leva tempo esperado constante, isso não aumenta os tempos de execução de + #t[i]#. Como a adição e a remoção de uma tabela hash leva tempo + esperado constante, isso não aumenta os tempos de execução de #add(x)# e #remove(x)# em mais do que um fator constante. -Omitimos os códigos para -#add(x)# e #remove(x)# pois são quase idênticos ao código longo previamente fornecido para os mesmos métodos em uma +Omitimos os códigos para #add(x)# e #remove(x)# pois são quase idênticos +ao código longo previamente fornecido para os mesmos métodos em uma #BinaryTrie#. -O teorema a seguir resume o desempenho de uma -#XFastTrie#: +O teorema a seguir resume o desempenho de uma #XFastTrie#: \begin{thm} Uma #XFastTrie# implementa a interface #SSet# para inteiros com #w# bits. Uma -#XFastTrie# aceita as operações +#XFastTrie# possui as operações \begin{itemize} -\item #add(x)# e #remove(x)# em tempo esperado $O(#w#)$ per operation e +\item #add(x)# e #remove(x)# em tempo esperado $O(#w#)$ por operation e \item #find(x)# em tempo esperado $O(\log #w#)$ por operação. \end{itemize} -O espaço usado por uma -#XFastTrie# que guarda #n# valores é -$O(#n#\cdot#w#)$. +O espaço usado por uma #XFastTrie# que guarda #n# valores é $O(#n#\cdot#w#)$. \end{thm} -\section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} +\section{#YFastTrie#: Um #SSet# com Tempo Duplamente Logarítmico} \seclabel{yfast} A #XFastTrie# é uma melhoria exponencial sobre a -#BinaryTrie# em termos de tempo de consulta, mas as operações #add(x)# eand #remove(x)# ainda não são muito rápidas. +#BinaryTrie# em termos de tempo de consulta, mas as operações #add(x)# e +#remove(x)# ainda não são muito rápidas. Além disso, o uso de espaço -$O(#n#\cdot#w#)$ é mais que em outras implementações de #SSet# descritas neste livro, que usam +$O(#n#\cdot#w#)$ é maior que em outras implementações de #SSet# +descritas neste livro, que usam $O(#n#)$ de espaço. Esses dois problemas são relacionados; se -#n# operações #add(x)# construirem uma estrutura de tamanho +#n# operações #add(x)# construírem uma estrutura de tamanho $#n#\cdot#w#$, então a operação #add(x)# exige pelo menos na ordem de #w# de tempo (e espaço) por operação. \index{YFastTrie@#YFastTrie#}% A #YFastTrie#, discutida a seguir, simultaneamente melhora os custos de espaço e tempo da -#XFastTrie#s. Uma #YFastTrie# uma #XFastTrie#, #xft#, mas guarda -somente -$O(#n#/#w#)$ valores em #xft#. Dessa maneira, o espaço total usado por -#xft# é somente $O(#n#)$. Adicionalmente, somente uma a cada #w# operações #add(x)# ou -#remove(x)# +#XFastTrie#. Uma #YFastTrie# usa uma #XFastTrie#, #xft#, mas guarda +somente $O(#n#/#w#)$ valores em #xft#. Dessa maneira, o espaço total usado por +#xft# é somente $O(#n#)$. Adicionalmente, somente uma a cada #w# +operações #add(x)# ou #remove(x)# da #YFastTrie# resulta em uma operação #add(x)# ou #remove(x)# em #xft#. Fazendo isso, o custo médio das chamadas às operações -#add(x)# e #remove(x)# da #xft# é somente uma constante. +#add(x)# e #remove(x)# da #xft# é constante. -A pergunta óbvia é: se -#xft# somente guarda #n#/#w# elementos, -onde o resto dos -$#n#(1-1/#w#)$ elementos vão? Esses elementos são movidos para +A pergunta óbvia é: se #xft# somente guarda #n#/#w# elementos, +para onde o resto dos $#n#(1-1/#w#)$ elementos vão? Esses elementos +são movidos para \emph{estruturas secundárias}, \index{estrutura secundária}% nesse caso uma versão estendida de uma treap (\secref{treap}). Existem aproximadamente #n#/#w# dessas estruturas secundárias então, em média, cada uma delas guarda -$O(#w#)$ itens. Treaps aceitam operações #SSet# em tempo logaritmico, então +$O(#w#)$ itens. Treaps aceitam operações #SSet# em tempo logarítmico, então as operações nessas treaps irão rodar em tempo $O(\log #w#)$, conforme exigido. -Mais concretamente, uma -#YFastTrie# contém uma #XFastTrie#, #xft#, +Mais concretamente, uma #YFastTrie# contém uma #XFastTrie#, #xft#, que contém uma amostra aleatória dos dados, onde cada elemento aparece na amostra independentemente com probabilidade $1/#w#$. @@ -311,8 +311,7 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} $#x#_0<#x#_1<\cdots<#x#_{k-1}$ representam os elementos guardados em #xft#. Uma treap $#t#_i$ é associada a cada elemento $#x#_i$ que guarda todos os valores no intervalo -$#x#_{i-1}+1,\ldots,#x#_i$. Isso é ilustrado em -\figref{yfast}. +$#x#_{i-1}+1,\ldots,#x#_i$. Isso é ilustrado na \figref{yfast}. \begin{figure} \begin{center} @@ -323,9 +322,9 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} \figlabel{yfast} \end{figure} -A operação #find(x)# em uma #YFastTrie# razoavelmente simples. Buscamos por +A operação #find(x)# em uma #YFastTrie# é razoavelmente simples. Buscamos por #x# em #xft# e achamos algum valor $#x#_i$ associado com a treap -$#t#_i$. Então usamos o método #find(x)# da treap $#t#_i$ para responder a +$#t#_i$. Então usamos o método #find(x)# da treap $#t#_i$ para responder à consulta. O método completo se resume a uma linha: \codeimport{ods/YFastTrie.find(x)} A primeira operação #find(x)# (em #xft#) leva $O(\log#w#)$ de tempo. @@ -333,11 +332,10 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} $O(\log r)$ de tempo, onde $r$ é o tamanho da treap. Mais adiante nesta seção, mostraremos que o tamanho esperado da treap é $O(#w#)$ tal que essa operação usa -$O(\log #w#)$ de tempo.\footnote{Essa é uma aplicação da \emph{Desigualdade de Jensen}: Se $\E[r]=#w#$, então $\E[\log r] -\le \log w$.} +$O(\log #w#)$ de tempo.\footnote{Essa é uma aplicação da \emph{Desigualdade de Jensen}: Se $\E[r]=#w#$, então $\E[\log r] \le \log w$.} Adicionar um elemento a uma -#YFastTrie# é razoavelmente simples -- a maior parte do tempo. +#YFastTrie# é razoavelmente simples -- na maior parte do tempo. O método #add(x)# chama #xft.find(x)# para achar a treap #t#, onde #x# deve ser inserido. Então utiliza-se #t.add(x)# para adicionar #x# a #t#. Nesse ponto, lançamos uma moeda tendenciosa @@ -347,10 +345,9 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} #xft#. É aqui que as coisas ficam um pouco mais complicadas. Quando #x# é -adicionado a -#xft#, a treap #t# precisa ser dividida em duas treaps, #t1# e #t'#. -A treap - #t1# contém todos os valore menores ou iguais a #x#; +adicionado a #xft#, a treap #t# precisa ser dividida em duas treaps, +#t1# e #t'#. +A treap #t1# contém todos os valores menores ou iguais a #x#; #t'# é a treap original, #t#, com os elementos de #t1# removidos. Assim que isso é feito, adicionamos o par #(x,t1)# em #xft#. A \figref{yfast-add} mostra um exemplo. @@ -364,8 +361,8 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} \figlabel{yfast-add} \end{figure} Adicionar #x# a #t# leva - $O(\log #w#)$ de tempo. \excref{treap-split} mostra que dividir #t# em #t1# - e #t'# também pode ser feito em + $O(\log #w#)$ de tempo. O \excref{treap-split} mostra que + dividir #t# em #t1# e #t'# também pode ser feito em $O(\log #w#)$ de tempo esperado. Adicionar o par (#x#,#t1#) em #xft# custa $O(#w#)$ de tempo, mas acontece com probabilidade $1/#w#$. Portanto, o tempo esperado de execução da operação @@ -379,14 +376,11 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} Usamos #xft# para achar a folha, #u#, em #xft# que contém a resposta a #xft.find(x)#. A partir de #u#, pegamos a treap, #t#, contendo #x# -e -removemos #x# de #t#. Se #x# também foi guardado em #xft# (e #x# -não é igual a -$2^{#w#}-1$) então removemos #x# de #xft# e adicionamos os elementos -da treap de -#x# à treap #t2#, que é guardada pelo sucessor de #u# na lista ligada. -Isso é ilustrado na -\figref{yfast-remove}. +e removemos #x# de #t#. Se #x# também foi guardado em #xft# (e #x# +não é igual a $2^{#w#}-1$) então removemos #x# de #xft# e adicionamos +os elementos da treap de #x# à treap #t2#, que é guardada pelo +sucessor de #u# na lista ligada. +Isso é ilustrado na \figref{yfast-remove}. \codeimport{ods/YFastTrie.remove(x)} \begin{figure} \begin{center} @@ -395,18 +389,17 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} \caption[Remoção de uma YFastTrie]{Remoção dos valores 1 e 9 de uma #YFastTrie# na \figref{yfast-add}.} \figlabel{yfast-remove} \end{figure} -Achar o nodo #u# em #xft# leva -$O(\log#w#)$ de tempo esperado. +Achar o nodo #u# em #xft# leva $O(\log#w#)$ de tempo esperado. Remover #x# de #t# leva -$O(\log#w#)$ de tempo esperado. Novamente, -\excref{treap-split} mostra que a junção de todos os elementos de #t# em #t2# +$O(\log#w#)$ de tempo esperado. Novamente, o \excref{treap-split} +mostra que a junção de todos os elementos de #t# em #t2# #t2# pode ser feito em tempo $O(\log#w#)$. Se necessária, a remoção de #x# de #xft# leva tempo $O(#w#)$, mas #x# é somente contido em #xft# com probabilidade $1/#w#$. Portanto, o tempo esperado para remover um elemento de uma #YFastTrie# é $O(\log #w#)$. -Anteriormente, postergamos a discussão sobre os tamanhos das treaps nesta +Anteriormente, postergamos a discussão sobre os tamanhos das treaps nessa estrutura. Antes de terminar este capítulo, provamos o resultado que necessitamos. \begin{lem}\lemlabel{yfast-subtreesize} @@ -444,7 +437,7 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} \begin{center} \includegraphics[width=\ScaleIfNeeded]{figs/yfast-sample} \end{center} - \caption[Tempo de uma consulta em uma YFastTrie]{O número de elementos na treap #t# contendo #x# é determinado por dois experimentos de lançamento de moedas.} + \caption[Tempo de uma consulta em uma YFastTrie]{O número de elementos na treap #t# contendo #x# é determinado por dois lançamentos de moedas.} \figlabel{yfast-sample} \end{figure} %Surprisingly, the bound in \lemref{yfast-subtreesize} is tight. (If this @@ -468,8 +461,9 @@ \section{#YFastTrie#: A Doubly-Logarithmic Time #SSet#} #YFastTrie# que guarda #n# valores é $O(#n#+#w#)$. \end{thm} -O temo #w# nos custos de espaço vem do fato que #xft# sempre guarda o -valor $2^#w#-1$. A implementação pode ser modificada (em troca de alguns casos extras para serem considerados no código) tal que é desnecessário guardar esse +O termo #w# nos custos de espaço vem do fato que #xft# sempre guarda o +valor $2^#w#-1$. A implementação pode ser modificada (em troca de alguns +casos extras para serem considerados no código) tal que é desnecessário guardar esse valor. Nesse caso, o custo de espaço no teorema torna-se $O(#n#)$. \section{Discussão e Exercícios} @@ -482,14 +476,14 @@ \section{Discussão e Exercícios} (ou \emph{árvore estratificada}) \index{árvore estratificada}% \cite{e77}. A estrutura original de van~Emde~Boas tinha tamanho -$2^{#w#}$, tornando-a imprática para inteiros grandes. +$2^{#w#}$, tornando-a inadequada para inteiros grandes. -As estruturas de dados #XFastTrie# e #YFastTrie# foram descubertas por +As estruturas de dados #XFastTrie# e #YFastTrie# foram descobertas por Willard \cite{w83}. A estrutura #XFastTrie# é relacionada às árvores de van~Emde~Boas; por exemplo, as tabelas hash em uma #XFastTrie# substituem arrays em uma árvore de van~Emde~Boas. Isto é, em vez de guardar a tabela hash -a tabela hash #t[i]#, uma árvore de van~Emde~Boas guarda um array de comprimento +#t[i]#, uma árvore de van~Emde~Boas guarda um array de comprimento $2^{#i#}$. Outra estrutura para guardar inteiros é a árvore de fusão Fredman e Willard @@ -509,7 +503,7 @@ \section{Discussão e Exercícios} \begin{exc} Projete e implemente uma versão simplificada de uma #BinaryTrie# que não tem uma lista ligada nem ponteiros de salto - mas que ainda tem #find(x)# que roda em tempo + mas cujo #find(x)# que roda em tempo $O(#w#)$. \end{exc} @@ -521,15 +515,15 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - Podemos pensar da #BinaryTrie# como usa estrutura que guarda strings de bits + Podemos pensar da #BinaryTrie# como uma estrutura que guarda strings de bits de comprimento #w# de forma que cada bitstring é representada como um caminho da raiz para uma folha. Amplie essa ideia em uma implementação #SSet# que guarda strings - de comprimento váriavel e implementa -#add(s)#, #remove(s)# e - #find(s)# em tempo proporcional ao comprimento de #s#. + de comprimento variável e implementa +#add(s)#, #remove(s)# e #find(s)# em tempo proporcional ao comprimento de #s#. - \noindent Dica: Cada nodo na sua estrutura de dados deve guardar uma tabela hash que é indexada por valores de caracteres. + \noindent Dica: Cada nodo na sua estrutura de dados deve guardar uma tabela + hash que é indexada por valores de caracteres. \end{exc} \begin{exc} @@ -542,7 +536,7 @@ \section{Discussão e Exercícios} \item Projete e implemente uma versão modificada da operação #find(x)# em uma #XFastTrie# que roda em tempo esperado $O(1+\log d(#x#))$ . Dica: a tabela hash $t[#w#]$ contém todos os valores - #x# tal que $d(#x#)=0$, tal que seria um bom lugar para começar. + #x# tal que $d(#x#)=0$, então esse seria um bom ponto para começar. \item Projete e implemente uma versão modificada da operação #find(x)# em uma #XFastTrie# que roda em tempo esperado $O(1+\log\log d(#x#))$. \end{enumerate} From de6e8b4a6f0806d5fc06ea36b556995f18932009 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 14 Sep 2020 11:25:49 -0300 Subject: [PATCH 58/66] finished revision of portuguese transltion of btree.tex --- latex/btree.tex | 316 ++++++++++++++++++++++++------------------------ 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/latex/btree.tex b/latex/btree.tex index a6474b14..60f1f25c 100644 --- a/latex/btree.tex +++ b/latex/btree.tex @@ -1,24 +1,28 @@ \chapter{Busca em Memória Externa} \chaplabel{btree} -Ao longo deste livro, consideremos o modelo de computação RAM com palavras de #w# bits definido na \secref{model}. Uma premissa implícita desse -modelo é que nosso computador tem memória de acesso aleatório grande o suficiente para guardar todos os dados na estrutura de dados. +Ao longo deste livro, consideramos o modelo de computação word-RAM +definido na \secref{model}. Uma premissa implícita desse +modelo é que nosso computador tem memória de acesso aleatório +grande o suficiente para guardar todos os dados na estrutura de dados. Em algumas situação essa premissa não é válida. Existem coleções de dados tão grandes que nenhum computador tem memória suficiente para guardá-las. Nesses casos, a aplicação deve guardar os dados em alguma mídia de armazenamento -externo tal como um disco rígido, um disco em estado sólido ou mesmo um servidor de arquivos em rede (que possui seu próprio armazenamento externo). +externo tal como um disco rígido, um disco em estado sólido ou mesmo +um servidor de arquivos em rede (que possui seu próprio armazenamento externo). \index{armazenamento externo}% \index{memória externa}% \index{disco rígido}% \index{solid-state drive}% Acessar um item em um armazenamento externo é extremamente lento. -O disco rígido conectado ao computador no qual este livro foi escrito um +O disco rígido conectado ao computador no qual este livro foi escrito tem um um tempo de acesso médio de 19ms e o disco em estado sólido conectado ao computador tem tempo médio de acesso de 0.3ms. Em comparação, a memória de acesso aleatório no computador tem tempo médio de acesso de menor que 0.000113ms. -Acessar a RAM é mais que 2\,500 vezes mais rápido que acessar um disco em estado sólido e mais de 160\,000 vezes mais ráído que acessar o disco rígido. +Acessar a RAM é mais do que 2\,500 vezes mais rápido que acessar um disco +em estado sólido e mais de 160\,000 vezes mais rápido do que acessar o disco rígido. % HDD: Fantom ST3000DM001-9YN166 USB 3 external drive (3TB) % SSD: ATA OCZ-AGILITY 3 (60GB) @@ -47,11 +51,11 @@ \chapter{Busca em Memória Externa} Essas velocidades são típicas; acessar um byte aleatório na RAM é milhares de vezes mais rápido que acessar um byte aleatório em um disco -rígido ou um disco de estado sólido. Tempo de acesso, entretando, não +rígido ou um disco de estado sólido. Tempo de acesso, entretanto, não conta a história por completo. Ao acessar um byte de um disco rígido ou disco de estado sólido, um \emph{bloco} inteiro \index{bloco}% -do disco é lido. Cada um dos discos conectados ao computador temum tamanho +do disco é lido. Cada um dos discos conectados ao computador tem um tamanho de bloco de 4\,096; cada vez que lemos um byte, o disco nos fornece um bloco contendo 4\,096 bytes. Se organizarmos nossa estrutura de dados cuidadosamente, isso significa que cada acesso a disco pode nos enviar 4\,096 bytes que podem @@ -64,17 +68,17 @@ \chapter{Busca em Memória Externa} Essa é a ideia por trás do \emph{modelo de memória externa} \index{modelo de memória externa}% -de computação, ilutrado esquematicamente na +de computação, ilustrado esquematicamente na \figref{em}. Neste modelo, o computador tem acesso a uma grande memória externa no qual todos os dados são guardados. Essa memória é dividida em \emph{blocos} de memória \index{bloco}% -cada qual contendo +, cada um contendo $B$ palavras. O computador também tem memória interna limitada na qual é possível realizar computações. Transferir um bloco entre a memória interna e a memória externa leva tempo constante. Computações realizadas na memória interna são \emph{grátis}; -elas não gastam nenhum instante de tempo. +elas não gastam \emph{nenhum} instante de tempo. O fato que computações em memória externa são grátis são parecer estranho, mas isso enfatiza o fato que memória externa é muito mais lenta que a RAM. @@ -85,9 +89,11 @@ \chapter{Busca em Memória Externa} \figlabel{em} \end{figure} -Em um modelo de memória externa mais detalhado, o tamanho da memória interna também é um parâmetro. Entretanto, para as estruturas de dados descritas neste capítulo, é +Em um modelo de memória externa mais detalhado, o tamanho da memória +interna também é um parâmetro. Entretanto, para as estruturas de dados +descritas neste capítulo, é suficiente ter uma memória interna de tamanho -$O(B+\log_B #n#)$. Por isso, a memória precisa ser apaz de guardar um um número +$O(B+\log_B #n#)$. Por isso, a memória precisa ser capaz de guardar um um número constante de blocos e uma pilha de recursão de altura $O(\log_B #n#)$. Na maior parte dos casos, o termo $O(B)$ domina os custos de memória. @@ -107,11 +113,11 @@ \section{A \emph{Block Store}} acessado com sua própria coleção de chamadas de sistema. Para simplificar a exposição deste capítulo para que possamos nos concentrar nas ideias em comum entre eles, encapsulamos os -dispositivos de memória externa com um objeto chamado de #BlockStore# (em português, armazém de blocos). +dispositivos de memória externa com um objeto chamado de #BlockStore# +(em português, armazém de blocos). Uma #BlockStore# guarda uma coleção de blocos de memória, cada uma com tamanho $B$. Cada bloco é unicamente identificado por seu índice inteiro. Uma - #BlockStore# -aceita as operações: + #BlockStore# possui as operações: \begin{enumerate} \item #readBlock(i)#: retorna o conteúdo do bloco cujo índice é #i#. @@ -121,15 +127,15 @@ \section{A \emph{Block Store}} \item #placeBlock(b)#: retorna um novo índice e guarda o conteúdo de #b# nesse índice. \item #freeBlock(i)#: Libera o bloco cujo índice é #i#. Isso indica - que o conteúdo desse bloco não serão mais usados então a memória externa alocada por esse bloco pode ser reusada. + que o conteúdo desse bloco não será mais usado, então a memória externa alocada por esse bloco pode ser reusada. \end{enumerate} -O jeito mais fácil de usar #BlockStore# é imaginá-lo como guardar um +O jeito mais fácil de usar a #BlockStore# é imaginá-la guardando um arquivo em disco que é particionado em blocos, cada contendo $B$ bytes. Dessa maneira, #readBlock(i)# e #writeBlock(i,b)# simplesmente lêem e escrevem -bytes $#i#B,\ldots,(#i#+1)B-1$ desse arquivo. Adicionalmente, -um simples +os bytes $#i#B,\ldots,(#i#+1)B-1$ desse arquivo. Adicionalmente, +uma simples #BlockStore# poderia manter uma \emph{lista de blocos livres} que lista aqueles que estão disponíveis para uso. Blocos liberados com @@ -150,19 +156,18 @@ \section{B-Trees} \index{B-tree@$B$-tree}% Para qualquer inteiro $B\ge 2$, uma \emph{$B$-tree} é uma árvore na qual todas -as folhas tem a mesma profundidade e todo nodo interno (exceto a raiz) +as folhas têm a mesma profundidade e todo nodo interno (exceto a raiz) #u# tem pelo menos $B$ filhos e no máximo $2B$ filhos. Os filhos de #u# são guardados -em um array - #u.children#. O número obrigatório de filhos é diferente na raiz, que pode tem entre 2 e $2B$ filhos. +em um array #u.children#. O número obrigatório de filhos é diferente +na raiz, que pode ter entre 2 e $2B$ filhos. - Se a altura de uma -$B$-tree é $h$, então o número + Se a altura de uma $B$-tree é $h$, então o número $\ell$, de folha em uma $B$-tree satisfaz \[ 2B^{h-1} \le \ell \le (2B)^{h} \enspace . \] -Aplicando o logaritmo da primeira desigualdade e rearranjando termos chegamos em: +Aplicando o logaritmo na primeira desigualdade e rearranjando termos chegamos em: \begin{align*} h & \le \frac{\log \ell-1}{\log B} + 1 \\ & \le \frac{\log \ell}{\log B} + 1 \\ @@ -176,20 +181,19 @@ \section{B-Trees} filhos, então o número de chaves guardado em #u# é exatamente $k-1$ e esses são guardados em $#u.keys#[0],\ldots,#u.keys#[k-2]$. O restante das $2B-k+1$ entradas do array -em #u.keys# são atribuídos a #null#. +em #u.keys# é atribuído a #null#. -Se #u# for uma folha não rais +Se #u# for uma folha não raiz , então #u# contém entre $B-1$ e $2B-1$ chaves. As chaves em uma $B$-tree respeitam uma ordem similar às chaves em uma árvore binária de busca. Para qualquer nodo #u# que guarda $k-1$ chaves, \[ #u.keys[0]# < #u.keys[1]# < \cdots < #u.keys#[k-2] \enspace . \] -Se -#u# for um nodo interno, então para todo $#i#\in\{0,\ldots,k-2\}$, +Se #u# for um nodo interno, então para todo $#i#\in\{0,\ldots,k-2\}$, $#u.keys[i]#$ é maior que toda chave guardada na subárvore enraizada em -#u.children[i]# mas menos que toda chave gurda na subárvores enraizada em -at $#u.children[i+1]#$. Informalmente, +#u.children[i]# mas menor que toda chave guardada nas subárvores enraizada em +$#u.children[i+1]#$. Informalmente, \[ #u.children[i]# \prec #u.keys[i]# \prec #u.children[i+1]# \enspace . \] @@ -204,7 +208,7 @@ \section{B-Trees} Note que os dados guardados em um nodo de uma $B$-tree tem tamanho $O(B)$. Portanto, em um contexto de memória externa, o valor de $B$ em uma $B$-tree é -escolhido tal que um nodo cabe en um único bloco de memória externo. +escolhido para que um nodo caiba em um único bloco de memória externo. Dessa forma, o tempo que leva para realizar uma operação da $B$-tree no modelo de memória externa é proporcional ao número de nodos que são acessados (lidos ou escritos) pela operação. @@ -223,7 +227,7 @@ \section{B-Trees} A classe #BTree#, que implementa uma $B$-tree, mantém uma #BlockStore#, -#bs#, que guarda nodos #BTree# assim nodo o índice #ri# do nodo raiz. +#bs#, que guarda nodos #BTree# assim como o índice #ri# do nodo raiz. Como de costume, um inteiro #n#, é usado para registrar o número de itens na estrutura de dados: @@ -234,13 +238,13 @@ \section{B-Trees} \subsection{Busca} A implementação da operação -#find(x)#, que é ilustrada em -\figref{btree-find}, generaliza a operação #find(x)# em uma árvore binária de busca. A busca por #x# inicia na raiz e usa as chaves guardadas em um nodo #u# para +#find(x)#, que é ilustrada na +\figref{btree-find}, generaliza a operação #find(x)# em uma árvore binária de busca. A busca por #x# começa na raiz e usa as chaves guardadas em um nodo #u# para determinar em qual dos filhos de #u# a busca deve continuar. \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/btree-2}} - \caption[Busca em uma $B$-tree]{Uma busca bem sucedida (pelo valor 4) e uma busca mal sucedida (pelo valor 16.5) em uma $B$-tree. Nodos sombreados onde o valor de #z# fio atualizado durante as buscas.}\figlabel{btree-find} + \caption[Busca em uma $B$-tree]{Uma busca bem sucedida (pelo valor 4) e uma busca mal sucedida (pelo valor 16.5) em uma $B$-tree. Nodos destacados mostram onde o valor de #z# é atualizado durante as buscas.}\figlabel{btree-find} \end{figure} Mais especificamente, em um nodo #u#, a busca verifica se #x# está guardada em #u.keys#. Caso positivo, #x# foi encontrado e a busca está completa. @@ -255,9 +259,9 @@ \subsection{Busca} \codeimport{ods/BTree.find(x)} -O método #findIt(a,x)# é central para o método #find(x)# ao -buscar em um array ordenado completada com valores #null# #a# -pelo valor #x#. Esse método, ilustrado em +O método #findIt(a,x)# é central para a operação #find(x)# que +busca em #a#, um array ordenado completado com valores #null#, +pelo valor #x#. Esse método, ilustrado na \figref{findit}, funciona para qualquer array #a#, onde $#a#[0],\ldots,#a#[k-1]$ é uma sequência de chaves ordenadas @@ -271,13 +275,13 @@ \subsection{Busca} \figlabel{findit} \end{figure} \codeimport{ods/BTree.findIt(a,x)} -O méotdo #findIt(a,x)# usa uma busca binária +O método #findIt(a,x)# usa uma busca binária \index{busca binária}% que divide o espaço de busca a cada passo, tal que ele roda em tempo $O(\log(#a.length#))$. No nosso cenário, $#a.length#=2B$, então #findIt(a,x)# roda em tempo $O(\log B)$. Podemos analisar o tempo de execução de uma operação #find(x)# da $B$-tree -tanto no modelo RAM com palavras de #w# bits usual (onde cada instrução conta) +tanto no modelo word-RAM usual (onde cada instrução conta) quanto no modelo de memória externa (onde somente contamos o número de nodos acessados). @@ -287,10 +291,10 @@ \subsection{Busca} $B$-tree que guarda #n# chaves é $O(\log_B #n#)$. Portanto, no modelo de memória externa, o tempo gasto pela operação #find(x)# é $O(\log_B #n#)$. -Para determinar o tempo de execução no modelo RAM, +Para determinar o tempo de execução no modelo word-RAM, temos que considerar o custo de chamar #findIt(a,x)# para cada nodo que acessamos, então o tempo de execução -de #find(x)# no modelo RAM é +de #find(x)# no modelo word-RAM é \[ O(\log_B #n#)\times O(\log B) = O(\log #n#) \enspace . \] @@ -300,7 +304,7 @@ \subsection{Adição} Uma diferença importante entre as estruturas de dados $B$-tree e #BinarySearchTree# da \secref{binarysearchtree} é que os nodos de uma -$B$-tree não guardam ponteiro para nodos pai. A razão disso vai ser explicada +$B$-tree não guardam ponteiros para nodos pai. A razão disso vai ser explicada logo a seguir. A falta de ponteiros dos nodos pai significa que as operações #add(x)# e #remove(x)# em $B$-trees são mais facilmente implementadas usando recursão. @@ -312,10 +316,9 @@ \subsection{Adição} \index{split}% Observe na \figref{btree-split} como isso ocorre. -Embora a divisão acontece entre dois níveis de recursão, ela é +Embora a divisão aconteça entre dois níveis de recursão, ela é melhor entendida como uma operação que recebe um nodo #u# contendo -$2B$ chave e com -$2B+1$ filhos. +$2B$ chaves e com $2B+1$ filhos. A operação cria um novo nodo #w# que adota $#u.children#[B],\ldots,#u.children#[2B]$. O novo nodo #w# também recebe as $B$ maiores chaves de #u#, $#u.keys#[B],\ldots,#u.keys#[2B-1]$. @@ -326,11 +329,12 @@ \subsection{Adição} Note que a operação de divisão modifica três nodos: #u#, o pai de #u# e o novo nodo #w#. Por isso é importante que os nodos de uma -$B$-tree não mantém ponteiros para os nodos pai. Se assim fosse, então +$B$-tree não mantenham ponteiros para os nodos pai. Se assim fosse, então os -$B+1$ filhos adotados por #w# todos necessitariam ter seu ponteiros para o nodo pai modificados. Isso aumentaria o número de acessos à memória externa de 3 para -$B+4$ e faria $B$-trees muito menos eficiente para valores altos de -$B$. +$B+1$ filhos adotados por #w# todos necessitariam ter os seus ponteiros +para o nodo pai modificados. Isso aumentaria o número de acessos à +memória externa de 3 para $B+4$ e faria a $B$-tree muito menos eficiente +para valores altos de $B$. \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -346,15 +350,16 @@ \subsection{Adição} \end{figure} O método #add(x)# em uma $B$-tree está ilustrado na \figref{btree-add}. -Em uma descrição sem se ater a detalhes, esse método acha uma folha #u# +Em uma descrição sem entrar em detalhes, esse método acha uma folha #u# para achar o valor #x#. Se isso faz com que #u# fique cheia demais, então o pai de #u# também é dividido, o que pode fazer que o avô de #u# fique cheio demais e assim por diante. -Esse processor continua, subindo na árvore um nível por vez até alcançar +Esse processo continua, subindo na árvore um nível por vez até alcançar um nodo que não está cheio e até que a raiz seja dividida. Ao encontrar um nodo com espaço, o processo é interrompido. -Caso contrário, uma nova raiz é criada com dois filhos obtidos da divisão da raiz original. +Caso contrário, uma nova raiz é criada com dois filhos obtidos a partir +da divisão da raiz original. \begin{figure} \centering{\begin{tabular}{@{}l@{}} @@ -365,28 +370,27 @@ \subsection{Adição} \includegraphics[width=\ScaleIfNeeded]{figs/btree-add-3} \end{tabular}} \caption[Adição a uma $B$-tree]{A operação #add(x)# em uma - #BTree#. A adição do valor 21 resulta em dois nodos sendo divididos.} + #BTree#. A adição do valor 21 resulta em dois nodos divididos.} \figlabel{btree-add} \end{figure} -O resumo do método #add(x)# é que é caminha da raiz para a folha +O resumo do método #add(x)# é que ele caminha da raiz para a folha em busca de #x#, adiciona #x# a essa folha e retorna de volta -para a raiz, divindindo qualquer nodo cheio demais que +para a raiz, dividindo qualquer nodo cheio demais que encontre no caminho. Com essa visão em mente, podemos entrar nos detalhes de como esse método pode ser implementado recursivamente. O principal trabalho de #add(x)# é feito pelo método #addRecursive(x,ui)#, que adiciona o valor #x# à subárvore cuja raiz #u# -tem o identificar #ui#. Se #u# é uma folha, então #x# simplesmente é +tem o identificador #ui#. Se #u# for uma folha, então #x# simplesmente é adicionado em #u.keys#. Caso contrário, #x# é adicionado recursivamente no filho $#u#'$ de #u#. O resultado dessa chamada recursiva é normalmente -#null# mas também pode ser uma raferência a um novo nodo #w# +#null# mas também pode ser uma referência a um novo nodo #w# que foi criado porque $#u#'$ foi dividido. Nesse caso, #u# adota #w# e recebe sua primeira chave, completando a operação -de divisão em -$#u#'$. +de divisão em $#u#'$. Após o valor #x# ser adicionado (em #u# ou em um descendente de #u#), @@ -401,8 +405,8 @@ \subsection{Adição} O método #addRecursive(x,ui)# é auxiliar ao método #add(x)#, que chama #addRecursive(x,ri)# para inserir #x# na raiz da $B$-tree. -Se #addRecursive(x,ri)# faz a raiz ser dividida, então uma nova raiz é criada -e ela recebe seus filhos tanto a antiga raiz quanto o novo nodo criado pela +Se #addRecursive(x,ri)# fizer que a raiz seja dividida, então uma nova raiz é criada +e ela recebe como filhos tanto a antiga raiz quanto o novo nodo criado pela divisão da antiga raiz. \codeimport{ods/BTree.add(x)} @@ -410,43 +414,41 @@ \subsection{Adição} \begin{description} \item[Fase da descida:] - Durante a fase da descida da recursão, antes que #x# foi adicionado, + Durante a fase da descida da recursão, antes da adição de #x#, é acessada uma sequência de nodos da #BTree# e é chamado #findIt(a,x)# em cada nodo. - Assim como o método #find(x)# isso leva - $O(\log_B #n#)$ de tempo no modelo de memória externa - $O(\log #n#)$ de tempo no modelo RAM com palavras de #w# bits. + Assim como o método #find(x)#, isso leva + $O(\log_B #n#)$ de tempo no modelo de memória externa e + $O(\log #n#)$ de tempo no modelo word-RAM. \item[Fase de subida:] - Durante a fase de subdia da recursão, após a adição de #x#, - esses métodos realizam uma sequência de até -$O(\log_B #n#)$ divisões. +Durante a fase de subida da recursão, após a adição de #x#, +esses métodos realizam uma sequência de até $O(\log_B #n#)$ divisões. Cada divisão envolve somente de três nodos e, então, essa fase -leva -$O(\log_B +leva $O(\log_B #n#)$ de tempo no modelo de memória externa. Entretanto, cada divisão - envolve mover $B$ chaves e filhos de um nodo a outro, então no modelo RAM com palavras de #w# bits, isso leva + envolve mover $B$ chaves e filhos de um nodo a outro, então no modelo word-RAM, isso leva $O(B\log #n#)$ de tempo. \end{description} Lembre-se que o valor de $B$ pode ser bem alto, muito maior que -$\log #n#$. Portanto no modelo RAM com palavras de #w# bits, adicionar um valor a uma -$B$-tree pode ser bem mais lento que adicionar em uma árvore binária de busca balanceada. Porteriormente, na +$\log #n#$. Portanto no modelo word-RAM com palavras de #w# bits, adicionar um valor a uma +$B$-tree pode ser bem mais lento do que adicionar em uma árvore binária de busca balanceada. Posteriormente, na \secref{btree-amortized}, mostraremos que a situação não é tão ruim; -o número amortizado de operações de divisão feito durante uma operação #add(x)# é constante. -Isso mostra que o tempo de execução amortizado da operação #add(x)# no modelo RAM é +o número amortizado de operações de divisão durante uma operação #add(x)# é constante. +Isso mostra que o tempo de execução amortizado da operação #add(x)# no modelo word-RAM é $O(B+\log #n#)$. - \subsection{Remoção} -A operação -#remove(x)# em uma #BTree# é, novamente, mais facilmente implementada como um método recursivo. Embora a implementação recursiva de +A operação #remove(x)# em uma #BTree# é, novamente, mais +facilmente implementada como um método recursivo. Embora a +implementação recursiva de #remove(x)# distribui sua complexidade entre vários métodos, o processo como um todo, que é ilustrado na \figref{btree-remove-full}, é razoavelmente direto. -Ao trabalhar com as chaves, remoção é reduzida ao problema de remoção de um -valor $#x#'$, de alguma folha #u#. Remover $#x#'$ +Ao trabalhar com as chaves, remoção é reduzida ao problema +de remoção de um valor $#x#'$, de alguma folha #u#. Remover $#x#'$ pode deixar #u# com menos que $B-1$ chaves; essa situação é chamada de \emph{underflow}. \index{underflow}% @@ -468,22 +470,23 @@ \subsection{Remoção} \figlabel{btree-remove-full} \end{figure} -Quando um underflow acontece, #u# pode emprestar chaves ou ser juntado com seus irmão. Se #u# é juntado com um irmão, então o pai de #u# +Quando um \emph{underflow} acontece, #u# pode emprestar chaves ou ser juntado com seus irmão. +Se #u# for juntado com um irmão, então o pai de #u# terá agora um filho a menos e uma chave a menos, o que pode -fazer o pai de #u# sofrer um underflow; isso é novamente corregido +fazer o pai de #u# sofrer um \emph{underflow}; isso é novamente corrigido por meio de um empréstimo ou de uma junção, embora a junção pode -fazer que o avô de #u# sofra underflow. -Esse processo se repete até a raiz até que não ocorra mais underflows +fazer que o avô de #u# sofra \emph{underflow}. +Esse processo se repete até a raiz até que não ocorra mais \emph{underflows} ou até que a raiz tenha seus dois filhos juntados em um filho único. Quando os filhos são juntados, a raiz é removida e seu único filho -se tona a nova raiz. +se torna a nova raiz. A seguir, entramos nos detalhes de como esses passos são implementados. O primeiro trabalho do método #remove(x)# é achar o elemento #x# que deve ser removido. Se #x# é achado em uma folha, então #x# é removido dessa folha. Caso contrário, se #x# é achado em #u.keys[i]# para algum nodo interno #u# então o algoritmo remove o menor valor -#x'#, na subárvore enraizada em +#x'# na subárvore enraizada em #u.children[i+1]#. O valor #x'# é o menor valor guardado na #BTree# que é maior que #x#. O valor de #x'# é então usado para substituir #x# em @@ -497,8 +500,8 @@ \subsection{Remoção} \includegraphics[width=\ScaleIfNeeded]{figs/btree-remove-2} \end{tabular}} \caption[Remoção em uma $B$-tree] {A operação #remove(x)# - em uma #BTree#. Para remover o valor $#x#=10$ substituimos com o valor - $#x'#=11$ e removemos 11 da folha que o contém. } + em uma #BTree#. Para remover o valor $#x#=10$, o substituímos com o valor + $#x'#=11$ e removemos 11 da folha que o contém.} \figlabel{btree-remove} \end{figure} @@ -508,25 +511,23 @@ \subsection{Remoção} Note que, após remover recursivamente o valor #x# o #i#-ésimo filho de #u#, #removeRecursive(x,ui)# precisa garantir que esse filho ainda tem pelo menos $B-1$ chaves. No código precedente, isso é feito usando um método chamado -#checkUnderflow(x,i)#, que verifica se ocorreu underflow e o corrije no #i#-ésimo filho de #u#. Seja #w# o #i#-ésimo filho de #u#. +#checkUnderflow(x,i)#, que verifica se ocorreu \emph{underflow} e o corrige no #i#-ésimo filho de #u#. Seja #w# o #i#-ésimo filho de #u#. Se #w# tem somente -$B-2$ chaves, então isso precisa se corrigido. +$B-2$ chaves, então isso precisa ser corrigido. A correção requer usar um irmão de #w#. -Ele pode ser o filho -$#i#+1$ ou $#i#-1$ de #u#. +Ele pode ser o filho $#i#+1$ ou $#i#-1$ de #u#. Normalmente, iremos usar o filho $#i#-1$ de #u#, que é o irmão #v# de #w# diretamente à sua esquerda. -A única vez que isso não funciona é quando +A única situação em que isso não funciona é quando $#i#=0$ e nesse caso em que usamos o irmão de #w# diretamente à sua direita. \codeimport{ods/BTree.checkUnderflow(u,i)} A seguir, nos concentramos no caso em que - $#i#\neq 0$ tal que qualquer underflow + $#i#\neq 0$ tal que qualquer \emph{underflow} no #i#-ésimo filho de #u# será corrigido com a ajuda -do filho -$(#i#-1)$ de #u#. O caso $#i#=0$ é similar e os detalhes podem ser encontrados +do filho $(#i#-1)$ de #u#. O caso $#i#=0$ é similar e os detalhes podem ser encontrados no código que acompanha este livro. -Para corrigir um underflow no nodo #w#, precisamos achar mais chaves +Para corrigir um \emph{underflow} no nodo #w#, precisamos achar mais chaves (e possivelmente também filhos) para #w#. Existem duas formas de fazer isso: \begin{description} @@ -538,7 +539,8 @@ \subsection{Remoção} \[ B-2 + #size(w)# \ge 2B-2 \] - chaves. Podemos portanto deslocar chaves de #v# a #w# tal que #v# e #w# têm pelo menos $B-1$ chaves. Esse processo está ilustrado na + chaves. Podemos portanto deslocar chaves de #v# a #w# para que #v# e #w# + tenham pelo menos $B-1$ chaves. Esse processo está ilustrado na \figref{btree-borrow}. \begin{figure} @@ -560,7 +562,7 @@ \subsection{Remoção} \emph{juntamos} #v# e #w# conforme mostrado na \figref{btree-merge}. A operação de junção é o oposto da operação de divisão. - Ela usa dois nodos que contém um total de $2B-3$ chaves e junta elees em um + Ela usa dois nodos que contêm um total de $2B-3$ chaves e os junta em um único nodo que contém $2B-2$ chaves. (A chave adicional vem do fato que, quando juntamos #v# e #w#, o pai deles #u# agora tem um filho a menos e portanto precisa ceder uma de suas chaves.) @@ -580,14 +582,16 @@ \subsection{Remoção} \codeimport{ods/BTree.checkUnderflowNonZero(u,i).checkUnderflowZero(u,i)} -Para resumir, o método #remove(x)# em uma $B$-tree segue um caminho da raiz para a folha, remove uma chave #x'# de uma folha #u# e então realiza zero -ou mais operação de junção envolvendo #u# e seus ancestrais e também +Para resumir, o método #remove(x)# em uma $B$-tree segue um caminho da +raiz para a folha, remove uma chave #x'# de uma folha #u# e então realiza zero +ou mais operações de junção envolvendo #u# e seus ancestrais e também realiza no máximo uma operação de empréstimo. Como cada operação de junção e empréstimo, envolve modificar somente três nodos e somente $O(\log_B #n#)$ dessas operações acontecem, o processo completo leva -$O(\log_B #n#)$ de tempo no modelo de memória externa. Novamente, entretanto, cada operação de junção e empréstimo leva cerca de $O(B)$ de tempo no modelo RAM, +$O(\log_B #n#)$ de tempo no modelo de memória externa. Novamente, entretanto, +cada operação de junção e empréstimo leva cerca de $O(B)$ de tempo no modelo word-RAM, então (por ora) o máximo que podemos dizer sobre o tempo de execução necessário para #remove(x)# no modelo RAM é que é $O(B\log_B #n#)$. @@ -597,16 +601,16 @@ \subsection{Análise Amortizada da $B$-Tree} Até agora, mostramos que \begin{enumerate} - \item No modelo de memória externa, o tempo de execução de #find(x)#, + \item no modelo de memória externa, o tempo de execução de #find(x)#, #add(x)# e #remove(x)# em uma $B$-tree é $O(\log_B #n#)$. - \item No modelo RAM com palavras de #w# bits, o tempo de execução de #find(x)# é $O(\log #n#)$ e o tempo de execução de #add(x)# e #remove(x)# é $O(B\log #n#)$. + \item no modelo RAM com palavras de #w# bits, o tempo de execução de #find(x)# é $O(\log #n#)$ e o tempo de execução de #add(x)# e #remove(x)# é $O(B\log #n#)$. \end{enumerate} -O lema a seguir mostra que, até o momento, superestimamos o número de operação de junção e divisão realizado por uma -$B$-tree. +O lema a seguir mostra que, até o momento, superestimamos o número de +operações de junção e divisão realizadas por uma $B$-tree. \begin{lem}\lemlabel{btree-split} - Começando com uma $B$-tree vazia e realizando qualquer sequência de + Começando com uma $B$-tree vazia e realizando uma sequência de $m$ operações #add(x)# e #remove(x)# resulta em até $3m/2$ divisões, junções e empréstimos realizados. \end{lem} @@ -639,21 +643,20 @@ \subsection{Análise Amortizada da $B$-Tree} $B$ três créditos. Um nodo que guarda pelo menos $B$ chaves e até $2B-2$ chaves não precisa guardar nenhum crédito. - O que resta mostrar é que podemos manter o invariante de crédito e satisfazer as propriedades 1 e 2 acima durante cada operação -#add(x)# - e #remove(x)#. + O que resta mostrar é que podemos manter a invariante de crédito + e satisfazer as propriedades 1 e 2 acima durante cada operação #add(x)# e #remove(x)#. \paragraph{Adição:} O método #add(x)# não faz nenhuma junção ou empréstimo, então - precisamos somente considerar operações de divisão que como resultado de chamadas a #add(x)#. + precisamos somente considerar as operações de divisão resultantes de chamadas a #add(x)#. Cada operação de divisão ocorre porque uma chave é adicionada a um nodo #u# - que possui - $2B-1$ chaves. Quando isso acontece, #u# é dividido em dois nodos, #u'# e #u''# com $B-1$ e $B$ chaves, respectivamente. Antes de operação, #u# guardava + que possui $2B-1$ chaves. Quando isso acontece, #u# é dividido em dois + nodos, #u'# e #u''# com $B-1$ e $B$ chaves, respectivamente. Antes de operação, #u# guardava $2B-1$ chaves e portanto três créditos. Dois desses créditos podem ser usados para pagar pela divisão e o outro crédito pode ser dado para #u'# (que tem $B-1$ chaves) para manter a - invariente de crédito. Portanto, podemos pagar pela divisão e manter + invariante de crédito. Portanto, podemos pagar pela divisão e manter a invariante de crédito durante qualquer divisão. O única outra modificação em nodos que ocorrem durante uma operação @@ -664,14 +667,15 @@ \subsection{Análise Amortizada da $B$-Tree} receber três créditos. Esses são os únicos créditos cedidos pelo método #add(x)#. \paragraph{Remoção:} - Durante uma chamada a -#remove(x)#, zero ou mais junções ocorrem e são possivelmente seguidas por um único empréstimo. Cada junção ocorre porque dois nodos, + Durante uma chamada a #remove(x)#, zero ou mais junções ocorrem e são + possivelmente seguidas por um único empréstimo. Cada junção ocorre porque dois nodos, #v# e #w#, possuem cada um $B-1$ chaves antes da chamada ao método #remove(x)# são juntados em um único nodo com exatamente $2B-2$ chaves. -Cada uma dessas junçõesa portanto liberar dois créditos que podem ser usados para pagar pela junção. +Cada uma dessas junções portanto liberam dois créditos que podem ser usados para pagar pela junção. - Após as junções são realizada, ocorre no máximo uma operação e depois dela nenhuma junção ou empréstimo adicional é realizada. -Essa operação de empréstimo somente ocorre se removemos uma chave de uma + Após as junções serem realizadas, ocorre no máximo uma operação e + depois dela nenhuma junção ou empréstimo adicional é realizado. +Essa operação de empréstimo somente ocorre se removermos uma chave de uma folha #v# que tem $B-1$ chaves. O nodo #v# portanto tem um crédito e esse crédito vai pagar o custo do empréstimo. Esse crédito não é suficiente para pagar o empréstimo, então @@ -679,14 +683,14 @@ \subsection{Análise Amortizada da $B$-Tree} Nesse ponto, criamos um crédito e ainda necessitamos mostrar que a invariante de crédito pode ser mantida. No pior caso, o irmão de #v#, #w#, tem -exatamento $B$ chaves antes do empréstimo tal que, após isso, tanto #v# quanto #w# -tem $B-1$ chaves. Isso significa que #v# e #w# devem estar guardando um +exatamente $B$ chaves antes do empréstimo tal que, após isso, tanto #v# quanto #w# +têm $B-1$ chaves. Isso significa que #v# e #w# devem estar guardando 1 crédito cada quando a operação terminar. Portanto, nesse caso, criamos dois créditos adicionais para dar para #v# e #w#. Como um empréstimo acontece no máximo uma vez durante uma operação #remove(x)#, isso significa que criamos no máximo três créditos, conforme necessário. -Se a operação #remove(x)# não inclui uma operação de empréstimo, isso é porque ela termina removendo uma chave de algum nodo que, antes da operação, tinha $B$ ou mais chaves. No pior caso, esse nodo tinha $B$ chaves, então que ele tem agora $B-1$ chaves e precisa receber um crédito, que criamos. +Se a operação #remove(x)# não inclui uma operação de empréstimo, isso é porque ela termina removendo uma chave de algum nodo que, antes da operação, tinha $B$ ou mais chaves. No pior caso, esse nodo tinha $B$ chaves, então que ele tem agora $B-1$ chaves e precisa receber um crédito, que então criamos. Nos dois casos---se a remoção termina com uma operação de empréstimo ou não--- no máximo três créditos precisam ser criados durante uma chamada a @@ -695,27 +699,26 @@ \subsection{Análise Amortizada da $B$-Tree} \end{proof} O propósito do - \lemref{btree-split} é mostrar que, no modelo RAM com palavras de #w# bits, + \lemref{btree-split} é mostrar que, no modelo word-RAM, o custo de divisões e junções durante %and joins??? - seria borrows?? uma sequência de $m$ operações #add(x)# e #remove(x)# é somente $O(Bm)$. Isto é, o custo amortizado por operação é somente $O(B)$, então o custo amortizado de -#add(x)# e #remove(x)# no modelo RAM com palavras de #w# bits é $O(B+\log #n#)$. +#add(x)# e #remove(x)# no modelo word-RAM é $O(B+\log #n#)$. Isso é resumido no seguinte par de teoremas: \begin{thm}[$B$-Tree em Memória Externa] Uma #BTree# implementa a interface #SSet#. No modelo de memória externa, uma #BTree# suporta as operações #add(x)#, #remove(x)# e #find(x)# - em tempo $O(\log_B #n#)$ por operação. + em tempo $O(\log_B #n#)$ por operação. \end{thm} -\begin{thm}[$B$-Tree em RAM com palavras de #w# bits] - Uma #BTree# implementa a interface #SSet#. No modelo RAM com palavras de #w# bits, e ignorando o custo das divisões, junções e empréstimos, uma -#BTree# provê as operações +\begin{thm}[$B$-Tree em word-RAM] + Uma #BTree# implementa a interface #SSet#. No modelo word-RAM, e ignorando o custo das divisões, junções e empréstimos, uma #BTree# provê as operações #add(x)#, #remove(x)# e #find(x)# em tempo $O(\log #n#)$ por operação. - Adicionalmente, iciando com uma -#BTree# vazia, qualquer sequência de $m$ operações + Adicionalmente, iniciando com uma +#BTree# vazia, uma sequência de $m$ operações #add(x)# e #remove(x)# resulta em um total de $O(Bm)$ tempo gasto realizando divisões, junções e empréstimos. \end{thm} @@ -728,9 +731,9 @@ \section{Discussão e Exercícios} ou como o \emph{modelo de acesso a disco}. \index{modelo de acesso a disco}% -$B$-Tree é para busca em memória externa o que árvores binárias de busca são para busca em memória interna. +$B$-Tree é para buscas em memória externa o que árvores binárias de busca são para busca em memória interna. $B$-trees foram inventadas por Bayer -e McCreight \cite{bm70} em 1970 e, em menos de 10 anos, o título do artigo de Comer na revista \emph{ACM Computing Surveys} as referia como onipresente \cite{c79}. +e McCreight \cite{bm70} em 1970 e, em menos de 10 anos, o título do artigo de Comer na revista \emph{ACM Computing Surveys} as referia como onipresentes \cite{c79}. Assim como para árvores binárias de busca, existem muitas variantes de $B$-Trees, incluindo $B^+$-trees, @@ -747,20 +750,20 @@ \section{Discussão e Exercícios} e o Ext4 usado no Linux; \index{Ext4}% todos os principais sistemas de banco de dados; e -armazéns de chave-valor usados em computação em núvem. +armazéns de chave-valor usados em computação em nuvem. O levantamento recente de Graefe \cite{g10} provê uma visão geral de mais de 200 páginas de muitas aplicações modernas, variantes e otimizações de $B$-trees. -$B$-trees implementa a interface #SSet#. Se a interface #USet# for necessária, então hashing em memória externa +$B$-trees implementa a interface #SSet#. Se a interface #USet# for necessária, +então hashing em memória externa \index{hashing em memória externa}% pode ser usado como alternativa à $B$-tree. -Esquemas de hashing em memória externa existem; ver, por exemplo, +Esquemas de hashing em memória externa existem; veja, por exemplo, o trabalho de Jensen e Pagh~\cite{jp08}. Esse esquemas implementam as operações #USet# em tempo $O(1)$ esperado no modelo de memória externa. Entretanto, devido a vários motivos, muitas aplicações ainda usam -$B$-tree embora somente sejam necessárias operações -#USet#. +$B$-tree embora somente sejam necessárias operações #USet#. Uma razão de $B$-trees serem tão populares é elas funcionam melhor que o limitante $O(\log_B #n#)$ de tempo de execução sugere. A razão para isso é, em cenários de memória externa, o valor de $B$ é tipicamente bem alto -- na casa das centenas ou milhares. Isso significa que @@ -774,21 +777,18 @@ \section{Discussão e Exercícios} nos nodos internos, seguida por um único acesso à memória externa para obter uma folha. \begin{exc} - Mostre o que acontece quando as chaves -1.5 e então 7.5 são adicionadas à - $B$-tree em \figref{btree}. + Mostre o que acontece quando as chaves 1.5 e então 7.5 são adicionadas à + $B$-tree na \figref{btree}. \end{exc} \begin{exc} - Mostre o que acontece quando as chaves -3 e então 4 são removidas da - $B$-tree em \figref{btree}. + Mostre o que acontece quando as chaves 3 e então 4 são removidas da + $B$-tree na \figref{btree}. \end{exc} \begin{exc} Qual é o número máximo de nodos internos em uma -$B$-tree que guarda - #n# chaves (como uma função de #n# e $B$)? +$B$-tree que guarda #n# chaves (como uma função de #n# e $B$)? \end{exc} \begin{exc} @@ -799,7 +799,7 @@ \section{Discussão e Exercícios} \item Mostre que a implementação dos métodos #add(x)# e #remove(x)# dados neste capítulo usam memória interna proporcional a $B\log_B #n#$. - \item Descreve como esses métodos poderiam ser modificados a fim de reduzir o consumo de memória a + \item Descreva como esses métodos poderiam ser modificados a fim de reduzir o consumo de memória a $O(B + \log_B #n#)$. \end{enumerate} \end{exc} @@ -807,17 +807,17 @@ \section{Discussão e Exercícios} \begin{exc} Desenhe os créditos usados na prova do \lemref{btree-split} nas árvores das - Figuras~\ref{fig:btree-add} e \ref{fig:btree-remove-full}. Verifique] + Figuras~\ref{fig:btree-add} e \ref{fig:btree-remove-full}. Verifique que (com três créditos adicionais) é possível pagar pelas divisões, junções e empréstimos e manter a invariante de crédito. \end{exc} \begin{exc} Projete uma versão modificada de uma -$B$-tree em que nodos pode ter de +$B$-tree em que nodos podem ter de $B$ a $3B$ filhos (e portanto de $B-1$ até $3B-1$ chaves). Mostre que essa nova versão de -$B$-trees realiza somente $O(m/B)$ divisões, junções e empréstimos durante +$B$-tree realiza somente $O(m/B)$ divisões, junções e empréstimos durante uma sequência de $m$ operações. (Dica: para isso funcionar, você deve ser mais agressivo com a junção e às vezes juntar dois novos antes que seja estritamente necessário.) @@ -829,7 +829,7 @@ \section{Discussão e Exercícios} $B$-trees que assintoticamente reduz o número de divisões, empréstimos e junções ao considerar três nodos por vez. \begin{enumerate} \item Seja #u# um nodo sobrecarregado e seja #v# um irmão imediatamente à direita de #u#. - Existem duas forma de concertar o excesso de chaves em #u#: + Existem duas formas de consertar o excesso de chaves em #u#: \begin{enumerate} \item #u# pode dar algumas de suas chaves a #v#; ou \item #u# pode ser dividido e as chaves de #u# e #v# podem ser igualmente distribuídas entre @@ -856,15 +856,15 @@ \section{Discussão e Exercícios} \begin{exc} - Uma $B^+$-tree, ilustrada na \figref{bplustree} guarda todas as chaves em folhas e mantém suas folhas guardadas como uma lista duplamente encadeada. -Como de costume, cada folha guarda entre - $B-1$ e $2B-1$ chaves. Acima dessa lista está uma - $B$-tree padrão que gaurda o maior valor de cada folha exceto o último. + Uma $B^+$-tree, ilustrada na \figref{bplustree}, guarda todas as chaves em folhas e mantém suas folhas guardadas como uma lista duplamente encadeada. +Como de costume, cada folha guarda entre $B-1$ e $2B-1$ chaves. + Acima dessa lista está uma + $B$-tree padrão que guarda o maior valor de cada folha exceto o último. \begin{enumerate} \item Descreva implementações rápidas de #add(x)#, #remove(x)#x, e #find(x)# em uma $B^+$-tree. \item Explique como implementar eficientemente o método #findRange(x,y)#, - que encontra todos os valores maior que #x# e menores que ou iguais a #y#, + que encontra todos os valores maiores que #x# e menores que ou iguais a #y#, em uma $B^+$-tree. \item Implemente uma classe, #BPlusTree#, que implementa #find(x)#, #add(x)#, #remove(x)# e #findRange(x,y)#. @@ -877,7 +877,7 @@ \section{Discussão e Exercícios} \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/bplustree}} - \caption{Uma $B^+$-tree é uma $B$-tree emcima de uma lista duplamente encadeada de blocos.} + \caption{Uma $B^+$-tree é uma $B$-tree em cima de uma lista duplamente encadeada de blocos.} \figlabel{bplustree} \end{figure} From 9bed7cdfcdc40b5df7c3005ee81ef8a3a72b6d87 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 14 Sep 2020 14:53:45 -0300 Subject: [PATCH 59/66] added translation notes and fixed minor mistakes/types in all the book --- latex/ack.tex | 12 +- latex/binarytrees.tex | 6 +- latex/figs/dependencies.ipe | 238 ++++++++++++++++++------------------ latex/figs/em.ipe | 40 +++--- latex/graphs.tex | 4 +- latex/hashing.tex | 2 +- latex/heaps.tex | 2 +- latex/integers.tex | 4 +- latex/intro.tex | 35 +++--- latex/linkedlists.tex | 12 +- latex/ods.tex | 22 ++-- latex/rbs.tex | 2 +- latex/sorting.tex | 2 +- latex/why.tex | 9 +- 14 files changed, 201 insertions(+), 189 deletions(-) diff --git a/latex/ack.tex b/latex/ack.tex index 7bb3c159..3b6f15d4 100644 --- a/latex/ack.tex +++ b/latex/ack.tex @@ -1,11 +1,17 @@ \chapter*{Agradecimentos} \addcontentsline{toc}{chapter}{Agradecimentos} -Agradeço à todos que ajudaram e motivaram a fazer esta tradução. Devido à natureza pessoal de agradecimentos, vou manter os agradecimentos originais deste livro a seguir. +\section*{\emph{Do Autor, Pat Morin}} -I am grateful to Nima~Hoda, who spent a summer tirelessly proofreading +\emph{I am grateful to Nima~Hoda, who spent a summer tirelessly proofreading many of the chapters in this book; to the students in the Fall 2011 offering of COMP2402/2002, who put up with the first draft of this book and spotted many typographic, grammatical, and factual errors; and to Morgan~Tunzelmann at Athabasca University Press, for patiently editing -several near-final drafts. +several near-final drafts.} + +\section*{\emph{Do Tradutor, Marcelo Keese Albertini }} + +Agradeço à todos que me motivaram a fazer esta tradução. Agradeço, Luiz F. A. Brito por revisar parte do texto. +Em especial, agradeço à minha esposa Madeleine por me apoiar neste trabalho, que exigiu mais horas de trabalho do que o normal. + diff --git a/latex/binarytrees.tex b/latex/binarytrees.tex index 7821a20f..27265e64 100644 --- a/latex/binarytrees.tex +++ b/latex/binarytrees.tex @@ -298,7 +298,7 @@ \subsection{Remoção} \begin{center} \includegraphics[scale=0.90909]{figs/bst-splice} \end{center} - \caption{Removendo uma folha ($6$) ou um nodo com somente um filho ($9$) é fácil.} + \caption{Remover uma folha ($6$) ou um nodo com somente um filho ($9$) é fácil.} \figlabel{bst-splice} \end{figure} @@ -541,7 +541,7 @@ \section{Discussão e Exercícios} \end{exc} \begin{exc} - É possível um operação #remove(x)# aumentar a altura de um nodo em uma + É possível uma operação #remove(x)# aumentar a altura de um nodo em uma #BinarySearchTree#? Se sim, em quanto? \end{exc} @@ -558,5 +558,5 @@ \section{Discussão e Exercícios} #u.depth# (a profundidade de #u#) e #u.height# (a altura da subárvore enraizada de #u#). Esses valores devem ser mantidos, mesmo durante chamadas às operações #add(x)# - e #remove(x)#, mas isso não devem aumentar o custo dessas operações em mais do que um fator constante. + e #remove(x)#, mas isso não deve aumentar o custo dessas operações em mais do que um fator constante. \end{exc} diff --git a/latex/figs/dependencies.ipe b/latex/figs/dependencies.ipe index 5780384a..caebd6ec 100644 --- a/latex/figs/dependencies.ipe +++ b/latex/figs/dependencies.ipe @@ -1,7 +1,7 @@ - - + + \usepackage{kpfonts}\fontsize{10pt}{10pt}\selectfont\usepackage{ods-colors} @@ -173,8 +173,8 @@ h - + @@ -198,8 +198,8 @@ h - + @@ -233,9 +233,9 @@ h -11.1.2. Quicksort +11.1.2. Quicksort -1. Introduction +1. Introdução 256 784 m 256 768 l @@ -244,111 +244,101 @@ h h - -6. Binary trees +3. Listas ligadas +3.3 Lista ligadas eficientes em espaço -64 608 m -64 592 l -144 592 l -144 608 l -h - - -3. Linked lists -3.3 Space-efficient linked lists - -368 720 m -368 704 l -512 704 l +336 736 m +336 720 l 512 720 l +512 736 l h - -368 720 m -368 704 l -512 704 l -512 720 l + +336 752 m +336 736 l +512 736 l +512 752 l h -2. Array-based lists - -208 768 m -208 752 l -304 752 l -304 768 l +2. Listas baseadas em arrays + +152 752 m +152 736 l +288 736 l +288 752 l h -7. Random binary search trees +7. Árvores binárias de busca aleatórias -240 624 m -240 608 l +208 624 m +208 608 l 384 608 l 384 624 l h -9. Red-black trees - -224 544 m -224 528 l -320 528 l -320 544 l +9. Árvores rubro-negras + +208 560 m +208 544 l +328 544 l +328 560 l h -10. Heaps - +10. Heaps + 224 512 m 224 496 l 288 496 l 288 512 l h -12. Graphs - +12. Grafos + 240 480 m 240 464 l 320 464 l 320 480 l h -13. Data structures for integers - -240 448 m -240 432 l -400 432 l -400 448 l +13. Estruturas de dados para inteiros + +208 464 m +208 448 l +384 448 l +384 464 l h -8. Scapegoat trees +8. Árvores Scapegoat -240 592 m -240 576 l -336 576 l -336 592 l +208 592 m +208 576 l +312 576 l +312 592 l h -11. Sorting algorithms - -448 608 m -448 592 l -560 592 l -560 608 l +11. Algoritmos de ordenação + +432 624 m +432 608 l +568 608 l +568 624 l h - -448 608 m -448 592 l -560 592 l -560 608 l + +432 608 m +432 592 l +568 592 l +568 608 l h - -448 608 m -448 592 l -560 592 l -560 608 l + +432 592 m +432 576 l +568 576 l +568 592 l h -11.1.3. Heapsort +11.1.3. Heapsort 296 784 m 288 640 @@ -362,48 +352,48 @@ h 488 624 s -480 744 m -512 736 -512 704 -416 704 -407.509 688 s +511.572 743.12 m +544 736 +528 704 +496 704 +480 688 s -192 616 m -240 616 l +160 616 m +208 616 l -192 616 m -208 608 -208 592 -240 584 s +160 616 m +176 608 +176 592 +208 584 s -192 616 m -208 608 -208 560 -240 552 s +160 616 m +176 608 +176 560 +208 552 s -192 616 m -208 608 -208 528 -240 520 s - - -192 616 m -208 608 -208 496 -240 488 s - - -192 616 m -208 608 -208 464 -240 456 s +160 616 m +176 608 +176 528 +208 520 s + + +160 616 m +176 608 +176 496 +208 488 s + + +160 616 m +176 608 +176 464 +208 456 s -304 520 m +272 520 m 368 528 384 576 432 584 s @@ -420,8 +410,8 @@ h 400 768 408 752 s - -4. Skiplists + +4. Skiplists 368 672 m 368 656 l @@ -431,7 +421,7 @@ h -5. Hash tables +5. Tabelas Hash 192 720 m 192 704 l @@ -440,7 +430,7 @@ h h - + 224 736 m 224 720 l @@ -450,25 +440,33 @@ h 416 600 432 600 s - + 224 704 m 224 464 224 464 240 456 s -14. External-memory searching - -240 448 m -240 432 l -400 432 l -400 448 l +14. Busca em memória externa + +208 432 m +208 416 l +352 416 l +352 432 l h -192 616 m -208 608 -208 432 -240 424 s +160 616 m +176 608 +176 432 +208 424 s + +6. Árvores binárias + +80 624 m +80 608 l +176 608 l +176 624 l +h diff --git a/latex/figs/em.ipe b/latex/figs/em.ipe index 90f65df8..0e2794ab 100644 --- a/latex/figs/em.ipe +++ b/latex/figs/em.ipe @@ -1,7 +1,7 @@ - - + + \usepackage{kpfonts}\fontsize{10pt}{10pt}\selectfont\usepackage{ods-colors} @@ -173,8 +173,8 @@ h - + @@ -198,8 +198,8 @@ h - + @@ -303,7 +303,7 @@ h 112 0 0 112 256 576 e -$\mathtt{\color{var}x}$ +\mathtt{\color{var}x} 241.167 621.651 m 48 0 0 48 256 576 217.167 604.214 a @@ -312,9 +312,9 @@ h 48 0 0 48 256 576 e -disk -RAM -External Memory +disco +RAM +Memória Externa 112 672 m 112 656 l @@ -371,35 +371,35 @@ h 96 672 l h - + 80 672 m 80 656 l 96 656 l 96 672 l h - + 80 672 m 80 656 l 96 656 l 96 672 l h - + 80 672 m 80 656 l 96 656 l 96 672 l h - + 80 672 m 80 656 l 96 656 l 96 672 l h - + 80 672 m 80 656 l 96 656 l @@ -469,15 +469,15 @@ h 96 672 l h -$\mathtt{\color{var}x}$ - +\mathtt{\color{var}x} + 80 672 m 80 656 l 96 656 l 96 672 l h -$\mathtt{\color{var}x}$ +\mathtt{\color{var}x} 204.778 747.999 m 204.778 705.024 l @@ -485,16 +485,16 @@ h 261.223 747.999 l h -CPU - +CPU + 256.378 607.667 m 296.574 640.24 l - + 230.732 594.77 m 216.493 640.24 l - + 256.417 656.24 m 256.417 680.847 l diff --git a/latex/graphs.tex b/latex/graphs.tex index f7c71ed8..a3e6b387 100644 --- a/latex/graphs.tex +++ b/latex/graphs.tex @@ -92,10 +92,10 @@ \chapter{Grafos} Entretanto, diferentes aplicações de grafos têm diferentes exigências de desempenho para essas operações e, idealmente, podemos usar a implementação mais simples que satisfaz todos os requisitos da aplicação. -Devido essa razão, nós discutimos duas grandes categorias de representações +Devido a essa razão, nós discutimos duas grandes categorias de representações de grafos. -\section{#AdjacencyMatrix#: Representando um Grafo com um Matriz} +\section{#AdjacencyMatrix#: Um Grafo com uma Matriz} \seclabel{adjacency-matrix} \index{matriz de adjacências}% diff --git a/latex/hashing.tex b/latex/hashing.tex index 1ed6ba24..a2dd38c2 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -933,7 +933,7 @@ \section{Discussão e Exercícios} \index{hashing perfeito}% Nesses métodos a operação #find(x)# leva $O(1)$ de tempo no pior caso. - Para conjuntos de dados estáticos isso pode ser realizar ao achar + Para conjuntos de dados estáticos isso pode ser realizado com \emph{funções hash perfeitas} \index{funções hash perfeitas}% \index{função hash!perfeita}% diff --git a/latex/heaps.tex b/latex/heaps.tex index 7c6fd030..5aae7546 100644 --- a/latex/heaps.tex +++ b/latex/heaps.tex @@ -7,7 +7,7 @@ \chapter{Heaps} \index{heap}% \index{heap binária}% \index{heap!binária}% -que significa ``uma pilha desorganizada.'' Isso contrasta com árvores binárias +que significa ``uma pilha desorganizada''. Isso contrasta com árvores binárias de busca que podem ser pensadas como pilhas altamente organizadas. A primeira implementação da heap usa um array que simula uma árvore binária diff --git a/latex/integers.tex b/latex/integers.tex index 694c0a54..479fa13c 100644 --- a/latex/integers.tex +++ b/latex/integers.tex @@ -159,7 +159,7 @@ \section{#BinaryTrie#: Uma Árvore de Busca Digital} \begin{thm} Uma #BinaryTrie# implementa a interface #SSet# para inteiros de #w# bits. -Uma #BinaryTrie# possu as operações #add(x)#, #remove(x)# e #find(x)# +Uma #BinaryTrie# possui as operações #add(x)#, #remove(x)# e #find(x)# em tempo de $O(#w#)$ por operação. O espaço usado por uma #BinaryTrie# que guarda #n# valores em $O(#n#\cdot#w#)$. \end{thm} @@ -421,7 +421,7 @@ \section{#YFastTrie#: Um #SSet# com Tempo Duplamente Logarítmico} $\E[j]$ é igual ao número esperado de lançamentos tendenciosos necessários para obter a primeira cara. \footnote{Essa análise ignora o fato que $j$ nunca passa de $#n#-i+1$. Entretanto, isso somente reduz - $\E[j]$, então o limitando superior ainda vale.} Cada lançamento é independente e + $\E[j]$, então o limitante superior ainda vale.} Cada lançamento é independente e sai cara com probabilidade $1/#w#$, então $\E[j]\le#w#$. (Veja o \lemref{coin-tosses} para uma análise para o caso $#w#=2$.) diff --git a/latex/intro.tex b/latex/intro.tex index 7ef8d0fd..a4f71c1c 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -148,7 +148,7 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} \begin{figure} \centering{\includegraphics[width=\ScaleIfNeeded]{figs/prioqueue}} - \caption[Uma fila com prioridades]{Uma #Queue# com prioridades --- itens são removidos de acordo com suas prioridades (em inglês, priority queue).} + \caption[Uma fila com prioridades]{Uma #Queue# com prioridades --- itens são removidos de acordo com suas prioridades (em inglês, \emph{priority queue}).} \figlabel{prioqueue} \end{figure} @@ -157,7 +157,7 @@ \subsection{As Interfaces #Queue#, #Stack# e #Deque#} %When a business-class seat becomes available it is given to the most %important customer waiting on an upgrade. -Uma política de enfileiramento muito comum é a política LIFO (last-in-first-out, último-a-entrar-primeiro-a-sair, em português) +Uma política de enfileiramento muito comum é a política LIFO (last-in-first-out, último a entrar, primeiro a sair, em português) \index{LIFO queue}% \index{LIFO queue|seealso{stack}}% \index{fila LIFO}% @@ -212,7 +212,7 @@ \subsection{A Interface #List#: Sequências Lineares} Atribua $#x#_{j}=#x#_{j+1}$, para todo $j\in\{#i#,\ldots,#n#-2\}$ e decremente #n# \end{enumerate} -Note que essas operações são facilmente suficientes para implementar +Note que essas operações são suficientes para implementar a interface #Deque#: \begin{eqnarray*} #addFirst(x)# &\Rightarrow& #add(0,x)# \\ @@ -669,8 +669,7 @@ \subsection{Randomização e Probabilidades} \section{O Modelo de Computação} \seclabel{model} -Neste livro, iremos analisar o tempo teórico de execução das operações das estruturas de dados que estudamos. Para fazer precisamente isso, precisamos um modelo matemático de computação. Para isso, usamos -o modelo \emph{#w#-bit word-RAM} +Neste livro, iremos analisar o tempo teórico de execução das operações das estruturas de dados que estudamos. Para fazer precisamente isso, precisamos um modelo matemático de computação. Para isso, usamos o modelo \emph{#w#-bit word-RAM}% \index{word-RAM}% \index{RAM}% . RAM é uma sigla para Random Access Machine --- Máquina de Acesso Aleatório. Nesse modelo, temos acesso a uma memória de acesso aleatório consistindo de \emph{células}, cada qual armazena uma #w#-bit \emph{word}, ou seja, uma palavra com #w# bits de memória. @@ -761,31 +760,31 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} \paragraph{Pior caso versus custo amortizado:} \index{custo amortizado}% -Suponha que uma casa custe \$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de -\$1\,200. Nesse caso, o custo mensal no pior caso de pagar o empréstimo é -\$1\,200. +Suponha que uma casa custe R\$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de +R\$1\,200. Nesse caso, o custo mensal no pior caso de pagar o empréstimo é +R\$1\,200. -Se temos suficiente dinheiro em mãos, podemos escolher comprar a casa sem empréstimo, com um pagamento de \$120\,000. +Se temos suficiente dinheiro em mãos, podemos escolher comprar a casa sem empréstimo, com um pagamento de R\$120\,000. Nesse caso, para o período de 10 anos, o custo amortizado mensal de comprar essa casa é \[ - \$120\,000 / 120\text{ meses} = \$1\,000\text{ por mês} \enspace . + \text{R}\$120\,000 / 120\text{ meses} = \text{R}\$1\,000\text{ por mês.} \] -Esse valor é bem menor que os \$1\,200 mensais que teríamos que pagar se pegássemos o empréstimo. +Esse valor é bem menor que os R\$1\,200 mensais que teríamos que pagar se pegássemos o empréstimo. \paragraph{Pior caso versus custo esperado:} \index{custo esperado}% -A seguir, considere o caso de um seguro de incêndio na nossa casa de \$120\,000. +A seguir, considere o caso de um seguro de incêndio na nossa casa de R\$120\,000. Por analisar centenas de milhares de casos, seguradoras têm determinado que a quantidade esperada de danos por incêndios causado a uma casa como a nossa é de -\$10 por mês. +R\$10 por mês. Esse é uma valor bem baixo, pois a maior parte das casa nunca pegam fogo, algumas poucas tem incêndios pequenos que causam algum dano e um número minúsculo de casas queimam até as cinzas. Baseada nessa informação, a seguradora cobra -\$15 mensais para a contratação de um seguro. +R\$15 mensais para a contratação de um seguro. Agora é o momento da decisão. Devemos pagar os - \$15 referente ao custo de pior caso mensalmente para o seguro ou devemos + R\$15 referentes ao custo de pior caso mensalmente para o seguro ou devemos apostar fazer uma poupança anti-incêndio ao custo esperado de -\$10 mensal? Claramente, \$10 por mês custa menos \emph{em expectativa}, +R\$10 mensal? Claramente, R\$10 por mês custa menos \emph{em expectativa}, mas temos que aceitar que a possibilidade de \emph{custo real} possa ser bem maior. -No evento improvável que a casa queime inteira, o custo real será de \$120\,000. +No evento improvável que a casa queime inteira, o custo real será de R\$120\,000. Esses exemplos financeiros também oferecem a oportunidade de vermos porque às vezes aceitamos um tempo de execução amortizado ou esperado em vez de considerarmos o tempo de execução no pior caso. Frequentemente é possível obter um tempo de execução esperado ou amortizado menor que o obtido no pior caso. No mínimo, frequentemente é possível obter uma estrutura de dados bem mais simples se estivermos dispostos a aceitar tempo de execução amortizado ou esperado. @@ -936,7 +935,7 @@ \section{Lista de Estruturas de Dados} #MeldableHeap# & $O(1)$ & $O(\log #n#)$\tnote{E} & \sref{meldableheap} \\ \hline \end{tabular} \begin{tablenotes} -\item[I]{Essa estrutura somente pode guardar dados inteiros de #w#-bit.} +\item[I]{Essa estrutura pode guardar somente dados inteiros de #w#-bit.} \javaonly{\item[X]{Isso denota o tempo de execução no modelo de memória externa; veja o \chapref{btree}.}} \end{tablenotes} %\renewcommand{\thefootnote}{\arabic{footnote}} diff --git a/latex/linkedlists.tex b/latex/linkedlists.tex index 5aa91441..aa26d0c9 100644 --- a/latex/linkedlists.tex +++ b/latex/linkedlists.tex @@ -161,8 +161,8 @@ \subsection{Adicionando e Removendo} Se temos uma referência a um nodo #w# em uma #DLList# e queremos inserir um nodo #u# antes de #w#, então isso é só uma questão de atribuir $#u.next#=#w#$, -$#u.prev#=#w.prev#$, e então ajustar #u.prev.next# e #u.next.prev#. (Veja a \figref{dllist-addbefore}.) -Graças o nodo dummy, não há necessidade de se preocupar sobre #w.prev# +$#u.prev#=#w.prev#$ e então ajustar #u.prev.next# e #u.next.prev#. (Veja a \figref{dllist-addbefore}.) +Graças ao nodo dummy, não há necessidade de se preocupar sobre #w.prev# existir ou não. \codeimport{ods/DLList.addBefore(w,x)} @@ -195,7 +195,7 @@ \subsection{Adicionando e Removendo} \codeimport{ods/DLList.remove(i)} Novamente, a única parte cara dessa operação é achar o #i#-ésimo nodo usando - #getNode(i)#, e então #remove(i)# roda em tempo $O(1+\min\{#i#, #n#-#i#\})$. + #getNode(i)# e então #remove(i)# roda em tempo $O(1+\min\{#i#, #n#-#i#\})$. \subsection{Resumo} O teorema a seguir resume o desempenho de uma #DLList#: @@ -303,7 +303,7 @@ \subsection{Procurando Elementos} partes: \begin{enumerate} - \item O nodo #u# que contém o bloco que contém o elemento com índice #i#; e + \item o nodo #u# que contém o bloco que contém o elemento com índice #i#; e \item o índice #j# do elemento dentro desse bloco. \end{enumerate} @@ -393,7 +393,7 @@ \subsection{Adicionando um Elemento} \item Podemos rapidamente (em $r+1\le #b#$ passos) ir ao fim da lista de blocos. Nesse caso, adicionamos um bloco vazio ao fim da lista de bloco e preceder como no primeiro caso. -\item Após #b# passos não achamos quaisquer blocos que não está vazio. +\item Após #b# passos não achamos nenhum bloco que não esteja cheio. Nesse caso, $#u#_0,\ldots,#u#_{#b#-1}$ é uma sequência de #b# blocos onde cada contém @@ -401,7 +401,7 @@ \subsection{Adicionando um Elemento} no fim dessa sequência e \emph{espalhamos} os $#b#(#b#+1)$ elementos originais para que cada bloco de $#u#_0,\ldots,#u#_{#b#}$ contenha exatamente -#b# elementos. Agora o bloco de $#u#_0$ contém somente #b# elementos então ele tem espaço para inserir #x#. +#b# elementos. Agora o bloco de $#u#_0$ contém somente #b# elementos, então ele tem espaço para inserir #x#. \end{enumerate} \codeimport{ods/SEList.add(i,x)} diff --git a/latex/ods.tex b/latex/ods.tex index 0b1f70b7..410649ae 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -37,7 +37,7 @@ \usepackage[mathlines]{lineno} -\linenumbers +%\linenumbers %\DeclareGraphicsExtensions{.pdf,.eps} % Leave this here - it gets substituted with language specific stuff @@ -65,6 +65,8 @@ \hyphenation{Binary-Heap} \hyphenation{Meld-able-Heap} \hyphenation{Java-Script} +\hyphenation{implementacoes} +\hyphenation{co-mo} \usepackage{everysel} \EverySelectfont{% @@ -176,16 +178,16 @@ \hypersetup{colorlinks=true, linkcolor=linkblue, anchorcolor=linkblue,% citecolor=linkblue, filecolor=linkblue, menucolor=linkblue,% urlcolor=linkblue,% - pdfauthor={Pat Morin. Tradução e adaptação: Marcelo Keese Albertini},% - pdftitle={Open Data Structures. Estruturas de Dados Abertas},% + pdfauthor={Pat Morin. Tradutor: Marcelo Keese Albertini},% + pdftitle={Estruturas de Dados Abertas},% pdfsubject={Computer Science, Data Structures},% pdfkeywords={Data structures, algorithms}} \DeclareMathOperator{\bdiv}{div} % Title page content -\title{Open Data Structures (in \lang). Estruturas de Dados Abertas (em \lang)} -\author{Autor: Pat Morin.\\Tradução e adaptação por: Marcelo Keese Albertini} +\title{Estruturas de Dados Abertas (em \lang)} +\author{Autor: Pat Morin, Tradutor: Marcelo Keese Albertini} \date{% Edição PT-BR-0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} \htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} @@ -245,6 +247,12 @@ \thispagestyle{empty} \cleardoublepage +\fancyhead[RO,LE]{} % disable section numbers, for now +\pagestyle{fancy} +\include{trad} +\thispagestyle{empty} +\cleardoublepage + \fancyhead[CE]{\small Por que este livro?} % chapter title, left center \include{why} \cleardoublepage @@ -283,12 +291,12 @@ \fancyhead[CO]{\small\nouppercase\rightmark} \cleardoublepage -\addcontentsline{toc}{chapter}{Bibliography} +\addcontentsline{toc}{chapter}{Referências} \bibliographystyle{abbrvurl} \bibliography{ods,odsproc} \cleardoublepage -\addcontentsline{toc}{chapter}{Index} +\addcontentsline{toc}{chapter}{Índice} \printindex \end{document} diff --git a/latex/rbs.tex b/latex/rbs.tex index 3eb0d366..d5600b4d 100644 --- a/latex/rbs.tex +++ b/latex/rbs.tex @@ -311,7 +311,7 @@ \section{#Treap#: Uma Árvore Binária de Busca Aleatória} ---e inseri-las à uma #BinarySearchTree#. Mas isso significa que a forma de uma treap é idêntica àquela de uma árvore binária de busca binária. -Em particular, se substituímos cad chave #x# por seu rank, \footnote{O +Em particular, se substituímos cada chave #x# por seu rank, \footnote{O rank de um elemento #x# em um conjunto $S$ de elementos é o número de elementos em $S$ que são menores que #x#.} então o \lemref{rbs} aplica-se. diff --git a/latex/sorting.tex b/latex/sorting.tex index 9d875526..e8f1c7e6 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -320,7 +320,7 @@ \subsection{Heapsort} uma representação válida de uma heap. Quando esse processo termina (porque $#n#=0$) os elementos de #a# são guardados em ordem decrescente, então #a# é invertido para obter a forma ordenada final. -\footnote{O algoritmo poderia como opção redefinir a função +\footnote{O algoritmo poderia redefinir a função #compare(x,y)# para que o heapsort guarde os elementos diretamente na ordem crescente.} A \figref{heapsort} mostra um exemplo da execução do #heapSort(a,c)#. diff --git a/latex/why.tex b/latex/why.tex index 4048baaf..22db720d 100644 --- a/latex/why.tex +++ b/latex/why.tex @@ -13,14 +13,14 @@ \chapter*{Por que este livro?} por duas razões: (1)~Os direitos autorais pertencem ao autor e/ou editor, os quais podem não autorizar tais atualizações. (2)~O \emph{código-fonte} desses livros muitas vezes não está disponível. Isto é, os arquivos Word, WordPerfect, -FrameMaker ou \LaTeX\ para o livro não são acessíveis e, ainda, a versão do +FrameMaker ou \LaTeX\ para o livro não estão acessíveis e, ainda, a versão do software que processa esses arquivos pode não estar mais disponível. A meta deste projeto é libertar estudantes de graduação de Ciência da Computação de ter que pagar por um livro introdutório à disciplina de estruturas de dados. Eu -\footnote{The translator to Portuguese language is deeply grateful to the original author of this book Pat Morin for his decision which allows the availability of a good quality and free book in the +\footnote{The translator to Portuguese language is deeply grateful to the original author of this book Pat Morin for his decision, which allows the availability of a good quality and free book in the native language of my Brazilian students.} decidi implementar essa meta ao tratar esse livro como um projeto de Software Aberto% \index{Open Source}% \index{Software Aberta}% @@ -45,6 +45,7 @@ \chapter*{Por que este livro?} de códigos-fonte \texttt{git}% \index{git@\texttt{git}}% . Qualquer pessoa também pode fazer fork (criar versão alternativa) dos arquivos-fonte deste livro e desenvolver uma versão separada (por exemplo, em outra linguagem de programação). -Minha esperança é que, ao assim fazer, este livro continuará a -ser didático mesmo depois que o meu interesse no projeto, ou meu batimento cardíaco, desapareça (o que quer que aconteça primeiro). +Minha esperança é que, desse modo, este livro continuará a +ser útil mesmo depois que o meu interesse no projeto diminua. + From f1f2793fb8bb3582c4060d47464a72791e9bdcfb Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Wed, 23 Sep 2020 17:54:42 -0300 Subject: [PATCH 60/66] adjusted phrasing and details for printed book --- latex/ack.tex | 4 ++-- latex/arrays.tex | 5 ++--- latex/binarytrees.tex | 2 +- latex/btree.tex | 6 +++--- latex/figs/dependencies.ipe | 8 ++++---- latex/graphs.tex | 5 ++--- latex/hashing.tex | 13 ++++++++----- latex/intro.tex | 31 ++++++++++++++++--------------- latex/linkedlists.tex | 9 ++++----- latex/ods.tex | 8 ++++---- latex/redblack.tex | 3 +-- latex/scapegoat.tex | 2 +- latex/sorting.tex | 6 +++--- 13 files changed, 51 insertions(+), 51 deletions(-) diff --git a/latex/ack.tex b/latex/ack.tex index 3b6f15d4..8e88bb23 100644 --- a/latex/ack.tex +++ b/latex/ack.tex @@ -12,6 +12,6 @@ \section*{\emph{Do Autor, Pat Morin}} \section*{\emph{Do Tradutor, Marcelo Keese Albertini }} -Agradeço à todos que me motivaram a fazer esta tradução. Agradeço, Luiz F. A. Brito por revisar parte do texto. -Em especial, agradeço à minha esposa Madeleine por me apoiar neste trabalho, que exigiu mais horas de trabalho do que o normal. +Agradeço à todos que me motivaram a fazer esta tradução. Agradeço Hélio Albertini e Luiz Brito por ajudarem na revisão do texto. +Em especial, agradeço à minha esposa Madeleine Albertini por me apoiar neste trabalho, que exigiu mais horas de trabalho do que o normal. diff --git a/latex/arrays.tex b/latex/arrays.tex index 2ebe4d3d..d23207b1 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -267,7 +267,7 @@ \section{#FastArrayStack#: Uma ArrayStack otimizada} \pcodeonly{Nas nossas implementações em C++ e Java, o uso de funções de cópia rápida de arrays } -\notpcode{Nas implementações em \lang\ aqui, o uso do nativo \javaonly{#System.arraycopy(s,i,d,j,n)#}\cpponly{#std::copy(a0,a1,b)#}} +\notpcode{Nas implementações em \lang\ deste livro, o uso do método disponível nativamente \javaonly{#System.arraycopy(s,i,d,j,n)#}\cpponly{#std::copy(a0,a1,b)#}} resultaram em um fator de aceleração, \emph{speedup}, entre 2 e 3, dependendo dos tipos de operações realizadas. O resultados podem variar de acordo com o caso. @@ -543,8 +543,7 @@ \section{#DualArrayDeque#: Construção de um Deque a Partir de Duas Stacks} O método #add(i,x)# realiza o balanceamento das duas #ArrayStack#s #front# e #back#, ao chamar o método #balance()#. -A implementação de -#balance()# é descrita a seguir, mas no momento é suficiente +A seguir descrevemos a implementação de #balance()#, mas no momento é suficiente saber que #balance()# garante que, a não ser que $#size()#<2$, #front.size()# e #back.size()# não diferem por mais de um fator de 3. Em especial, $3\cdot#front.size()# \ge #back.size()#$ e diff --git a/latex/binarytrees.tex b/latex/binarytrees.tex index 27265e64..ce253ea9 100644 --- a/latex/binarytrees.tex +++ b/latex/binarytrees.tex @@ -437,7 +437,7 @@ \section{Discussão e Exercícios} \index{árvore binária de busca!balanceada no tamanho}% se, para todo nodo #u#, o tamanho das subárvores enraizadas em #u.left# e #u.right# diferem em no máximo uma unidade. - Escreva um método recursivo, #isBalanced()#, que testa se uma árvore + Escreva #isBalanced()#, um método recursivo que testa se uma árvore binária é balanceada. O seu método deve rodar em $O(#n#)$ de tempo. (Teste o seu código em algumas árvores maiores com diferentes formas; é fácil escrever um método que usa bem mais diff --git a/latex/btree.tex b/latex/btree.tex index 60f1f25c..85d4e436 100644 --- a/latex/btree.tex +++ b/latex/btree.tex @@ -176,7 +176,7 @@ \section{B-Trees} Isto é, a altura de uma $B$-tree é proporcional ao logaritmo base-$B$ do número de folhas. -Cada nodo #u# na $B$-tree guarda um array de chaves +Cada nodo #u# na $B$-tree guarda um array de até $2B-1$ posições contendo as chaves $#u.keys#[0],\ldots,#u.keys#[2B-1]$. Se #u# é um nodo interno com $k$ filhos, então o número de chaves guardado em #u# é exatamente $k-1$ e esses são guardados em @@ -191,8 +191,8 @@ \section{B-Trees} #u.keys[0]# < #u.keys[1]# < \cdots < #u.keys#[k-2] \enspace . \] Se #u# for um nodo interno, então para todo $#i#\in\{0,\ldots,k-2\}$, -$#u.keys[i]#$ é maior que toda chave guardada na subárvore enraizada em -#u.children[i]# mas menor que toda chave guardada nas subárvores enraizada em +$#u.keys[i]#$ é maior que toda chave na subárvore enraizada em +#u.children[i]# e menor que toda chave guardada nas subárvores enraizadas em $#u.children[i+1]#$. Informalmente, \[ #u.children[i]# \prec #u.keys[i]# \prec #u.children[i+1]# \enspace . diff --git a/latex/figs/dependencies.ipe b/latex/figs/dependencies.ipe index caebd6ec..75948fb6 100644 --- a/latex/figs/dependencies.ipe +++ b/latex/figs/dependencies.ipe @@ -1,7 +1,7 @@ - + \usepackage{kpfonts}\fontsize{10pt}{10pt}\selectfont\usepackage{ods-colors} @@ -442,9 +442,9 @@ h 224 704 m -224 464 -224 464 -240 456 s +181.419 587.842 +190.966 476.283 +208.052 463.72 s 14. Busca em memória externa diff --git a/latex/graphs.tex b/latex/graphs.tex index a3e6b387..a1834243 100644 --- a/latex/graphs.tex +++ b/latex/graphs.tex @@ -145,9 +145,8 @@ \section{#AdjacencyMatrix#: Um Grafo com uma Matriz} \figlabel{graph-adj} \end{figure} -A matriz de adjacências tem desempenho ruim com as operações -#outEdges(i)# e -#inEdges(i)#. Para implementá-las, precisamos varrer todas as #n# entradas na correspondente linha ou coluna de #a# e reunir todos os índices #j# +A matriz de adjacências tem desempenho insatisfatório com as operações +#outEdges(i)# e #inEdges(i)#. Para implementá-las, precisamos varrer todas as #n# entradas na correspondente linha ou coluna de #a# e reunir todos os índices #j# onde #a[i][j]#, respectivamente #a[j][i]#, seja verdadeiro. \javaimport{ods/AdjacencyMatrix.outEdges(i).inEdges(i)} \cppimport{ods/AdjacencyMatrix.outEdges(i,edges).inEdges(i,edges)} diff --git a/latex/hashing.tex b/latex/hashing.tex index a2dd38c2..67b9585a 100644 --- a/latex/hashing.tex +++ b/latex/hashing.tex @@ -332,7 +332,7 @@ \subsection{Resumo} #remove(x)# e #find(x)# em $O(1)$ de tempo esperado por operação. Além disso, começando com uma - #ChainedHashTable# vazia, qualquer sequência de $m$ operações + #ChainedHashTable# vazia, uma sequência de $m$ operações #add(x)# e #remove(x)# resulta em um total de $O(m)$ de tempo gasto durante todas a chamadas a #grow()#. \end{thm} @@ -522,12 +522,12 @@ \subsection{Análise da Sondagem Linear} é parte de um agrupamento de tamanho $k$, então o tempo que leva para executar a operação #find(x)# é até $O(1+k)$. Então, o tempo esperado de execução pode ser limitado superiormente por \[ - O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k\Pr\{\text{#i# é parte de um agrupamento de tamanho $k$}\}\right) \enspace . + O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k\Pr\{\text{#i# \small{está em um agrupamento de tamanho} $k$}\}\right).%\enspace. \] Note que cada agrupamento de tamanho $k$ contribui à soma interna $k$ vezes para uma contribuição total de $k^2$, então a soma anterior pode ser reescrita como \begin{align*} - & { } O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2\Pr\{\mbox{#i# inicia um agrupamento de tamanho $k$}\}\right) \\ + & { } O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2\Pr\{\mbox{#i# \small{inicia um agrupamento de tamanho} $k$}\}\right) \\ & \le O\left(1 + \left(\frac{1}{#t.length#}\right)\sum_{i=1}^{#t.length#}\sum_{k=0}^{\infty} k^2p_k\right) \\ & = O\left(1 + \sum_{k=0}^{\infty} k^2p_k\right) \\ & = O\left(1 + \sum_{k=0}^{\infty} k^2\cdot O(c^k)\right) \\ @@ -753,8 +753,11 @@ \subsection{Códigos Hash para Objetos Compostos} O passo final da função hash é aplicar o hashing multiplicativo para reduzir nosso resultado intermediário de $2#w#$ bits $h'(#x#_0,\ldots,#x#_{r-1})$ a um - resultado final com #w# bits $h(#x#_0,\ldots,#x#_{r-1})$. Usando o \thmref{multihash}, - se $h'(#x#_0,\ldots,#x#_{r-1})\neq h'(#y#_0,\ldots,#y#_{r-1})$, então + resultado final com #w# bits $h(#x#_0,\ldots,#x#_{r-1})$. + + Usando o \thmref{multihash}, + se $h'(#x#_0,\ldots,#x#_{r-1})\neq h'(#y#_0,\ldots,#y#_{r-1})$, então podemos + concluir que $\Pr\{h(#x#_0,\ldots,#x#_{r-1}) = h(#y#_0,\ldots,#y#_{r-1})\} \le 2/2^{#w#}$. Para resumir, diff --git a/latex/intro.tex b/latex/intro.tex index a4f71c1c..e87f248c 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -98,7 +98,7 @@ \section{Interfaces} Uma \emph{interface}, \index{interface}% \index{tipo abstrato de dados|see{interface}}% -às vezes também chamada de \emph{tipo abstrato de dados}(\emph{abstract data type}, em inglês), +às vezes também chamada de \emph{tipo abstrato de dados} (\emph{abstract data type}, em inglês), define o conjunto de operações aceitas por uma estrutura de dados e a semântica, ou significado, dessas operações. Uma interface nos diz nada sobre como a estrutura de dados implementa essas operações; ela somente provê uma lista de operações aceitas juntamente com as especificações sobre quais tipos de argumentos cada operação aceita e o valor retornado por cada operação. @@ -760,7 +760,8 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} \paragraph{Pior caso versus custo amortizado:} \index{custo amortizado}% -Suponha que uma casa custe R\$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de + +Considere uma casa que o dono está vendendo por R\$120\,000. A fim de comprar essa casa, podemos pegar um empréstimo de 120 meses (10 anos) com pagamentos mensais de R\$1\,200. Nesse caso, o custo mensal no pior caso de pagar o empréstimo é R\$1\,200. @@ -784,7 +785,7 @@ \section{Corretude, Complexidade de Tempo e Complexidade de Espaço} apostar fazer uma poupança anti-incêndio ao custo esperado de R\$10 mensal? Claramente, R\$10 por mês custa menos \emph{em expectativa}, mas temos que aceitar que a possibilidade de \emph{custo real} possa ser bem maior. -No evento improvável que a casa queime inteira, o custo real será de R\$120\,000. +No evento improvável em que a casa inteira queime, o custo real será de R\$120\,000. Esses exemplos financeiros também oferecem a oportunidade de vermos porque às vezes aceitamos um tempo de execução amortizado ou esperado em vez de considerarmos o tempo de execução no pior caso. Frequentemente é possível obter um tempo de execução esperado ou amortizado menor que o obtido no pior caso. No mínimo, frequentemente é possível obter uma estrutura de dados bem mais simples se estivermos dispostos a aceitar tempo de execução amortizado ou esperado. @@ -869,7 +870,7 @@ \section{Trechos de Código} Este livro mistura análise matemática de tempos de execução com o código-fonte da linguagem \lang\ para os algoritmos em análise. Isso significa que algumas equações contêm variáveis também encontradas no código-fonte. -Essas variáveis são formatadas consistentemente, tanto dentro do código-fonte quanto nas equações. A variável mais comum desse tipo é a variável #n# +Essas variáveis são formatadas consistentemente, tanto dentro do código fonte quanto nas equações. A variável mais comum desse tipo é a variável #n# \index{n@#n#}% que, sem exceção, sempre refere-se ao número de itens atualmente armazenados na estrutura de dados. } @@ -969,7 +970,7 @@ \section{Discussão e Exercícios} Leyman, Leighton e Meyer \cite{llm11}. Para um texto de cálculo suave que inclui definições formais de exponenciais e logaritmos, veja o (disponível livremente) texto clássico de Thompson \cite{t14}. -Para maiores informações em probabilidade básica, especialmente como ela se relaciona com Ciência da Computação, veja o livro didático de Ross \cite{r01}. +Para maiores informações sobre probabilidade básica, especialmente como ela se relaciona com Ciência da Computação, veja o livro didático de Ross \cite{r01}. Outra boa referência, que cobre notação assintótica e probabilidades, é o livro-texto de Graham, Knuth e Patashnik \cite{gkp94}. \javaonly{Leitores que desejam afinar suas habilidades de programação Java @@ -984,26 +985,26 @@ \section{Discussão e Exercícios} Resolva os seguintes problemas fazendo a leitura de um arquivo de texto uma linha por vez e executando operações em cada linha nas estrutura(s) de dados adequada(s). Suas implementações devem ser rápidas o suficiente tal que até arquivos com um milhão de linhas podem ser processadas em alguns segundos. \begin{enumerate} - \item Leia a entrada uma linha por vez e então escreva as linha em ordem invertida, tal que a última linha lida é a primeira imprimida, e então a penúltima lida é a segunda a ser imprimida e assim por diante. - \item Leia as primeiras 50 linhas da entrada e então as escreva na saída em ordem reversa. Leia as seguintes 50 linhas e então escreva na saída em ordem reversa. Faça isso até que não haja mais linhas a serem linhas. Nesse ponto as linhas restantes lidas também devem ser imprimidas invertidas. + \item Leia a entrada uma linha por vez e então escreva na tela as linha em ordem invertida, tal que a última linha lida é a primeira mostrada, e então a penúltima lida é a segunda a ser mostrada e assim por diante. + \item Leia as primeiras 50 linhas da entrada e então as escreva na saída em ordem reversa. Leia as seguintes 50 linhas e então escreva na saída em ordem reversa. Faça isso até que não haja mais linhas a serem linhas. Nesse ponto as linhas restantes lidas também devem ser mostradas em ordem invertida. Em outras palavras, sua saída iniciará com a 50ª linha, então 49ª linha, então a 48ª e assim até a primeira linha. Isso deverá ser seguido pela centésima linha, seguida pela 99ª até a 51ª linha. Assim por diante. O seu código nunca deverá manter mais de 50 linhas a qualquer momento. \item Leia a entrada uma linha por vez. - A qualquer momento após ler as primeiras 42 linhas, se alguma linha está em branco (i.e., uma string de comprimento 0), então produza a linha que ocorreu 42 linhas antes dela. Por exemplo, se Linha 242 está em branco, então o seu programa deve imprimir a linha 200. Esse programa deve ser implementado tal que ele nunca guarda mais que 43 linhas da entrada a qualquer momento. + A qualquer momento após ler as primeiras 42 linhas, se alguma linha está em branco (i.e., uma string de comprimento 0), então produza a linha que ocorreu 42 linhas antes dela. Por exemplo, se Linha 242 está em branco, então o seu programa deve mostrar a linha 200. Esse programa deve ser implementado tal que ele nunca guarda mais que 43 linhas da entrada a qualquer momento. -\item Leia a entrada uma linha por vez e imprima cada linha se não for uma duplicata de alguma linha anterior. Tenha cuidado especial para que um arquivo com muitas linhas duplicadas não use mais memória do que é necessário para o número de linhas únicas. +\item Leia a entrada uma linha por vez e mostre cada linha se não for uma duplicata de alguma linha anterior. Tenha cuidado especial para que um arquivo com muitas linhas duplicadas não use mais memória do que é necessário para o número de linhas únicas. -\item Leia a entrada uma linha por vez e imprima cada linha somente se você já encontrou uma linha igual antes. (O resultado final é que você remove a primeira ocorrência de cada linha.). Tenha cuidado para que um arquivo com muitas linhas duplicadas não use mais memória do que seja necessário para o número de linhas únicas. +\item Leia a entrada uma linha por vez e mostre na tela cada linha somente se você já encontrou uma linha igual antes. (O resultado final é que você remove a primeira ocorrência de cada linha.). Tenha cuidado para que um arquivo com muitas linhas duplicadas não use mais memória do que seja necessário para o número de linhas únicas. -\item Leia a entrada uma linha por vez. Então, imprima todas as linhas ordenadas por comprimento, com as linhas mais curtas primeiro. No caso em que duas linhas - tem o mesmo tamanho, decida sua ordem usando a ordem usual de texto. Linhas duplicadas devem ser impressas somente uma vez. +\item Leia a entrada uma linha por vez. Então, mostre na tela todas as linhas ordenadas por comprimento, com as linhas mais curtas primeiro. No caso em que duas linhas + tem o mesmo tamanho, decida sua ordem usando a ordem usual de texto. Linhas duplicadas devem ser mostradas somente uma vez. -\item Faça o mesmo que a questão anterior exceto que linhas duplicadas devem ser impressas o mesmo número de vezes que elas aparecem na entrada. +\item Faça o mesmo que a questão anterior exceto que linhas duplicadas devem ser mostradas o mesmo número de vezes que elas aparecem na entrada. -\item Leia a entrada uma linha por vez e então imprima as linhas pares (começando com a primeira linha, linha 0) seguidas das linhas ímpares. +\item Leia a entrada uma linha por vez e então mostre na tela as linhas pares (começando com a primeira linha, linha 0) seguidas das linhas ímpares. -\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de imprimir. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser impressa, mas em ordem aleatória. +\item Leia a entrada inteira uma linha por vez e aleatoriamente troque as linhas antes de mostrá-las. Para ser claro: você não deve mudar o conteúdo de qualquer linha. Em vez disso, a mesma coleção de linhas deve ser mostrada, mas em ordem aleatória. \end{enumerate} \end{exc} diff --git a/latex/linkedlists.tex b/latex/linkedlists.tex index aa26d0c9..0d3d1a40 100644 --- a/latex/linkedlists.tex +++ b/latex/linkedlists.tex @@ -638,7 +638,7 @@ \section{Discussão e Exercícios} de #prev# e #next# dos nodos existentes. \begin{exc} -Escreva um método para a #DLList# chamado de #isPalindrome()# que retorna #true# se a lista for um +Escreva um método #isPalindrome()# para a #DLList# que retorna #true# se a lista for um \emph{palíndromo}, \index{palíndromo}% isto é, o elemento na posição #i# é igual ao elemento na posição @@ -761,10 +761,9 @@ \section{Discussão e Exercícios} a respeito da implementação e análise da lista eficiente em uso do espaço #SEList#: \begin{exc} - Prove que, se uma #SEList# é usada como uma #Stack# (tal que as - únicas modificações à #SEList# são feitas usando - $#push(x)#\equiv - #add(size(),x)#$ e $#pop()#\equiv #remove(size()-1)#$), então essas +Prove que, se uma #SEList# é usada como uma #Stack# ( + adicionado à #SEList# as operações de inserção + $#push(x)#\equiv#add(size(),x)#$ e remoção $#pop()#\equiv #remove(size()-1)#$), então elas operações rodam em tempo constante amortizado, independentemente do valor de #b#. \end{exc} diff --git a/latex/ods.tex b/latex/ods.tex index 410649ae..4c1d07b5 100644 --- a/latex/ods.tex +++ b/latex/ods.tex @@ -19,7 +19,7 @@ %\usepackage[math]{iwona} %\SetMathAlphabet{\mathtt}{iwona}{OT1}{\ttdefault}{m}{n} -%\usepackage[T1]{fontenc} +\usepackage[T1]{fontenc} \usepackage{amsopn} \usepackage{amsmath} @@ -186,11 +186,11 @@ \DeclareMathOperator{\bdiv}{div} % Title page content -\title{Estruturas de Dados Abertas (em \lang)} +\title{Estruturas de Dados Abertas: Uma introdução em \lang} \author{Autor: Pat Morin, Tradutor: Marcelo Keese Albertini} \date{% -Edição PT-BR-0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} -\htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} + 1ª edição}%PT-BR-0.1G\cpponly{$\beta$}\pcodeonly{$\beta$} +%\htmlonly{\\ \includegraphics[scale=0.90909,scale=0.5]{images/cc-by}}} %Version 0.0 pre $\alpha$: \today} \pagenumbering{roman} diff --git a/latex/redblack.tex b/latex/redblack.tex index 51297b94..e959c64b 100644 --- a/latex/redblack.tex +++ b/latex/redblack.tex @@ -565,8 +565,7 @@ \section{Resumo} durante todas as chamadas #addFixup(u)# e #removeFixup(u)#. \end{thm} -Apenas rascunhamos uma prova do \thmref{redblack-amortized}. Ao comparar -#addFixup(u)# e #removeFixup(u)# com os algoritmos para adicionar ou remover uma folha em uma +Apenas rascunhamos uma prova do \thmref{redblack-amortized}. Ao comparar as operações #addFixup(u)# e #removeFixup(u)# com os algoritmos para adicionar ou remover uma folha em uma árvore 2-4, podemos nos convencer que essa propriedade é herdada de uma árvore 2-4. Em particular, se podemos mostrar que o tempo total gasto com repartições de nodos, uniões e empréstimos em uma árvore 2-4 é $O(m)$ diff --git a/latex/scapegoat.tex b/latex/scapegoat.tex index 6586c94c..7608a3a0 100644 --- a/latex/scapegoat.tex +++ b/latex/scapegoat.tex @@ -213,7 +213,7 @@ \subsection{Análise de Corretude e Tempo de Execução} \begin{lem}\lemlabel{scapegoat-amortized} Iniciando com uma #ScapegoatTree# vazia, uma sequência de $m$ operações #add(x)# - e #remove(x)# faz com que as operações #rebuild(u) usem tempo #$O(m\log m)$. + e #remove(x)# faz com que as operações #rebuild(u)# usem tempo $O(m\log m)$. \end{lem} \begin{proof} diff --git a/latex/sorting.tex b/latex/sorting.tex index e8f1c7e6..c0cba245 100644 --- a/latex/sorting.tex +++ b/latex/sorting.tex @@ -212,7 +212,7 @@ \subsection{Quicksort} Isso tudo é feito \emph{in place} (ou seja, no próprio espaço de memória do array), então em vez de fazer cópias de subarrays sendo ordenados, o método #quickSort(a,i,n,c)# somente ordena o subarray -$#a[i]#,\ldots,#a[i+n-1]#$. Inicialmente, esse método é invocado com os argumentos +$#a[i]#,\ldots,#a[i+n-1]#$. Inicialmente, esse método é chamado com os argumentos que indicam o uso do array completo da seguinte forma #quickSort(a,0,a.length,c)#. No coração do algoritmo quicksort está o particionamento \emph{in place}. @@ -349,14 +349,14 @@ \subsection{Heapsort} Lembre-se que, em uma heap binária, os filhos de #a[i]# são guardados nas posições -#a[2i+1]# e #a[2i+2]#. Isso implica que os elementos +#a[2i+1]# e #a[2i+2]#. Isso implica que os elementos nas posições $#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ não têm filhos. Em outras palavras, cada um dos elementos em $#a#[\lfloor#n#/2\rfloor],\ldots,#a[n-1]#$ é uma subheap de tamanho 1. Agora, trabalhando de trás para frente, podemos chamar #trickleDown(i)# para cada $#i#\in\{\lfloor #n#/2\rfloor-1,\ldots,0\}$. Isso funciona porque -ao chamarmos +ao chamarmos a operação #trickleDown(i)#, cada um dos dois filhos de #a[i]# são raízes de uma subheap, então chamar #trickleDown(i)# torna #a[i]# a raiz de sua própria subheap. From 765ec5af8ba79fe217a83c2fdd4a88b39296c08f Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Thu, 24 Sep 2020 10:55:54 -0300 Subject: [PATCH 61/66] Update README --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index f6b72198..99780332 100644 --- a/README +++ b/README @@ -1,3 +1,7 @@ + +O livro impresso da versão em Java é encontrado em: + - https://www.amazon.com/-/pt/dp/B08JF5KTKR + Versões PDF em PORTUGUÊS: - http://www.facom.ufu.br/~albertini/ed2/ods-java.pdf - http://www.facom.ufu.br/~albertini/ed2/ods-python.pdf From db3cc3a53283ab2546edd1c44783d4a9be1e286c Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Sat, 24 Oct 2020 20:08:59 -0300 Subject: [PATCH 62/66] small changes --- latex/ack.tex | 2 +- latex/images/bigoh-1.gp | 12 +++++++----- latex/images/bigoh-2.gp | 2 +- latex/trad.tex | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 latex/trad.tex diff --git a/latex/ack.tex b/latex/ack.tex index 8e88bb23..ecf79871 100644 --- a/latex/ack.tex +++ b/latex/ack.tex @@ -13,5 +13,5 @@ \section*{\emph{Do Autor, Pat Morin}} \section*{\emph{Do Tradutor, Marcelo Keese Albertini }} Agradeço à todos que me motivaram a fazer esta tradução. Agradeço Hélio Albertini e Luiz Brito por ajudarem na revisão do texto. -Em especial, agradeço à minha esposa Madeleine Albertini por me apoiar neste trabalho, que exigiu mais horas de trabalho do que o normal. +Em especial, agradeço à minha esposa Madeleine Albertini por me apoiar neste trabalho, que exigiu mais horas do que o normal. diff --git a/latex/images/bigoh-1.gp b/latex/images/bigoh-1.gp index c492cba9..f124ec0d 100644 --- a/latex/images/bigoh-1.gp +++ b/latex/images/bigoh-1.gp @@ -1,11 +1,13 @@ -set term tikz color size 5in,3in +set term lua tikz size 5in,3in set output 'bigoh-1.tex' set xlabel '{\color{var}\texttt{n}}' set ylabel '$f({\color{var}\mathtt{n}})$' set key right bottom set xrange [1:100] -set style line 1 linecolor rgb '#0060ad' linetype 1 linewidth 2 -set style line 2 linecolor rgb '#dd181f' linetype 2 linewidth 2 -plot 15*x title '$15{\mathtt{n}}$' with lines linestyle 1, \ - 2*x*(log(x)/log(2)) title '$2{\mathtt{n}}\log{\mathtt{n}}$' with lines linestyle 2 +#set style line 1 linecolor rgb '#0060ad' dashtype 1 linewidth 2 +#set style line 2 linecolor rgb '#dd181f' dashtype 2 linewidth 2 +set linetype 1 dashtype 2 +set linetype 2 dashtype '..-' +plot 15*x title '$15{\mathtt{n}}$', \ + 2*x*(log(x)/log(2)) title '$2{\mathtt{n}}\log{\mathtt{n}}$' diff --git a/latex/images/bigoh-2.gp b/latex/images/bigoh-2.gp index 8557bf56..96aebe0b 100644 --- a/latex/images/bigoh-2.gp +++ b/latex/images/bigoh-2.gp @@ -4,7 +4,7 @@ set xlabel '{\color{var}\texttt{n}}' set ylabel '$f({\color{var}\mathtt{n}})$' set key right bottom set style line 1 linecolor rgb '#0060ad' linetype 1 linewidth 2 -set style line 2 linecolor rgb '#dd181f' linetype 2 linewidth 2 +set style line 2 linecolor rgb '#dd181f' linetype 2 linewidth 2 dt 4 set xrange [1:10000] plot 15*x title '$15{\mathtt{n}}$' with lines linestyle 1, \ 2*x*(log(x)/log(2)) title '$2{\mathtt{n}}\log{\mathtt{n}}$' with lines linestyle 2 diff --git a/latex/trad.tex b/latex/trad.tex new file mode 100644 index 00000000..f2be6c97 --- /dev/null +++ b/latex/trad.tex @@ -0,0 +1,17 @@ +\chapter*{Comentários do Tradutor} +\addcontentsline{toc}{chapter}{Comentários do Tradutor} + +Na tradução deste livro, várias decisões de traduções foram tomadas ponderando, simultâneamente, +a simplicidade de compreensão do texto e a fidelidade à palavra originalmente usada na literatura especializada. + +Os nomes de estruturas de dados foram usados em inglês somente quando não há +tradução consolidada para o português. + +Uma decisão importante foi optar por não traduzir os códigos fontes +(e algoritmos), incluindo nomes de variáveis, campos, métodos e comentários +pois assim eles serão encontrados nos códigos disponíveis nos repositórios +de softwares abertos e nas \emph{Application Programming Interface} (API) de +bibliotecas e linguagens de programação. + +Em especial, acredito que os comentários nos códigos sejam curtos e simples o suficientes para serem +compreendidos usando o contexto em questão. From 8435b2c8311874ed97bbe1aee9a7ae6e33d3c28d Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 26 Oct 2020 11:07:01 -0300 Subject: [PATCH 63/66] minor typo --- latex/intro.tex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/latex/intro.tex b/latex/intro.tex index e87f248c..517b5246 100644 --- a/latex/intro.tex +++ b/latex/intro.tex @@ -16,14 +16,14 @@ \chapter{Introdução} O conteúdo do seu arquivo pode estar armazenado em qualquer um deles. \item Procurar contato no telefone: Uma estrutura de dados é usada para procurar por um número de telefone na sua lista de contatos \index{lista de contatos}% - baseada em informação parcial mesmo antes de você terminar de discar/digitar + baseada em informação parcial mesmo antes de você terminar de discar/digitar. Isso não é fácil; o seu telefone pode ter informações sobre muitas pessoas---todos que você já entrou em contato via telefone ou email---e seu telefone não tem um processador muito rápido ou muita memória. \item Entrar na sua rede social preferida: \index{rede social}% Os servidores da rede usam a sua informação de login para procurar as informações da sua conta. - Isso não é fácil; as redes sociais mais populares tem centenas de milhões de usuários ativos. + Isso não é fácil; as redes sociais mais populares têm centenas de milhões de usuários ativos. \item Fazer uma busca na web: \index{busca na web}% Um motor de busca usa estruturas de dados para buscar @@ -680,7 +680,7 @@ \section{O Modelo de Computação} Isso inclui operações aritméticas (#+#, #-#, #*#, #/#, #%#), comparações ($<$, $>$, $=$, $\le$, $\ge$) e operações booleanas bit-a-bit (AND, OR e OR exclusivo bit-a-bit). -Qualquer célula pode ler lida ou escrita em tempo constante. +Qualquer célula pode ser lida ou escrita em tempo constante. A memória do computador é gerenciada por um sistema gerenciador de memória a partir do qual podemos alocar ou desalocar um bloco de memória de qualquer tamanho que quisermos. Alocar um bloco de memória de tamanho $k$ leva tempo $O(k)$ e retorna uma referência (um ponteiro) para o bloco recém alocado. Essa referência é pequena o suficiente para ser representada por uma única word. O tamanho da word #w# é um parâmetro muito importante desse modelo. @@ -878,7 +878,7 @@ \section{Trechos de Código} \section{Lista de Estruturas de Dados} As tabelas~\ref{tab:summary-i} e \ref{tab:summary-ii} resumem o desempenho -das estruturas de dados que neste livro implementam cada uma das interfaces, #List#, #Uset# e #SSet#, descritas em \secref{interfaces}. +das estruturas de dados que neste livro implementam cada uma das interfaces, #List#, #USet# e #SSet#, descritas em \secref{interfaces}. A \Figref{dependencies} mostra as dependências entre vários capítulos neste livro. \index{dependências}% Uma linha tracejada indica somente uma dependência fraca, na qual somente uma pequena parte do capítulo depende em um capítulo anterior ou somente nos resultados principais do capítulo anterior. From 16f78e4c96d741226e2a886c79de0272caa6e6ec Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Mon, 26 Oct 2020 14:24:09 -0300 Subject: [PATCH 64/66] fix minor typo --- latex/arrays.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index d23207b1..f6534fc5 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -41,7 +41,7 @@ \chapter{Listas Baseadas em Arrays} Um terceiro ponto é importante. Os tempos de execução citados na tabela acima não incluem o custo associado com expandir ou encolher o array de apoio. Veremos que, se não gerenciado com cuidado, o custo de expandir ou encolher o array de apoio não aumenta muito o custo \emph{médio} de uma operação. -Mais precisamente, se iniciarmos com um estrutura de dados vazia e +Mais precisamente, se iniciarmos com uma estrutura de dados vazia e realizarmos qualquer sequência de $m$ operações #add(i,x)# ou #remove(i)# , então o custo total de expandir e encolher o array de apoio, sobre a sequência inteira de $m$ operações é $O(m)$. Embora algumas operações individuais sejam mais caras, o custo amortizado, quando dividido por todas as $m$ operações, é somente $O(1)$ por operação. From 8b6b6ca63116694b5e28bd33ff44736b2cc709e3 Mon Sep 17 00:00:00 2001 From: Marcelo Keese Albertini <42211606+albertiniufu@users.noreply.github.com> Date: Mon, 26 Oct 2020 14:52:30 -0300 Subject: [PATCH 65/66] Update PDF locations --- README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index 99780332..776252bb 100644 --- a/README +++ b/README @@ -3,9 +3,9 @@ O livro impresso da versão em Java é encontrado em: - https://www.amazon.com/-/pt/dp/B08JF5KTKR Versões PDF em PORTUGUÊS: - - http://www.facom.ufu.br/~albertini/ed2/ods-java.pdf - - http://www.facom.ufu.br/~albertini/ed2/ods-python.pdf - - http://www.facom.ufu.br/~albertini/ed2/ods-cpp.pdf + - http://www.facom.ufu.br/~albertini/ods-java.pdf + - http://www.facom.ufu.br/~albertini/ods-python.pdf + - http://www.facom.ufu.br/~albertini/ods-cpp.pdf ------ From 9eb0e269d9a45d72d32021ee2642ea809e7e0ad1 Mon Sep 17 00:00:00 2001 From: Marcelo Albertini Date: Tue, 27 Oct 2020 17:46:58 -0300 Subject: [PATCH 66/66] remove word in english --- latex/arrays.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latex/arrays.tex b/latex/arrays.tex index f6534fc5..e32158b2 100644 --- a/latex/arrays.tex +++ b/latex/arrays.tex @@ -327,7 +327,7 @@ \section{#ArrayQueue#: Uma Queue Baseada Em Array} \pcodeonly{Em muitas linguagens de programação, incluindo C, C++ e Java, o operador mod é representado usando o símbolo \%.} \notpcode{Em muitas linguagens de programação incluindo \javaonly{Java}\cpponly{C++}, o operador $\bmod$ é representado -usando o símbolo #%# symbol.\footnote{Isso às vezes é chamado de +usando o símbolo #%#.\footnote{Isso às vezes é chamado de operador mod com \emph{morte cerebral}, pois não implementa corretamente o operador matemático mod quando o primeiro argumento é negativo.}}