问题:
假设有这样一个拥有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最后一个元素比较大元素,则它的位置是begin if (nd ->data >= vec[nextbegin]->data) break; vec[begin] = vec[nextbegin]; // 元素的移动可能会使队列中最后一个节点的位置发生改变 if (nextbegin == end) end = begin; begin = nextbegin; nextbegin <<= 1; } vec[begin] = nd; // 如果vec最后一个元素是最后一个节点的位置,则将其设为begin if (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]); }