您的位置:首页 > 理论基础 > 数据结构算法

数据结构Java实现07----队列:顺序队列&顺序循环队列、链式队列、顺序优先队列

2017-10-04 21:23 776 查看
一、队列的概念:

  队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在其一端进行插入操作在其另一端进行删除操作。

队列中允许进行插入操作的一端称为队尾,允许进行删除操作的一端称为队头。队列的插入操作通常称作入队列,队列的删除操作通常称作出队列。

下图是一个依次向队列中插入数据元素a0,a1,...,an-1后的示意图:





上图中,a0是当前队头数据元素,an-1是当前队尾数据元素。

为了避免当只有一个元素时,对头和队尾重合使得处理变得麻烦,所以引入两个指针:front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样的话,当front指针等于rear时,此队列不是还剩一个元素,而是空队列。

二、队列的抽象数据类型:

数据集合:

  队列的数据集合可以表示为a0,a1,…,an-1,每个数据元素的数据类型可以是任意的类型。

操作集合:

(1)入队列append(obj):把数据元素obj插入队尾。
(2)出队列delete():把队头数据元素删除并由函数返回。
(3)取队头数据元素getFront():取队头数据元素并由函数返回。
(4)非空否isEmpty():非空否。若队列非空,则函数返回false,否则函数返回true。

三、循环顺序队列:

线性表有顺序存储和链式存储,队列是一种特殊的线性表,同样也存在这两种存储方式。我们先来看一下队列的顺序存储。

1、顺序队列的“假溢出”:





上图中,front指针指向队头元素,rear指针指向队尾元素的下一个位置。图(d)中b、c、d出队后,front指针指向元素e,rear指针在数组外面。假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经被占用,再向后加就会产生数组越界的错误,可实际上队列在下标为0、1、2、3、4的地方还是空闲的,我们把这种现象叫做“假溢出”。

2、循环顺序队列:

所以解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种逻辑上首尾相连的顺序存储结构称为循环队列。

如何判断循环队列究竟是空的还是满的:

  现在问题又来了,我们之前说,空队列时,front指针等于rear指针,那么现在循环队列满的时候,也是front等于rear,那么如何判断循环队列究竟是空的还是满的?有如下办法:

办法1:设置一个标志位flag。初始时置flag=0;每当入队列操作成功就置flag=1;每当出队列操作成功就置flag=0。则队列空的判断条件为:rear==front&&tag==0;队列满的判断条件为:rear==front&&tag==1。

办法2:保留一个元素的存储空间。此时,队列满时的判断条件为(rear+1)%maxSize==front;队列空的判断条件还是front==rear。
办法3:设计一个计数器count,统计队列中的元素个数。此时,队列满的判断条件为:count>0&&rear==front;队列空的判断条件为count==0。

我们在接下来的代码中采用方法3来实现。

3、代码实现:(循环顺序队列的创建)

(1)Queue.java:(队列接口)

1//队列接口
2publicinterfaceQueue{
3
4//入队
5publicvoidappend(Objectobj)throwsException;
6
7//出队
8publicObjectdelete()throwsException;
9
10//获得队头元素
11publicObjectgetFront()throwsException;
12
13//判断对列是否为空
14publicbooleanisEmpty();
15}


(2)CircleSequenceQueue.java:(循环顺序队列)

