📜  中位数为整数流(运行整数)

📅  最后修改于: 2021-04-30 02:43:40             🧑  作者: Mango

假定从数据流中读取整数。查找所读取元素的中位数,以便高效地进行。为简单起见,假设没有重复项。例如,让我们考虑流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个元素之后可以保证i元素输出的任何算法都称为在线算法。让我们讨论上述问题的三种解决方案。

方法1:插入排序

如果我们可以对显示的数据进行排序,则可以轻松地找到中值元素。插入排序是一种在线算法,可以对到目前为止出现的数据进行排序。在任何排序实例中,例如,在对第i个元素进行排序之后,都会对数组的前i个元素进行排序。在此之前,插入排序不依赖于将来的数据来对输入的数据进行排序。换句话说,插入排序考虑到插入下一个元素时到目前为止已排序的数据。这是使其成为在线算法的插入排序的关键部分。

但是,插入排序需要O(n 2 )时间来排序n个元素。也许我们可以对插入排序使用二进制搜索来找到O(log n)时间中下一个元素的位置。但是,我们无法在O(log n)时间内进行数据移动。无论实现的效率如何,在插入排序的情况下都需要多项式时间。

有兴趣的读者可以尝试方法1的实现。

方法2:增强型自平衡二叉搜索树(AVL,RB等)

在BST的每个节点上,维护以该节点为根的子树中的元素数量。我们可以将节点用作简单二叉树的根,其左子节点是具有小于根的元素的自平衡BST,右子节点是具有大于根的元素的自平衡BST。根元素始终保持有效中位数

如果左右子树包含相同数量的元素,则根节点将保存左右子树根数据的平均值。否则,root包含与具有更多元素的子树的根相同的数据。处理输入元素后,左右子树(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的运行整数流的中位数