Jekyll2019-01-02T15:45:45+00:00andreshp.github.com/feed.xmlThe mathematics of algorithmsMy personal web pageAndrés Herrera PoyatosSegment trees y Range minimum query2015-07-17T12:00:00+00:002015-07-17T12:00:00+00:00andreshp.github.com/data%20structures/2015/07/17/segment-tree<!-- remote_include https://raw.githubusercontent.com/libreim/blog/sites/blog/_posts/2015-7-17-segment-tree.md -->
<p>En los cursos de estructuras de datos y algoritmos el número de estructuras de datos que se suelen estudiar es bastante reducido. Generalmente se introducen heaps, árboles binarios de búsqueda balanceados (AVL), tablas Hash y algunos algoritmos sobre grafos. Sin embargo, el mundo de las estructuras de datos es mucho más amplio <sup id="fnref:list"><a href="#fn:list" class="footnote">1</a></sup> y probablemente requeriría una asignatura de estructuras de datos avanzadas como sucede en algunas universidades. El MIT, por ejemplo, proporciona <a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/">vídeos</a> con el contenido de esta asignatura. Por ello, intentaré escribir entradas en el blog que profundicen en esta temática.</p>
<p>En este caso trataremos los <strong>segment trees</strong> o árboles de segmentos. Introduciremos en primer lugar un problema importante de la teoría de algoritmos, <strong>range minimum query problem</strong>, que servirá como motivación para los segment trees. Posteriormente se explicará el funcionamiento de estos, proporcionando para cada operación su correspondiente código en Python. Por último, se proponen como ejercicio algunos problemas resolubles mediante segment trees.</p>
<!--more-->
<h2 id="range-minimum-query-problem">Range Minimum Query Problem</h2>
<p>Consideremos un vector con objetos de un tipo <script type="math/tex">T</script> sobre el que se ha definido una relación de orden total. Por claridad, ejemplificaremos el problema sobre números enteros. Sea <script type="math/tex">n</script> la longitud del vector, se define <script type="math/tex">RMQ(i,j)</script> como el mínimo del subvector formado por las componentes entre <script type="math/tex">i</script> y <script type="math/tex">j</script> (inclusive) para <script type="math/tex">i, j</script> en <script type="math/tex">\{0,\ldots,n-1\}</script> con <script type="math/tex">i \le j</script>. El problema consiste en proporcionar el valor de <script type="math/tex">RMQ(i,j)</script> para cualquier número posible de consultas.</p>
<p>Normalmente se denomina <em>subintervalo del vector</em> a un subvector formado por componentes consecutivas, como los que se estudian en este caso. Una posible traducción al castellano de <em>range minimum query problem</em> sería <em>problema de las consultas del mínimo de cualquier subintervalo</em> (pero mantendremos el nombre en inglés por ser el estándar).</p>
<p>La solución trivial para el problema consiste en calcular para cada consulta el mínimo del subintervalo correspondiente de forma lineal. Esto proporciona una eficiencia media de <script type="math/tex">\theta(n)</script> para las consultas. Se pretende reducir esta eficiencia significativamente para poder atender el mayor número de consultas posible.</p>
<p>La forma habitual de abordar el problema consiste en preprocesar los datos. Un primer preprocesamiento puede ser calcular directamente el mínimo para cada subintervalo del vector, lo que puede conseguirse sin mucha dificultad en <script type="math/tex">\theta(n^2)</script>. Posteriormente, las consultas pueden ser realizadas en tiempo constante. Esta solución tiene dos grandes problemas:</p>
<ol>
<li>Un preprocesamiento de eficiencia <script type="math/tex">\theta(n^2)</script> es excesivo cuando se trate con vectores de tamaño mayor o igual que <script type="math/tex">10^4</script>. Esto nos hace distinguir dos eficiencias a la hora de resolver el problema, la eficiencia del preprocesamiento y la eficiencia de la consulta. La solución trivial minimizaba el preprocesamiento mientras que la nueva solución minimiza el tiempo de consulta, no siendo ninguna de las dos óptimas.</li>
<li>El problema suele complicarse permitiendo actualizar el valor de una componente del vector entre consultas, lo que no consigue de forma eficiente el segundo algoritmo, que requiere un tiempo <script type="math/tex">\theta(n)</script> para actualizar también la matriz <script type="math/tex">RMQ</script>.</li>
</ol>
<p>Los segment trees o árboles de segmentos surgieron para resolver este problema. Se pueden formular de forma incluso más general, teniendo aplicaciones en problemas relacionados con los subintervalos de un vector. Como veremos a continuación, los segment trees abordarán el range minimum query problem utilizando un preprocesamiento con eficiencia lineal tras el cual podremos realizar consultas y actualizar elementos del vector en tiempo logarítmico. Esto supone una gran mejora con respecto a las soluciones anteriores.</p>
<h2 id="segment-trees">Segment Trees</h2>
<p>Un segment tree es una estructura de datos que permite, a partir de un vector <script type="math/tex">V</script>, dos operaciones:</p>
<ol>
<li>Consultar determinada información para cualquiera de los subintervalos del vector.</li>
<li>Actualizar una componente del vector.</li>
</ol>
<p>Como caso particular esta información puede ser el mínimo del subintervalo, en cuyo caso ambas operaciones pueden llevarse a cabo en tiempo logarítmico.</p>
<p>Supongamos por el momento que el vector tiene longitud <script type="math/tex">n = 2^m</script>.</p>
<p>La idea subyacente consiste en preprocesar la información correspondiente a las particiones del vector formadas por subintervalos de igual longitud siendo esta una potencia de <script type="math/tex">2</script>. Formalmente, estos intervalos se corresponden con <script type="math/tex">V[k2^l, (k+1)2^l-1]</script> para <script type="math/tex">l \in \{0, 1, \ldots, m\}</script> y <script type="math/tex">k \in \{0, 1, \ldots, \frac{n}{2^l}-1\}</script>. Los intervalos preprocesados pueden verse como los nodos de un árbol binario construido de la siguiente forma:</p>
<ol>
<li><script type="math/tex">V[0,n-1]</script> es la raíz.</li>
<li>Todo nodo que se corresponda a <script type="math/tex">V[i,j]</script> con <script type="math/tex">% <![CDATA[
i < j %]]></script> tiene dos hijos, izquierda y derecha, que se obtienen a dividir el subintervalo correspondiente en las mitades izquerda y derecha.</li>
</ol>
<p>La Imagen 1 muestra el árbol binario a generar si se tuviese <script type="math/tex">n = 8</script>. Nótese que las hojas del árbol son los subintervalos con una sola componente.</p>
<p class="fig"><img src="https://raw.githubusercontent.com/libreim/blog/site/images/segment_trees/segment_trees_visualizacion.png" alt="" />
<strong>Imagen 1.</strong> Segment tree asociado a un vector <script type="math/tex">V</script> de longitud 8 representado como un árbol binario.</p>
<p>Tras generar el árbol binario podemos expresar un subintervalo <script type="math/tex">V[i, j]</script> como la unión del menor número de subintervalos como los preprocesados previamente. Tiene sentido hablar de esta unión pues siempre existe (basta expresar <script type="math/tex">V[i, j]</script> como unión de sus componentes). Por ejemplo, para <script type="math/tex">n=8</script> se tiene:</p>
<script type="math/tex; mode=display">V[2,7] = V[2,2] +\mkern-5mu+ V[3,4] +\mkern-5mu+ V[5,6] +\mkern-5mu+ V[7,7]</script>
<p>Si la información que deseamos consultar puede obtenerse a partir de la información de una partición de subintervalos entonces habremos resuelto el problema. Este es el caso del range minimum query. El mínimo del subintervalo <script type="math/tex">V[i, j]</script> es el mínimo de los mínimos obtenidos para los subintervalos preprocesados que formen una partición de <script type="math/tex">V[i, j]</script>. Por ejemplo, si <script type="math/tex">V = [3,2,8,5,6,1,7,4]</script>:</p>
<script type="math/tex; mode=display">\min(V[2,7]) = \min\{\min(V[2,2]), \min(V[3,4]), \min(V[5,6]), \min(V[7,7])\} = \min\{2, 5, 1, 7\} = 1</script>
<p>En lo que sigue estudiaremos cómo construir el segment tree (librándonos de la suposición <script type="math/tex">n= 2^m</script>) y probaremos que es posible realizar una consulta y actualizar el vector de forma eficiente. Sin embargo, antes debemos saber qué operaciones tienen que realizar los nodos del segment tree para que esto sea posible.</p>
<h3 id="nodos-del-segment-tree">Nodos del segment tree</h3>
<p>La información relativa a los subintervalos del tipo <script type="math/tex">V[k2^l, (k+1)2^l-1]</script> debe almacenarse en un nodo. Los subintervalos <script type="math/tex">V[i,i]</script> son los casos base y sus nodos formarán las hojas del segment tree. Los nodos deben mantener 3 operaciones:</p>
<ol>
<li>Asignar la información correspondiente al nodo en el caso de que este sea una hoja del árbol.</li>
<li>Generar la información del nodo a partir de dos nodos cuyos subintervalos sean una partición del subintervalo actual. Esta operación se denomina <code class="highlighter-rouge">merge</code>.</li>
<li>Devolver la información del subintervalo que el nodo representa.</li>
</ol>
<p>Una plantilla para un nodo del segment tree sería la siguiente:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Template for a Segment Tree Node.</span>
<span class="c"># A node contains the information related with a vector subinterval.</span>
<span class="k">class</span> <span class="nc">SegmentTreeNode</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="c"># Init the node.</span>
<span class="c"># info = Subinterval information</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">info</span> <span class="o">=</span> <span class="bp">None</span>
<span class="c"># Given the value of an array element,</span>
<span class="c"># build the information for this leaf.</span>
<span class="k">def</span> <span class="nf">assignLeaf</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">pass</span> <span class="c"># Insert the code to build the leaf information</span>
<span class="c"># Merge the information of left and right</span>
<span class="c"># children to form the parent node information.</span>
<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
<span class="k">pass</span> <span class="c"># Insert the merge code</span>
<span class="c"># Return the information contained in this node.</span>
<span class="k">def</span> <span class="nf">getInfo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">info</span>
</code></pre></div></div>
<p>En el caso del range minimum query asignar la información a una hoja es asignarle el valor de la componente y realizar un <code class="highlighter-rouge">merge</code> es tomar el mínimo de la información de los nodos <code class="highlighter-rouge">left</code> y <code class="highlighter-rouge">right</code>.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">assignLeaf</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">info</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">info</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">left</span><span class="o">.</span><span class="n">getInfo</span><span class="p">(),</span> <span class="n">right</span><span class="o">.</span><span class="n">getInfo</span><span class="p">())</span>
</code></pre></div></div>
<p>Veremos que para que la eficiencia de las dos operaciones soportadas por el segment tree sea logarítmica las operaciones anteriores deben ser realizadas en tiempo constante, como sucede para este problema.</p>
<h3 id="construcción-del-segment-tree">Construcción del segment tree</h3>
<p>La construcción del segment tree consiste en crear un árbol binario como el de la Imagen 1. Sin embargo, se puede conseguir una implementación más eficiente al darse cuenta de que el árbol binario es completo. Por tanto, podemos almacenarlo en memoria mediante un heap <sup id="fnref:heap"><a href="#fn:heap" class="footnote">2</a></sup>. Esto es, embebemos el árbol en un vector mediante un recorrido por niveles como sucede en la Imagen 2. A cada nodo le corresponde un índice del vector y para estos índices se verifica:</p>
<ol>
<li><script type="math/tex">IndiceHijoIzquierda(nodo) =</script> <script type="math/tex">2nodo</script></li>
<li><script type="math/tex">IndiceHijoDerecha(nodo) =</script> <script type="math/tex">2nodo+1</script></li>
</ol>
<p>Estas relaciones nos permiten acceder a los hijos de forma constante. Además, la longitud del vector que representa al segment tree es <script type="math/tex">2n-1</script> donde <script type="math/tex">n</script> es la longitud de <script type="math/tex">V</script>.</p>
<p class="fig"><img src="https://raw.githubusercontent.com/libreim/blog/site/images/segment_trees/segment_trees_heap.png" alt="" />
<strong>Imagen 2.</strong> Segment tree asociado a un vector <script type="math/tex">V</script> de longitud 8 representado como un heap.</p>
<p>Nótese que el subintervalo correspondiente a cada nodo se deduce de su índice, por lo que no es necesario almacenar esta información. Se puede construir el árbol recursivamente. Si el nodo actual es una hoja se obtiene su información mediante el método <code class="highlighter-rouge">assignLeaf</code>. Si no se da este caso, se construyen recursivamente los dos hijos y se obtiene la información para el nodo actual aplicando el método <code class="highlighter-rouge">merge</code> a ambos hijos.</p>
<p>Con el proceso de construcción anterior obtendremos sin problemas el árbol aunque el vector no tenga como tamaño una potencia de dos. En tal caso el árbol resultante puede no ser completo. Por tanto, habrá componentes del heap en memoria sin usar. Esto nos es irrelevante puesto que el tamaño del heap será a lo sumo <script type="math/tex">2m-1</script> donde <script type="math/tex">m</script> es la menor potencia de 2 mayor que <script type="math/tex">n</script> (si extendemos el vector con elementos nulos hasta que tenga longitud <script type="math/tex">m</script> y construimos este heap necesitaremos un vector de longitud <script type="math/tex">2m-1</script>). Por tanto, la memoria utilizada será <script type="math/tex">\theta(n)</script> en cualquier caso.</p>
<p>El siguiente código proporciona un constructor para la clase <code class="highlighter-rouge">SegmentTree</code>.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SegmentTree</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="c"># Build a segment tree from the given array.</span>
<span class="c"># array: Array from which the segment tree is built.</span>
<span class="c"># st_index: current segment tree node index.</span>
<span class="c"># lo and hi : Range of input array subinterval that this node is responsible of.</span>
<span class="k">def</span> <span class="nf">_buildTree</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">array</span><span class="p">,</span> <span class="n">st_index</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">):</span>
<span class="k">if</span> <span class="n">lo</span> <span class="o">==</span> <span class="n">hi</span><span class="p">:</span>
<span class="c"># The node is a leaf responsible of V[lo,lo]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">assignLeaf</span><span class="p">(</span><span class="n">array</span><span class="p">[</span><span class="n">lo</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="c"># The node is not a leaf.</span>
<span class="c"># Both children are built and merged afterwards for this node.</span>
<span class="n">left</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">st_index</span>
<span class="n">right</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">mid</span> <span class="o">=</span> <span class="p">(</span><span class="n">lo</span> <span class="o">+</span> <span class="n">hi</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_buildTree</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">mid</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_buildTree</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">right</span><span class="p">])</span>
<span class="c"># Get the segment tree size for a input of size N.</span>
<span class="c"># It compute the smallest 2 to the power of m greater than N.</span>
<span class="k">def</span> <span class="nf">_getSegmentTreeSize</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
<span class="n">size</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">size</span> <span class="o"><</span> <span class="n">N</span><span class="p">:</span>
<span class="n">size</span> <span class="o"><<=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">size</span> <span class="o"><<</span> <span class="mi">1</span>
<span class="c"># Initializes a Segment Tree.</span>
<span class="c"># array : Array from which the segment tree is built.</span>
<span class="c"># Node : Class that will be used as a segment tree node.</span>
<span class="c"># It obtains the desired information from the array.</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">array</span><span class="p">,</span> <span class="n">Node</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">SegmentTreeNode</span> <span class="o">=</span> <span class="n">Node</span>
<span class="c"># Segment tree size (number of nodes)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">SegmentTree</span><span class="o">.</span><span class="n">_getSegmentTreeSize</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">))</span>
<span class="c"># Heap with the nodes</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">SegmentTreeNode</span><span class="p">()</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">)]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">array</span> <span class="o">=</span> <span class="n">array</span>
<span class="c"># The tree is built</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_buildTree</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>
<p>Como se construyen menos de <script type="math/tex">4n-1</script> nodos, el proceso anterior es <script type="math/tex">\theta(n \max(a(n), m(n)))</script> donde <script type="math/tex">a(n)</script> es la eficiencia del método <code class="highlighter-rouge">assignLeaf</code> y <script type="math/tex">m(n)</script> es la eficiencia del método <code class="highlighter-rouge">merge</code>.</p>
<p>En el caso del range minimum query la eficiencia obtenida es lineal como se había pronosticado.</p>
<h3 id="operación-1-consultas">Operación 1: Consultas</h3>
<p>Para realizar una consulta debemos encontrar la descomposición de <script type="math/tex">V[i,j]</script> en el menor número posible de nodos del árbol. Esto se puede consequir de forma recursiva. Partimos del nodo raíz. Se distinguen los siguientes casos:</p>
<ol>
<li>Si <script type="math/tex">V[i,j]</script> es el subintervalo que corresponde al nodo actual se devuelve la información contenida en el nodo.</li>
<li>Si <script type="math/tex">V[i,j]</script> es un subintervalo del subintervalo del hijo izquierda se devuelve el resultado de la búsqueda obtenida para el hijo izquierda.</li>
<li>Si <script type="math/tex">V[i,j]</script> es un subintervalo del subintervalo del hijo derecha se devuelve el resultado de la búsqueda obtenida para el hijo derecha.</li>
<li>Si <script type="math/tex">V[i,j]</script> tiene elementos en ambos hijos se obtiene el valor de la consulta haciendo un <code class="highlighter-rouge">merge</code> de la información obtenida para el sector relativo al hijo izquierda y el sector relativo al hijo derecha.</li>
</ol>
<p>El siguiente código realiza la operación descrita:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># Get recursively a SegmentTreeNode with the information associated with the range [lo, hi].</span>
<span class="c"># st_index : Current Segment Tree Node. It is responsible of [left, right] range.</span>
<span class="k">def</span> <span class="nf">_getInfo</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">st_index</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">):</span>
<span class="c"># Check if the range is the current node in the tree.</span>
<span class="c"># In that case return it.</span>
<span class="k">if</span> <span class="n">left</span> <span class="o">==</span> <span class="n">lo</span> <span class="ow">and</span> <span class="n">right</span> <span class="o">==</span> <span class="n">hi</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span>
<span class="c"># Look for the range in the children of the current node</span>
<span class="c"># if it could be just there.</span>
<span class="n">mid</span> <span class="o">=</span> <span class="p">(</span><span class="n">left</span> <span class="o">+</span> <span class="n">right</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">lo</span> <span class="o">></span> <span class="n">mid</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_getInfo</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">mid</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
<span class="k">if</span> <span class="n">hi</span> <span class="o"><=</span> <span class="n">mid</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_getInfo</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
<span class="c"># If we keep executing the method then the range is divided between</span>
<span class="c"># the left child and the right child of the current node. Let's get</span>
<span class="c"># each part of the range and merge it.</span>
<span class="n">left_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_getInfo</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">mid</span><span class="p">)</span>
<span class="n">right_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_getInfo</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">mid</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">mid</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">SegmentTreeNode</span><span class="p">()</span>
<span class="n">result</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="n">left_result</span><span class="p">,</span> <span class="n">right_result</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span>
<span class="c"># Get the value associated with the range [lo, hi]</span>
<span class="k">def</span> <span class="nf">getInfo</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">):</span>
<span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_getInfo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">array</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">getInfo</span><span class="p">()</span>
</code></pre></div></div>
<p>Es claro que si el subintervalo es precisamente uno de los que se tienen almacenados en el árbol entonces el tiempo de la consulta es <script type="math/tex">O(\log n)</script>. ¿Qué sucede en cualquier otro caso?</p>
<dl>
<dt>Proposición</dt>
<dd>El tiempo de consulta para cualquier subintervalo es <script type="math/tex">O(m(n)\log n)</script>, donde <script type="math/tex">m(n)</script> es la eficiencia del método <code class="highlighter-rouge">merge</code>.</dd>
</dl>
<p><strong>Demostración</strong></p>
<p>La implementación previa consiste en una búsqueda en profundidad pues es más cómoda de programar. Sin embargo, en la prueba es más útil ver el algoritmo como una búsqueda en anchura. Puesto que ambas búsquedas visitarían los mismos nodos, podemos situarnos en esta última. Definimos una iteración del algoritmo como procesar todos los nodos de un nivel <script type="math/tex">t</script> del árbol. Tras una iteración los nodos que quedan activos pertenecen al siguiente nivel del árbol.</p>
<p>Buscamos la información del subintervalo <script type="math/tex">V[i,j]</script>. Podemos observar que de una iteración a otra se mantiene la búsqueda sobre a lo sumo dos nuevos nodos. Además, estos nodos son precisamente aquellos cuyos subinvervalos contienen a las componentes i-ésima y j-ésima respectivamente.</p>
<p>En efecto, esto se prueba por inducción sobre el nivel del árbol en el que nos encontremos:</p>
<ul>
<li>Para la raíz (nivel 1) esto es evidente pues el algoritmo, en el peor de los casos, prosigue con los dos hijos.</li>
<li>Supongamos cierta la afirmación para el nivel <script type="math/tex">% <![CDATA[
t < \log_2 n %]]></script> y veamos que se cumple para <script type="math/tex">t+1</script>. Por la hipótesis de inducción, la búsqueda se mantiene a lo sumo en dos nodos. Si no hubiese nodos activos hemos terminado. Si por el contrario solo hubiese un nodo activo el resultado también es evidente (el nodo activo se divide como mucho en dos). Por último, si hay dos nodos activos verificando la hipótesis de inducción se tiene que <script type="math/tex">% <![CDATA[
i < j %]]></script> (los nodos tienen subintervalos disjuntos). Cada uno de los nodos activos puede dividir la búsqueda como mucho sobre sus dos hijos. Para el nodo izquierda (el que contiene la componente <script type="math/tex">i</script>) se tienen las siguientes opciones:
<ul>
<li>El subintervalo del nodo está contenido en <script type="math/tex">V[i,j]</script> en cuyo caso se para la búsqueda en esa rama.</li>
<li>El subintervalo que buscamos está contenido en el hijo derecha (tiene intersección vacía con el hijo izquierda). En tal caso se añade ese nodo a la búsqueda.</li>
<li>El subintervalo que buscamos tiene intersección no vacía con el hijo izquierda. Entonces, este hijo se añade a la búsqueda. El subintervalo del hijo derecha está contenido en <script type="math/tex">V[i,j]</script> (está acotado por <script type="math/tex">i</script> y por <script type="math/tex">j</script>). Por tanto, no hay que continuar la búsqueda con el hijo derecha. Cuando finalice la búsqueda en el hijo izquierda se realizará un <code class="highlighter-rouge">merge</code> entre la información de ambos hijos.</li>
</ul>
</li>
</ul>
<p>En cualquier caso, se añade a lo sumo un hijo a la búsqueda. Lo mismo sucede con el nodo que contiene a <script type="math/tex">j</script>, verificándose, por tanto, la afirmación.</p>
<p>Como consecuencia, el número de nodos que se visitan está acotado por <script type="math/tex">4 \log n</script> . A cada nodo visitado le corresponde como mucho una operación de <code class="highlighter-rouge">merge</code>. Por tanto, la consulta es <script type="math/tex">O(m(n)\log n)</script>.</p>
<script type="math/tex; mode=display">\tag*{$\blacksquare$}</script>
<p>Nótese que para <script type="math/tex">V[1,n-2]</script> con <script type="math/tex">n</script> potencia de <script type="math/tex">2</script> se realizan precisamente <script type="math/tex">\Omega(m(n)\log n)</script> operaciones, luego la cota dada para la eficiencia del algoritmo es la mejor posible. Como pronosticábamos, si el <code class="highlighter-rouge">merge</code> es constante entonces la consulta es logarítmica.</p>
<h3 id="operación-2-actualización-de-una-componente-del-vector">Operación 2: Actualización de una componente del vector</h3>
<p>Con la operación anterior ya habríamos resuelto la versión básica del range minimum query. Veamos que también podemos actualizar componentes del vector eficientemente y de forma sencilla.</p>
<p>En primer lugar, habría que actualizar la hoja correspondiente a la componente del vector. Después hay que arreglar los desperfectos que esto haya podido causar a sus antecesores. Para ello habrá que recorrer el camino que une la hoja con la raíz.</p>
<p>La implementación más sencilla de este proceso es recursiva. Realizamos una búsqueda en profundidad desde la raíz hasta la hoja correspondiente que actualizaremos mediante la operación <code class="highlighter-rouge">assignLeaf</code>. Posteriormente, se irán actualizando los antecesores en orden mediante operaciones <code class="highlighter-rouge">merge</code> de sus hijos, que ya están actualizados.</p>
<p>El siguiente código realiza la operación descrita:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># Update the segment tree.</span>
<span class="c"># The given value is assigned to the array's component at index place.</span>
<span class="c"># The segment tree is updated accordingly in a recursive way.</span>
<span class="c"># st_index : Current segment tree node index.</span>
<span class="c"># lo and hi : The current range is [lo, hi]</span>
<span class="c"># index : Array's component to be updated.</span>
<span class="c"># value : New value for the array's component to update.</span>
<span class="k">def</span> <span class="nf">_update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">st_index</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">hi</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="c"># If current node is a leaf we have ended the search.</span>
<span class="c"># The value information is assigned to the leaf.</span>
<span class="k">if</span> <span class="n">lo</span> <span class="o">==</span> <span class="n">hi</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">assignLeaf</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="c"># If the current node is not a leaf, the search continues recursively</span>
<span class="c"># and the current node information is updated afterwards.</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">left</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">st_index</span>
<span class="n">right</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">mid</span> <span class="o">=</span> <span class="p">(</span><span class="n">lo</span> <span class="o">+</span> <span class="n">hi</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
<span class="c"># Continue the search by the correct path</span>
<span class="k">if</span> <span class="n">index</span> <span class="o"><=</span> <span class="n">mid</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_update</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">lo</span><span class="p">,</span> <span class="n">mid</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_update</span><span class="p">(</span><span class="n">right</span><span class="p">,</span> <span class="n">mid</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">hi</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="c"># Update current node information</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">right</span><span class="p">])</span>
<span class="c"># Update the segment tree.</span>
<span class="c"># The given value is assigned to the array's</span>
<span class="c"># component at index place. The segment tree is updated accordingly.</span>
<span class="c"># index : Array's component to be updated.</span>
<span class="c"># value : New value for the array's component to update.</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_update</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">array</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</code></pre></div></div>
<p>La eficiencia es claramente <script type="math/tex">\theta(m(n) \log n + a(n))</script>.</p>
<p>Una mejor implementación es una versión iterativa del proceso. Comenzamos en la hoja y recorremos el camino desde esta a la raíz usando el siguiente hecho:</p>
<script type="math/tex; mode=display">IndicePadre(nodo) = \frac{nodo}{2}</script>
<p>Si en determinado momento la información de un nodo a actualizar no cambia con el <code class="highlighter-rouge">merge</code> se finaliza algoritmo. Sin embargo, aunque podamos terminar la ejecución antes, la eficiencia en el peor caso sigue siendo <script type="math/tex">\theta(m(n) \log n + a(n))</script>. Se necesitaría, además, un nuevo método <code class="highlighter-rouge">isSameInfo</code> que nos indique si la información que se le pasa como argumento es la misma que la contenida por el nodo. Este método debe ser <script type="math/tex">O(m(n))</script> para que la implementación sea rentable. El siguiente código contiene esta nueva versión del algoritmo:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># Update the segment tree.</span>
<span class="c"># The given value is assigned to the array's</span>
<span class="c"># component at index place. The segment tree is updated accordingly.</span>
<span class="c"># index : Array's component to be updated.</span>
<span class="c"># value : New value for the array's component to update.</span>
<span class="k">def</span> <span class="nf">update2</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="n">st_index</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">//</span> <span class="mi">2</span> <span class="o">+</span> <span class="n">index</span> <span class="c"># Leaf index</span>
<span class="c"># Update leaf and array</span>
<span class="bp">self</span><span class="o">.</span><span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">assignLeaf</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="c"># Update leaf ancestors</span>
<span class="n">st_index</span> <span class="o">=</span> <span class="n">st_index</span> <span class="o">//</span> <span class="mi">2</span>
<span class="k">while</span> <span class="n">st_index</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span>
<span class="c"># Get current info and update it with a merge from the children</span>
<span class="n">current_info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">st_index</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span>
<span class="c"># If the info has not changed then the algorithm ends</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">st_index</span><span class="p">]</span><span class="o">.</span><span class="n">isSameInfo</span><span class="p">(</span><span class="n">current_info</span><span class="p">):</span>
<span class="k">break</span>
<span class="c"># Go to node's parent</span>
<span class="n">st_index</span> <span class="o">=</span> <span class="n">st_index</span> <span class="o">//</span> <span class="mi">2</span>
</code></pre></div></div>
<h2 id="problemas">Problemas</h2>
<p>Los siguientes problemas son útiles para practicar con los segment trees.</p>
<ul>
<li>
<p>Dado un vector <script type="math/tex">V</script> con <script type="math/tex">N</script> elementos, se pide realizar <script type="math/tex">Q</script> consultas. Cada consulta consiste en obtener la media del subintervalo <script type="math/tex">V[i,j]</script>. Desarrollar un algoritmo para este cometido.</p>
</li>
<li>
<p>Dado un vector <script type="math/tex">V</script> con <script type="math/tex">N</script> elementos, se pide realizar <script type="math/tex">Q</script> consultas. Cada consulta consiste en obtener la suma de los elementos del subintervalo <script type="math/tex">V[i,j]</script>. Encontrar una estructura de datos con preprocesamiento lineal y tiempo de consulta constante (diferente al segment tree) que resuelva el problema. ¿Es válida la solución si en lugar de la suma se utilizase la operación OR lógica de los números en binario? ¿Por qué? Extender el algoritmo a este último caso, estudiar las nuevas eficiencias obtenidas y compararlas con las de una solución basada en segment trees.</p>
</li>
<li><a href="https://www.hackerrank.com/contests/hindley-milner-feb14/challenges/range-minimum-query">Hackerrank - Functional Programming Contest - Range Minimum Query</a></li>
<li>
<p><a href="https://www.hackerrank.com/contests/indeed-prime-challenge/challenges/minimum-product-sub-interval">Hackerrank - Minimum Product Subinterval</a></p>
</li>
<li><a href="http://www.ahmed-aly.com/Category.jsp?ID=25">90 Segment Trees Problems</a></li>
</ul>
<h2 id="código">Código</h2>
<p>Todo el código proporcionado se encuentra en un único <a href="https://github.com/Andrés Herrera/Algorithms/tree/master/DataStructures/SegmentTree">archivo en Python</a>. Una implementación similar se puede encontrar en C++ <sup id="fnref:segment-c"><a href="#fn:segment-c" class="footnote">3</a></sup>.</p>
<h2 id="para-profundizar">Para profundizar</h2>
<p>Los siguientes enlaces profundizan en la temática. Incluyen desde operaciones más avanzadas, como el uso de lazy propagation, hasta la relación del range minimum query con otros problemas, como el lowest common ancestor. Espero tratar estos temas en un futuro próximo.</p>
<ul>
<li><a href="https://kartikkukreja.wordpress.com/2015/01/10/a-simple-approach-to-segment-trees-part-2/">A simple approach to segment trees, part 2 - Kartik Kukreja</a></li>
<li><a href="https://community.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor#Segment_Trees">Range Minimum Query and Lowest Common Ancestor - danielp - TopCoder</a></li>
</ul>
<h2 id="referencias">Referencias</h2>
<div class="footnotes">
<ol>
<li id="fn:list">
<p><a href="https://en.wikipedia.org/wiki/List_of_data_structures">List of Data Structures</a> <a href="#fnref:list" class="reversefootnote">↩</a></p>
</li>
<li id="fn:heap">
<p><a href="https://www.youtube.com/watch?v=B7hVxCmfPtM">Heaps and Heapsort, MIT 6.006 Introduction to Algorithms, Fall 2011</a> <a href="#fnref:heap" class="reversefootnote">↩</a></p>
</li>
<li id="fn:segment-c">
<p><a href="https://kartikkukreja.wordpress.com/2014/11/09/a-simple-approach-to-segment-trees/">A simple approach to segment trees - Kartik Kukreja</a> <a href="#fnref:segment-c" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Andrés Herrera PoyatosTeorema de Dini2015-04-20T12:00:00+00:002015-04-20T12:00:00+00:00andreshp.github.com/analysis/2015/04/20/teorema-dini<h2 id="convergencia-puntual-y-convergencia-uniforme">Convergencia Puntual y Convergencia Uniforme</h2>
<p>En el Análisis Matemático es frecuente que la función solución a determinado problema sea desconocida o no la podamos expresar en términos de las funciones elementales que conocemos. Incluso en las funciones más simples, como la exponencial o el logaritmo, nos encontramos esta situación. Conocemos su existencia y propiedades características pero tenemos ciertas dificultades a la hora de evaluarlas en un punto. Es frecuente para ello utilizar los famosos polinomios de Taylor, en cuyo caso estamos aproximando la función por polinomios.</p>
<p>En más situaciones es habitual disponer de funciones que se aproximan cada vez más a la solucion del problema. Para formalizar este hecho surge el concepto de convergencia para sucesiones de funciones. Podemos distinguir dos tipos de convergencia: convergencia puntual y convergencia uniforme.</p>
<dl>
<dt>Definición 1</dt>
<dd>Sea <script type="math/tex">A \subseteq \mathbb{R}</script> y <script type="math/tex">\{f_n\}</script> una sucesión de funciones con <script type="math/tex">\ f_n : A \rightarrow \mathbb{R}</script>. Se dice que <script type="math/tex">\{f_n\}</script> <strong>converge puntualmente</strong> en <script type="math/tex">x \in A</script> si la sucesión <script type="math/tex">\{f_n(x)\}</script> converge. Podemos considerar el conjunto <script type="math/tex">C</script> de los elementos <script type="math/tex">x \in A</script> en los que hay convergencia puntual y definir la <strong>función límite</strong>:</dd>
<dd>
<script type="math/tex; mode=display">f : C \rightarrow \mathbb{R}, \ f(x) = \lim f_n(x) \ \forall x \in C</script>
</dd>
</dl>
<!--more-->
<p>Nótese que la definición es puramente topológica para espacios con unicidad del límite (como los espacios de Hausdorff). Sin embargo, para que todo lector pueda seguir el texto sin un curso inicial de topología trabajaremos solamente con conjuntos de números reales.</p>
<p>Podemos exigirle más a la convergencia puntual y obtener la convergencia uniforme:</p>
<dl>
<dt>Definición 2</dt>
<dd>Sea <script type="math/tex">A \subseteq \mathbb{R}</script> y <script type="math/tex">\{f_n\}</script> una sucesión de funciones con <script type="math/tex">\ f_n : A \rightarrow \mathbb{R}</script>. Se dice que <script type="math/tex">\{f_n\}</script> <strong>converge uniformemente</strong> en <script type="math/tex">B \subseteq A</script> si para cada <script type="math/tex">\varepsilon > 0</script> existe <script type="math/tex">n_o \in \mathbb{N}</script> tal que:</dd>
<dd>
<script type="math/tex; mode=display">% <![CDATA[
\forall n \ge n_o : \left| f_n(x) - f(x) \right| < \varepsilon \ \forall x \in B %]]></script>
</dd>
</dl>
<p>La convergencia uniforme implica la convergencia puntual, pero el recíproco no es cierto. Un ejemplo de este caso es el siguiente, que se deja como ejercicio para el lector.</p>
<dl>
<dt>Ejemplo 1</dt>
<dd>Probar que la siguiente sucesión de funciones converge puntualmente pero no converge uniformemente en <script type="math/tex">[0,1]</script>:</dd>
<dd>
<script type="math/tex; mode=display">f_n : [0,1] \rightarrow \mathbb{R}, \ f_n(x) = x^n \ \forall x \in [0,1]</script>
</dd>
</dl>
<p>Cabe preguntarse para qué sirve la convergencia uniforme. Intuitivamente, nos dice que la sucesión de funciones converge con igual rapidez en todo <script type="math/tex">B</script>. Analíticamente podemos sacarle provecho al relacionarla con conceptos como la continuidad, la derivabilidad y la integral. Por ejemplo, en todo curso de análisis en el que se trate este tema se demuestra que si la convergencia es uniforme y las funciones que conforman la sucesión son continuas, entonces la función límite es continua. En definitiva, la convergencia uniforme nos permite obtener información analítica de la función límite exigiendo ciertas propiedades a las funciones que conforman la sucesión.</p>
<p>Sin embargo, muchos de los ejercicios a realizar en el correspondiente curso simplemente piden estudiar la convergencia uniforme de una sucesión de funciones. A veces esto se vuelve tedioso como es el caso del siguiente ejercicio <sup id="fnref:ejercicios"><a href="#fn:ejercicios" class="footnote">1</a></sup>:</p>
<dl>
<dt>Ejemplo 2</dt>
<dd>Estudia la convergencia puntual y uniforme de la sucesión de funciones <script type="math/tex">\{f_n\}</script> donde <script type="math/tex">\ f_n : \mathbb{R} \rightarrow \mathbb{R}</script> está definida por:</dd>
<dd>
<script type="math/tex; mode=display">f_n(x) = \left(1+\frac{x^2}{n}\right)^n \ \forall x \in \mathbb{R}</script>
</dd>
</dl>
<p><strong>Solución.</strong> Usando que</p>
<script type="math/tex; mode=display">\lim_{x \rightarrow \infty}\left(1+\frac{1}{x}\right)^x = e</script>
<p>Se obtiene</p>
<script type="math/tex; mode=display">\lim_{n \rightarrow \infty}\left(1+\frac{x^2}{n}\right)^n = \exp\left(x^2\right)</script>
<p>Luego la función límite es <script type="math/tex">\ f(x) = \exp\left(x^2\right)</script> en todo <script type="math/tex">\mathbb{R}</script>.</p>
<p>No hay convergencia uniforme en ninguna semirrecta de <script type="math/tex">\mathbb{R}</script> pues para <script type="math/tex">x_n = \sqrt n</script> se tiene que</p>
<script type="math/tex; mode=display">\lim_{n \rightarrow \infty} f_n(x_n) - f(x_n) = \lim_{n \rightarrow \infty}\left(1+\frac{n}{n}\right)^n - e^n = \lim_{n \rightarrow \infty} 2^n - e^n = -\infty</script>
<script type="math/tex; mode=display">\lim_{n \rightarrow \infty} f_n(-x_n) - f(-x_n) = \lim_{n \rightarrow \infty} f_n(x_n) - f(x_n) = -\infty</script>
<p>y de haber convergencia uniforme para cualquier sucesión de números reales <script type="math/tex">\{x_n\}</script> se verificaría que la sucesión <script type="math/tex">\{f_n(x_n) - f(x_n)\}</script> converge a 0.</p>
<p>Sin embargo, sí hay convergencia uniforme en los intervalos del tipo <script type="math/tex">[-\alpha, \alpha]</script> para <script type="math/tex">\alpha > 0</script> como probaremos a continuación. Puede el lector saltarse este tedioso desarrollo ya que conseguiremos el mismo resultado aplicando el teorema de Dini.</p>
<p>Sea <script type="math/tex">\alpha > 0</script>. Definimos la siguiente función, que resulta ser continua:</p>
<script type="math/tex; mode=display">% <![CDATA[
\phi(t) = \frac{\log(1+t)}{t}, \ \forall -1 < t \neq 0, \phi(0) = 1 %]]></script>
<p>Tenemos que</p>
<script type="math/tex; mode=display">\left| \left(1+\frac{x^2}{n}\right)^n - \exp\left(x^2\right) \right| =
\exp\left(x^2\right) \left| \left(\frac{\left(1+\frac{x^2}{n}\right)^\frac{n}{x^2}}{e}\right)^{x^2} - 1 \right| =</script>
<script type="math/tex; mode=display">\exp\left(x^2\right) \left| \exp\left( x^2\left(\phi\left(\frac{x^2}{n}\right) - 1\right)\right) -1 \right| \le
\exp\left(\alpha^2 \right) \left| \exp\left( x^2\left(\phi \left(\frac{x^2}{n}\right) - 1\right)\right) -1 \right|</script>
<p>Sea <script type="math/tex">\varepsilon > 0</script>, utilizaremos la continuidad de <script type="math/tex">\phi</script> junto con la continuidad de la función exponencial para obtener el resultado deseado.</p>
<p>Por la continuidad de la exponencial en 0, existe <script type="math/tex">\delta_1 > 0</script> tal que si <script type="math/tex">% <![CDATA[
\vert u \vert < \delta_1 %]]></script> entonces <script type="math/tex">% <![CDATA[
\ \vert e^u -1 \vert < \frac{\varepsilon}{\exp\left(\alpha^2\right)} %]]></script>. Aplicamos ahora la continuidad de <script type="math/tex">\phi</script> en 0 para obtener <script type="math/tex">\delta_2 > 0</script> tal que si <script type="math/tex">% <![CDATA[
\ \vert t\vert < \delta_2 %]]></script> entonces <script type="math/tex">% <![CDATA[
\ \vert \phi(t) -1\vert < \frac{\delta_1}{\alpha^2} %]]></script>.</p>
<p>Tomemos <script type="math/tex">n_0 \in \mathbb{N}</script> tal que <script type="math/tex">% <![CDATA[
\frac{\alpha^2}{n_0} < \delta_2 %]]></script>. Entonces, para todo <script type="math/tex">x \in [-\alpha, \alpha]</script> y <script type="math/tex">n \ge n_0</script> se tiene que:</p>
<script type="math/tex; mode=display">% <![CDATA[
\frac{x^2}{n} < \delta_2 \Rightarrow
\left| \phi \left(\frac{x^2}{n}\right) -1\right| < \frac{\delta_1}{\alpha^2} \Rightarrow
\left| x^2 \left( \phi \left(\frac{x^2}{n}\right) -1 \right) \right| < \delta_1 \Rightarrow %]]></script>
<script type="math/tex; mode=display">% <![CDATA[
\left| \exp\left(x^2\left(\phi \left(\frac{x^2}{n}\right) - 1\right)\right) - 1\right| < \frac{\varepsilon}{\exp(\alpha^2)} %]]></script>
<p>De donde se deduce que</p>
<script type="math/tex; mode=display">% <![CDATA[
\left| \left(1+\frac{x^2}{n}\right)^n - \exp\left(x^2\right) \right| \le \exp\left(\alpha^2\right) \left| \exp\left( x\left(\phi \left(\frac{x^2}{n}\right) - 1\right)\right) -1\right| < \varepsilon %]]></script>
<p>Se ha obtenido la definición de convergencia uniforme en <script type="math/tex">[-\alpha, \alpha]</script>. Nótese que esto implica que hay convergencia uniforme en cualquier intervalo cerrado y acotado.</p>
<script type="math/tex; mode=display">\tag*{$\blacksquare$}</script>
<h2 id="teorema-de-dini">Teorema de Dini</h2>
<p>El teorema de Dini es uno de los pocos resultados que transforman la convergencia puntual en convergencia uniforme. Podremos aplicarlo en intervalos del tipo <script type="math/tex">[a, b]</script> si se verifican ciertas condiciones, como veremos que sucede en el ejemplo anterior.</p>
<dl>
<dt>Enunciado</dt>
<dd>Sean <script type="math/tex">a,b \in \mathbb{R}</script> y <script type="math/tex">\ f_n : [a,b] \rightarrow \mathbb{R}</script> continua para todo <script type="math/tex">n \in \mathbb{N}</script>. Se considera la sucesión de funciones <script type="math/tex">\{f_n\}</script>. Si la sucesión es monótona y converge puntualmente a <script type="math/tex">\ f</script> continua, entonces la convergencia es uniforme.</dd>
</dl>
<p><strong>Demostración</strong></p>
<p>El gran salto de la convergencia puntual a la convergencia uniforme se va a producir gracias a la continuidad de todas las funciones, la monotonía de la sucesión y la compacidad del dominio.</p>
<p>En la prueba se utilizará el concepto de entorno de un punto para simplificar la redacción al aplicar la continuidad. Un entorno de <script type="math/tex">x</script> es un conjunto que contiene a un abierto del espacio que a su vez contiene a <script type="math/tex">x</script>. En nuestro caso, los abiertos son los intervalos abiertos intersecados con <script type="math/tex">[a, b]</script>.</p>
<p>Supongamos en primer lugar que <script type="math/tex">\{f_n\}</script> es creciente. Nótese que en tal caso <script type="math/tex">\ f(x) \ge f_n(x)</script> para todo <script type="math/tex">x</script> y <script type="math/tex">n</script>. Sea <script type="math/tex">\varepsilon > 0</script>:</p>
<ol>
<li>
<p>De la convergencia puntual se tiene que para todo <script type="math/tex">x \in [a,b]</script> existe un natural <script type="math/tex">n_x</script> tal que</p>
<script type="math/tex; mode=display">% <![CDATA[
\forall n \ge n_x : \left| f_n(x) - f(x) \right| < \frac{\varepsilon}{3} %]]></script>
</li>
<li>
<p>De la continuidad de <script type="math/tex">\ f</script> y <script type="math/tex">\ f_n</script> se tiene que para todo <script type="math/tex">x \in [a,b]</script> existe un entorno <script type="math/tex">V_x</script> de <script type="math/tex">x</script> tal que para todo elemento <script type="math/tex">y</script> del entorno se verifica:</p>
<script type="math/tex; mode=display">% <![CDATA[
\left| f_n(y) - f_n(x) \right| < \frac{\varepsilon}{3} %]]></script>
<script type="math/tex; mode=display">% <![CDATA[
\left| f(y) - f(x) \right| < \frac{\varepsilon}{3} %]]></script>
</li>
<li>
<p>Utilizando la desigualdad triangular se obtiene que para todo <script type="math/tex">y \in V_x</script> se cumple:</p>
<script type="math/tex; mode=display">% <![CDATA[
\left| f(y) - f_{n_x}(y) \right| \le \left| f(y) - f(x) \right| + \left| f(x) - f_{n_x}(x) \right| + \left| f_{n_x}(x) - f_{n_x}(y) \right| < \frac{\varepsilon}{3} + \frac{\varepsilon}{3} + \frac{\varepsilon}{3} < \varepsilon %]]></script>
</li>
<li>
<p>Como <script type="math/tex">[a,b] = \cup_{x \in [a,b]} V_x</script>, usando la caracterización topológica de la compacidad, existen <script type="math/tex">x_1, \ldots, x_m \in [a,b]</script> tales que:</p>
<script type="math/tex; mode=display">[a,b] = \bigcup_{i = 1}^m V_{x_i}</script>
</li>
<li>
<p>Tomamos <script type="math/tex">n_0 = \max\{n_{x_i}: i = 1, \ldots, m\}</script>. Veamos que efectivamente se da la convergencia uniforme. Sean <script type="math/tex">n \ge n_0, y \in [a,b]</script>. Aplicando la igualdad obtenida en <strong>(4)</strong>, debe existir <script type="math/tex">i \in \{1, \ldots, m\}</script> tal que <script type="math/tex">y</script> pertenece a <script type="math/tex">V_{x_i}</script>. Juntando este hecho con <strong>(3)</strong> y la monotonía:</p>
<script type="math/tex; mode=display">% <![CDATA[
\left| f(y) - f_n(y) \right| = f(y) - f_n(y) \le f(y) - f_{n_0}(y) \le f(y) - f_{n_{x_i}}(y) < \varepsilon %]]></script>
</li>
</ol>
<p>Por otro lado, si <script type="math/tex">\{f_n\}</script> es decreciente, basta aplicar lo anterior a <script type="math/tex">\{-f_n\}</script>, que es creciente y converge a <script type="math/tex">\ -f</script>. Fácilmente se extrapola su convergencia uniforme a la de <script type="math/tex">\{f_n\}</script>.</p>
<script type="math/tex; mode=display">\tag*{$\blacksquare$}</script>
<p>Nótese que análogamente se podría haber probado este resultado:</p>
<dl>
<dt>Teorema de Dini (en espacios topológicos)</dt>
<dd>Sean <script type="math/tex">(X, \mathcal{T})</script> un espacio topológico compacto y sea <script type="math/tex">\ f_n : X \rightarrow \mathbb{R}</script> continua para todo <script type="math/tex">n \in \mathbb{N}</script>. Se considera la sucesión de funciones <script type="math/tex">\{f_n\}</script>. Si la sucesión es monótona y converge puntualmente a <script type="math/tex">f</script> continua, entonces la convergencia es uniforme.</dd>
</dl>
<p>La exigencia de continuidad a la función límite no supone una condición extra para poder aplicar el teorema. Recordemos que en caso de que haya convergencia uniforme se tiene automáticamente la continuidad de la función límite ya que las funciones <script type="math/tex">\ f_n</script> son continuas.</p>
<p>Volvemos al Ejemplo 2 cuya resolución será una mera consecuencia del teorema de Dini.</p>
<dl>
<dt>Ejemplo 2</dt>
<dd>Estudia la convergencia puntual y uniforme de la sucesión de funciones <script type="math/tex">\{f_n\}</script> donde <script type="math/tex">f_n : [0,1] \rightarrow \mathbb{R}</script> está definida por:</dd>
<dd>
<script type="math/tex; mode=display">f_n(x) = \left(1+\frac{x^2}{n}\right)^n \ \forall x \in \mathbb{R}</script>
</dd>
</dl>
<p><strong>Solución.</strong> Se había obtenido que la sucesión convergía puntualmente a <script type="math/tex">\exp\left(x^2\right)</script>. Además, no había convergencia uniforme en ninguna semirrecta. Veamos que sí hay convergencia uniforme en los intervalos cerrados y acotados.</p>
<p>Sean <script type="math/tex">a, b \in \mathbb{R}</script> con <script type="math/tex">% <![CDATA[
a < b %]]></script>. En <script type="math/tex">[a, b]</script> la sucesión verifica las condiciones del teorema de Dini:</p>
<ul>
<li>Las funciones <script type="math/tex">\ f_n</script> y <script type="math/tex">\ f</script> son continuas.</li>
<li>Las sucesiones <script type="math/tex">\ f_n</script> son crecientes. En efecto, aplicando la desigualdad de las medias <sup id="fnref:medias"><a href="#fn:medias" class="footnote">2</a></sup> para <script type="math/tex">x_1 = 1, x_i = 1+\frac{x^2}{n} \ \forall i = 2, \ldots, n+1</script>:</li>
</ul>
<script type="math/tex; mode=display">% <![CDATA[
\sqrt[n+1]{\left(1+\frac{x^2}{n}\right)^n} < \frac{1 + n \left(1+\frac{x^2}{n}\right)}{n+1} = 1 + \frac{x^2}{n+1} %]]></script>
<p>Por tanto, hay convergencia uniforme en <script type="math/tex">[a, b]</script>.</p>
<script type="math/tex; mode=display">\tag*{$\blacksquare$}</script>
<h2 id="referencias">Referencias</h2>
<!-- remote_include https://raw.githubusercontent.com/libreim/blog/sites/blog/_posts/2015-4-20-teorema-dini.md -->
<div class="footnotes">
<ol>
<li id="fn:ejercicios">
<p><a href="http://www.ugr.es/~fjperez/textos/sucesiones_series_funciones.pdf">Ejercicios de sucesiones y series de funciones. Javier Pérez</a> <a href="#fnref:ejercicios" class="reversefootnote">↩</a></p>
</li>
<li id="fn:medias">
<p><a href="/blog/2014/04/12/desigualdad-medias/">La desigualdad de las medias. Mario Román</a> <a href="#fnref:medias" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Andrés Herrera PoyatosConvergencia Puntual y Convergencia Uniforme