1//循环顺序队列
2publicclassCircleSequenceQueueimplementsQueue{
3
4staticfinalintdefaultSize=10;//默认队列的长度
5intfront;//队头
6intrear;//队尾
7intcount;//统计元素个数的计数器
8intmaxSize;//队的最大长度
9Object[]queue;//队列
10
11publicCircleSequenceQueue(){
12init(defaultSize);
13}
14
15publicCircleSequenceQueue(intsize){
16init(size);
17}
18
19publicvoidinit(intsize){
20maxSize=size;
21front=rear=0;
22count=0;
23queue=newObject[size];
24}
25
26@Override
27publicvoidappend(Objectobj)throwsException{
28//TODOAuto-generatedmethodstub
29if(count>0&&front==rear){
30thrownewException("队列已满!");
31}
32queue[rear]=obj;
33rear=(rear+1)%maxSize;
34count++;
35}
36
37@Override
38publicObjectdelete()throwsException{
39//TODOAuto-generatedmethodstub
40if(isEmpty()){
41thrownewException("队列为空!");
42}
43Objectobj=queue[front];
44front=(front+1)%maxSize;
45count--;
46returnobj;
47}
48
49@Override
50publicObjectgetFront()throwsException{
51//TODOAuto-generatedmethodstub
52if(!isEmpty()){
53returnqueue[front];
54}else{
55returnnull;
56}
57}
58
59@Override
60publicbooleanisEmpty(){
61//TODOAuto-generatedmethodstub
62returncount==0;
63}
64
65}


(3)Test.java:

1publicclassTest{
2publicstaticvoidmain(String[]args)throwsException{
3
4CircleSequenceQueuequeue=newCircleSequenceQueue();
5queue.append("a");
6queue.append("b");
7queue.append("c");
8queue.append("d");
9queue.append("e");
10queue.append("f");
11
12queue.delete();
13queue.delete();
14
15queue.append("g");
16
17while(!queue.isEmpty()){
18System.out.println(queue.delete());
19}
20}
21}


运行效果:





4、循环队列应用:

  使用顺序循环队列和多线程实现一个排队买票的例子。而且,我们只允许这个队伍中同时排队的只有10个人,那就需要用到队列了。

    生产者(等候买票)

    消费者(买票离开)

这里面我们需要用到上面的Queue.java类和CircleSequenceQueue.java类。

代码结构:





(3)WindowQueue.java:

1//卖票窗口
2publicclassWindowQueue{
3
4//卖票的队列
5intmaxSize=10;
6CircleSequenceQueuequeue=newCircleSequenceQueue(maxSize);
7intnum=0;//统计卖票的数量,一天最多卖100张票。
8booleanisAlive=true;//判断是否继续卖票。
9
10//排队买票
11publicsynchronizedvoidproducer()throwsException{
12if(queue.count<maxSize){
13queue.append(num++);//等待买票的数量加1
14System.out.println("第"+num+"个客户排队等待买票!");
15this.notifyAll();//唤醒卖票的线程
16}else{
17try{
18System.out.println("队列已满...请等待!");
19this.wait();//队列满时,排队买票线程等待。
20}catch(Exceptionex){
21ex.printStackTrace();
22}
23}
24}
25
26//卖票
27publicsynchronizedvoidconsumer()throwsException{
28if(queue.count>0){
29Objectobj=queue.delete();
30inttemp=Integer.parseInt(obj.toString());
31System.out.println("第"+(temp+1)+"个客户买到票离开队列!");
32//如果当前队列为空,并且卖出票的数量大于等于100,说明卖票结束
33if(queue.isEmpty()&&this.num>=100){
34this.isAlive=false;
35}
36this.notifyAll();//唤醒排队买票的线程。
37}else{
38try{
39System.out.println("队列已空...请等待!");
40this.wait();//队列空时,卖票线程等待。
41}catch(Exceptionex){
42ex.printStackTrace();
43}
44}
45}
46}


(4)Producer.java:

1//买票者
2publicclassProducerimplementsRunnable{
3
4WindowQueuequeue;
5
6publicProducer(WindowQueuequeue){
7this.queue=queue;
8}
9
10@Override
11publicvoidrun(){
12//TODOAuto-generatedmethodstub
13while(queue.num<100){
14try{
15queue.producer();
16}catch(Exceptionex){
17ex.printStackTrace();
18}
19}
20}
21
22}


(5)Consumer.java:

//卖票者
publicclassConsumerimplementsRunnable{

WindowQueuequeue;

publicConsumer(WindowQueuequeue){
this.queue=queue;
}

@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(queue.isAlive){
try{
queue.consumer();
}catch(Exceptionex){
ex.printStackTrace();
}
}
}

}


(6)test.java:

1publicclassTest{
2
3publicstaticvoidmain(String[]args)throwsException{
4
5WindowQueuequeue=newWindowQueue();
6
7Producerp=newProducer(queue);//注意一定要传同一个窗口对象
8Consumerc=newConsumer(queue);
9
10//排队买票线程
11ThreadpThread=newThread(p);
12//卖票线程
13ThreadcThread=newThread(c);
14
15pThread.start();//开始排队买票
16cThread.start();//开始卖票
17}
18
19}


注意第07行的注释。

运行效果:





四、链式队列:

链式队列其实就是特殊的单链表,只不过它只能尾进头出而已。链式队列的存储结构如下图所示:





1、链式队列的实现:

(1)Node.java:结点类

1//结点类
2publicclassNode{
3
4Objectelement;//数据域
5Nodenext;//指针域
6
7//头结点的构造方法
8publicNode(Nodenextval){
9this.next=nextval;
10}
11
12//非头结点的构造方法
13publicNode(Objectobj,Nodenextval){
14this.element=obj;
15this.next=nextval;
16}
17
18//获得当前结点的后继结点
19publicNodegetNext(){
20returnthis.next;
21}
22
23//获得当前的数据域的值
24publicObjectgetElement(){
25returnthis.element;
26}
27
28//设置当前结点的指针域
29publicvoidsetNext(Nodenextval){
30this.next=nextval;
31}
32
33//设置当前结点的数据域
34publicvoidsetElement(Objectobj){
35this.element=obj;
36}
37
38publicStringtoString(){
39returnthis.element.toString();
40}
41}


(2)Queue.java:

1//队列接口
2publicinterfaceQueue{
3
4//入队
5publicvoidappend(Objectobj)throwsException;
6
7//出队
8publicObjectdelete()throwsException;
9
10//获得队头元素
11publicObjectgetFront()throwsException;
12
13//判断对列是否为空
14publicbooleanisEmpty();
15}


(3)LinkQueue.java:

1publicclassLinkQueueimplementsQueue{
2
3Nodefront;//队头
4Noderear;//队尾
5intcount;//计数器
6
7publicLinkQueue(){
8init();
9}
10
11publicvoidinit(){
12front=rear=null;
13count=0;
14}
15
16@Override
17publicvoidappend(Objectobj)throwsException{
18//TODOAuto-generatedmethodstub
19Nodenode=newNode(obj,null);
20
21//如果当前队列不为空。
22if(rear!=null){
23rear.next=node;//队尾结点指向新结点
24}
25
26rear=node;//设置队尾结点为新结点
27
28//说明要插入的结点是队列的第一个结点
29if(front==null){
30front=node;
31}
32count++;
33}
34
35@Override
36publicObjectdelete()throwsException{
37//TODOAuto-generatedmethodstub
38if(isEmpty()){
39newException("队列已空!");
40}
41Nodenode=front;
42front=front.next;
43count--;
44returnnode.getElement();
45}
46
47@Override
48publicObjectgetFront()throwsException{
49//TODOAuto-generatedmethodstub
50if(!isEmpty()){
51returnfront.getElement();
52}else{
53returnnull;
54}
55}
56
57@Override
58publicbooleanisEmpty(){
59//TODOAuto-generatedmethodstub
60returncount==0;
61}
62
63}


(4)Test.java:

1publicclassTest{
2
3publicstaticvoidmain(String[]args)throwsException{
4
5LinkQueuequeue=newLinkQueue();
6queue.append("a");
7queue.append("b");
8queue.append("c");
9queue.append("d");
10queue.append("e");
11queue.append("f");
12
13queue.delete();
14queue.delete();
15
16queue.append("g");
17
18while(!queue.isEmpty()){
19System.out.println(queue.delete());
20}
21}
22}


运行效果:





2、链式队列的应用:

题目:

  编写一个判断一个字符串是否是回文的算法。

思路:

  设字符数组str中存放了要判断的字符串。把字符数组中的字符逐个分别存入一个队列和栈中,然后逐个出队和出栈比较出队的字符与出栈的字符是否相同,若全部相等则该字符串为回文。

