Mostrando postagens com marcador Divisão e Conquista. Mostrar todas as postagens
Mostrando postagens com marcador Divisão e Conquista. Mostrar todas as postagens

quarta-feira, 20 de março de 2013

Problema da Maioria

Como determinar se algum candidato recebeu mais de 50% dos votos em tempo linear. Esse problema é conhecido como problema da maioria. Formalmente,

Dada uma sequência de n elementos onde cada elemento é um inteiro x $\in$ [1..k] devolva o elemento majoritário, ou seja, o elemento que aparece mais do que $\frac{n}{2}$ ou zero se nenhum elemento é encontrado.

Algoritmo baseado em contagem


int MajorityCount(int v[], int n, int k){
  int c[k];
  int i;
  for(i=0;i<k;i++) c[i] = 0;
  for(i=0;i<n;i++) c[v[i]]++;
  for(i=0;i<k;i++)
    if(c[i] > n/2 ) return i;
  return 0;
}


Complexidade de tempo O(n+k)
Complexidade de espaço extra O(k)

Algoritmo baseado em comparações

Força Bruta

int MajorityBrute(int v[], int n)
{
  int i;
  for(i=0; i< n; i++)
  {
    int count = 1;
    int item = v[i];
    int j;
    for(j=i+1; j <= n; j++)
    {
      if (v[j] == item) count++; }
      if (count > n/2) return item;
    }
    return 0;
}

Complexidade de tempo O($n^2$)
Complexidade de espaço extra O(1)

Divisão e Conquista


int Count(int v[], int lo, int hi, int x){
  int k;
  int cont = 0;
  for(k=lo;k<=hi;k++)
    if(v[k]==x) cont++;
  return cont;
}



int MajorityDivideConquer(int v[], int lo, int hi)
{
  if (lo > hi) return 0; // empty sequence case
  else if (lo == hi) return v[lo]; // one-element sequence case
  else // general case
  {
    int mid = lo + (hi-lo)/2; // integer division
    int x = MajorityDivideConquer(v,lo,mid);
    int y = MajorityDivideConquer(v,mid+1,hi);
    if (x==y) return x; // x and y are both zero or both majority
    if (x > 0) // x is a majority in 1st half
    if ( Count(v,lo, hi,x) > (hi-lo+1)/2 ) return x;
    if (y > 0) // y is a majority in 2nd half
    if ( Count(v,lo, hi,y) > (hi-lo+1)/2 ) return y;
    return 0;
  }
}


Complexidade de tempo


C(n) = C(n/2) + C(n−n/2) + 2n
C(0) = C(1) = 0

Simplificando para C(n)=2C(n/2)+2n


Complexidade de tempo O(n lg n)

Moore’s Voting Algorithm


int MajorityLinearMoore(int v[], int n){
     int maj_index = 0;
     int count = 1;
     int i;
     for (i = 1; i < n; i++ ){
      if (v[maj_index] == v[i]) count++;
      else count--;
      if (count == 0){
        maj_index = i;
        count = 1;
      }
     }
    if( Count(v, 0, n-1, v[maj_index]) > n/2 )
      return v[maj_index];
    else
      return 0;
}


A corretude deste algoritmo é baseado nas seguintes observações:

  1. se v[1..n] tem um valor majoritário e se A[n-1] $\neq$ A[n] então A[1..n-2] também tem um valor majoritário. A mesma observação vale para quaisquer dois elementos do vetor: se A[i] $\neq$ A[j] e A tem um valor majoritário então o vetor que se obtém pela eliminação das posições i e j também tem um valor majoritário. Observe que quando o valor count == 0, o algoritmo recomeça. Isso quer dizer que todos os valores anteriores podem ser eliminados.
  2. Para qualquer k $\in$ [1..n], se x é majoritário de A[1..n] se somente se x é majoritário em A[1..k-1] ou A[k..n] ou em ambos. 

Complexidade de tempo O(n)

Problema SPOJ
http://www.spoj.com/problems/MAJORITY/
http://www.spoj.com/problems/MAJOR/


Referências:
Majority Element : http://www.geeksforgeeks.org/majority-element/
Minicurso de Análise de Algoritmos http://www.ime.usp.br/~pf/livrinho-AA/



quarta-feira, 10 de outubro de 2012

Divisão e Conquista - Busca Binária

Na math.h tem o valor da raiz de 2 com uma constante:
#define M_SQRT2 1.41421356237309504880

Vamos fazer um programa que calcula o valor de $\sqrt{2}$ sem utilizar uma função pré-definida como pow(2.0,0.5).

