数据结构Java实现07----队列:顺序队列&顺序循环队列、链式队列、顺序优先队列
2015-09-08 23:26
1106 查看
一、队列的概念:
队列(简称作队,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。
队列(简称作队,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:(队列接口)
//队列接口 publicinterfaceQueue{ //入队 publicvoidappend(Objectobj)throwsException; //出队 publicObjectdelete()throwsException; //获得队头元素 publicObjectgetFront()throwsException; //判断对列是否为空 publicbooleanisEmpty(); }
(2)CircleSequenceQueue.java:(循环顺序队列)
//循环顺序队列 publicclassCircleSequenceQueueimplementsQueue{ staticfinalintdefaultSize=10;//默认队列的长度 intfront;//队头 intrear;//队尾 intcount;//统计元素个数的计数器 intmaxSize;//队的最大长度 Object[]queue;//队列 publicCircleSequenceQueue(){ init(defaultSize); } publicCircleSequenceQueue(intsize){ init(size); } publicvoidinit(intsize){ maxSize=size; front=rear=0; count=0; queue=newObject[size]; } @Override publicvoidappend(Objectobj)throwsException{ //TODOAuto-generatedmethodstub if(count>0&&front==rear){ thrownewException("队列已满!"); } queue[rear]=obj; rear=(rear+1)%maxSize; count++; } @Override publicObjectdelete()throwsException{ //TODOAuto-generatedmethodstub if(isEmpty()){ thrownewException("队列为空!"); } Objectobj=queue[front]; front=(front+1)%maxSize; count--; returnobj; } @Override publicObjectgetFront()throwsException{ //TODOAuto-generatedmethodstub if(!isEmpty()){ returnqueue[front]; }else{ returnnull; } } @Override publicbooleanisEmpty(){ //TODOAuto-generatedmethodstub returncount==0; } }
(3)Test.java:
publicclassTest{ publicstaticvoidmain(String[]args)throwsException{ CircleSequenceQueuequeue=newCircleSequenceQueue(); queue.append("a"); queue.append("b"); queue.append("c"); queue.append("d"); queue.append("e"); queue.append("f"); queue.delete(); queue.delete(); queue.append("g"); while(!queue.isEmpty()){ System.out.println(queue.delete()); } } }
运行效果:
4、循环队列应用:
使用顺序循环队列和多线程实现一个排队买票的例子。而且,我们只允许这个队伍中同时排队的只有10个人,那就需要用到队列了。
生产者(等候买票)
消费者(买票离开)
这里面我们需要用到上面的Queue.java类和CircleSequenceQueue.java类。
代码结构:
(3)WindowQueue.java:
//卖票窗口 publicclassWindowQueue{ //卖票的队列 intmaxSize=10; CircleSequenceQueuequeue=newCircleSequenceQueue(maxSize); intnum=0;//统计卖票的数量,一天最多卖100张票。 booleanisAlive=true;//判断是否继续卖票。 //排队买票 publicsynchronizedvoidproducer()throwsException{ if(queue.count<maxSize){ queue.append(num++);//等待买票的数量加1 System.out.println("第"+num+"个客户排队等待买票!"); this.notifyAll();//唤醒卖票的线程 }else{ try{ System.out.println("队列已满...请等待!"); this.wait();//队列满时,排队买票线程等待。 }catch(Exceptionex){ ex.printStackTrace(); } } } //卖票 publicsynchronizedvoidconsumer()throwsException{ if(queue.count>0){ Objectobj=queue.delete(); inttemp=Integer.parseInt(obj.toString()); System.out.println("第"+(temp+1)+"个客户买到票离开队列!"); //如果当前队列为空,并且卖出票的数量大于等于100,说明卖票结束 if(queue.isEmpty()&&this.num>=100){ this.isAlive=false; } this.notifyAll();//唤醒排队买票的线程。 }else{ try{ System.out.println("队列已空...请等待!"); this.wait();//队列空时,卖票线程等待。 }catch(Exceptionex){ ex.printStackTrace(); } } } }
(4)Producer.java:
//买票者 publicclassProducerimplementsRunnable{ WindowQueuequeue; publicProducer(WindowQueuequeue){ this.queue=queue; } @Override publicvoidrun(){ //TODOAuto-generatedmethodstub while(queue.num<100){ try{ queue.producer(); }catch(Exceptionex){ ex.printStackTrace(); } } } }
(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:
publicclassTest{ publicstaticvoidmain(String[]args)throwsException{ WindowQueuequeue=newWindowQueue(); Producerp=newProducer(queue);//注意一定要传同一个窗口对象 Consumerc=newConsumer(queue); //排队买票线程 ThreadpThread=newThread(p); //卖票线程 ThreadcThread=newThread(c); pThread.start();//开始排队买票 cThread.start();//开始卖票 } }
注意第07行的注释。
运行效果:
四、链式队列:
链式队列其实就是特殊的单链表,只不过它只能尾进头出而已。链式队列的存储结构如下图所示:
1、链式队列的实现:
(1)Node.java:结点类
//结点类 publicclassNode{ Objectelement;//数据域 Nodenext;//指针域 //头结点的构造方法 publicNode(Nodenextval){ this.next=nextval; } //非头结点的构造方法 publicNode(Objectobj,Nodenextval){ this.element=obj; this.next=nextval; } //获得当前结点的后继结点 publicNodegetNext(){ returnthis.next; } //获得当前的数据域的值 publicObjectgetElement(){ returnthis.element; } //设置当前结点的指针域 publicvoidsetNext(Nodenextval){ this.next=nextval; } //设置当前结点的数据域 publicvoidsetElement(Objectobj){ this.element=obj; } publicStringtoString(){ returnthis.element.toString(); } }
(2)Queue.java:
//队列接口 publicinterfaceQueue{ //入队 publicvoidappend(Objectobj)throwsException; //出队 publicObjectdelete()throwsException; //获得队头元素 publicObjectgetFront()throwsException; //判断对列是否为空 publicbooleanisEmpty(); }
(3)LinkQueue.java:
publicclassLinkQueueimplementsQueue{ Nodefront;//队头 Noderear;//队尾 intcount;//计数器 publicLinkQueue(){ init(); } publicvoidinit(){ front=rear=null; count=0; } @Override publicvoidappend(Objectobj)throwsException{ //TODOAuto-generatedmethodstub Nodenode=newNode(obj,null); //如果当前队列不为空。 if(rear!=null){ rear.next=node;//队尾结点指向新结点 } rear=node;//设置队尾结点为新结点 //说明要插入的结点是队列的第一个结点 if(front==null){ front=node; } count++; } @Override publicObjectdelete()throwsException{ //TODOAuto-generatedmethodstub if(isEmpty()){ newException("队列已空!"); } Nodenode=front; front=front.next; count--; returnnode.getElement(); } @Override publicObjectgetFront()throwsException{ //TODOAuto-generatedmethodstub if(!isEmpty()){ returnfront.getElement(); }else{ returnnull; } } @Override publicbooleanisEmpty(){ //TODOAuto-generatedmethodstub returncount==0; } }
(4)Test.java:
publicclassTest{ publicstaticvoidmain(String[]args)throwsException{ LinkQueuequeue=newLinkQueue(); queue.append("a"); queue.append("b"); queue.append("c"); queue.append("d"); queue.append("e"); queue.append("f"); queue.delete(); queue.delete(); queue.append("g"); while(!queue.isEmpty()){ System.out.println(queue.delete()); } } }
运行效果:
2、链式队列的应用:
题目:
编写一个判断一个字符串是否是回文的算法。
思路:
设字符数组str中存放了要判断的字符串。把字符数组中的字符逐个分别存入一个队列和栈中,然后逐个出队和出栈比较出队的字符与出栈的字符是否相同,若全部相等则该字符串为回文。
代码实现:
这里面需要用到上面一段中的LinkQueue类。代码结构如下:
(4)Stack.java:栈接口
//栈接口 publicinterfaceStack{ //入栈 publicvoidpush(Objectobj)throwsException; //出栈 publicObjectpop()throwsException; //获得栈顶元素 publicObjectgetTop()throwsException; //判断栈是否为空 publicbooleanisEmpty(); }
(5)LinkStack.java:
publicclassLinkStackimplementsStack{ Nodehead;//栈顶指针 intsize;//结点的个数 publicLinkStack(){ head=null; size=0; } @Override publicObjectgetTop()throwsException{ //TODOAuto-generatedmethodstub returnhead.getElement(); } @Override publicbooleanisEmpty(){ //TODOAuto-generatedmethodstub returnhead==null; } @Override publicObjectpop()throwsException{ //TODOAuto-generatedmethodstub if(isEmpty()){ thrownewException("栈为空!"); } Objectobj=head.getElement(); head=head.getNext(); size--; returnobj; } @Override publicvoidpush(Objectobj)throwsException{ //TODOAuto-generatedmethodstub head=newNode(obj,head); size++; } }
(6)Test.java:测试类
publicclassTest{ publicstaticvoidmain(String[]args)throwsException{ Stringstr1="ABCDCBA";//是回文 Stringstr2="ABCDECAB";//不是回文 try{ if(Test.isHuiWen(str1)){ System.out.println(str2+":是回文!"); }else{ System.out.println(str2+":不是回文!"); } }catch(Exceptionex){ ex.printStackTrace(); } } //方法:判断字符串是否回文 publicstaticbooleanisHuiWen(Stringstr)throwsException{ intn=str.length(); LinkStackstack=newLinkStack();//创建堆栈 LinkQueuequeue=newLinkQueue();//创建队列 for(inti=0;i<n;i++){ stack.push(str.subSequence(i,i+1));//把字符串每个字符压进堆栈 queue.append(str.subSequence(i,i+1));//把字符串每个字符压入队列 } while(!queue.isEmpty()&&!stack.isEmpty()){ if(!queue.delete().equals(stack.pop())){//出队列,出栈,同时判断是否相同 returnfalse; } } returntrue; } }
3、循环队列和链式队列的比较:
(1)从时间上看,它们的基本操作都是常数时间,即O(1)的。不过循环队列是事先申请好空间,使用期间不释放;而链式队列,每次申请和释放结点也会存在一定的时间开销,如果入栈和出栈比较频繁,则两者还是有细微的差别。
(2)从空间上看,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链式队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链式队列更加灵活。
总结:总的来说,在可以确定队列长度的最大值的情况下,建议用循环队列,如果你无法估计队列的长度,那就用链式队列。
五、优先级队列:
优先级队列是带有优先级的队列。
用顺序存储结构实现的优先级队列称作顺序优先级队列。
用链式存储结构存储的优先级队列称作链式优先级队列。
顺序优先级队列和顺序循环队列相比主要有两点不同:
(1)对于顺序优先级队列来说,出队列操作不是把队头数据元素出队列,而是把队列中优先级最高的数据元素出队列。(入队操作没区别)
(2)对于顺序优先级队列来说,数据元素由两部分组成,一部分是原先意义上的数据元素,另一部分是优先级。通常设计优先级为int类型的数值,并规定数值越小优先级越高。
1、顺序优先队列的实现:
设计顺序优先级队列分为两个类:
数据元素类
优先级队列类
代码实现:
(1)Element.java:
//优先级队列元素类
publicclassElement{
privateObjectelement;//数据
privateintpriority;//优先级
publicElement(Objectobj,intpriority){
this.element=obj;
this.priority=priority;
}
publicObjectgetElement(){
returnelement;
}
publicvoidsetElement(Objectelement){
this.element=element;
}
publicintgetPriority(){
returnpriority;
}
publicvoidsetPriority(intpriority){
this.priority=priority;
}
}
(2)Queue.java:
//队列接口 publicinterfaceQueue{ //入队 publicvoidappend(Objectobj)throwsException; //出队 publicObjectdelete()throwsException; //获得队头元素 publicObjectgetFront()throwsException; //判断对列是否为空 publicbooleanisEmpty(); }
(3)PrioritySequenceQueue.java:
//优先级队列
publicclassPrioritySequenceQueueimplementsQueue{
staticfinalintdefaultSize=10;//默认队列长度
intfront;//队头
intrear;//队尾
intcount;//计数器
intmaxSize;//队列最大长度
Element[]queue;//队列
publicPrioritySequenceQueue(){
init(defaultSize);
}
publicPrioritySequenceQueue(intsize){
init(size);
}
publicvoidinit(intsize){
maxSize=size;
front=rear=0;
count=0;
queue=newElement[size];
}
@Override
publicvoidappend(Objectobj)throwsException{
//TODOAuto-generatedmethodstub
//如果队列已满
if(count>=maxSize){
thrownewException("队列已满!");
}
queue[rear]=(Element)obj;
rear++;
count++;
}
@Override
publicObjectdelete()throwsException{
//TODOAuto-generatedmethodstub
if(isEmpty()){
thrownewException("队列为空!");
}
//默认第一个元素为优先级最高的。
Elementmin=queue[0];
intminIndex=0;
for(inti=0;i<count;i++){
if(queue[i].getPriority()<min.getPriority()){
min=queue[i];
minIndex=i;
}
}
//找的优先级别最高的元素后,把该元素后面的元素向前移动。
for(inti=minIndex+1;i<count;i++){
queue[i-1]=queue[i];//移动元素
}
rear--;
count--;
returnmin;
}
@Override
publicObjectgetFront()throwsException{
//TODOAuto-generatedmethodstub
if(isEmpty()){
thrownewException("队列为空!");
}
//默认第一个元素为优先级最高的。
Elementmin=queue[0];
intminIndex=0;
for(inti=0;i<count;i++){
if(queue[i].getPriority()<min.getPriority()){
min=queue[i];
minIndex=i;
}
}
returnmin;
}
@Override
publicbooleanisEmpty(){
//TODOAuto-generatedmethodstub
returncount==0;
}
}
2、代码测试:
设计一个程序模仿操作系统的进程管理问题。进程服务按优先级高的先服务,优先级相同的先到先服务的原则管理。
模仿数据包含两个部分:进程编号和优先级。如下有五个进程:
130
220
340
420
50----------优先级最高,先服务
(4)Test.java:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
PrioritySequenceQueuequeue=newPrioritySequenceQueue();
Elementtemp;
//五个进程入队
queue.append(newElement(1,30));
queue.append(newElement(2,20));
queue.append(newElement(3,40));
queue.append(newElement(4,20));
queue.append(newElement(5,0));
//按照优先级出队。
System.out.println("编号优先级");
while(!queue.isEmpty()){
temp=(Element)queue.delete();
System.out.println(temp.getElement()+""+temp.getPriority());
}
}
}
运行效果:
相关文章推荐
- 数据结构之队列
- java数据结构与算法值优先级队列
- 数据结构---线性顺序表操作(c++)
- 数据结构——二叉树的层次遍历
- 数据结构学习之路-第三章:栈的应用
- 数据结构面试题1.2.2-下排每个数都是先前上排那十个数在下排出现的次数
- 数据结构之线性表的顺序结构操作2-(删除,插入,查值等)
- 数据结构之---C语言实现快速排序(多个版本)
- 数据结构——有序线性表的的插入与删除
- 数据结构学习之 union 共用体(union 到底有什么用?)
- 浅谈数据结构-顺序表查找
- 20150908数据结构(C语言版)算法时间复杂度问题
- 数据结构之栈
- 数据结构之---C语言实现归并排序
- 数据结构之---C语言实现堆排序
- 数据结构面试题1.2.1-把二元查找树转变成排序的双向链表
- 数据结构之单向链表操作1-(插入,删除,交换,反转,排序等操作)
- 浅谈数据结构-查找
- 数据结构之---C语言实现希尔排序
- 线性表的基本操作-数据结构