数据结构学习笔记——表、栈、队列的Java实现
首先介绍抽象数据类型(Abstract data type, ADT)是带有一组操作的一些对象的集合。举个例子,假设“空调”这个ADT必须要有“制冷”、“干燥”这两个功能(操作),但是我们并不关心A品牌空调和B品牌空调是如何实现这两个功能,以及C品牌可能拥有额外的“制热”功能,但我们关注的是只要是“空调”这个ADT,就必须要有“制冷”、“干燥”这两个功能(操作)。
概念介绍
-
表(List):可分为ArrayList和LinkedList。
- ArrayList:使用数组储存元素,通过数组的下标对表内元素进行增删改查等系列操作,并记录表中元素个数;
- LinkedList:使用嵌套类Node来储存元素,通过用pre和next把两个元素连接起来,从而使元素的排列形成一条链,可对元素进行增删改查等操作,并记录表中元素的个数和链的头尾两个节点或其一。链表可分为单链表和双链表。
- 可调用方法包括:add(插入)、get(查找)、set(改值)、remove(删除)、clear(初始化)、isEmpty(判断表是否为空)
- 优缺点:由于数组的下标存在,使得ArrayList的get(查找)和set(改值)花费常数时间O(1),但是add(插入)和remove(删除)花销很大,原因是插入和删除的同时需要把数组中其他位置的元素移动位置,时间复杂度为O(N);LinkedList却刚好相反,插入和删除一个元素的花销很小O(1),因为不会影响到表中其他的元素(除了与之相连的两个元素),但是由于只储存了头尾两个节点,对于链中的任意元素,都需要从头节点或者尾节点出发去查找,因此get和set的花销较大,时间复杂度为O(N)。
-
栈(Stack):栈是一个特殊的表,具有Last in first out(后进先出)的特点,只能查找栈顶的元素,进栈时元素压入栈底,出栈时返回栈顶的元素,并弹出该元素。栈也可分为ArrayStack和LinkedStack。
- 可调用方法包括:push(进栈)、pop(出栈)、top(查找顶栈元素)、isEmpty(判断栈是否为空)
- 优缺点:由于栈默认只能操作栈顶的元素,因此优点是运行速度快,不管是push、top还是pop,时间复杂度都为O(1),缺点是看不到栈内的元素,必须等栈顶的元素出栈后才能看到下一个栈内的元素。
-
队列(Queue):队列是一个特殊的表,具有First in first out(先进先出)的特点,入队在队尾插入元素,出队在队头删除元素,记录队列中的元素个数。队列也可分为ArrayQueue和LinkedQueue。
- 可调用方法包括:enQueue(入队)、deQueue(出队)、isEmpty(判断队列是否为空)
- 优缺点:队列的优点也是运行速度快,无论是enQueue(入队)还是deQueue(出队),花销都很小,时间复杂度为O(1)。
应用场景
-
表(List):一般现实生活中可以用表格记录的内容都可以用数据结构表来表示,如学校班级学生名单、实验结果记录等
-
栈(Stack):如某仓库的某个库位的货物竖直堆起来存放,则该货位的储存情况可以用栈来表示,首先进栈的是堆放在最底下的货物,最后进栈的是堆放在最顶上的货物;想要把最底下的货物取出,必须先把堆放在上方的货物依次清空。
-
队列(Queue):如打印机处理任务的序列可用队列来表示,先提交的任务先处理,后提交的任务后处理,符合先进先出的特点。
代码实现
ArrayList的Java实现:
public class myArrayList<AnyType> implements Iterable<AnyType>{
private static final int DEFAULT_CAPACITY=100;//默认表的大小
private int theSize;//表的大小
private AnyType[] theItems;//存放在表里的数据类型
public myArrayList(){
doClear();
}
public void clear(){
doClear();
}
private void doClear(){
theSize=0;
ensureCapacity(DEFAULT_CAPACITY);
}
private void ensureCapacity(int newCapacity){
if(newCapacity<theSize)
return;
AnyType[] old=theItems;
theItems=(AnyType[]) new Object[newCapacity];
for(int i=0;i<size();i++)
theItems[i]=old[i];
}
public int size(){
return theSize;
}
public boolean isEmpty(){
return size()==0;
}
public AnyType get(int idx){//查找
if(idx<0||idx>=size())
throw new ArrayIndexOutOfBoundsException();
return theItems[idx];
}
public void set(int idx,AnyType newVal){//改值
if(idx<0||idx>=size())
throw new ArrayIndexOutOfBoundsException();
theItems[idx]=newVal;
}
public boolean addAfter(AnyType x){//在表尾插入
add(size(),x);
return true;
}
public void add(int idx,AnyType x){//指定位置插入
if(theItems.length==size())
ensureCapacity(size()*2+1);
for(int i=size();i>idx;i--){
theItems[i]=theItems[i-1];
}
theItems[idx]=x;
theSize++;
}
public AnyType remove(int idx){//删除
AnyType removeItem=theItems[idx];
for(int i=idx;i<size()-1;i++){
theItems[i]=theItems[i+1];
}
theSize--;
return removeItem;
}
public java.util.Iterator<AnyType> iterator(){
return new ArrayListIterator();
}
private class ArrayListIterator implements java.util.Iterator<AnyType>{
private int current=0;
@Override
public boolean hasNext() {
return current<size();
}
public AnyType next(){
return theItems[current++];
}
@Override
public void remove() {
myArrayList.this.remove(--current);
}
}
}
LinkedList的Java实现:
public class myLinkedList<AnyType> implements Iterable<AnyType> {
public static class Node<AnyType>{
public AnyType data;//存放在表里的数据类型
public Node<AnyType> pre;
public Node<AnyType> next;
public Node(AnyType d,Node<AnyType> p,Node<AnyType> n){
data=d;
pre=p;
next=n;
}
}
public myLinkedList(){
doClear();
}
public void clear(){
doClear();
}
private void doClear(){
beginMarker=new Node<AnyType>(null,null,null);
endMarker=new Node<AnyType>(null,beginMarker,null);
beginMarker.next=endMarker;
int theSize=0;
}
public int size(){
return theSize;
}
public boolean isEmpty(){
return size()==0;
}
public AnyType get(int idx){//查找
return getNode(idx).data;
}
private Node<AnyType> getNode(int idx){
Node<AnyType> p;
p=beginMarker.next;
for (int i=0;i<idx;i++){
p=p.next;
}
return p;
}
public void addAfter(AnyType x){//在表尾插入
add(size(),x);
}
public void add(int idx,AnyType x){//在指定位置插入
addBefore(getNode(idx),x);
}
private void addBefore(Node<AnyType> p,AnyType val){
Node<AnyType> newNode=new Node<>(val,p.pre,p);
p.pre.next=newNode;
p.pre=newNode;
theSize++;
}
public void remove(int idx){//删除
remove(getNode(idx));
}
private void remove(Node<AnyType> p){
p.pre.next=p.next;
p.next.pre=p.pre;
theSize--;
}
public void set(int idx,AnyType val){//改值
getNode(idx).data=val;
}
private int theSize;//表的大小
private Node<AnyType> beginMarker;
private Node<AnyType> endMarker;
public java.util.Iterator<AnyType> iterator(){
return new LinkedListIterator();
}
private class LinkedListIterator implements java.util.Iterator<AnyType>{
private Node<AnyType> current=beginMarker.next;
public boolean hasNext(){
return current!=endMarker;
}
public AnyType next(){
if(!hasNext())
throw new java.util.NoSuchElementException();
AnyType nextItem=current.data;
current=current.next;
return nextItem;
}
}
}
由于ArrayStack、LinkedStack和ArrayQueue、LinkedQueue两者的实现大同小异,因此这里只选择展示ArrayStack和LinkedQueue的代码实现。
ArrayStack的Java实现:
public class myArrayStack<AnyType> extends myArrayList {
private int topOfStack;//栈顶的下标
private int theSize;//栈的大小
private AnyType[] theArray;//存放在栈里的数据类型
private int DEFAULT_CAPACITY=100;默认栈的大小
public myArrayStack(){//新建一个myStack
doClear();
}
public void clear(){
doClear();
}
private void doClear(){
theSize=0;
topOfStack=-1;
ensureCapacity(DEFAULT_CAPACITY);
}
private void ensureCapacity(int newCapacity){
if(newCapacity<theSize)
return;
AnyType[] old=theArray;
theArray=(AnyType[]) new Object[newCapacity];
for(int i=0;i<size();i++)
theArray[i]=old[i];
}
public int size(){
return theSize;
}
public boolean isEmpty(){
return theSize==0;
}
public void push(AnyType val){//进栈
if(theArray.length==size()){
ensureCapacity(size()*2+1);
}
topOfStack++;
theArray[topOfStack]=val;
theSize++;
}
public AnyType pop(){//出栈
AnyType popItem=theArray[topOfStack];
topOfStack--;
theSize--;
return popItem;
}
public AnyType top(){//读取栈顶的元素
return theArray[topOfStack];
}
}
LinkedQueue的Java实现:
public class myLinkedQueue<AnyType> extends myLinkedList {
private int currentSize;
private Node<AnyType> beginMarker;
private Node<AnyType> endMarker;
public myLinkedQueue(){
doClear();
}
public void clear(){
doClear();
}
private void doClear(){
beginMarker=new Node(null,null,null);
endMarker=new Node(null,beginMarker,null);
beginMarker.next=endMarker;
currentSize=0;
}
public boolean isEmpty(){
return currentSize==0;
}
public void enQueue(AnyType val){//入队
addBefore(endMarker,val);
}
private void addBefore(Node<AnyType> p,AnyType val){
Node<AnyType> newNode=new Node(val,p.pre,p);
p.pre.next=newNode;
p.pre=newNode;
currentSize++;
}
public AnyType deQueue(){//出队
return remove(beginMarker.next);
}
private AnyType remove(Node<AnyType> p){
p.next.pre=p.pre;
p.pre.next=p.next;
currentSize--;
return p.data;
}
public AnyType peek(){//查找队头元素
return beginMarker.next.data;
}
}
测试结果
使用System.nanoTime()来记录运行时间,测试ArrayList、LinkedList、ArrayStack、LinkedStack、ArrayQueue和LinkedQueue这六种数据结构增删改查四种操作各100次,记录其运行时间,每一项操作均记录了5次,得到下面的测试结果:
从表中数据对比可以看出,ArrayList的add和remove操作的运行时间对比LinkedList花销非常大,几乎相差了一个数量级,但是get和set两项操作的花销对比LinkedList又非常小,两者也是几乎相差了一个数量级。充分说明了ArrayList的优点在于get和set的运行速度快,原因是ArrayList使用数组来储存元素,因此运用数组自带的下标来获取元素的花销是十分小的,但是add和remove操作由于需要调整其他元素的位置,因此花销巨大;而LinkedList却恰恰相反,其运用链式结构把相邻的元素关联起来,对于add和remove操作,都只会影响到相邻元素,而对链条上的其他元素没有太大的影响,因此花销较小,但是对于get和set操作,由于没有下标可以寻找,因此需要从头节点或尾节点出发,找到相应的元素,因此花销不小。
Stack和Queue都是一种特殊的List,两者的不同点在于Stack具有后进先出的特点,而Queue具有先进先出的特点,两者都是只能操作处于List最外端的元素(栈顶/队头/队尾),因此增删的操作花销都并不大,运行速度较快。由于两者的特点不同,其对应的应用也不尽相同。
以上博文仅基于博主根据教材《数据结构与算法分析 Java语言描述》(Mark Allen Weiss)第3版中的内容以自己的理解加以转化和记录,若有理解错误或者不到位的地方,欢迎大家指正,多相互交流,谢谢!