Observação 1:
Sabemos que $\sqrt{2}$ é a raiz da seguinte função f(x) = $x^2$ - 2. Sabemos que a função f é contínua e monótona no intervalo [1,2] e f(1) < 0 < f(2).

Observação 2:
O teorema do valor intermediário garante que existe um ponto 1<=x<=2 tal que f(x)=0. O valor de x é o valor de $\sqrt{2}$. Vamos utilizar a busca binária para encontrar o valor de $\sqrt{2}$.

Observação 3:
Seja x $\in$ [a,b] , f(a) < 0  e f(b) > 0 então
  • se f(x) > 0 então existe um x $\in$ [a,x] tal que f(x) = 0
  • se f(x) < 0 então existe um x $\in$ [x,b] tal que f(x) = 0
Observação 4:
A condição de parada do algoritmo será |f(x)| < eps



Observação 5:
tipo            | bits | expoente | precisão decimal
-----------------|-----------|--------------------------
float            | 32 | 38            | 6
double        | 64 | 308          | 15
long double | 80 | 19.728     | 18
 
#include <iostream>
#include <math.h>
#include <iomanip>

using namespace std;
template <typename T>
T precision(int n){
  T p = (T)1.0;
  for(int i=1;i<=n;i++)
   p = p/10.0;
  return p;
  
}

template <typename T>
T f(T x){
  return x*x - 2;
}


template <typename T>
void sqrt2(T inicio, T fim,T eps){
  T meio;
  T res;
  long long int numiter = 0;
  cout <<"precisao" << setprecision(18) << eps << endl;
  while(1){
    meio = (inicio+fim)/2;
    res = f(meio);
    if( fabs( res ) < eps ) break;
    else if ( res > 0 ) fim = meio;
    else if ( res < 0 ) inicio = meio;
    numiter++;
  }
  
  cout << "num iter " << numiter << endl;
  cout << "x " << setprecision(18) << meio << endl; 
}
int main()
{
    int i;
    double a = 1.0;
    double b = 2.0;

    for(int i=1;i<=15;i++){
      sqrt2(a,b,precision<double>(i));
    }
    
    system("PAUSE");
    return 0;
} 

Saída
precisao 0.10000000000000001
num iter 3
x 1.4375
precisao 0.01
num iter 6
x 1.4140625
precisao 0.001
num iter 6
x 1.4140625
precisao 0.0001
num iter 12
x 1.4141845703125
precisao 1.0000000000000001e-005
num iter 14
x 1.414215087890625
precisao 1.0000000000000002e-006
num iter 20
x 1.4142136573791504
precisao 1.0000000000000002e-007
num iter 22
x 1.4142135381698608
precisao 1.0000000000000002e-008
num iter 26
x 1.4142135605216026
precisao 1.0000000000000003e-009
num iter 28
x 1.4142135623842478
precisao 1.0000000000000003e-010
num iter 28
x 1.4142135623842478
precisao 1.0000000000000003e-011
num iter 35
x 1.4142135623696959
precisao 1.0000000000000002e-012
num iter 37
x 1.4142135623733338
precisao 1.0000000000000002e-013
num iter 41
x 1.4142135623731065
precisao 1.0000000000000002e-014
num iter 45
x 1.4142135623730923
precisao 1.0000000000000001e-015
num iter 49
x 1.4142135623730949
Pressione qualquer tecla para continuar. . .

Para adicionar mais precisão, vamos utilizar o tipo long double com maior precisão.

int main()
{
    int i;
    long double a = 1.0;
    long double b = 2.0;

    for(int i=1;i<=18;i++){
      sqrt2(a,b,precision<long double>(i));
    }
    
    system("PAUSE");
    return 0;
}
Saída

precisao 0.10000000000000001
num iter 3
x 1.4375
precisao 0.01
num iter 6
x 1.4140625
precisao 0.001
num iter 6
x 1.4140625
precisao 0.0001
num iter 12
x 1.4141845703125
precisao 1.0000000000000001e-005
num iter 14
x 1.414215087890625
precisao 9.9999999999999995e-007
num iter 20
x 1.4142136573791504
precisao 9.9999999999999995e-008
num iter 22
x 1.4142135381698608
precisao 1e-008
num iter 26
x 1.4142135605216026
precisao 1.0000000000000001e-009
num iter 28
x 1.4142135623842478
precisao 1e-010
num iter 28
x 1.4142135623842478
precisao 9.9999999999999994e-012
num iter 35
x 1.4142135623696959
precisao 9.9999999999999998e-013
num iter 37
x 1.4142135623733338
precisao 1e-013
num iter 41
x 1.4142135623731065
precisao 1e-014
num iter 45
x 1.4142135623730923
precisao 1.0000000000000001e-015
num iter 49
x 1.4142135623730949
precisao 9.9999999999999998e-017
num iter 52
x 1.4142135623730949
precisao 1.0000000000000001e-017
num iter 55
x 1.4142135623730951
precisao 1.0000000000000001e-018
num iter 60
x 1.4142135623730951
Pressione qualquer tecla para continuar. . .

