编程之美3.7——队列中取最大值操作问题

来源:互联网 发布:文华atr止损指标源码 编辑:程序博客网 时间:2024/06/03 02:25

问题:

假设有这样一个拥有3个操作的队列:

1. EnQueue(v): 将v加入队列中

2. DeQueue(): 使队列中的队首元素删除并返回此元素

3. MaxElement: 返回队列中的最大元素

设计一种数据结构和算法,让MaxElement操作的时间复杂度尽可能地低。


解法1:

用最大堆来维护队列中的节点,队列用单链表表示,每个节点包含数据,而最大堆用数组表示,数组元素为节点的指针。入队的时间复杂度为O(logn),出队的时间复杂度为O(n),达不到书上的O(logn),取最大值的时间复杂度为O(1)。

#include <iostream>#include <algorithm>#include <vector>using namespace std;// 用最大堆来维护队列中的节点,队列用单链表表示,每个节点包含数据,// 而最大堆用数组表示,数组元素为节点的指针class Queue{public:// 模拟队列的链表节点struct Node{Node(int d):data(d),next(0){}int data;Node *next;};Queue() {begin=end=0;vec.push_back(NULL);}// O(logn)时间内将节点加入队列void EnQueue(int data){Node *nd = new Node(data);// 若队列中没有节点if (vec.size() == 1){begin = end = 1;vec.push_back(nd);return;}// 若队列中已有节点,将其连上单链表vec.push_back(nd);vec[end]->next = nd;// 用shift_up在大顶堆中确定其位置end = vec.size()-1;while (end>>1 >= 1){if (nd->data <= vec[end>>1]->data)break;vec[end] = vec[end>>1];// 元素移动可能会使队列中第一个节点的位置发生改变if (end>>1 == begin)begin = end;end >>= 1;}vec[end] = nd;}// O(n)时间内从队列中删去节点int DeQueue(){// 若队列中没有节点if (vec.size() == 1) {begin = end = 0;return 0;}// 将第一个节点删去int data = vec[begin]->data;Node *nextnd = vec[begin]->next;delete vec[begin];// 若删除的节点的位置不是vec的最后一个元素if (begin < vec.size()-1){Node *nd = vec[vec.size()-1];vec.pop_back();// 执行shift_down确定vec最后一个元素在begin子树中的位置int nextbegin = begin<<1;while (nextbegin <= vec.size()-1)// 边界约束{// 找到两个子元素中较大的元素if (nextbegin+1 <= vec.size()-1 && vec[nextbegin+1]->data > vec[nextbegin]->data)nextbegin++;// 若vec最后一个元素比较大元素,则它的位置是beginif (nd ->data >= vec[nextbegin]->data)break;vec[begin] = vec[nextbegin];// 元素的移动可能会使队列中最后一个节点的位置发生改变if (nextbegin == end)end = begin;begin = nextbegin;nextbegin <<= 1;}vec[begin] = nd;// 如果vec最后一个元素是最后一个节点的位置,则将其设为beginif (end >= vec.size())end = begin;}else// 若删除的节点的位置是vec的最后一个元素vec.pop_back();// 在大顶堆中找到队列中的第二个元素耗时O(n),还需优化int i;for (i=1; i<vec.size(); ++i)if (vec[i] == nextnd)break;begin = i;return data;}// O(1)的时间内得到最大值元素int maxElement(){return vec[1]->data;}private:vector<Node*> vec;// 模拟大顶堆的数组(从1开始)int begin, end;// 队列的第一个节点和最后一个节点在数组中的位置};int main(){const int n = 11;int data[] = {7, 4, 15, 9, 5, 10, 13, 3, 20, 17, 19};int i;Queue q;for (i=0; i<n/2; i++)q.EnQueue(data[i]);int d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();for (; i<n; i++)q.EnQueue(data[i]);}

解法2:

将队列用两个栈来表示。栈在加入数据时,判断最大数是否发生改变,若改变,要记录新的最大数的位置。栈在删除数据时,要判断被删除的数是否是最大数,若是,则要弹出当前的最大数的位置。队列的入队操作的时间复杂度为O(1)。对于出队操作,虽然如果栈sa为空时,栈sb会将所有数据弹出并压入到栈sa中,这个操作不是O(1),但经过平摊分析的记账方法(具体见算法导论)可知其平均时间时间复杂度为O(1)。取最大值的时间复杂度为O(1)。

#include <iostream>#include <algorithm>#include <vector>using namespace std;class Stack{public:void Push(int d){vec.push_back(d);// 判断最大数是否发生改变if (maxvec.size()==0 || d>vec[maxvec[maxvec.size()-1]]){maxvec.push_back(vec.size()-1);}}int Pop(){if (vec.size()==0)return 0;// 若删除的是当前的最大数,则pop到前一个最大数if (vec.size()-1 == maxvec[maxvec.size()-1])maxvec.pop_back();int d = vec[vec.size()-1];vec.pop_back();return d;}bool empty(){return maxvec.size()==0;}int maxElement(){int maxpos = maxvec[maxvec.size()-1];return vec[maxpos];} private:vector<int> vec;// 存入压进来的数据vector<int> maxvec;// 每当最大数发生改变时,存入新的最大数的位置};class Queue{public:void EnQueue(int d){sb.Push(d);}int DeQueue(){if (sa.empty()){while (!sb.empty()){sa.Push(sb.Pop());}}return sa.Pop();}int maxElement(){return max(sa.maxElement(), sb.maxElement());}private:Stack sa, sb;// 两个后进先出的stack相当于先进先出的queue};int main(){const int n = 11;int data[] = {7, 4, 15, 9, 5, 10, 13, 3, 20, 17, 19};int i;Queue q;for (i=0; i<n/2; i++)q.EnQueue(data[i]);int d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();d = q.DeQueue();for (; i<n; i++)q.EnQueue(data[i]);}



原创粉丝点击