用数组/链表实现的队列

在之前的重绘中,用到了数组来传递数据,但是这个数组只能用于某种特定情形中,如果要存入别的数据,就又要定义新的数组了,这样既麻烦又耗时。为了使用方便,我们可以定义一个通用的存储数据的结构,使用时只需更改传入对象的类型就好。这样一个用来存储数据的通用结构就是队列,队列中一般有几种基本的方法:数据对象的添加、插入、删除、获取、队列长度的获取。
有两种基本的实现队列的方法: 用数组实现的队列和用链表实现的队列。
一、用数组实现的队列
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;
}
}
当然,链表队列中也可以用泛型。但是,要注意将结点类也用泛型表示。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值