数据结构(三)---------队列

对于今天的内容,我相信大家都很容易理解!
在上结说过 栈 先进后出,今天介绍一种先进先出的数据结构---------------队列!

那么我们来看看什么是队列呢?
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

队列有点类似于栈,和栈相比,队列中第一个进,第一个出,而栈则第一个进最后一个出。

说形象一点,就像去排队买早餐一样,只有先排在队尾就可以早一点去买到早饭,越往后排队的人就会越晚买到早餐。
在这里插入图片描述
既然数据结构都有多种物理结构!那么通过接口就可以实现各自对应的结构了。
看看队列接口是如何编写?
在这里插入图片描述
那么动手来试一试吧!

public interface Queue<E> {
		/**
		 * 获取有效元素个数
		 * */
		public int getSize();
		/**
		 * 判空
		 * */
		public boolean isEmpty();
		/**
		 * 清除
		 * */
		public void clear();
		
		/**
		 * 入队一个新元素e
		 * */
		public void enqueue(E e);
		
		/**
		 * 出队一个元素e
		 * */
		public E dequeue();
		
		/**
		 * 获取队首元素(不删除)
		 * */
		public E getFront();
		
		/**
		 * 获取队尾元素(不删除)
		 * */
		public E getRear();
	}

接口写好了,那么如何实现队列的顺序存储结构呢?
定义一个ArrayQueue类去实现队列,看看该如何实现。
在这里插入图片描述
从类图中分析到,ArrayQueue和ArrayList是聚合关系,那么就是说可以在线性表的基础上去实现队列。
接下来我们看看怎么去实现!!!

public class ArrayQueue<E> implements Queue<E> {
		//定义属性
		
		private ArrayList<E> list;
		//构造方法
		public ArrayQueue() {
			list=new ArrayList<E>();
		}
		//有参构造方法 参数为 队列大小
		public ArrayQueue(int capacity){
			list=new ArrayList<E>(capacity);
		}
		
		//获取有效元素大小
		@Override
		public int getSize() {
			return list.getSize();
		}
		//判空
		@Override
		public boolean isEmpty() {
			return list.isEmpty();
		}
		//清除
		@Override
		public void clear() {
			list.clear();
		}
  • 进队列是从线性表的队尾进,从表头出。看看图是不是好理解许多!从线性表的尾进即rear的位置,进入队列后队尾指针后移。
    在这里插入图片描述

     	@Override
     	public void enqueue(E e) {
     		list.addLast(e);
     	}
    
  • 出队列就是从队头出,那么就是在线性表的表头去出,队尾向前移,所有元素也向前移一位。
    在这里插入图片描述

     	@Override
     	public E dequeue() {
     		return list.removeFirst();
     	}
     	//获取队头
     	@Override
     	public E getFront() {
     		return list.getFirst();
     	}
     	//获取队尾
     	@Override
     	public E getRear() {
     		return list.getLast();
     	}
    
  • 队列的实质是ArrayList就和线性表 toString一样 获取内容然后按顺序输出出来。

     	@Override
     	public String toString() {
     		StringBuilder sb=new StringBuilder();
     		sb.append("ArrayQu: size="+getSize()+",capacity="+list.getCapacity()+"\n");
     		if(isEmpty()){
     			sb.append("[]");
     		}else{
     			sb.append('[');
     			for(int i=0;i<getSize();i++){
     				sb.append(list.get(i));
     				if(i==getSize()-1){
     					sb.append(']');
     				}else{
     					sb.append(',');
     				}
     			}
     		}
     		return sb.toString();
     	}
    
  • 重写equals()方法对于比较方法的实质来说,还是对ArrayList的比较,那么我们就用ArrayList的方法去比较。
    那么问题来了,怎么去比较呢? 将比较对象类型进行类型转换,用这个类型的实质,去实现队列的比较。用最底层实现的ArrayList去实现。

     	@Override
     	public boolean equals(Object obj) {
     		if(obj==null){
     			return false;
     		}
     		if(obj==this){
     			return true;
     		}
     		if(obj instanceof ArrayQueue){
     			ArrayQueue l=(ArrayQueue) obj;
     				return list.equlas(l.list);
     		}
     		return false;
     	}
     }
    
  • 看过队列的顺序存储发现了什么问题?
    首先时间复杂度上
    队列---------入队平均 O(1)每一次只需在队尾增加就行
    队列---------出队O(n).(只要出队,在队列中的每一个元素都要移动)

    那么想要时间复杂度小,就需要让数据执行的次数不随N的增大而变大。
    这么说也不好理解的话,不管有多少数据,让每一处理都执行1次。
    相对队头来说,移动元素 和 移动指针来说,相对指针会容易一点。
    所以移动队头指针。
    在这里插入图片描述
    显而易见,问题又来了?
    出队后的的空间怎么在利用?
    扩容?保证 永远能存数据,但是空间只会越来越多的浪费。
    那么只要想到重复利用—可能想法就是,循环了吧!
    那么来看看循环会怎样的效果?
    在这里插入图片描述

在这个基础上去添加数据,用循环的想法的话是不是这样呢

在这里插入图片描述
是不是解决了问题了!开心的的赶紧鼓鼓掌?
那么请问如何知道这是一个空的队列,满队列呢?

  • 判满 (rear + 1)%list.length == front
  • 判空 (rear + 1)%list.length == front

蒙了吧? 空满如何区分呢?
其实区分的意义是为了让它有自己的特性,那么用队尾永远指向一个空,就可以区分空还是满了!
在这里插入图片描述
这样?
那么我问你是不是永远会有一个空间是浪费的呢? 其实浪费一个可以忽略的,但是你必须要记住一点就是,有效元素存储空间会减一的!
那么在创建空间是不是应该,在原有的长度上加1避免空间缺失。

在这里插入图片描述
你在容积创建的时候加1,在使用的时候不会发现它的长度会发生变化。在使用的时候肯定不是在内部去使用。

看来循环队列可以轻松去处理队列的问题。
那么我们来看看如何编写循环队列ArrayQueueLoop。理解循环的意思其实是角标的循环

	public class ArrayQueueLoop<E> implements Queue<E>{
  • 属性的定义

     	private E[] data;    //创建一个数组
     	private int front;    //头指针
     	private int rear;		//尾指针
     	private int size;     //有效元素个数
     	private static int DEFAULT_SIZE=10;   //默认容积
     	
     	public ArrayQueueLoop() {
     		this(DEFAULT_SIZE);
     	}
     	public ArrayQueueLoop(int capacity){
     		data=(E[]) new Object[capacity+1];
     		front=0;   //默认开始位置为
     		rear=0;
     		size=0;
     	}
     	//获取有效元素个数
     	@Override
     	public int getSize() {
     		return size;
     	}
     
     	@Override
     	public boolean isEmpty() {    // 第三布优化 让rear 指向空 
     										//所以 一旦Front = rear 就都为空
     		return front==rear&&size==0;
     	}
     
     	@Override
     	public void clear() {
     		size=0;
     		front=0;
     		rear=0;
     		//与其缩容清空 不如重新创建数组   (相比之下 自己决定)
     	}
    
  • 进队列只要遇到插入操作就必须要考率到,是否满?满则扩容或者抛异常等,进队列只需在队尾添加即可,将队尾指针后移。然后队尾指针常指向有效长度的位置,并为空。

     	@Override
     	public void enqueue(E e) {
     		if((rear+1)%data.length==front){  //判满
     										//让尾指针下一位是头指针就行
     			//【扩容】
     			resize(data.length*2-1);   //因为rear 总指向空
     		}
     		data[rear]=e;   // 进队列  从队尾进 初始为 0 的位置
     		rear=(rear+1)%data.length;
     		size++;
     	}
    
  • 扩容/缩容的思想就是,创建新的数组存放需要复制的元素,将新的数组地址给原数组。

     	private void resize(int newLen) {
     		E[] newData=(E[]) new Object[newLen];
     		int index=0;//表示新数组角标 
     		for(int i=front;i!=rear;i=(i+1)%data.length){
     			newData[index++]=data[i]; //index++ 先用在加
     		//循环条件 i != rear  指的是front 下一个是 rear指向的为空 
     		// 每一次  i=(i+1)%data.length    找到循环队列中所 对应下标
     			
     		}	
     		front=0;
     		rear=index;  // 只有指向为空的时候 才会跳出循环此时index为空
     		data=newData;
     	}
    
  • 出队先判断是否有元素,出队需要返回出队的数据,队头需要后移,那么,删除了很多元素后,容积过大,是不是就会有不必要的空间浪费,所以需要扩容。

     	@Override
     	public E dequeue() {
     		if(isEmpty()){
     			throw new NullPointerException("队列为空!");
     		}
     		E e=data[front];    //获取
     		front=(front+1)%data.length;  
     		size--;
     		if(size<=data.length/4&&data.length>DEFAULT_SIZE){
     			resize(data.length/2+1);
     		}
     		return e;
     	}
     	//获取队头信息
     	@Override
     	public E getFront() {
     		return data[front];
     	}
     	//获取队尾信息
     	@Override
     	public E getRear() {
     		return data[(data.length+rear-1)%data.length];
     		//在循环队列中 总长度 加  队尾指针 数 减 1 对 数组取余
     	//因为 循环 所以 rear(指向有效元素下一位  为 空) 可以为 
     //任意的指针  如果指向 0 直接减1 如何减?
     		// 所以 用长度 加 队尾 减 1 指向  循环列表中 的 前一个位置
     		//就好像  一种有7天 今天星期1 再过 7天星期几  当然星期1
     //(循环)  
     		
     	}
    
  • 遍历出数据就可,注意只要知道如何结束,就行。

     	@Override
     	public String toString() {
     		
     		StringBuilder sb=new StringBuilder();
     		sb.append("ArrayQueueLoop: size="+getSize()+",capacity="+(data.length-1)+"\n");
     		if(isEmpty()){
     			sb.append("[]");
     		}else{
     			sb.append('[');
     			for(int i=front;i!=rear;i=(i+1)%data.length){
     				sb.append(data[i]);
     				if((i+1)%data.length==rear){
     						//一但为空就 结束
     					sb.append(']');
     				}else{
     					sb.append(',');
     				}
     			}
     		}
     		return sb.toString();
     	}
    
  • 比较方法的重写,实质是对底层数组元素比较

     	@Override
     	public boolean equals(Object obj) {
     		if(obj == null) {
     			return false;
     		}
     		if(obj == this) {
     			return true;
     		}
     		if(obj instanceof ArrayQueueLoop) {
     			ArrayQueueLoop aql = (ArrayQueueLoop) obj;
     			if(getSize() == aql.getSize()) {
     				for(int i = 0 ; i != rear;i = (i+1)%data.length) {
     					if(data[i] != aql.data[i]) {
     						return false;
     					}
     				}
     				return true;
     			}
     		}
     		return false;
     	}
     }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值