《剑指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  
class Heap 
{  
private:  
	vector min;  
	vector max;  
public:  
	void Insert(T num)  
	{  
		if(((min.size()+max.size())&1)==0)//偶数插入左边最大堆;
		{  
			if(max.size()>0 && num());  
				num=max[0];  
				pop_heap(max.begin(),max.end(),less());  
				max.pop_back();  
			}  
			min.push_back(num);  
			push_heap(min.begin(),min.end(),greater());  


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


		}  
	}  
	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;  
	}  
};  





你可能感兴趣的:(《剑指Offer》,剑指offer)