Когда алгоритм имеет квадратную корневую (n) временную сложность?

Может ли кто-нибудь дать мне пример алгоритма с квадратной корневой (n) временной сложностью. Что означает сложность квадратного корня?

Ответ 1

  • Сложность квадратного корня времени означает, что алгоритм требует O(N^(1/2)) оценок, где размер ввода равен N.
  • В качестве примера для алгоритма, который занимает O(sqrt(n)) время, алгоритм Гровера - тот, который занимает столько времени. Алгоритм Гровера - это квантовый алгоритм для поиска в несортированной базе данных из n записей за O(sqrt(n)).
  • Давайте возьмем пример, чтобы понять, как мы можем достичь сложности O(sqrt(N)) время выполнения, учитывая проблему. Это будет сложно, но интересно понять. (Следующий пример в контексте ответа на этот вопрос взят из байта конкурса кодирования: уловка квадратного корня, очень интересная задача и интересная уловка для достижения сложности O(sqrt(n)))

    • Для заданного A, содержащего массив из n элементов, реализуйте структуру данных для обновлений точек и запросов суммы диапазонов.

      • обновление (i, x) → A [i]: = x (запрос обновления точки)
      • query (lo, hi) → возвращает A [lo] + A [lo + 1] +.. + A [hi]. (Range Sum Query)
    • Наивное решение использует массив. Требуется O(1) время для обновления (доступ к индексу массива) и O(hi - lo) = O(n) для суммы диапазона (итерация от начального индекса до конечного индекса и сложение).

    • Более эффективное решение разбивает массив на длину k срезов и сохраняет суммы срезов в массиве S.
    • Обновление занимает постоянное время, потому что мы должны обновить значение для A и значение для соответствующего S. В обновлении (6, 5) мы должны изменить A [6] на 5, что приводит к изменению значения S 1 на держать S в курсе. Updating element
    • Запрос диапазона суммы интересен. Элементы первого и последнего среза (частично содержащиеся в запрашиваемом диапазоне) должны просматриваться один за другим, но для срезов, полностью содержащихся в нашем диапазоне, мы можем напрямую использовать значения в S и получить повышение производительности. Range-sum query
    • В запросе (2, 14) получаем,

 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;
  • Код для обновления и запроса:

  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
  • Давайте теперь определимся со сложностью.
  • Каждый запрос занимает в среднем
    • Секция 1 занимает к /2 времени в среднем. (вы можете повторить максимум k/2)
    • Раздел 2 занимает в среднем н/к времени, в основном количество срезов
    • Раздел 3 занимает к /2 времени в среднем. (вы можете повторить максимум k/2)
    • Таким образом, в целом мы получаем k/2 + n/k + k/2 = k + n/k время.
  • И это минимизируется для k = sqrt (n). sqrt (n) + n/sqrt (n) = 2 * sqrt (n)
  • Таким образом, мы получаем запрос сложности времени O(sqrt(n)).

Ответ 2

Есть много случаев. Вот несколько проблем, которые могут быть решены с помощью root (n) сложности [лучше также возможно].

  • Найдите, является ли число простым или нет.
  • Алгоритм Гровер: позволяет искать (в квантовом контексте) несортированный вход во времени, пропорциональный квадратному корню размера ввода. link
  • Факторизация числа.

Есть много проблем, с которыми вам придется столкнуться, что потребует использования алгоритма сложности sqrt(n).

В качестве ответа на вторую часть:

Значение sqrt (n) означает 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.

Проанализируем третью проблему, и это будет ясно.

let 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.

Итак, если мы проверим 2 на sqrt (n), то мы будем учитывать все факторы (1 и n - тривиальные факторы).

Фрагмент кода:

   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<<" ";

Примечание.. Вы можете подумать, что, не учитывая дубликат, мы также можем достичь вышеуказанного поведения, перейдя от 1 до n. Да, возможно, но кто хочет запустить программу, которая может работать в O (sqrt (n)) в O (n). Мы всегда ищем лучший.

Пройдите в книгу Cormen Введение в алгоритмы.

Я также попрошу вас прочитать следующий вопрос и ответы из stackoverflow, они наверняка очистят все сомнения:)

Ответ 3

Основные номера

Как упоминалось в некоторых других ответах, некоторые основные вещи, связанные с простыми числами, принимают O (sqrt (n)) время:

  • Найти число делителей
  • Найти сумму делителей
  • Найти Euler totient

Ниже я упоминаю два продвинутых алгоритма, которые также несут член sqrt (n) в их сложности.

Алгоритм MO

попробуйте эту проблему: Мощный массив

Мое решение:

#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;
}

Буферизация запросов

попробуйте эту проблему: Запросы на дереве

Мое решение:

#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();
    }
}

Ответ 4

Тест на первичность

Решение в JavaScript

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

сложность

O (N ^ 1/2) Поскольку для данного значения n вам нужно только найти, делится ли его на числа от 2 до его корня.