Эта задача имеет очень большое практическое применение: в статистической обработке данных, обработке временных рядов, в цифровой обработке сигналов применительно к цифровым отсчётам сигнала. Постановка задачи такая:
– вводится последовательность (вещественных) чисел …
– нужно просчитать, в итоге, среднее значение и дисперсию (или СКО: средне-квадратичное отклонение) числового ряда.
Напоминание для тех, кто не дружит с математикой:
– для последовательности N чисел a1, a2, … aN
– среднее значение вычисляем как M = 1 / N * SUM( i=1, N, ai )
– дисперсию вычисляем как D = 1 / N * SUM( i=1, N, ( ai – M ) ** 2 )
– СКО вычисляем как S = SQRT( D )
Фишка здесь в том, что для вычисления дисперсии прежде нужно вычислить среднее значение M. Но такое прямое вычисление потребует 2-х проходов по последовательности: сначала для вычисления M, а затем снова для вычисления D. Это непозволительная роскошь, если нужно делать вычисления для ряда в 10 000, 100 000 или 1 000 000 значений. Вычисления нужно произвести в один проход, без хранения всей последовательности: после ввода очередного числа все предыдущие теряются…
Подсказка: прежде программирования попробуйте преобразовать выражение для D так, чтобы входящее значение M вынести из-под операции суммирования.
Решение:
Прежде, чем писать код, займёмся преобразованием формулы для вычисления дисперсии (раскроем квадрат разности под суммой):
D = 1 / N * SUM( i=1, N, ( ai – M ) ** 2 ) =
= 1 / N * SUM( i=1, N, ai ** 2 ) – 2 * 1 / N * SUM( i=1, N, ai ** 2 ) * M + 1 / N * N * M ** 2 =
= 1 / N * SUM( i=1, N, ai ** 2 ) – 2 * M ** 2 + M ** 2 =
= 1 / N * SUM( i=1, N, ai ** 2 ) – M ** 2
Или, если выразить это словами: усреднённая (делённая на N) сумма квадратов чисел, минус квадрат их среднего. Теперь мы можем по ходу поступления чисел ряда накапливать их сумму (s1 – для последующего вычисления среднего) и сумму квадратов (s2 – для последующего вычисления дисперсии), а вычисление итоговых характеристик отложить до завершения поступления числового ряда.
А теперь спокойно пишем реализующий это код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <iostream> #include <sstream> using namespace std; int main() { setlocale(LC_ALL, "rus"); string e; int n; // счётчик чисел double d, s1, s2; while (true) { s1 = s2 = 0.0; n = 0; cout << "Вводите последовательность чисел: "; getline(cin, e); istringstream ist(e); while (ist >> d) { n++; s1 += d; s2 += d * d; } s1 /= n; s2 = s2 / n - s1 * s1; cout << "Введено чисел " << n << ", среднее = " << s1 << ", дисперсия = " << s2 << endl; } } |
Всего то! (код сильно упрощён: весь числовой ряд вводится в одной строке, заканчивающейся Enter, не включена обработка ошибочного ввода и др.).
А теперь – как это работает: