一. 定義
1.移動平均值是什么?
(1)移動平均值,是一種統計指標,用於觀測一組隨時間變化的量。
(2)M-移動平均值,是最后 M 個數的移動平均值。一定要注意,這邊算出的平均值是一組數,而不是一個數。
2. 移動平均值怎么算?
首先我們給出一組數據,data = ( 1, 2, 3, 4, 5, 6 ),現在我們需要計算 M = 3 時的移動平均值。那么第一個移動平均值就是(1, 2, 3)的算術平均值,第二個移動平均值就是(2, 3, 4)的算術平均值,其余同理。
3. 移動平均值的作用是什么?
給定一組數據,這組數據應該是隨時間變化記錄得到的,它可以是某商品一年的銷量,也可以是你一年考試的成績。通過計算這組數據的移動平均值,我們可以簡單預測未來的表現。
二. 代碼實現
1.函數代碼
1 vector<double> moving_average(const vector<int>& data, int m) { 2 vector<double> m_averages; 3 4 for (int i = m - 1; i < data.size(); ++i) { 5 double current_sum = 0.0; 6 7 for (int j = i - m + 1; j <= i; ++j) { 8 current_sum += data[j]; 9 } 10 double avg = current_sum / m; 11 m_averages.push_back(avg); 12 } 13 14 return m_averages; 15 }
2.算法分析
我們來分析一下這段代碼。首先,函數傳入一個需要處理的數據列,並且需要一個 M ,來告訴我們將會處理幾個數的平均值。前面提到,移動平均值實際上是一組數,所以函數也應該返回一組數:這里返回一個 vector。
外層循環控制整體范圍,我們希望它的下標不要超過數據的數量。每次計算 M 個數的平均值,所以應該從 M - 1 開始(如果從 M 位置開始,那么最后一個數字將不能被計算到,也就是說,返回的平均值里面,會丟掉最后一次的計算)。
內層循環控制小的范圍,每次只累加固定 M 個數的部分和。在每次計算完成后,我們將當前的平均值加入到平均值組中。
不難看出,下標的開始與結束,非常復雜,不容易理解,並且算法的時間復雜度是 O(n^2),這個復雜度對於小型數據組可以接受,但是處理大型數據組的時候,難免顯得力不從心。
當一件事情的解決方式並不優雅的時候,一般來說都會出錯,那么我們來優化算法。
3. 優化
通過上述分析,我們得知,很多功夫都花費在計算部分和上,而這個部分和很大一部分是不變的,每次變化的,只是第一個數據和最后一個數據。立即推,我們對於重復部分不進行重復計算。優化后的代碼如下:
1 vector<double> moving_average(const vector<int>& data, int m) { 2 vector<double> m_averages; 3 double current_sum = 0.0; 4 5 for (int i = 0; i < m - 1; ++i) { 6 current_sum += data[i]; 7 } 8 9 for (int i = m - 1; i < data.size(); ++i) { 10 current_sum += data[i]; 11 double avg = current_sum / m; 12 m_averages.push_back(avg); 13 current_sum -= data[i - m + 1]; 14 } 15 16 return m_averages; 17 }
現在再來分析一下優化后的新函數。
第一個循環計算 M - 1 個部分和,這個部分和其實是缺失的,因為少了最后一個數據,但是它開啟了后續的步驟。在第二個循環中,我們先把缺失的數據加進去,然后計算平均值,接着將平均值放到平均值組里,再將最前面的一個元素,從部分和中減去,這樣就為下一次循環做好了准備。新函數的時間復雜度是 O(n),應對大型數據的時候,效果也很不錯。
