1. 前言
本文的一些图片, 资料 截取自编程之美
2. 问题描述
3. 问题分析
解法一 : 在队列添加元素的时候 如果添加的元素大于当前的最大元素, 则对最大元素进行更新,
在队列删除元素的时候, 如果删除元素即为最大的元素, 则遍历一次列表更新最大的元素
解法二 : 使用一个列表维护进队, 出队的顺序, 然后使用一个大根堆来维护最大的元素,
如果添加的元素大于当前的最大元素, 则添加元素重新整堆,
在队列删除元素的时候, 如果删除元素即为最大的元素, 则删除最大的元素重新整堆
解法三 : 利用两个栈来构造一个队列, 一个栈用作处理业务, 另一个栈用作存储入队的数据, 当数据入队的时候, 进入存储栈, 当数据出队的时候, 优先出业务栈中的数据, 如果其中没有元素了, 则将存储栈中的数据全部倒到业务栈中, 然后在出栈业务栈栈顶元素
其中这里的栈可以在O(1)的时间复杂度内, 找到最大值 [设计的很巧妙]
4. 代码
备注 : 这里的对于堆的处理, 并没有采用规范的处理方式
/**
* file name : Test08FindMaxEleInQueue.java
* created at : 2:52:32 PM May 28, 2015
* created by 970655147
*/
package com.hx.test04;
public class Test08FindMaxEleInQueue {
// 找出队列中的最大值
public static void main(String []args) {
// Queue Test
// Queue01 que = new Queue01();
// Queue02 que = new Queue02();
Queue03 que = new Queue03();
que.enqueue(1223);
que.enqueue(34);
que.enqueue(21);
que.enqueue(1);
que.enqueue(3);
Log.log(que.getMaxVal());
Log.log(que);
que.dequeue();
que.dequeue();
que.dequeue();
Log.log(que.getMaxVal());
Log.log(que);
// Stack Test
// Stack stack = new Stack();
// stack.push(33);
// stack.push(234);
// stack.push(3);
// stack.push(322);
// Log.log(stack.getMaxVal());
//
// stack.pop();
// stack.pop();
// Log.log(stack.getMaxVal());
// Log.log(stack);
}
// 维护一个maxVal的值 来表示最大的值
// 如果有其他元素入队 并且大于当前的maxVal 更新maxVal
// 如果最大的元素 出队了, 则更新maxVal
static class Queue01 {
// data 维护队列的数据
// maxVal 保存当前队列最大值
Deque<Integer> data;
int maxVal;
// 初始化
public Queue01() {
data = new LinkedList<Integer>();
maxVal = -1;
}
// 入队 如果该值大于maxVal 更新maxVal
public void enqueue(Integer ele) {
if(ele > maxVal) {
maxVal = ele;
}
data.addLast(ele);
}
// 出队 如果该出队值为maxVal 则更新maxVal[遍历队列查找]
public Integer dequeue() {
Integer res = data.removeFirst();
if(res == maxVal) {
updateMaxVal();
}
return res;
}
// 遍历队列找出最大值
private void updateMaxVal() {
Integer max = Integer.MIN_VALUE;
Iterator<Integer> it = data.iterator();
while(it.hasNext()) {
Integer ele = it.next();
if(ele > max) {
max = ele;
}
}
maxVal = max;
}
// 获取最大值
public int getMaxVal() {
return maxVal;
}
// for debug ...
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<Integer> it = data.iterator();
while(it.hasNext()) {
Integer ele = it.next();
sb.append(ele + " ");
}
return sb.toString();
}
}
// 思想 : 维护一个队列 维护了队列的进出顺序, 并且维护了一个堆 来找最大的元素
static class Queue02 {
// 维护数据顺序的队列 以及维护最大元素的堆
Deque<Integer> que;
List<Integer> heap;
// 初始化
public Queue02() {
que = new LinkedList<Integer>();
heap = new ArrayList<Integer>();
}
// 入队操作 将数据添加到que, heap中
// 如果ele大于当前的最大元素 并进行整堆
public void enqueue(Integer ele) {
que.addLast(ele);
heap.add(ele);
if(ele > heap.get(0)) {
buildMaxHeap(heap);
}
}
// 出队操作 从que中移除队列头部的元素, 并移除heap中响应的元素
// 如果移除的是最大的元素 进行整堆
public Integer dequeue() {
Integer res = que.removeFirst();
Integer tmp = heap.get(0);
heap.remove(res);
if(res.equals(tmp) ) {
buildMaxHeap(heap);
}
return res;
}
// 获取最大的元素 即堆顶
public Integer getMaxVal() {
return heap.get(0);
}
// 构建大根堆
private void buildMaxHeap(List<Integer> heap) {
if(heap.size() > 1) {
int now = heap.size()/2 - 1;
// 将now所在的结点 置为该节点,左子节点,右子节点中最小的数的结点交换位置
getMax(heap, now, getLeft(now));
if((heap.size() & 1) == 1){
getMax(heap, now, getRight(now));
}
// 然后从now开始不断的整堆 知道now<0 结果堆顶为最大的元素
for(int j=now-1; j>=0; j--){
getMax(heap, j, getLeft(j));
getMax(heap, j, getRight(j));
}
}
}
// 如果array[child]>array[parent] 则交换他们两个的数据
public static void getMax(List<Integer> array, int parent, int child){
if(array.get(parent) < array.get(child) ){
_swap(array, parent, child);
}
}
// 交换arr的两个元素
private static void _swap(List<Integer> arr, int idx, int i) {
int tmp = arr.get(idx);
arr.set(idx, arr.get(i));
arr.set(i, tmp);
}
// 获取左孩子的索引
public static int getLeft(int now){
return 2*now + 1;
}
// 获取右孩子的索引
public static int getRight(int now){
return 2*(now+1);
}
// Debug
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<Integer> it = que.iterator();
while(it.hasNext()) {
Integer ele = it.next();
sb.append(ele + " ");
}
return sb.toString();
}
}
// 思想 : 因为下面的Stack获取最大值是线性的 所以现在用两个Stack来构造一个Queue
static class Queue03 {
// bus 表示业务队列 控制出队 如果bus为空 则从store向bus传输数据
// store 表示存储队列 控制入队
Stack bus;
Stack store;
// 初始化
public Queue03() {
bus = new Stack();
store = new Stack();
}
// 入队
public void enqueue(int ele) {
store.push(ele);
}
// 出队 如果bus队列为空的话 将数据转从store转移到bus中
public int dequeue() {
if(bus.isEmpty() ) {
while(!store.isEmpty() ) {
bus.push(store.pop() );
}
}
return bus.pop();
}
// 获取两个数的较大者
public int getMax(int x, int y) {
return x > y ? x : y;
}
// 获取整个队列的最大值 即获取两个Stack中最大值较大的那个
public int getMaxVal() {
return getMax(bus.getMaxVal(), store.getMaxVal() );
}
// for debug ...
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("business stack : ");
sb.append(bus.toString() );
sb.append("store stack : ");
sb.append(store.toString() );
return sb.toString();
}
}
// item 存放栈中的数据, link2NextMaxItem 如果其元素为-1 则表示该元素之前的最大的元素值为 向左数最靠近该项的大于-1的索引对应的值
// 否则如果该元素大于-1 则说明当前元素之前的所有元素 最大值为该索引对应的元素值
// sp 表示下一个添加元素的位置, maxItemIdx 表示当前栈中最大元素的索引
static class Stack {
final int CAP = 10;
int[] item;
Deque<Integer> link2NextMaxItem;
int sp, maxItemIdx;
// 初始化
public Stack() {
sp = 0;
maxItemIdx = -1;
item = new int[CAP];
link2NextMaxItem = new LinkedList<Integer>();
}
// 向栈中中添加一个元素 如果超过了CAP 则忽略该元素
// 如果该元素 大于当前最大元素 则设置当前sp之前的最大的元素索引为maxItemIdx 然后更新maxItemIdx为sp
// 否则 设置link2NextMaxItem的该项为-1[表示该项之前的所有数据最大的值为 左数最靠近该项的大于0的的索引的数]
public void push(int ele) {
if(isFull() ) {
return ;
}
item[sp] = ele;
if(ele > getMaxVal() ) {
link2NextMaxItem.push(maxItemIdx);
maxItemIdx = sp;
}
sp ++;
}
// 将栈顶的值出栈
// 如果sp退到了maxItemIdx 这时说明已经将maxItemIdx对应的值pop出去了
// 这时获取接下来的最大的元素索引
public int pop() {
if(isEmpty() ) {
return -1 ;
}
sp --;
int res = item[sp];
if(sp == maxItemIdx ) {
maxItemIdx = link2NextMaxItem.pop();
}
return res;
}
// 判断当前Stack是否为空 因为sp为下一个添加的元素的位置 所以如果其为0的话表示Stack为空
public boolean isEmpty() {
return sp == 0;
}
// 判断当前Stack是否满了 因为sp为下一个添加的元素的位置 所以如果其为CAP的话表示Stack满了
public boolean isFull() {
return sp == CAP;
}
// 获取最大值 如果maxItemIdx退到了-1 则返回minValue, 否则 返回maxItemIdx对应的值
private int getMaxVal() {
if(maxItemIdx >= 0) {
return item[maxItemIdx];
} else {
return Integer.MIN_VALUE;
}
}
// Debug
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("bottom -> top : ");
for(int i=0; i<sp; i++) {
sb.append(item[i] + " ");
}
sb.append("\r\n");
return sb.toString();
}
}
}
5. 运行结果
6. 总结
对于第一种解法, 每次在最大元素出队的时候, 都需要遍历一次队列, 重新更新最大元素
对于第二种解法, 需要使用额外的空间来存储队列中的元素
对于第三种解法, 非常巧妙的使用了一两个栈来构建一个队列, 尤其是这里的栈的构造非常巧妙, Stack中使用了一个栈来维护当前最大元素之前的最大元素, 这样当当前最大元素出栈的时候, 就可以轻松的获取到第二大的元素[当前最大元素] 了
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!