代码实现:

  这里面需要用到上面一段中的LinkQueue类。代码结构如下:





(4)Stack.java:栈接口

1//栈接口
2publicinterfaceStack{
3
4//入栈
5publicvoidpush(Objectobj)throwsException;
6
7//出栈
8publicObjectpop()throwsException;
9
10//获得栈顶元素
11publicObjectgetTop()throwsException;
12
13//判断栈是否为空
14publicbooleanisEmpty();
15}


(5)LinkStack.java:

1publicclassLinkStackimplementsStack{
2
3Nodehead;//栈顶指针
4intsize;//结点的个数
5
6publicLinkStack(){
7head=null;
8size=0;
9}
10
11@Override
12publicObjectgetTop()throwsException{
13//TODOAuto-generatedmethodstub
14returnhead.getElement();
15}
16
17@Override
18publicbooleanisEmpty(){
19//TODOAuto-generatedmethodstub
20returnhead==null;
21}
22
23@Override
24publicObjectpop()throwsException{
25//TODOAuto-generatedmethodstub
26if(isEmpty()){
27thrownewException("栈为空!");
28}
29Objectobj=head.getElement();
30head=head.getNext();
31size--;
32returnobj;
33
34}
35
36@Override
37publicvoidpush(Objectobj)throwsException{
38//TODOAuto-generatedmethodstub
39head=newNode(obj,head);
40size++;
41}
42
43}


(6)Test.java:测试类

1publicclassTest{
2
3publicstaticvoidmain(String[]args)throwsException{
4
5Stringstr1="ABCDCBA";//是回文
6Stringstr2="ABCDECAB";//不是回文
7
8try{
9if(Test.isHuiWen(str1)){
10System.out.println(str2+":是回文!");
11}else{
12System.out.println(str2+":不是回文!");
13}
14}catch(Exceptionex){
15ex.printStackTrace();
16}
17}
18
19
20//方法:判断字符串是否回文
21publicstaticbooleanisHuiWen(Stringstr)throwsException{
22intn=str.length();
23LinkStackstack=newLinkStack();//创建堆栈
24LinkQueuequeue=newLinkQueue();//创建队列
25for(inti=0;i<n;i++){
26stack.push(str.subSequence(i,i+1));//把字符串每个字符压进堆栈
27queue.append(str.subSequence(i,i+1));//把字符串每个字符压入队列
28}
29while(!queue.isEmpty()&&!stack.isEmpty()){
30if(!queue.delete().equals(stack.pop())){//出队列,出栈,同时判断是否相同
31returnfalse;
32}
33}
34
35returntrue;
36}
37
38}




3、循环队列和链式队列的比较:

(1)从时间上看,它们的基本操作都是常数时间,即O(1)的。不过循环队列是事先申请好空间,使用期间不释放;而链式队列,每次申请和释放结点也会存在一定的时间开销,如果入栈和出栈比较频繁,则两者还是有细微的差别。

(2)从空间上看,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链式队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链式队列更加灵活。

总结:总的来说,在可以确定队列长度的最大值的情况下,建议用循环队列,如果你无法估计队列的长度,那就用链式队列。

五、优先级队列:

  优先级队列是带有优先级的队列。

    用顺序存储结构实现的优先级队列称作顺序优先级队列。

    用链式存储结构存储的优先级队列称作链式优先级队列。

顺序优先级队列和顺序循环队列相比主要有两点不同:

(1)对于顺序优先级队列来说,出队列操作不是把队头数据元素出队列,而是把队列中优先级最高的数据元素出队列。(入队操作没区别)
(2)对于顺序优先级队列来说,数据元素由两部分组成,一部分是原先意义上的数据元素,另一部分是优先级。通常设计优先级为int类型的数值,并规定数值越小优先级越高。

1、顺序优先队列的实现:

设计顺序优先级队列分为两个类:

  数据元素类

  优先级队列类

代码实现:

(1)Element.java:

