web-dev-qa-db-fra.com

Quand un algorithme peut-il avoir une complexité temporelle racine carrée (n)?

Quelqu'un peut-il me donner un exemple d'algorithme qui a une complexité temporelle racine carrée (n). Que signifie la complexité du temps de racine carrée?

22
vaibhav9225
  • La complexité du temps de racine carrée signifie que l'algorithme nécessite des évaluations O(N^(1/2)) où la taille de l'entrée est N.
  • À titre d'exemple pour un algorithme qui prend O(sqrt(n)) temps, l'algorithme de Grover est celui qui prend autant de temps. algorithme de Grover est un algorithme quantique pour rechercher une base de données non triée de n entrées en O(sqrt(n)) temps.
  • Prenons un exemple pour comprendre comment arriver à la complexité d'exécution de O(sqrt(N)), étant donné un problème. Cela va être élaboré, mais c'est intéressant à comprendre. (L'exemple suivant, dans le contexte de réponse à cette question, est tiré de Coding Contest Byte: The Square Root Trick , problème très intéressant et astuce intéressante pour arriver à la complexité de O(sqrt(n)))

    • Étant donné A, contenant un tableau de n éléments, implémentez une structure de données pour les mises à jour de points et les requêtes de somme de plage.

      • update (i, x) -> A [i]: = x (Requête de mises à jour ponctuelles)
      • query (lo, hi) -> renvoie A [lo] + A [lo + 1] + .. + A [hi]. (Requête de somme de plage)
    • La solution naïve utilise un tableau. Cela prend O(1) temps pour une mise à jour (accès à l'index du tableau) et O(hi - lo) = O(n) pour la somme de la plage (itération de l'index de début à l'index de fin et additionnement).

    • Une solution plus efficace divise le tableau en longueurs k tranches et stocke les sommes des tranches dans un tableau S.
    • La mise à jour prend un temps constant, car nous devons mettre à jour la valeur de A et la valeur du S. correspondant. Dans la mise à jour (6, 5), nous devons changer A [6] en 5, ce qui entraîne la modification de la valeur de S-- 1 pour garder S à jour. Updating element
    • La requête de somme de plage est intéressante. Les éléments de la première et de la dernière tranche (partiellement contenus dans la plage interrogée) doivent être parcourus un par un, mais pour les tranches complètement contenues dans notre plage, nous pouvons utiliser directement les valeurs de S et obtenir une amélioration des performances. Range-sum query
    • Dans la requête (2, 14), nous obtenons,

 query(2, 14) = A[2] + A[3]+ (A[4] + A[5] + A[6] + A[7]) + (A[8] + A[9] + A[10] + A[11]) + A[12] + A[13] + A[14] ; 
 query(2, 14) = A[2] + A[3] + S[1] + S[2] + A[12] + A[13] + A[14] ;
 query(2, 14) = 0 + 7 + 11 + 9 + 5 + 2 + 0;
 query(2, 14) = 34;
  • Le code de mise à jour et de requête est:

  def update(S, A, i, k, x):
      S[i/k] = S[i/k] - A[i] + x
      A[i] = x

  def query(S, A, lo, hi, k):
      s = 0
      i = lo
      //Section 1 (Getting sum from Array A itself, starting part)
      while (i + 1) % k != 0 and i <= hi:
          s += A[i]
          i += 1
      //Section 2 (Getting sum from Slices directly, intermediary part)
      while i + k <= hi:
          s += S[i/k]
          i += k
      //Section 3 (Getting sum from Array A itself, ending part)
      while i <= hi:
          s += A[i]
          i += 1
  return s
  • Déterminons maintenant la complexité.
  • Chaque requête prend en moyenne
    • La section 1 prend k/2 de temps en moyenne. (vous pouvez répéter au maximum k/2)
    • La section 2 prend n/k de temps en moyenne, essentiellement le nombre de tranches
    • La section 3 prend k/2 de temps en moyenne. (vous pouvez répéter au maximum k/2)
    • Donc, totalement, nous obtenons k/2 + n/k + k/2 = k + n/k temps.
  • Et cela est minimisé pour k = sqrt (n). sqrt (n) + n/sqrt (n) = 2 * sqrt (n)
  • Nous obtenons donc une requête de complexité temporelle O(sqrt(n)).
22
a3.14_Infinity

Nombres premiers

Comme mentionné dans certaines autres réponses, certaines choses de base liées aux nombres premiers prennent O(sqrt(n)) fois:

  1. Trouver le nombre de diviseurs
  2. Trouver la somme des diviseurs
  3. Trouver le totient d'Euler

Ci-dessous, je mentionne deux algorithmes avancés qui portent également le terme sqrt (n) dans leur complexité.

Algorithme de MO

essayez ce problème: Puissant tablea

Ma solution:

#include <bits/stdc++.h>
using namespace std;
const int N = 1E6 + 10, k = 500;

struct node {
    int l, r, id;
    bool operator<(const node &a) {
        if(l / k == a.l / k) return r < a.r;
        else return l < a.l;
    }
} q[N];

long long a[N], cnt[N], ans[N], cur_count;
void add(int pos) {
    cur_count += a[pos] * cnt[a[pos]];
    ++cnt[a[pos]];
    cur_count += a[pos] * cnt[a[pos]];
}
void rm(int pos) {
    cur_count -= a[pos] * cnt[a[pos]];
    --cnt[a[pos]];
    cur_count -= a[pos] * cnt[a[pos]];
}

int main() {
    int n, t;
    cin >> n >> t;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for(int i = 0; i < t; i++) {
        cin >> q[i].l >> q[i].r;
        q[i].id = i;
    }
    sort(q, q + t);
    memset(cnt, 0, sizeof(cnt));
    memset(ans, 0, sizeof(ans));

    int curl(0), curr(0), l, r;
    for(int i = 0; i < t; i++) {
        l = q[i].l;
        r = q[i].r;

/* This part takes O(n * sqrt(n)) time */
        while(curl < l)
            rm(curl++);
        while(curl > l)
            add(--curl);
        while(curr > r)
            rm(curr--);
        while(curr < r)
            add(++curr);

        ans[q[i].id] = cur_count;
    }
    for(int i = 0; i < t; i++) {
        cout << ans[i] << '\n';
    }
    return 0;
}

Mise en mémoire tampon des requêtes

essayez ce problème: Requêtes sur un arbre

Ma solution:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, k = 333;

vector<int> t[N], ht;
int tm_, h[N], st[N], nd[N];
inline int hei(int v, int p) {
    for(int ch: t[v]) {
        if(ch != p) {
            h[ch] = h[v] + 1;
            hei(ch, v);
        }
    }
}
inline void tour(int v, int p) {
    st[v] = tm_++;
    ht.Push_back(h[v]);
    for(int ch: t[v]) {
        if(ch != p) {
            tour(ch, v);
        }
    }
    ht.Push_back(h[v]);
    nd[v] = tm_++;
}

int n, tc[N];
vector<int> loc[N];
long long balance[N];
vector<pair<long long,long long>> buf;
inline long long cbal(int v, int p) {
    long long ans = balance[h[v]];
    for(int ch: t[v]) {
        if(ch != p) {
            ans += cbal(ch, v);
        }
    }
    tc[v] += ans;
    return ans;
}
inline void bal() {
    memset(balance, 0, sizeof(balance));
    for(auto arg: buf) {
        balance[arg.first] += arg.second;
    }
    buf.clear();
    cbal(1,1);
}

int main() {
    int q;
    cin >> n >> q;
    for(int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        t[x].Push_back(y); t[y].Push_back(x);
    }
    hei(1,1);
    tour(1,1);
    for(int i = 0; i < ht.size(); i++) {
        loc[ht[i]].Push_back(i);
    }
    vector<int>::iterator lo, hi;
    int x, y, type;
    for(int i = 0; i < q; i++) {
        cin >> type;
        if(type == 1) {
            cin >> x >> y;
            buf.Push_back(make_pair(x,y));
        }
        else if(type == 2) {
            cin >> x;
            long long ans(0);
            for(auto arg: buf) {
                hi = upper_bound(loc[arg.first].begin(), loc[arg.first].end(), nd[x]);
                lo = lower_bound(loc[arg.first].begin(), loc[arg.first].end(), st[x]);
                ans += arg.second * (hi - lo);
            }
            cout << tc[x] + ans/2 << '\n';
        }
        else assert(0);

        if(i % k == 0) bal();
    }
}
5
kaushal agrawal

Il existe de nombreux cas. Ce sont les quelques problèmes qui peuvent être résolus dans la complexité root (n) [mieux peut être possible aussi].

  • Trouvez si un nombre est premier ou non.
  • Algorithme de Grover: permet une recherche (dans un contexte quantique) sur une entrée non triée en temps proportionnel à la racine carrée de la taille de l'entrée . lien
  • Factorisation du nombre.

Vous rencontrerez de nombreux problèmes qui nécessiteront l'utilisation de l'algorithme de complexité sqrt(n).

En réponse à la deuxième partie:

la complexité sqrt (n) signifie if the input size to your algorithm is n then there approximately sqrt(n) basic operations ( like **comparison** in case of sorting). Then we can say that the algorithm has sqrt(n) time complexity.

Analysons le 3ème problème et ce sera clair.

let's n= positive integer. Now there exists 2 positive integer x and y such that
     x*y=n;
     Now we know that whatever be the value of x and y one of them will be less than sqrt(n). As if both are greater than sqrt(n) 
  x>sqrt(n) y>sqrt(n) then x*y>sqrt(n)*sqrt(n) => n>n--->contradiction.

Donc, si nous vérifions 2 à sqrt (n) alors nous aurons tous les facteurs considérés (1 et n sont des facteurs triviaux).

Extrait de code:

   int n;
   cin>>n;
   print 1,n;
   for(int i=2;i<=sqrt(n);i++) // or for(int i=2;i*i<=n;i++)
     if((n%i)==0)
       cout<<i<<" ";

Remarque: Vous pourriez penser qu'en ne tenant pas compte du doublon, nous pouvons également obtenir le comportement ci-dessus en bouclant de 1 à n. Oui c'est possible mais qui veut exécuter un programme qui peut s'exécuter en O(sqrt(n)) en O (n) .. Nous recherchons toujours le meilleur.

Parcourez le livre de Cormen Introduction aux algorithmes .

Je vous demanderai également de lire la question et les réponses de stackoverflow suivantes, ils dissiperont tous les doutes à coup sûr :)

  1. Existe-t-il des algorithmes O(1/n)?
  2. Explication en anglais simple Big-O
  3. Lequel est le meilleur?
  4. Comment calculez-vous la complexité du big-O?
4
user2736738

Test de primalité

const isPrime = n => {
    for(let i = 2; i <= Math.sqrt(n); i++) {
        if(n % i === 0) return false;
    }
    return true;
};

Complexité

O (N ^ 1/2) Parce que, pour une valeur donnée de n, il suffit de trouver si son divisible par des nombres de 2 à sa racine.

0