viernes, 25 de mayo de 2012

trabajo de librerias


ALGORITMOS
Combinando la utilización de templates y un estilo específico para denotar tipos y variables, la STL ofrece una serie de funciones que representan operaciones comunes, y cuyo objetivo es "parametrizar" las operaciones en que estas funciones se ven involucradas de modo que su lectura, comprensión y mantenimiento, sean más fáciles de realizar.
Un ejemplo es la función copy, la cual simplemente copia variables desde un lugar a otro. Más estrictamente, copia los contenidos cuyas ubicaciones están delimitadas por dos iteradores, al espacio indicado por un tercer iterador. La sintaxis es:
copy (inicio_origen, fin_origen, inicio_destino);
De este modo, todos los datos que están entre inicio_origen y fin_origen, excluyendo el dato ubicado en este último, son copiados a un lugar descrito o apuntado por inicio_destino.
Un algoritmo muy importante que viene implementado en la biblioteca STL, es el sort. El algoritmo sort, ordena cualquier tipo de contenedor, siempre y cuando se le pasen como argumentos, desde donde y hasta donde se quiere ordenarlo.
#include <vector>
#include <deque>
#include <algorithm>
 
int main() {
  vector<int> intVector;
  intVector.push_back(60);
  intVector.push_back(12);
  intVector.push_back(54); //para este momento, el vector tiene 60,12,54
  sort(intVector.begin(), intVector.end()); //listo, arreglo ordenado, ahora tiene 12,54,60
  /*Notar que si en vez de un vector, fuese una deque, se ordenaria de la misma manera. */
}
Entre las funciones más conocidas están swap (variable1, variable2), que simplemente intercambia los valores de variable1 y variable2; max (variable1, variable2) y su símil min (variable1, variable2), que retornan el máximo o mínimo entre dos valores; find (inicio, fin, valor) que busca valor en el espacio de variables entre inicio y fin; etcétera.
Los algoritmos son muy variados, algunos incluso tienen versiones específicas para operar con ciertos iteradores o contenedores, y proveen un nivel de abstracción extra que permite obtener un código más "limpio", que "describe" lo que se está haciendo, en vez de hacerlo paso a paso explícitamente.

