眾數問題分析

net_assassin發表於2013-05-29

問題描述
•給定一個陣列,找出其中出現次數最多的那個元素(即眾數)。


核心思想
•普遍的解決思路。
•如果我們將所有元素的出現次數進行統計,並從中找出次數中的最大值,那麼,這個最大值對應的元素就是眾數。
•從這一思想出發,我總結出以下兩種演算法:
–演算法1:利用排序演算法統計
–演算法2:利用陣列或雜湊表統計


演算法1
•演算法思路:首先將陣列元素按照大小排序,然後按順序掃描一遍陣列,掃描的同時進行統計。這樣,通過一次排序和一遍掃描,我們就能夠找到眾數。
•關鍵步驟:排序,掃描統計


演算法代價分析
1、時間代價
•由於掃描的時間代價是Θ(n)的,所以演算法的總時間代價主要依賴於排序演算法的代價。
•如果我們選取Θ(nlgn)的排序演算法,最終的時間代價是Θ(nlgn)+Θ(n)= Θ(nlgn)
•Θ(nlgn)是基於比較的排序不可逾越的時間下界,也是該演算法能夠達到的最低代價。


2、空間代價
•掃描統計時,我們只需要三個輔助變數:一個用於計數,一個用於記錄當前出現次數最多的元素的出現次數,一個用於記錄當前出現次數最多的元素。
•所以,如果排序演算法的輔助空間是O(1)的,那麼整個演算法的輔助空間代價就是O(1)的。


改進想法
•思想:改進排序演算法,使得排序演算法在排序的同時就統計出現次數,並記錄次數最多的元素,以及刪除所有重複的元素。這樣,不僅可以減小排序的規模,並且省去了最後掃描的步驟,在一定程度上優化了該演算法。
•然而,雖然改進後的演算法比原始演算法效率高,但是由於其本質仍然是基於比較的排序演算法,所以時間代價還是Θ(nlgn)的,並沒有在數量級上取得突破。


演算法2
•演算法思路:直接統計各元素出現的次數,用某一種線性資料結構儲存統計結果。
•樸素的實現方法:用一個輔助陣列儲存統計結果,統計時用陣列下標對應相應元素。


演算法代價分析
1、時間代價
•用下標對應元素,訪問時間O(1)。順序掃描一遍原陣列,就可以得到統計結果。
•總的時間代價Θ(n)。
演算法代價分析
2、空間代價
•依賴於原陣列中元素的範圍。
•假設我們知道元素集中分佈在寬度為m的區間內,那麼我們就可以開闢大小為m的陣列用於統計。這時,最後使用的輔助空間便是O(m)的。
•然而我們一般並不能確切地知道陣列中數的範圍,或者陣列中元素的分佈非常稀疏(即陣列中有相當比例的空間儲存的統計數為0)。這時,下標連續分佈的、上下界明確的陣列就難以承擔其職責了。
•因此,我們必須改進資料結構,以更加廣泛地適應演算法的需求。


改進想法
•核心思想:採用雜湊表。
•雜湊表的優點:儲存靈活,檢索效率高。
•因此,使用雜湊表能夠有效地替代陣列,實現演算法的功能。
•雜湊表的缺點:空間利用率低。
•據實驗,當雜湊表的負載因子小於0.5時,雜湊表在大部分情況下的檢索長度小於2。但是,如果超過0.5,雜湊表的效能就會急劇下降。
•因此,如果原陣列元素分佈稠密,使用雜湊表的空間效率就要低於使用陣列。
改進後演算法的代價分析
•前提假設:雜湊表的效能良好(負載因子小於0.5)


1、時間代價
•同樣是掃描一遍,然而每一次的平均探查長度大於1。因此,時間效率要比陣列低。
•但是,由於雜湊表效能良好,平均探查長度小於2,所以時間代價依然是O(n)的。
改進後演算法的代價分析
2、空間代價
•如果原陣列中共有m個不同的元素,那麼,由於負載因子小於0.5,因此雜湊表的大小至少是2m。空間效率低於元素稠密時的陣列,但是可能會遠高於資料稀疏時的陣列。


