鉴于整数是从数据流中读取的。查找以有效方式读取的元素的中位数。为简单起见,假设没有重复项。例如,让我们考虑流 5, 15, 1, 3 …
After reading 1st element of stream - 5 -> median - 5
After reading 2nd element of stream - 5, 15 -> median - 10
After reading 3rd element of stream - 5, 15, 1 -> median - 5
After reading 4th element of stream - 5, 15, 1, 3 -> median - 4, so on...
明确地说,当输入大小为奇数时,我们取已排序数据的中间元素。如果输入大小是偶数,我们选择排序流中中间两个元素的平均值。
请注意,输出是到目前为止从流中读取的整数的有效中位数。这样的算法称为在线算法。可以保证输出的我处理第i个元素之后-elements任何算法,据说是在线算法。让我们讨论上述问题的三种解决方案。
方法一:插入排序
如果我们可以对出现的数据进行排序,我们就可以轻松定位中值元素。插入排序就是这样一种在线算法,可以对迄今为止出现的数据进行排序。在任何排序实例中,比如在对第 i个元素进行排序之后,数组的前 i 个元素被排序。插入排序不依赖于未来的数据来对数据输入进行排序。换句话说,插入排序在插入下一个元素时考虑到目前为止排序的数据。这是插入排序的关键部分,使其成为在线算法。
但是,插入排序需要 O(n 2 ) 时间来对n 个元素进行排序。也许我们可以在插入排序上使用二分搜索来在 O(log n) 时间内找到下一个元素的位置。然而,我们不能在 O(log n) 时间内进行数据移动。无论实现的效率如何,在插入排序的情况下都需要多项式时间。
有兴趣的读者可以尝试实现方法一。
方法 2:增强自平衡二叉搜索树(AVL、RB 等…)
在 BST 的每个节点上,维护以该节点为根的子树中元素的数量。我们可以使用一个节点作为简单二叉树的根,其左孩子是元素小于根的自平衡BST,右孩子是元素大于根的自平衡BST。根元素始终保持有效中位数。
如果左右子树包含相同数量的元素,则根节点保存左右子树根数据的平均值。否则,根包含与具有更多元素的子树的根相同的数据。处理传入元素后,左右子树 (BST) 相差最大 1。
自平衡 BST 在管理 BST 的平衡因子方面成本很高。但是,它们提供了我们不需要的排序数据。我们只需要中位数。下一个方法利用堆来跟踪中位数。
方法 3:堆
与上面方法 2 中的平衡 BST 类似,我们可以使用左侧的最大堆来表示小于有效中值的元素,使用右侧的最小堆来表示大于有效中值的元素。
处理传入元素后,堆中元素的数量相差最大 1 个元素。当两个堆包含相同数量的元素时,我们选择堆根数据的平均值作为有效中值。当堆不平衡时,我们从包含更多元素的堆的根中选择有效中位数。
下面给出了上述方法的实现。有关构建这些堆的算法,请阅读突出显示的代码。
#include
using namespace std;
// Heap capacity
#define MAX_HEAP_SIZE (128)
#define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0])
//// Utility functions
// exchange a and b
inline
void Exch(int &a, int &b)
{
int aux = a;
a = b;
b = aux;
}
// Greater and Smaller are used as comparators
bool Greater(int a, int b)
{
return a > b;
}
bool Smaller(int a, int b)
{
return a < b;
}
int Average(int a, int b)
{
return (a + b) / 2;
}
// Signum function
// = 0 if a == b - heaps are balanced
// = -1 if a < b - left contains less elements than right
// = 1 if a > b - left contains more elements than right
int Signum(int a, int b)
{
if( a == b )
return 0;
return a < b ? -1 : 1;
}
// Heap implementation
// The functionality is embedded into
// Heap abstract class to avoid code duplication
class Heap
{
public:
// Initializes heap array and comparator required
// in heapification
Heap(int *b, bool (*c)(int, int)) : A(b), comp(c)
{
heapSize = -1;
}
// Frees up dynamic memory
virtual ~Heap()
{
if( A )
{
delete[] A;
}
}
// We need only these four interfaces of Heap ADT
virtual bool Insert(int e) = 0;
virtual int GetTop() = 0;
virtual int ExtractTop() = 0;
virtual int GetCount() = 0;
protected:
// We are also using location 0 of array
int left(int i)
{
return 2 * i + 1;
}
int right(int i)
{
return 2 * (i + 1);
}
int parent(int i)
{
if( i <= 0 )
{
return -1;
}
return (i - 1)/2;
}
// Heap array
int *A;
// Comparator
bool (*comp)(int, int);
// Heap size
int heapSize;
// Returns top element of heap data structure
int top(void)
{
int max = -1;
if( heapSize >= 0 )
{
max = A[0];
}
return max;
}
// Returns number of elements in heap
int count()
{
return heapSize + 1;
}
// Heapification
// Note that, for the current median tracing problem
// we need to heapify only towards root, always
void heapify(int i)
{
int p = parent(i);
// comp - differentiate MaxHeap and MinHeap
// percolates up
if( p >= 0 && comp(A[i], A[p]) )
{
Exch(A[i], A[p]);
heapify(p);
}
}
// Deletes root of heap
int deleteTop()
{
int del = -1;
if( heapSize > -1)
{
del = A[0];
Exch(A[0], A[heapSize]);
heapSize--;
heapify(parent(heapSize+1));
}
return del;
}
// Helper to insert key into Heap
bool insertHelper(int key)
{
bool ret = false;
if( heapSize < MAX_HEAP_SIZE )
{
ret = true;
heapSize++;
A[heapSize] = key;
heapify(heapSize);
}
return ret;
}
};
// Specilization of Heap to define MaxHeap
class MaxHeap : public Heap
{
private:
public:
MaxHeap() : Heap(new int[MAX_HEAP_SIZE], &Greater) { }
~MaxHeap() { }
// Wrapper to return root of Max Heap
int GetTop()
{
return top();
}
// Wrapper to delete and return root of Max Heap
int ExtractTop()
{
return deleteTop();
}
// Wrapper to return # elements of Max Heap
int GetCount()
{
return count();
}
// Wrapper to insert into Max Heap
bool Insert(int key)
{
return insertHelper(key);
}
};
// Specilization of Heap to define MinHeap
class MinHeap : public Heap
{
private:
public:
MinHeap() : Heap(new int[MAX_HEAP_SIZE], &Smaller) { }
~MinHeap() { }
// Wrapper to return root of Min Heap
int GetTop()
{
return top();
}
// Wrapper to delete and return root of Min Heap
int ExtractTop()
{
return deleteTop();
}
// Wrapper to return # elements of Min Heap
int GetCount()
{
return count();
}
// Wrapper to insert into Min Heap
bool Insert(int key)
{
return insertHelper(key);
}
};
// Function implementing algorithm to find median so far.
int getMedian(int e, int &m, Heap &l, Heap &r)
{
// Are heaps balanced? If yes, sig will be 0
int sig = Signum(l.GetCount(), r.GetCount());
switch(sig)
{
case 1: // There are more elements in left (max) heap
if( e < m ) // current element fits in left (max) heap
{
// Remore top element from left heap and
// insert into right heap
r.Insert(l.ExtractTop());
// current element fits in left (max) heap
l.Insert(e);
}
else
{
// current element fits in right (min) heap
r.Insert(e);
}
// Both heaps are balanced
m = Average(l.GetTop(), r.GetTop());
break;
case 0: // The left and right heaps contain same number of elements
if( e < m ) // current element fits in left (max) heap
{
l.Insert(e);
m = l.GetTop();
}
else
{
// current element fits in right (min) heap
r.Insert(e);
m = r.GetTop();
}
break;
case -1: // There are more elements in right (min) heap
if( e < m ) // current element fits in left (max) heap
{
l.Insert(e);
}
else
{
// Remove top element from right heap and
// insert into left heap
l.Insert(r.ExtractTop());
// current element fits in right (min) heap
r.Insert(e);
}
// Both heaps are balanced
m = Average(l.GetTop(), r.GetTop());
break;
}
// No need to return, m already updated
return m;
}
void printMedian(int A[], int size)
{
int m = 0; // effective median
Heap *left = new MaxHeap();
Heap *right = new MinHeap();
for(int i = 0; i < size; i++)
{
m = getMedian(A[i], m, *left, *right);
cout << m << endl;
}
// C++ more flexible, ensure no leaks
delete left;
delete right;
}
// Driver code
int main()
{
int A[] = {5, 15, 1, 3, 2, 8, 7, 9, 10, 6, 11, 4};
int size = ARRAY_SIZE(A);
// In lieu of A, we can also use data read from a stream
printMedian(A, size);
return 0;
}
时间复杂度:如果我们省略读取流的方式,中值查找的复杂度为O(N log N) ,因为我们需要读取流,并且由于堆插入/删除。
乍一看,上面的代码可能看起来很复杂。如果你仔细阅读代码,它是一个简单的算法。
使用 STL 的运行整数流的中位数
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。