定义一个通用的存储数据的结构,使用时只需更改传入对象的类型就好。这样一个用来存储数据的通用结构就是队列,队列中一般有几种基本的方法:数据对象的添加、插入、删除、获取、队列长度的获取。
有两种基本的实现队列的方法: 用数组实现的队列和用链表实现的队列。
一、用数组实现的队列
1、如何用数组实现队列
用数组实现的队列实质上就是用队列包装了的数组。在队列中还是用一个数组来存储信息,并定义了几个队数组进行基本操作(添加、获取数组元素、数组长度)的方法。
现在假设要定义一个用来存储在画布上画直线信息的队列。那么首先要定义一个直线信息类MyLine,类中有四个属性,即直线的起点终点坐标,以及设置和获取四个属性的方法。
然后要定义一个队列类MyQueue。在队列类中应该有一个存储信息的数组str,由于用户需求的不确定性,一个确定长度的数组肯定是不能满足要求的,所以我们应该使这个数组的长度“可变”。在“添加”方法中,可以这样来实现数组长度的可变性:将数组的初始长度设为0,在方法中新建一个长度比原数组长度大1的数组;将要添加的数据放在新数组的最后一位;遍历原数组,将原数组中数据复制到新数组中;使str指向新数组。这样,每新增一个数组元素,数组长度就加1。能够满足用户的存储要求,同时有不浪费空间。
最后,可以建立一个测试类,对队列功能进行测试。
队列类的具体代码如下:
/** * 用数组实现的队列类 */ public class MyQueue { MyLine myl; private MyLine[] str = new MyLine[0]; /** * 添加直线信息的方法 * @param myl 接收的MyLine类对象 */ public void add(MyLine myl){ //1、创建一个临时数组,它的长度是str的长度加1 MyLine[] str2 = new MyLine[str.length +1]; //2、将接收的ml对象添加到新数组的最后一位 str2[str.length] = myl; //3、将原数组中的信息复制到新数组中 for(int i=0;i<str.length;i++){ str2[i]=str[i]; } //4、使原数组指向新数组 str=str2; } /** * 获得第index位的数组元素的方法 * @param index 指定的位数 * @return 第index位的数组元素信息 */ public MyLine get(int index){ MyLine myl =str[index]; return myl; } /** * 获得队列的长度的方法 * @return 队列长度数值 */ public int size(){ return str.length; } }
2、数组队列的优化
现在,我们已经建立一个数组队列。那么,这个队列是否就是最优队列了呢?
显然不是的,这个队列就有两个明显的缺点:
(1) 数组只能接收MyLine类型的对象。
(2) 每次添加数组元素都要新建一个数组,并把原数组中的数据复制到新数组中,这样不仅浪费内存空间,还延长了执行时间。
(3) 队列中缺少插入和删除的方法。
要解决第一个问题,需要先要理解一个概念:泛型。
所谓泛型,其实就是使一个类中的方法可以接收各种类型的对象,只需在实例化该类是进行指定就行。实现方式为:在类名后加上“<E>”。
定义:public class 类名<E>{}
实例化:类名<要接收的对象类型> 对象名 = new类名<要接收的对象类型>();
要解决第二个问题,可以为数组设置一个初始长度(比如说100),当数组中存满100个数组元素后,新建一个比原数组长10的数组取代原数组。这样,添加前100个数组元素就不用新建数组了。
这样改之后又有一个问题了,用户的需求不同,偏好的数组初始长度和每次增加的长度也不同。为使用户根据自己的需求更改设置,我们可以重载构造方法来设置这两个值。
优化后的数组队列类代码如下:
/** * 队列类,存放数组元素信息(优化后的数组队列) */ public class MyQueue<E> { private int sLength; //数组初始长度 private short inLength; //数组每次增加长度 private int count = 0; //计数器,数值为数组当前长度 private Object[] str; /** * 重载的构造方法,由使用者决定sLength和inLength; * @param sLength 数组初始长度 * @param inLength 数组每次增加的长度 */ public MyQueue(int sLength, short inLength) { this.sLength = sLength; this.inLength = inLength; str = new Object[sLength]; } /** * 重载的构造方法,有使用者决定sLength值,inLength值默认 * @param sLength 数组初始长度 */ public MyQueue(int sLength) { this(sLength, (short) 10); } /** * 重载的构造方法,由使用者决定inLength值,sLength值默认 * @param inLength 数组每次增加的长度 */ public MyQueue(short inLength) { this(100, inLength); } /** * 无参构造器,使用默认的inLength和sLength值 */ public MyQueue() { this(100, (short) 10); } /** * 添加数组元素的方法 * @param e 接受的数组元素对象 */ public void add(E e){ if(count<sLength){ str[count]=e; count++; }else if(count>=sLength){ //1、创建一个临时数组,它的长度是str的长度加inLength Object[] str2 = new Object[str.length +inLength]; //2、将接收的ml对象添加到新数组的最后一位 str2[count] = e; //3、将原数组中的信息复制到新数组中 for(int i=0;i<count;i++) str2[i]=str[i]; //4、使原数组指向新数组 str=str2; count++; } } /** * 获得第index位的数组元素 * @param index 指定的位数 * @return 第index位的数组元素信息 */ public E get(int index) { E e = (E)str[index]; return e; } /** * 将新元素插入到给定的index位置上,原index位置及之后的元素向后移一位 * @param e 要插入的元素 * @param index 插入的位置 */ public void insert(E e,int index){ if(index<0||index>=str.length){ System.out.println("超出数组范围"); }else if(index>=count){ System.out.println("插入的为空区"); }else if(count<str.length){ for(int i=count-1;i>=index;i--) str[i+1]=str[i]; str[index]=e; count++; }else if(count==str.length){ Object[] str3=new Object[str.length +inLength]; for(int i=0;i<index;i++) str3[i]=str[i]; str3[index]=e; for(int i=index;i<count;i++) str3[i+1]=str[i]; str=str3; count++; } } /** * 删除指定位置的数组元素 * @param index 要删除的数组元素位置 * @return 被删除的数组元素 */ public E remove(int index){ if(index<0||index>=str.length){ System.out.println("超出数组范围"); }else if(index>=count){ System.out.println("删除的为空区"); }else if(index<count){ E e =(E)str[index]; for(int i=index;i<count-1;i++) str[i]=str[i+1]; count--; return e; } return null; } /** * 获得队列的长度 * * @return 队列长度数值 */ public int size() { return count; } }
二、用链表实现的队列
在C语言中,链表是一种数据结构。以单链表为例,单链表即由若干物理上不相连的结点链接而成的表,每个结点有一个数据域和一个指针域,数据域中存储要放置的数据,指针域中的指针指向下个结点的存储地址。
在这里我们还是沿用C语言中的定义。要用链表实现队列,首先要定义一个结点类ListNode,其中包括它的数据域和指针域。具体代码如下:
/** * 结点类,定义结点数据域和指针域 */ public class ListNode { private Object obj; private ListNode next; //重载构造方法,初始化数据域的值 public ListNode(Object obj){ this.obj=obj; } /** * 设置数据域的值 * @param obj */ public void setObj(Object obj){ this.obj=obj; } /** * 获取数据域的值 * @return 数据域值 */ public Object getObj(){ return obj; } /** * 设置指针域指向 * @param next */ public void setNext(ListNode next){ this.next=next; } /** * 获得指针域指向 * @return 指针域指向的结点 */ public ListNode getNext(){ return next; } }
这之后,我们就可以建立链表队列类了。链表队列中的基本方法与数字队列一样,只是因结构不同,各个方法内部的实现不同。具体代码如下(以下代码中的方法还有别的实现方法有待探索):
/** * 用链表实现的队列类 */ public class SingleList { //定义头结点 ListNode head;; /** * 向链表中添加新的结点 * @param obj 新结点数据域值 */ public void add(Object obj) { //实例化一个要加入的新结点 ListNode node = new ListNode(obj); //判断头结点是否为空,若为空,则将新结点设为头结点 if (head == null) { head = node; } else { //若头结点非空,将新结点添加到链表最后 ListNode temp = head; //遍历链表,找到目前的最后一个结点 while (temp.getNext() != null) temp = temp.getNext(); //将新结点设为目前最后一个结点的下一个结点 temp.setNext(node); } } /** * 向链表中某位置插入新结点,插入后原位置及之后的结点向后移 * @param obj 新结点数据域值 * @param index 插入位置 */ public void insert(Object obj, int index) { int len = size(); ListNode node = new ListNode(obj); ListNode temp = head; //判断给出的位置是否超出队列范围 if (index < 0 || index >= len) { System.out.println("超出队列范围"); return; } //判断要插入的位置是否为第一个位置 if (index == 0) { //将头结点接到新结点后,并将新结点设为头结点 node.setNext(temp); head = node; } else { //遍历链表,寻找要插入结点的前一个结点 for (int i = 0; i < index-1; i++){ temp = temp.getNext(); } //在index位置插入新结点 node.setNext(temp.getNext()); temp.setNext(node); } } /** * 从链表中移除某位置的结点,移除后该位置之后的结点前移 * @param index 要移除结点的位置 * @return 移除的结点数据域的值 */ public Object remove(int index) { int len = size(); ListNode temp = head; ListNode curNode = head.getNext(); //判断给出的位置是否超出队列范围 if (index < 0 || index >= len) { System.out.println("超出队列范围"); return null; } //判断要移除的结点是否在第一个位置 if(index==0){ //若是,则将头结点的下一个结点设为新的头结点 head=head.getNext(); return temp.getObj(); } //遍历链表,最后curNode为要移除的结点,temp为要移除结点的前一个结点 for(int i=0;i<index-1;i++){ temp=curNode; curNode=curNode.getNext(); } //将temp直接与curNode的下一个结点相连 temp.setNext(curNode.getNext()); return curNode.getObj(); } /** * 获得某位置的结点的数据域值 * @param index 需获得结点的位置 * @return 结点的数据域值 */ public Object get(int index) { int len = size(); ListNode temp = head; //判断给出的位置是否超出队列范围 if (index < 0 || index >= len) { System.out.println("超出队列范围"); return null; } //遍历链表,获得index位置的结点 for (int i = 0; i < index; i++) temp = temp.getNext(); return temp.getObj(); } /** * 求链表长度 * @return 链表长度 */ public int size() { int length = 0; ListNode temp = head; while (temp != null) { temp = temp.getNext(); length++; } //System.out.println("length="+length); return length; } }
当然,链表队列中也可以用泛型。但是,要注意将结点类也用泛型表示。