数据结构与算法(上) Datawhale组队学习——队列

引言

学习了JAVA的基础知识以后,这是本人第一次接触数据结构与算法的知识,为了更高效以及自律的学习,我加入了Datawhale学习小组


Task04------队列


理论部分:


一、用数组实现一个顺序队列

1.队列的特征

与 “栈” 类似,“队列”也偏向于一种 “工具”,“先进先出(FIFO)“是其特性,队列的基本操作

2.使用单指针的顺序队列

只用一个 rear 指针完成队列的基本操作,只要保证 queArray[0] 的位置都是队首元素即可
绘制不易

class SQueue<T>{
	private T[] queArray;
	private int maxSize;
	private int rear;
	
	SQueue(int max){
		maxSize=max;
		queArray=(T[])new Object[maxSize]; 
		rear=0;	//此时没有元素
	}
	SQueue(){
		this(10);
	}
	
	public void insert(T data) {
		if(rear<maxSize)
			queArray[rear++]=data;
		else
			System.out.println("队列已满!");
	}
	public void remove() {
		if(rear>0){	//队列中还有元素
			for(int i=0;i<rear-1;i++)	//队首元素舍弃,依次将队列元素前移
				queArray[i]=queArray[i+1];
			rear--;
		}
		else
			System.out.println("队列已空!");
	}
	public T peek() {
		if(rear>0)
			return queArray[0];
		else
			return null;
	}
}
3.使用双指针的顺序队列(常用)

实际上队列应该使用双指针,一个 front 标志队首,一个 rear 标志队尾。
仅用一个 rear指针时,每次删除一个元素时都需要搬移所有元素,效率并不高,这里介绍一个利用双指针进行删除的方法,基本原理如下:

1.当front与rear不重叠时,直接将指向队首的指针 front 往后移动一个
绘制不易
2.当 front 与 rear 重叠时,即删除最后一个元素,只要将 front 与 rear 全部复位即可
3.只有当 rear==maxSize 时,才将搬移所有元素
绘制不易
以下是代码实现:

class SQueue<T>{
	private T[] queArray;
	private int maxSize;
	private int front;
	private int rear;
	
	SQueue(int max){
		maxSize=max;
		queArray=(T[])new Object[maxSize]; 
		front=0;
		rear=0;
	}
	SQueue(){
		this(10);
	}
	
	public void insert(T data) {
		if(rear<maxSize)
			queArray[rear++]=data;
		else if((rear-front)==maxSize)
			System.out.println("队列已满!");
		else {
			for(int i=0;i<(rear-front);i++)
				queArray[i]=queArray[front+i];
		}
	}
	public void remove() {
		if(rear==0)
			System.out.println("队列已空!");
		else if(front==(rear-1)) {
			front=0;
			rear=0;
		}
		else
			front++;
	}
	public T peek() {
		if(rear!=0)
			return queArray[front];
		else
			return null;
	}
}

二、用数组实现一个循环队列

1.循环队列的特征

循环队列是十分巧妙的,它允许 队首指针front 和 队尾指针rear 在超过maxSize后回到0,其实可以看做把直线的数组给围成一个圆,从而省去了以上两种队列的“搬移”问题。具体看下面的图解:

  • 删除一个元素依旧是将 front 后移,不过当front已经指向maxSize时,将它移向0
    绘制不易
  • 插入一个元素时,同样,将 rear 后移,当rear==maxSize时,rear=0;

绘制不易
那么我们怎么知道队列是否为空?队列是否满了?其实也很简单,当rear追上front时,队列就空了:当front追上rear时,队列就满了。
但是这里有个小插曲,因为rear与front一开始都是指向0,所以判断rear是否追上front时,只能通过比较 (rear+1) 与 front ,否则第一个元素永远无法插入。但是用rear+1进行比较会导致数组始终有一个空间闲置,容量为10的空间实际只能储存9个数据。这其实是以 “空间换时间”,浪费一个空间,省下了搬移的时间。

绘制不易

2.代码实现
class CQueue<T>{
	private T[] queArray;
	private int maxSize;
	private int front;
	private int rear;
	
	CQueue(int max){
		maxSize=max+1;	//由于会浪费一个空间,为了用户考虑,在这里加上
		queArray=(T[])new Object[maxSize]; 
		front=0;
		rear=0;
	}
	CQueue(){
		this(10);
	}
	
	public void insert(T data) {
		if((rear+1)%maxSize==front)	//判断队尾是否追到队首
			System.out.println("队列已满!");
		else {
			rear%=maxSize;	//队尾循环起来
			queArray[rear++]=data;
		}
	}
	public void remove() {
		if(front==rear)	//判断队首是否追到队尾
			System.out.println("队列已空!");
		else 
			front=(front+1)%maxSize;	//队首也循环起来
	}
	public T peek() {
		if(front==rear)
			return null;
		else
			return queArray[front];
	}
}

二、用链表实现一个链式队列

1.链式队列的特点

同链式栈一样,链式队列理论上也是无限容量,只是出入方式为 “先入先出”罢了

2.代码实现(部分功能)
class LinkedQueue<T> {
	private SLinkedList<T> list;
	
	LinkedQueue(){
		list=new SLinkedList<T>();
	}
	public int getLength() {
		return list.getSize();
	}
	public void insert(T data) {
		list.addLast(data);
	}
	public T remove() {
		return list.remove();
	}
	public boolean isEmpty() {
		return list.getSize()==0;
	}
}

练习部分:


一、模拟银行服务

1.要求

目前,在以银行营业大厅为代表的窗口行业中大量使用排队(叫号)系统,该系统完全模拟了人群排队全过程,通过取票进队、排队等待、叫号服务等功能,代替了人们站队的辛苦。

排队叫号软件的具体操作流程为:

  • 顾客取服务序号
    当顾客抵达服务大厅时,前往放置在入口处旁的取号机,并按一下其上的相应服务按钮,取号机会自动打印出一张服务单。单上显示服务号及该服务号前面正在等待服务的人数。

  • 服务员工呼叫顾客
    服务员工只需按一下其柜台上呼叫器的相应按钮,则顾客的服务号就会按顺序的显示在显示屏上,并发出“叮咚”和相关语音信息,提示顾客前往该窗口办事。当一位顾客办事完毕后,柜台服务员工只需按呼叫器相应键,即可自动呼叫下一位顾客。
    编写程序模拟上面的工作过程.

主要要求如下:

  • 程序运行后,当看到“请点击触摸屏获取号码:”的提示时,只要按回车键,即可显示“您的号码是:XXX,您前面有YYY位”的提示,其中XXX是所获得的服务号码,YYY是在XXX之前来到的正在等待服务的人数。

  • 用多线程技术模拟服务窗口(可模拟多个),具有服务员呼叫顾客的行为,假设每个顾客服务的时间是10000ms,时间到后,显示“请XXX号到ZZZ号窗口!”的提示。其中ZZZ是即将为客户服务的窗口号。

2.分析

用java实现多线程其实很简单,不过要实现线程同步就需要稍微深入的理解对象锁。此题的难点也就在于 “管理多线程”。在看代码前,首先声明一个对之前的代码稍作修改的地方:

  • 之前的所有 remove() 只是删除元素,此次为了方便,remove方法在删除元素的同时返回被删除的元素
3.代码实现

首先是 链队列类 的实现代码,包括Node结点类、SLinkedList单向链表类以及用单向链表实现的链队列

class Node<T> {}
class SLinkedList<T> {}
class LinkedQueue<T> {}

其次是 服务窗口Server类,这是个实现了Runnable的线程类,其中有两个细节点:

  • synchronized(Object obj) 中Object的选取
  • 每次服务10s的实现

第一个点,要让服务窗口之间 “互斥”,必须让对象锁唯一,三个方法:一个是利用static 类锁, 第二个是利用 “字符串常量池”,第三个是将队列queue放入(我看好多同学都用的是这个,因为所有服务窗口共享的就是唯一的队列queue)
第二个点,要让 服务窗口在接收用户的时候“秒接”,在接收后停顿10s,我引入了一个判断服务窗口是否工作的boolean working,看看代码就能明白了。

public class Server implements Runnable{
	private LinkedQueue<Integer> queue;
	static String key=new String("key");
	String key="key"; 
	public boolean working=false;
	Server(LinkedQueue<Integer> queue){
		this.queue=queue;
	}
	public void run() {
		while(true) {
			try {				
				synchronized(key) {
					if(!queue.isEmpty()) {
									  //此处remove方法删除并返回队首ID
						System.out.println("请"+queue.remove()+"号客户到"+Thread.currentThread().getName()+"号窗口!");
						working=true;
					}
				}
				if(working) {
					Thread.sleep(10000);
					working=false;
				}
				else
					Thread.sleep(500);
			}catch(Exception e) {}	
		}
	}
}

最后就是主方法所在的测试类啦,因为客户端一直输入,就不单独做一个类了。这里也有几个点要注意:

  • 实现单个回车就结束输入不能用Scanner,而要用BufferedReader(由于本人是菜鸡还没有细学异常,我的所有的异常全部用Exception且不做处理)
  • 有几个服务窗口就要new几个Server对象,不能将一个Server反复装给Thread,那样只是对一个窗口操作。
  • ”前面有几个人“这个选项根据个人代码的差异有不同的算法,由于我修改了remove方法,“前面有几个人”直接就是队列长度
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Text {
	public static void main(String[] args) {
		LinkedQueue<Integer> queue=new LinkedQueue();
		int server_num=2;
		Server[] server=new Server[server_num];
		Thread[] t=new Thread[server_num];
		for(int i=0;i<server_num;i++) {
			server[i]=new Server(queue);
			t[i]=new Thread(server[i]);
			t[i].setName(i+1+"");
			t[i].start();
		}
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		int ID=0;
		while(true) {
			System.out.println("请点击屏幕取号!");
			try {
				String res=br.readLine();
			} catch (Exception e) {}
			System.out.println("您的号码是: "+(++ID)+" ,您前面还有"+queue.getLength()+"人");
			queue.insert(ID);
		}
	}
}

以下是执行后的效果:
个人觉得用户体验极佳

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值