Outros links:
http://diegodonah.wordpress.com/2009/11/16/bug-secreto-na-busca-binaria/
http://www.ime.usp.br/~pf/algoritmos/aulas/footnotes/epigraphs.html
http://blog.ricbit.com/2012/06/formula-de-bhaskara.html
http://searchforquality.blogspot.com.br/2007/06/very-interesting-bug.html
http://locklessinc.com/articles/binary_search/


domingo, 3 de junho de 2012

Contando número de Inversões


Este problema surge na análise de rankings (classificações) que tem ser tornado importante para um grande número de aplicações. Por exemplo, considere  uma ferramenta de meta-search na internet, que executa uma mesma query em diferentes motores de buscas (search engines)  como Google,Yahoo entre outros  e tenta sintetiza o resultados procurando por similaridades e diferenças entre os vários rankings que são retornados pelos motores de busca.
Outra aplicação interessante seria comparar um conjunto de rankings sobre alguma preferência (livros, filmes, restaurantes) para criar um tipo de filtro colaborativo.
Agora, qual é a melhor medida que pode ser usada para saber quão similar são duas classificações?
Problema
Uma maneira natural seria quantificar a noção de similaridade é contando o número de inversões. Nós dizemos que dois índices i < j formam uma inversão se ai > aj, ou seja, se dois elementos  ai e aj estão fora de ordem.
Existe um apelo geométrico para visualiza o número de inversões em uma permutação.

Neste exemplo, temos 3  inversões nestas sequência: (2,1), (4,1) e (4,3). Note que uma cada intersecção de um par de segmentos corresponde a uma inversão.
Modelando e Analisando o Algoritmo
O algoritmo ingênuo para contar as inversões seria checar todos os pares de números (ai,aj) e determinar se eles formam uma inversão; isto seria O(n2).
Nós vamos mostrar como contar o número de inversão muito mais rapidamente, em O(n log n). Observe que todo par pode formar uma inversão, e então existe no máximo  inversões. Logo, o número de inversões é quadrático. Dessa maneira, o algoritmo que vamos desenvolver tem que ser capaz de computar o total de inversões sem precisar checar cada inversão individualmente.
Idéia Básica
Considere m = $\lfloor \frac{n}{2} \rfloor$ e divida a lista em duas sublistas a1,..,am e am+1,…,an. Primeiramente, vamos contar o número de inversões em cada metade separadamente. Então vamos contar o  número de inversões (ai,aj), onde os dois números pertencem a metades diferentes; o truque é fazer esta parte em O(n).
Vamos fazer uma adaptação do algoritmo Merge-Sort.










 
Durante a intercalação, precisamos contar o número de inversões. 









Se o elemento ai for colocado na lista ordenada, nenhuma inversão é encontrada, uma vez que ai é menor que todos os elementos da lista B. Por outro lado, se bj for colocado na lista ordenada, então bj é menor que todos os elementos restantes em A, e ele aparece depois de todos eles, então nós precisamos incrementar o número de inversões pelo número de elementos restantes em A. Esta é a idéia crucial: em tempo constante, nós podemos calcular um número grande de inversões em potencial.

























long long int merge_count(int A[], int B[], int p,int q,int r){
 int i,j,k;
 
 long long int c;
 
 for(i=p;i<=q;i++)
  B[i] = A[i];
  
 for(j=q+1;j<=r;j++)
  B[r+q+1-j]=A[j];
  
 i = p;
 j = r;
 c = 0; 
 
 for(k=p;k<=r;k++){
  if(B[i] <= B[j]){
   A[k] = B[i];
   i = i+1;
  }else{
   A[k] = B[j];
   j = j-1;
   c = c + (q-i+1);
  }
 }
 
 return c;

}



long long int sort_count(int A[], int B[], int i,int j){
 int q;
 if(i >=j ) return 0;
 else{
  q = (i+j)/2;
  return sort_count(A, B, i, q) +
      sort_count(A, B, q+1, j)+
      merge_count(A, B, i, q, j);  
 } 
 
}
Problema
Lembrando que o número de inversões é O( $ \binom {n} {2} $ ) . Em alguns casos pode estourar a capacidade de inteiro de 32-bit.