Hemos señalado que los contenedores pueden ser considerados como estructuras de datos y que los algoritmos son como operadores que actúan sobre los elementos de estas estructuras. Hemos señalado también que en la STL los algoritmos adoptan la forma de funciones que incluyen iteradores entre sus argumentos, lo que les permite manipular los elementos de los contenedores subyacentes. En el contexto de la STL, un algoritmo genérico es un algoritmo que no pertenece a ningún tipo particular de contenedor; obtiene información sobre la forma de manipular los tipos que usa a través del análisis de los argumentos que se le pasan.
La STL ofrece un conjunto de algoritmos que puede evitarnos tener que escribir los detalles de ciertas manipulaciones de datos que se repiten una y otra vez (por ejemplo rutinas de ordenación). Generalmente están contenidos en la cabecera <algorithm> y generalmente conducen a un código increíblemente rápido y compacto, en sustitución de lo que serían varias páginas de código si tuviésemos que escribirlo por nuestros propios medios partiendo de cero.
Como en el caso de los iteradores, los algoritmos de la STL utilizan una interfaz homogénea cualquiera que sea el tipo de objeto (contenedor) sobre el que se apliquen. Esto permite que los conocimientos obtenidos del estudio de uno de ellos puedan ser extendidos a los demás y que su dominio no sea tan difícil como lo sería de no mantenerse este isomorfismo.
Nota: los algoritmos no están supeditados a operar exclusivamente sobre las estructuras de datos (contenedores) definidas en la STL. También pueden operar sobre clases definidas por el usuario, a condición de que estas proporcionen punteros que satisfagan las premisas exigidas por los algoritmos a sus argumentos.
Un concepto de vital importancia para entender la mecánica de la STL es que los algoritmos no operan sobre los contenedores, sino sobre sus elementos.  De forma que en ningún caso alteran la estructura de datos como tal, sino los elementos que la componen, o su orden interno. En ocasiones pueden copiar total o parcialmente el contenido de un contenedor en otro.
En ciertos casos, los algoritmos que alteran el contenido del contenedor se presentan en dos versiones denominadas "in-situ" ("In-place") y de copia. La primera altera el contenido del original, la segunda produce una copia con el contenido modificado [2]. Estas últimas se distinguen por que presentan la terminación _copy en el nombre del algoritmo. Por ejemplo, replace() y replace_copy().
En ocasiones los algoritmos aceptan una función o un objeto-función como argumento;  este tipo de argumentos se denomina un predicado si devuelven un booleano o un entero (que puede reducirse a un booleano. Estos argumentos son generalmente utilizados para señalar una condición o realizar cierta computación adicional determinada por el usuario .
Los algoritmos de ordenación y similares se presentan en dos versiones: una acepta como argumento un objeto-función, la otra utiliza para las comparaciones la función-operador operator<
Clasificación
Aunque los sistemas de clasificación son siempre artificiosos, máxime cuando se trata de este tipo de entidades, seguiremos aquí la pauta de la mayoría de libros sobre la STL ofreciendo una clasificación de los más de 60 algoritmos ofrecidos por la Librería Estándar. La que sigue es la utilizada en el Estándar que los agrupa en tres grandes grupos y algunos subgrupos:
  • Operaciones no-modificativas
En esta sección se incluyen algoritmos como count o search que no modifican el iterador ni los elementos de los contenedores asociados.
Ø  for_each()
Ø  find()
Ø  find_if()
Ø  find_end()
Ø  find_first_of()
Ø  adyacent_find()
Ø  count()
Ø  count_if()
Ø  mismatch()
Ø  equal()
Ø  search()
Ø  search_n()
  • Operaciones modificativas
En esta sección se incluyen algoritmos que realizan modificaciones en el iterador y/o en los elementos de los contenedores asociados. Comprende doce funciones genéricas que se presentan en varias versiones.
Ø  copy()
Ø  swap()
Ø  transform()
Ø  replace()
Ø  fill()
Ø  generate()
Ø  remove()
Ø  unique()
Ø  reverse()
Ø  rotate()
Ø  random_shuffle()
Ø  partition()
  • Operaciones de ordenación y similares
Ø  Ordenación
§  sort()
§  stable_sort()
§  partial_sort()
§  partial_sort_copy()
Ø  Elemento enésimo
§  nth_element()
Ø  Búsqueda binaria.
En esta sección se incluyen algoritmos que realizan búsquedas binarias, lo que supone que la búsqueda se realiza sobre estructuras de datos (contenedores) ordenados según cierto criterio. Trabajan con iteradores aleatorios y secuenciales, aunque son especialmente adecuadas para los primeros, ya que para cualquier búsqueda, realizan un número logarítmico de pasos por la estructura (ver árboles binarios . En el caso de accesos secuenciales le número de pasos tiende a ser lineal.
§  lower_bound()
§  upper_bound()
§  equal_range()
§  binary_search()
Ø  Composición ("Merge")
§  merge()
§  inplace_merge()
Ø  Operaciones de comprobación
Comprobar si un conjunto es un subconjunto de otro.
§  includes()
Operaciones de álgebra de conjuntos (solo pueden ser realizadas sobre contenedores ordenados).
§  set_union()
§  set_intersection()
§  set_difference()
§  set_symmetric_difference()
Ø  Operaciones de montón
No tienen ninguna relación con el espacio de almacenamiento persistente del mismo nombre . Un montón ("Heap") es también un tipo especial de organización de datos entre dos límites; en estos casos ambos límites están definidos por dos iteradores a y b, con las siguientes propiedades:
Ø  *a es el mayor elemento del rango.
Ø  *a puede ser eliminado del conjunto mediante una operación pop_head(), también puede ser colocado un nuevo elemento en esta posición mediante una operación push_head() en un tiempo que está en relación logarítmoca con el número de elementos en el montón.
Estas propiedades hacen a estas estructuras muy adecuadas para manejar colas
§  push_heap()
§  pop_heap()
§  make_heap()
§  sort_heap() 
Ø  Mínimos y máximos
§  min()
§  max()
§  min_element()
§  max_element()
Ø  Comparaciones lexicográficas
§  lexicographical_compare()
Ø  Generadores de permutaciones
§  next_permutation()
§  prev_permutation()

Además de las anteriores, el Estándar C++ define un par de algoritmos para sustituir a dos utilidades del mismo nombre existentes en la primitiva librería C Estándar. Son las funciones
 bsearch() y qsort().
Por supuesto pueden plantearse muchas otras clasificaciones para los algoritmos según el criterio que se considere más relevante. Sin embargo, los criterios de clasificación más significativos (que coinciden aproximadamente con los utilizados por el Estándar http://www.zator.com/Cpp/images/Ico_hojaFup.gif) son los siguientes:
  • Si el algoritmo es o no modificativo, en el sentido de alterar el contenido del contenedor.
  • El tipo de iterador que acepta como argumento
  • Si el algoritmo exige actuar sobre un contenedor previamente ordenado, o puede actuar sobre contenedores sin ordenar.
Nota: un algoritmo puede exigir ciertas condiciones adicionales para que pueda ser utilizado sobre un contenedor. Por ejemplo, la mayoría de algoritmos de modificación de estructuras ordenadas http://www.zator.com/Cpp/images/Ico_hojaFup.gif (cuyos nombres comienzan por set_) requieren un contenedor ordenado, y además, que el criterio utilizado en la ordenación coincida con el de la función y objeto utilizado por el algoritmo.
Complejidad
La librería Estándar es singular en el sentido que la norma señala lo que se denomina complejidad de cada algoritmo. En la descripción que acompaña a cada algoritmo en el documento oficial hay un apartado ("Complexity") en el que pueden encontrarse expresiones del siguiente tenor [1]:
Complexity:  Exactly ( last ­first)/2 swaps.
Complexity:  At most ( last ­first)* log( last ­first) swaps.
Complexity:  Approximately N log N (where N == last ­first)
Complexity:  It does at most N(log N)2 (where N == last ­first) comparisons; if enough extra memory is available, it is N log N.

Lo que se pretende indicar es el grado de eficacia que puede esperarse del algoritmo en el peor de los casos, y operando sobre un número suficientemente grande de elementos de un contenedor. Su singularidad reside en que hasta ahora no había sido usual que ningún fabricante de algoritmos garantizara su rendimiento de ningún modo. Su importancia estriba en que esta indicación puede ser de gran ayuda a la hora de decidir la utilización de un algoritmo u otro; e indirectamente sobre el tipo de contenedor a utilizar.
La complejidad señala el tiempo que emplea el algoritmo en realizar la computación correspondiente sobre un contenedor. Naturalmente esto depende del número de elementos de la serie sobre la que se ejecuta. En unos casos es constante (independiente del número de elementos); en otros casos es lineal (una función constante del número de elementos); finalmente en ciertos casos es una función de otro tipo, que crece exponencialmente con el tamaño del contenedor asociado.
Es importante no perder de vista las condiciones para las que se garantiza la eficacia, que se refieren a operación sobre un número suficientemente grande de elementos, y que el valor mostrado corresponde al peor escenario posible. Es frecuente que en condiciones reales, con número no excesivamente pequeños de elementos, el rendimiento de los algoritmos de la STL sea muy superior al indicado, por lo que si diseñamos aplicaciones medianamente importantes se recomienda realizar pruebas comparativas con ficheros representativos de las condiciones reales.
En ocasiones es frecuente también encontrar referencias a la complejidad amortizada que se refiere al tiempo de realizar una operación sobre N elementos dividido por el número de ellos. Viene a ser una indicación del tiempo medio que se emplea en la operación del algoritmo sobre un miembro de un contenedor.