欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

用数组/链表实现的队列

程序员文章站 2022-03-24 17:28:20
...
在之前的重绘中,用到了数组来传递数据,但是这个数组只能用于某种特定情形中,如果要存入别的数据,就又要定义新的数组了,这样既麻烦又耗时。为了使用方便,我们可以定义一个通用的存储数据的结构,使用时只需更改传入对象的类型就好。这样一个用来存储数据的通用结构就是队列,队列中一般有几种基本的方法:数据对象的添加、插入、删除、获取、队列长度的获取。
有两种基本的实现队列的方法: 用数组实现的队列和用链表实现的队列。
一、用数组实现的队列
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;
}
}
当然,链表队列中也可以用泛型。但是,要注意将结点类也用泛型表示。
相关标签: java 数据结构