演算法比較
•這份報告中主要介紹了兩種演算法思想。他們各有利弊,對比如下:
1、基於排序的演算法
•時間代價:Θ(nlgn)
•空間代價:O(1)
2、直接統計的演算法
•時間代價:Θ(n)
•空間代價:Ω(m)
•從以上對比,我們可以看出:演算法2是利用空間換時間,雖然使用了大量的輔助空間,但是時間代價要遠低於演算法1。
•我們的演算法設計,就是一個權衡利弊的過程。很多時候,時間和空間不可兼得,那麼,我們就要根據使用者的需求選擇合適的演算法,以實現時間更快或者空間更省。
•而對於演算法2,我們同樣面臨著選擇:使用陣列還是使用雜湊表。這一選擇基於陣列元素的分佈:如果陣列元素分佈稠密,使用陣列自然是又快又省;可是,如果分佈稀疏或是不確定,雜湊表的靈活性就派上了用場。
•因此,除了使用者需求,我們面對的資料本身也左右著我們的選擇。
•在實際應用中,眾數問題一般用於對大量資料的統計。
•下面舉一些例項,來探討實際問題中眾數問題的解決方案。
例項1
•選票統計
•data*+=,“張三”,“李四”,“李四”,“張三”,“李四”,“李四”,“棄權”,“李四”,“張三”,“李四”,“張三”,“棄權”,“張三”……-
•資料規模大,資料範圍有限,顯然用陣列更為方便。
例項2
•在一些科學實驗中,我們要對實驗資料進行統計和分析,這時眾數只是我們研究的一部分,還有其他方面(例如分佈情況)需要研究。這時,我們希望一次運算得到的資訊量更多。
•這時我們也需要用陣列。
010203040506070809010~2020~3030~4040~50資料:23.1,14.6,39.7,32.0,12.2……
範圍
10~20
20~30
30~40
40~50
分佈數
20
28
90
21
例項3
•假設1:有海量的資料需要統計,如果要排序只能通過外排序。
•假設2:硬碟空間足夠用(操作過程中不必考慮空間開銷)
•這時有一個使用者需求,要得到這些資料的眾數。
•如果用排序,我們必須考慮外排序可怕的時間代價。
•如果用陣列或雜湊表,雖然會佔用額外的硬碟儲存空間。但是,如果這些資料需要經常增刪,並且需要經常呼叫取眾數操作,那麼,儲存這些統計結果將帶來極大的方便。
總結
•解決眾數問題主要有兩種演算法思想,其中演算法2可以使用兩種不同的輔助資料結構。這三者本身都是解決眾數問題的很好的辦法,他們之間並無優劣。具體實現時,我們選擇哪種方法,要依賴於我們對時空的權衡,以及資料本身的性質。
•眾數問題雖然很簡單,但是從中折射出的關於時空權衡的考慮,是演算法設計時會普遍遇到的問題,值得大家討論和思考。

//眾數問題
/*
問題描述:
給定含有n個元素的多重集合S,每個元素在S中出現的次數成為該元素的重數。多重
集S中重數最大的元素稱為眾數。
例如, S = {1,2,2,2,3,5}。
多重集s的眾數是2,其重數為3。
程式設計任務:
對於給定的由n個自然陣列成的多重集S,程式設計計算S的眾數及其重數。
*/
#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <map>

using namespace std;

int main()
{
	ifstream input("input.txt",ios::in);
	ofstream output("output.txt",ios::out);

	if (!input)
	{
		cerr<<"inputfile could not be opened"<<endl;
		exit(1);
	}

	map<int,int> number_count;

	int number;
	while(input >>number)
	{
		++number_count[number];
	}

	map<int,int>::iterator map_it  = number_count.begin();

	map_it++;
	int key = map_it->first;
	int maxcount = map_it->second;
	while(map_it != number_count.end())
	{
		if (maxcount < map_it->second)
		{
			maxcount = map_it->second;
			key = map_it->first;
		}
		++map_it;
	}
	output<<key<<endl<<maxcount;
	return 0;
}


相關文章