Какое влияние оказывает этот оператор if на временную сложность этих вложенных циклов?

#c #performance #time-complexity

#c #Производительность #временная сложность

Вопрос:

У меня здесь есть 2 программы, которые отчаянно нуждаются в оценке эффективности с использованием нотации big o. Что касается первого, я не уверен в эффекте оператора if. У этого есть вложенный цикл, и они обычно имеют эффективность O (n ^ 2), но я не могу быть уверен. Другой также имеет вложенный цикл, но с командой break; .

 /* modifies a so that all the even numbers appear before
all of the odd numbers. there are multiple possible results
-the order of the even numbers does not matter
-the order of the odd numbers does not matter
*/

void even_first(int a[], int len) {
  int temp = 0;
  for (int i = 0; i < len;   i) {
    if (a[i] % 2 == 0) {
      temp = a[i];
      for (int j = i; j > 0; --j) {
        a[j] = a[j - 1];
      }
      a[0] = temp;
    }
  }
}
////////////////////////////////////////////////////////////////////
/*  finds the single integer in the inclusive range 1...[len   1] that does 
not appear in a. Because the elements of a are unique and all in the range
       1...(len   1), there must be a single integer in that range
that is "missing" from a */
int missing(int a[], int len) { 
  for (int j = 1; j <= len   1;   j) {
    bool found = false;
    for (int i = 0; i < len;   i) {
      if (a[i] == j) {
        found = true;
        break;
      }
    }
    if (!found) {
      return j;
    }
  }
  return -1;
}
 

Комментарии:

1. Если вы действительно заботитесь об эффективности, тогда разберите оптимизированный код и поищите там. «Big O» в основном устарел, поскольку количество итераций не обязательно равно производительности. В настоящее время алгоритмы линейного поиска с использованием грубой силы во многих случаях превосходят бинарный поиск.

2. Простой способ выяснить это: протестируйте этот код при различных условиях N , например, начиная с 1 и повышаясь в 10 раз с каждым шагом. Вы очень быстро обнаружите характеристики производительности.

3. @Lundin Это, вероятно, домашнее задание / упражнение для теоретического курса информатики или что-то в этом роде. Это бесполезно.

4. @tadman Однако это не поможет вам получить доказательства…

5. @user202729 Не доказательство, как в «какое обозначение O это имеет», а эмпирические данные о том, как оно работает на реальном оборудовании. Обычно они коррелируют таким образом, что вы можете подогнать кривую к своим точкам, и она будет явно напоминать одну из основных функций O .

Ответ №1:

Первый имеет временную сложность в наихудшем случае O (n 2) (все четные числа или, по крайней мере, количество четных чисел, масштабируемых по отношению к n, например, случайные целые числа). Эффект оператора if заключается в том, что он улучшает временную сложность в лучшем случае (все нечетные числа, поэтому оператор if всегда false и внутренний цикл никогда не выполняется), что равно O (n) . Сам внутренний цикл равен O (n), потому что число итераций все еще увеличивается на n .

Второй — O (n 2) в худшем случае, потому что он масштабируется с длиной массива и самим пропущенным числом. Если бы отсутствующий элемент всегда был равен 1, это было бы O (n), потому что внешний цикл выполнялся бы только один раз. Предполагая, что отсутствующий элемент является случайным, это O (n 2), потому что тогда оба цикла будут масштабироваться по длине.

Комментарии:

1. Вы уверены? Разве O(n * log n) не for(i=0; i<n; i ) for(j=i; j<n; j ) является O(n * log n) ?

2. @klutt Внутренний цикл выполняется время от 1 n времени. Среднее число итераций, таким образом (1 n) / 2 , равно или n/2 0.5 . Это масштабируется с n , не log(n) .

3. Но хммм, ну, когда я думаю об этом, это выглядит как пузырьковая сортировка

4. @klutt действительно, и сортировка пузырьков имеет временную сложность O (n ^ 2)

Ответ №2:

Эффект оператора if в первой функции заключается в том, что второе n в n ^ 2 применяется только для четных чисел. Эффект более короткого цикла заключается в том, что он как бы делит второе n пополам. Если вы посмотрите на худший случай, все четы, вы получите n * (n 1) / 2, из-за этого более короткого цикла, который доходит до O (n ^ 2). Средний случай по-прежнему будет O (n ^ 2), хотя и с коэффициентом, близким к 1/4.

Редактировать: после прочтения некоторых комментариев я думаю, что мне следует добавить наилучшие, наихудшие и средние сценарии:

В лучшем случае нет четных, O (n)

В худшем случае все четные, O (n * (n 1) / 2) = O (n ^ 2)

Средний случай, половина четных O (n * (n 1) / 4) (средний результат для половины четных соответствует половине посещений в худшем случае) = O (n ^ 2)

Разрыв во второй функции сокращает второе n в n ^ 2 примерно до половины, так что, опять же, все еще O (n ^ 2).

Если вы пытаетесь добиться большего, чем O (n ^ 2), я почти уверен, что вы можете решить обе проблемы с помощью O (n), если просто создадите дополнительный массив, но вы только спросили, каков эффект оператора if и какова нотация big O.

Комментарии:

1. Вы правы в отношении оператора if в первом, но внутренний цикл не проходит через весь массив

2. Он переходит из текущего местоположения i в 1, так что в среднем половина — это то, что я рассчитал. Вот почему я сказал, что в среднем коэффициент составляет 1/4, примерно в половине случаев он переходит во второй цикл и примерно на полпути в среднем по списку на протяжении всего цикла. Может быть, это не точно, но я не вижу, как это умножает эффективность на что-либо, кроме скаляра, в среднем случае, что означает, что все равно O (n ^ 2)

3. Циклы в форме for(i=0; i<n; i ) for(j=i; j<n; j ) — O (n * log n), если я не ошибаюсь

4. Ну, я не уверен. Я оставляю это. 🙂

5. Хорошо, рассматривая наихудший сценарий, он лишь немного хуже половины n ^ 2 (то есть n (n 1) / 2), поэтому 1/4, вероятно, неверно. Несмотря на это, я думал, что весь смысл был отчасти спорным, поскольку обе проблемы должны быть решаемы с помощью O (n), если вам разрешено использовать больше памяти.