1//优先级队列元素类
2publicclassElement{
3
4privateObjectelement;//数据
5privateintpriority;//优先级
6
7publicElement(Objectobj,intpriority){
8this.element=obj;
9this.priority=priority;
10}
11
12publicObjectgetElement(){
13returnelement;
14}
15
16publicvoidsetElement(Objectelement){
17this.element=element;
18}
19
20publicintgetPriority(){
21returnpriority;
22}
23
24publicvoidsetPriority(intpriority){
25this.priority=priority;
26}
27
28}


(2)Queue.java:

1//队列接口
2publicinterfaceQueue{
3
4//入队
5publicvoidappend(Objectobj)throwsException;
6
7//出队
8publicObjectdelete()throwsException;
9
10//获得队头元素
11publicObjectgetFront()throwsException;
12
13//判断对列是否为空
14publicbooleanisEmpty();
15}


(3)PrioritySequenceQueue.java:

1//优先级队列
2publicclassPrioritySequenceQueueimplementsQueue{
3
4staticfinalintdefaultSize=10;//默认队列长度
5intfront;//队头
6intrear;//队尾
7intcount;//计数器
8intmaxSize;//队列最大长度
9Element[]queue;//队列
10
11publicPrioritySequenceQueue(){
12init(defaultSize);
13}
14
15publicPrioritySequenceQueue(intsize){
16init(size);
17}
18
19publicvoidinit(intsize){
20maxSize=size;
21front=rear=0;
22count=0;
23queue=newElement[size];
24}
25
26@Override
27publicvoidappend(Objectobj)throwsException{
28//TODOAuto-generatedmethodstub
29//如果队列已满
30if(count>=maxSize){
31thrownewException("队列已满!");
32}
33queue[rear]=(Element)obj;
34rear++;
35count++;
36}
37
38@Override
39publicObjectdelete()throwsException{
40//TODOAuto-generatedmethodstub
41if(isEmpty()){
42thrownewException("队列为空!");
43}
44//默认第一个元素为优先级最高的。
45Elementmin=queue[0];
46intminIndex=0;
47for(inti=0;i<count;i++){
48if(queue[i].getPriority()<min.getPriority()){
49min=queue[i];
50minIndex=i;
51}
52}
53
54//找的优先级别最高的元素后,把该元素后面的元素向前移动。
55for(inti=minIndex+1;i<count;i++){
56queue[i-1]=queue[i];//移动元素
57}
58rear--;
59count--;
60returnmin;
61}
62
63@Override
64publicObjectgetFront()throwsException{
65//TODOAuto-generatedmethodstub
66if(isEmpty()){
67thrownewException("队列为空!");
68}
69//默认第一个元素为优先级最高的。
70Elementmin=queue[0];
71intminIndex=0;
72for(inti=0;i<count;i++){
73if(queue[i].getPriority()<min.getPriority()){
74min=queue[i];
75minIndex=i;
76}
77}
78returnmin;
79}
80
81@Override
82publicbooleanisEmpty(){
83//TODOAuto-generatedmethodstub
84returncount==0;
85}
86
87}


2、代码测试:

  设计一个程序模仿操作系统的进程管理问题。进程服务按优先级高的先服务,优先级相同的先到先服务的原则管理。

  模仿数据包含两个部分:进程编号和优先级。如下有五个进程:

    130

    220

    340

    420

    50----------优先级最高,先服务

(4)Test.java:

1publicclassTest{
2
3publicstaticvoidmain(String[]args)throwsException{
4
5PrioritySequenceQueuequeue=newPrioritySequenceQueue();
6Elementtemp;
7
8//五个进程入队
9queue.append(newElement(1,30));
10queue.append(newElement(2,20));
11queue.append(newElement(3,40));
12queue.append(newElement(4,20));
13queue.append(newElement(5,0));
14
15//按照优先级出队。
16System.out.println("编号优先级");
17while(!queue.isEmpty()){
18temp=(Element)queue.delete();
19System.out.println(temp.getElement()+""+temp.getPriority());
20}
21}
22}


运行效果:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: