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

塵虛緣_KY發表於2016-06-30
題目:如何得到一個資料流中的中位數?如果從資料流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從資料流中讀出偶數個數值,那麼中位數就是所有資料排序後中間兩個數的平均值。
例如: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;  
	}  
};  





相關文章