队列基本操作有如队列和出队列两个,很少涉及其他操作,但是队列的应用有很多,也很实用,我觉得最大的价值还是其思想多编程和解决问题的影响。我一般喜欢结合数组使用队列的思想解决一些问题,一个是时间复杂度较低,第二个是比较简单易操作。
队列的实现方式有两种:一种是数组实现,另一种是链表实现,个人比较喜欢数组实现,一个是简单易操作,另一个是占用的资源较少。
一、一般队列操作
1、数组实现
1 package Queue; 2 3 public class DoQueue { 4 private final int DEFUALT_SIZE=5; 5 private int []queue; 6 private int flag=1;//用于拓展队列空间时的标志量 7 private int prior=0;//队列队头指针 8 private int rear=0;//队列队尾指针 9 //用于申请一些队列空间 10 public DoQueue(){ 11 queue=new int [DEFUALT_SIZE]; 12 } 13 //用于扩展队列的空间(满足队列无线长的想法) 14 public void addQueueSize(){ 15 System.out.println("队列拓展了一次空间!"); 16 flag++; 17 int []queue1=queue; 18 queue=new int[DEFUALT_SIZE*flag]; 19 for(int i=0;i<queue1.length;i++){ 20 queue[i]=queue1[i]; 21 } 22 } 23 //入队列操作 24 public void push(int m){ 25 if(rear==queue.length){ 26 addQueueSize(); 27 } 28 queue[rear]=m; 29 rear++; 30 } 31 //出队列操作 32 public int pop() throws Exception{ 33 if(rear==prior){ 34 throw new Exception("队列不能已经为空!不能进行出队列操作!"); 35 }else{ 36 int mark=queue[0]; 37 for(int i=0;i<rear-1;i++){ 38 queue[i]=queue[i+1]; 39 } 40 rear--; 41 return mark; 42 } 43 } 44 45 public static void main(String[] args) throws Exception { 46 DoQueue queue1=new DoQueue(); 47 queue1.push(1); 48 queue1.push(2); 49 queue1.push(3); 50 queue1.push(4); 51 queue1.push(5); 52 queue1.push(6); 53 queue1.push(7); 54 queue1.push(8); 55 System.out.println(queue1.pop()+" "+queue1.pop()+" "+queue1.pop()+" " 56 +queue1.pop()+" "+queue1.pop()+" "+queue1.pop()+" "+queue1.pop() 57 +" "+queue1.pop()); 58 System.out.println(queue1.pop()); 59 } 60 }
运行结果:
队列拓展了一次空间! 1 2 3 4 5 6 7 8 Exception in thread "main" java.lang.Exception: 队列不能已经为空!不能进行出队列操作! at Queue.DoQueue.pop(DoQueue.java:34) at Queue.DoQueue.main(DoQueue.java:58)
这个队列中增加了拓展队列内存的方法,此队列可无限增长。
2、链表实现(在这里我们尽量将数据进行封装,这样对于Java代码来说更规范)
1 package Queue; 2 3 //节点封装类 4 class Node{ 5 private int munber; 6 private Node next; 7 public int getMunber() { 8 return munber; 9 } 10 public void setMunber(int munber) { 11 this.munber = munber; 12 } 13 public Node getNext() { 14 return next; 15 } 16 public void setNext(Node next) { 17 this.next = next; 18 } 19 20 } 21 public class DoQueue1 { 22 private Node prior;//队列队头指针 23 private Node rear;//队列队尾指针 24 //用于初始化一些变量 25 public DoQueue1(){ 26 prior=new Node(); 27 rear=prior; 28 } 29 30 //入队列操作 31 public void push(int m){ 32 rear.setMunber(m); 33 rear.setNext(new Node()); 34 rear=rear.getNext(); 35 } 36 //出队列操作 37 public int pop() throws Exception{ 38 if(prior==rear){ 39 throw new Exception("队列已经为空,不能进行出队列操作!"); 40 }else{ 41 int mark=prior.getMunber(); 42 prior=prior.getNext(); 43 return mark; 44 } 45 } 46 public static void main(String[] args) throws Exception { 47 DoQueue1 queue2=new DoQueue1(); 48 queue2.push(1); 49 queue2.push(2); 50 queue2.push(3); 51 queue2.push(4); 52 System.out.println(queue2.pop()+" "+queue2.pop()+" "+queue2.pop()+" "+queue2.pop()); 53 System.out.println(queue2.pop()); 54 55 } 56 57 }
运行结果:
1 2 3 4 Exception in thread "main" java.lang.Exception: 队列已经为空,不能进行出队列操作! at Queue.DoQueue1.pop(DoQueue1.java:39) at Queue.DoQueue1.main(DoQueue1.java:53)
二、循环队列
大家都知道,如果使用数组实现队列的时候对于队列的插入操作说非常的方便,时间复杂度为O(1),但是很明显其出队列操作非常的不好,需要将每个位置的元素向前移动一个位置,时间复杂度达到了O(n),这个其实是非常耗费资源的,而循环队列的出现,就有效的解决了这个问题。在这里我们为了方便,数组方式创建的队列将使用定长。而通过循环链表实现循环队列其实也是有很明显的好处的,首先,我们不会产生大量的垃圾,其次不需要经常性的申请存储空间(对于固定size的循环队列),这样大大节省了很多资源。
1、数组实现
package Queue; public class CircleQueue1 { private final int DEFUALT_SIZE=5; private int []queue; private int prior=0;//队列队头指针 private int rear=0;//队列队尾指针 private int size=0; //用于申请一些队列空间 public CircleQueue1(){ queue=new int [DEFUALT_SIZE]; } //入队列操作 public void push(int m) throws Exception{ if(size==DEFUALT_SIZE){ throw new Exception("队列已经满了,不能进行入队列操作!"); }else{ queue[rear]=m; rear++; size++; rear=rear%(DEFUALT_SIZE+1);//这个地方要注意一下 } } //出队列操作 public int pop() throws Exception{ //System.out.println(rear+" "+prior); if(rear==prior){ throw new Exception("队列已经为空!不能进行出队列操作!"); }else{ int mark=queue[prior]; prior++; prior=prior%(DEFUALT_SIZE+1);//这里注意一下 size--; return mark; } } public static void main(String[] args) throws Exception { CircleQueue1 queue3=new CircleQueue1(); queue3.push(1); queue3.push(2); queue3.push(3); queue3.push(4); queue3.push(5); System.out.println(queue3.pop()+" "+queue3.pop()+" "+queue3.pop() +" "+queue3.pop()+" "+queue3.pop()+" "); System.out.println(queue3.pop()); } }
运行结果:
1 2 3 4 5 Exception in thread "main" java.lang.Exception: 队列已经为空!不能进行出队列操作! at Queue.CircleQueue1.pop(CircleQueue1.java:30) at Queue.CircleQueue1.main(CircleQueue1.java:49)
红色区域的代码是数组实现循环队列的核心代码;
2、链表实现
1 package Queue; 2 public class CircleQueue2 { 3 private final int DEFUALT_SIZE=5; 4 private Node prior;//队列队头指针 5 private Node rear;//队列队尾指针 6 private Node head; 7 private int size=0; 8 //构造一个空的循环队列 9 public CircleQueue2(){ 10 Node p; 11 p=head=new Node(); 12 prior=rear=head;//将两个队列指针指向头结点 13 for(int i=0;i<DEFUALT_SIZE-1;i++){ 14 p.setNext(new Node()); 15 p=p.getNext(); 16 } 17 p.setNext(head); 18 } 19 20 //入队列操作 21 public void push(int m) throws Exception{ 22 if(size==DEFUALT_SIZE) 23 throw new Exception("队列已经吗满了,不能进行如队列操作!"); 24 rear.setMunber(m); 25 rear=rear.getNext(); 26 size++; 27 } 28 //出队列操作 29 public int pop() throws Exception{ 30 if(size==0){ 31 throw new Exception("队列已经为空,不能进行出队列操作!"); 32 }else{ 33 int mark=prior.getMunber(); 34 prior=prior.getNext(); 35 size--; 36 return mark; 37 } 38 } 39 40 public static void main(String[] args) throws Exception { 41 DoQueue1 queue2=new DoQueue1(); 42 queue2.push(1); 43 queue2.push(2); 44 queue2.push(3); 45 queue2.push(4); 46 System.out.println(queue2.pop()+" "+queue2.pop()+" "+queue2.pop()+" "+queue2.pop()); 47 System.out.println(queue2.pop()); 48 } 49 50 }
运行结果:
1 2 3 4 Exception in thread "main" java.lang.Exception: 队列已经为空,不能进行出队列操作! at Queue.DoQueue1.pop(DoQueue1.java:39) at Queue.CircleQueue2.main(CircleQueue2.java:47)
三、阻塞队列
首先得说一下什么是阻塞队列,阻塞队列事一个固定大小的队列,进出入队列规则不变为先进先出,但是一般队列当队列为空时,进行出队列操作会报错,当队列满了的时候,进行如队列操作,也会报错;而阻塞队列的则是当队列为空时,进行出队列操作,进行出队列的线程将会被阻塞,直到队列部位空时,进行出队列操作,当队列满的时候,进行入队列操作,入队列的线程将会被阻塞,直到队列不为满时,进行入队列操作。
代码(数组的循环队列实现):
1 package Queue; 2 3 import java.util.Date; 4 5 class BlockQueue { 6 private final int DEFUALT_SIZE=5; 7 private static volatile int []queue; 8 private static volatile int prior=0;//队列队头指针 9 private static volatile int rear=0;//队列队尾指针 10 private static volatile int size=0; 11 //用于申请一些队列空间 12 public BlockQueue(){ 13 queue=new int [DEFUALT_SIZE]; 14 } 15 16 //入队列操作 17 public void push(int m) throws Exception{ 18 if(size==DEFUALT_SIZE){ 19 Date now=new Date(); 20 System.out.println("当队列满时,入队列等待开始时间:"+now.toString()); 21 while(size==DEFUALT_SIZE){ 22 } 23 now=new Date(); 24 System.out.println("当队列为空时,入队列等待结束时间:"+now.toString()); 25 rear=rear%DEFUALT_SIZE; 26 } 27 queue[rear]=m; 28 //System.out.println("rear:"+rear+" queue[rear]:"+queue[rear]); 29 rear++; 30 size++; 31 rear=rear%(DEFUALT_SIZE);//这个地方要注意一下 32 } 33 //出队列操作 34 public int pop() throws Exception{ 35 //System.out.println(rear+" "+prior); 36 if(size==0){ 37 Date now=new Date(); 38 System.out.println();//为了输出时好看一点 39 System.out.println("当队列为空时,出队列等待开始时间:"+now.toString()); 40 while(size==0){ 41 42 } 43 Thread.sleep(100); 44 now=new Date(); 45 System.out.println("当队列为空时,出队列等待结束时间:"+now.toString()); 46 } 47 int mark=queue[prior]; 48 //System.out.println("prior:"+prior+" queue[prior]:"+queue[prior]); 49 prior++; 50 prior=prior%(DEFUALT_SIZE);//这里注意一下 51 size--; 52 return mark; 53 } 54 } 55 56 class BlockQueue1 extends Thread{ 57 58 @Override 59 public void run() { 60 BlockQueue blockQueue1=new BlockQueue(); 61 try { 62 for(int i=0;i<6;i++){ 63 blockQueue1.push(i); 64 } 65 System.out.println("BlockQueue1线程共进队列6次"); 66 System.out.println("BlockQueue1线程出队列:"); 67 for(int i=0;i<6;i++){ 68 System.out.print(blockQueue1.pop()+" "); 69 } 70 System.out.println(); 71 System.out.println("BlockQueue1线程共出队列6次"); 72 } catch (Exception e) { 73 e.printStackTrace(); 74 } 75 76 } 77 } 78 class BlockQueue2 extends Thread{ 79 80 @Override 81 public void run() { 82 BlockQueue blockQueue2=new BlockQueue(); 83 try { 84 System.out.println("blockQueue2线程出队列:"+blockQueue2.pop()); 85 Thread.sleep(1000); 86 blockQueue2.push(7); 87 } catch (Exception e) { 88 e.printStackTrace(); 89 } 90 } 91 } 92 public class BolckQueueTest{ 93 94 public static void main(String[] args) throws InterruptedException { 95 Thread thread1=new BlockQueue1(); 96 Thread thread2=new BlockQueue2(); 97 thread1.start(); 98 Thread.sleep(1000); 99 thread2.start(); 100 } 101 }
运行结果:
当队列满时,入队列等待开始时间:Tue May 23 21:00:42 CST 2017 blockQueue2线程出队列:0 当队列为空时,入队列等待结束时间:Tue May 23 21:00:43 CST 2017 BlockQueue1线程共进队列6次 BlockQueue1线程出队列: 0 0 0 0 5 当队列为空时,出队列等待开始时间:Tue May 23 21:00:43 CST 2017 当队列为空时,出队列等待结束时间:Tue May 23 21:00:44 CST 2017 7 BlockQueue1线程共出队列6次
注:输出结果红色结果行表明代码在多线程上还有不足之处,咱们主要是谈论阻塞队列,就不要太纠结于此处结果。我们看到当队列满时在进行入队列操作时会有阻塞效果,当队列空时进行出队列操作也会有阻塞效果。红色代码去是实现阻塞的一种方式。
四、优先级队列(数组实现,堆思想)
首先说一下什么是优先级队列,一般队列实现的是先进先出的原则,而优先级队列则实现的是当前已入队列元素中优先级最高的先出队列,表现为最小的先出队列或者最大的数先出队列。这种队列一般使用堆得思想实现。
代码实现:
1 public class PriorityQueue1 { 2 private final int DEFUALTSIZE=4;//默认的堆大小+1,即2的平方 3 private int []queue1;//默认底层的数组存储实现 4 private int nowQueueSize=0;//记录当前堆得元素的个数,队列的元素个数 5 //对优先队列进行相关的初始化,申请一些空间 6 public PriorityQueue1(){ 7 queue1=new int[DEFUALTSIZE]; 8 } 9 //给优先队列进行拓展空间存储大小 10 public void enlargeArray(int newSize){ 11 System.out.println("优先队列进行拓展一次空间"); 12 int []mark=queue1; 13 queue1=new int [newSize]; 14 for(int i=0;i<mark.length;i++){ 15 queue1[i]=mark[i]; 16 } 17 } 18 public void push(int m){ 19 //如果存储空间不够,申请新的空间 20 if(nowQueueSize==queue1.length-1){ 21 enlargeArray(queue1.length*2);//拓展的大小是拓展了堆(树)得下一层 22 } 23 //上溢操作 24 int markLocation=++nowQueueSize; 25 for(queue1[0]=m;queue1[markLocation/2]>m;markLocation/=2){ 26 queue1[markLocation]=queue1[markLocation/2]; 27 } 28 queue1[markLocation]=m; 29 //测试入队列后的数组元素分布 30 for(int i=1;i<=nowQueueSize;i++){ 31 System.out.print(queue1[i]+" "); 32 } 33 System.out.println(); 34 35 } 36 public int pop() throws Exception{ 37 if(nowQueueSize==0){ 38 throw new Exception("优先队列已空,不能进行出队列操作!"); 39 } 40 //优先队列的下溢操作 41 int mark=queue1[1]; 42 for(int i=1;i<nowQueueSize;){ 43 int markMin; 44 int markI; 45 if(2*i>nowQueueSize-1){ 46 queue1[i]=queue1[nowQueueSize]; 47 break; 48 }else if(2*i+1<=nowQueueSize-1){ 49 markMin=queue1[2*i]>queue1[2*i+1]?queue1[2*i+1]:queue1[2*i]; 50 markI=queue1[2*i]>queue1[2*i+1]?(2*i+1):(2*i); 51 if(queue1[nowQueueSize]<markMin){ 52 queue1[i]=queue1[nowQueueSize]; 53 break; 54 }else{ 55 queue1[i]=markMin; 56 i=markI; 57 } 58 }else{ 59 markMin=queue1[2*i]; 60 markI=2*i; 61 if(queue1[nowQueueSize]<markMin){ 62 queue1[i]=queue1[nowQueueSize]; 63 break; 64 }else{ 65 queue1[i]=markMin; 66 i=markI; 67 } 68 } 69 } 70 nowQueueSize--; 71 return mark; 72 } 73 public static void main(String[] args) throws Exception { 74 PriorityQueue1 queueTest=new PriorityQueue1(); 75 queueTest.push(2); 76 queueTest.push(4); 77 queueTest.push(3); 78 queueTest.push(1); 79 queueTest.push(6); 80 queueTest.push(5); 81 for(int i=1;i<=6;i++){ 82 System.out.print(queueTest.pop()+" "); 83 } 84 System.out.println(); 85 System.out.println(queueTest.pop()); 86 87 } 88 89 }
运行结果:
2 2 4 2 4 3 优先队列进行拓展一次空间 1 2 3 4 1 2 3 4 6 1 2 3 4 6 5 1 2 3 4 5 6 Exception in thread "main" java.lang.Exception: 优先队列已空,不能进行出队列操作! at Queue.PriorityQueue1.pop(PriorityQueue1.java:40) at Queue.PriorityQueue1.main(PriorityQueue1.java:87)
优先队列操作,重点理解上溢和下溢的流程,这是代码实现的关键。
五、总结
1、队列是一种思想,不要纠结于队列本身,关注思想。
2、要灵活的使用队列,结合实际需要,考虑优化效果等各方面,灵活采取数组实现和链表实现。数组实现的队列也可以通过一定的处理,实现链表实现的优点。(循环队列)
3、队列在线程,特别是多线程的同步的实现都有很重要的作用。(队列同步器)
4、队列可以用于排序等操作。(堆排序)