《劍指offer》:[64]數據流中的中位數

題目:如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從數據流中讀出偶數個數值,那麼中位數就是所有數據排序後中間兩個數的平均值。
例如:1,2,3,4,5的中位數爲:3。1,2,3,4的中位數爲:(2+3)/2=3。

方案一:採用Partition來解決。在[29]中我們講過,快速查找中的Partition函數是十分重要,是一個比較常用的算法。所以這裏我們採用partion函數來解決。從字符流裏讀字符,插入到一個無需的數組中的複雜度爲O(1),查找中位數的時間複雜度爲O(N)。
方案二:採用排序數組。採用插入排序,讀取一個字符進行有序的插入操作。這樣排序的時間複雜度爲O(N*N),但是取得中位數的時間複雜度爲O(1)。
方案三:考慮到方案二數組的插入需要移動數據,所以這裏我們可以採用鏈表來解決,這樣我們需要定義兩個額外的指針指向中間結點。插入數據排序的時間複雜度爲O(N),但是得到中位數的時間效率爲:O(1)。
方案四:爲了提高方案三中插入數據的效率,我們採用二叉排序樹,此時的時間複雜度爲O(logN),但是當二叉搜索樹看起來不平衡看起來像個鏈表的時候,其插入的時間複雜度任然爲O(logN)。爲了得到中位數,我們可以在結點中添加一個表示結點數目的字段,有了這個字段我們可以在平均O(logN)時間得到中位數,但是最差情況任然需要O(N)的時間。
方案五:爲了避免方案四中極度不平衡的情況,我們採用平衡二叉樹(AVL樹)。但是AVL樹中的平衡因子是左右子樹的高度差,我們可以將該平衡因子修改爲左右子樹的結點數目的差。有了這個改動,可以用O(logN)時間向該樹中添加一個新的結點。同時用O(1)的時間來得到中位數的值。
方案六:採取大頂堆和小頂堆。雖然AVL樹的效率較高,但是大部分編程語言函數庫裏沒有實現這個數據結構,需要對平衡因子修改的同時還要在短時間內寫出其實現代碼有點兒困難。這裏我們用兩個容器來實現和方案五一樣的效果。方法如下:
(1)用兩個堆,一個大頂堆,一個小頂堆。將數據分割成兩部分,左邊的大頂堆餓數據都小於右邊的小頂堆的數據。
(2)先往小頂堆裏面存數,並保持: 0 <= 小頂堆的size()-大頂堆的size() <= 1
(3)保持兩邊數量幾乎一致就需要在插入的時候進行比較、調整。
(4)返回中位數的時候,如果小頂堆和大頂堆size()相同,就返回他們堆頂元素的平均值;否則返回小頂堆的堆頂元素。
這種方法插入時間複雜度是O(log n),返回中位數的時間複雜度是O(1)
這樣我們可以在O(logN)的時間複雜度裏完成數據的插入,在O(1)的時間複雜度裏完成中位數的提取操作。所以綜合其時間複雜度爲O(logN)。
核心類實現代碼如下:
template<typename T>  
class Heap 
{  
private:  
	vector<T> min;  
	vector<T> max;  
public:  
	void Insert(T num)  
	{  
		if(((min.size()+max.size())&1)==0)//偶數插入左邊最大堆;
		{  
			if(max.size()>0 && num<max[0])  
			{  
				max.push_back(num);  
				push_heap(max.begin(),max.end(),less<T>());  
				num=max[0];  
				pop_heap(max.begin(),max.end(),less<T>());  
				max.pop_back();  
			}  
			min.push_back(num);  
			push_heap(min.begin(),min.end(),greater<T>());  


		}  
		else  //奇數插入右邊最小堆;
		{  
			if(min.size()>0&&num>min[0])  
			{  
				min.push_back(num);  
				push_heap(min.begin(),min.end(),greater<T>());  
				num=min[0];  
				pop_heap(min.begin(),min.end(),greater<T>());  
				min.pop_back();  
			}  
			max.push_back(num);  
			push_heap(max.begin(),max.end(),less<T>());  


		}  
	}  
	T get_median()  
	{  
		int size=min.size()+max.size();  
		if(size==0)  
			throw exception("no numbers are available");  
		T median=0;  
		if((size&1)!=0)  //如果是奇數返回最小堆的第一個元素;
		{  
			median=min[0];  
		}  
		else  //如果是偶數則返回中間兩個數的平均值;
		{  
			median=(max[0]+min[0])/2;  
		}  
		return median;  
	}  
};  





發佈了250 篇原創文章 · 獲贊 191 · 訪問量 57萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章