一:线性表的设计与实现
第1关:顺序表的实现之增删功能
1.任务描述
本关任务:实现一个顺序表,并实现增加元素,删除元素功能
2.相关知识
顺序表:顺序表是将表中的结点依次存放在计算机内存中,一组地址连续的存储单元中
顺序表说白了也就是数组,本关就是利用数组来实现集合
3.题目要求
- 向表中添加元素;
- 向表中指定位置添加元素;
- 删除指定位置的元素并返回被删除的元素。
4.思路解析
我们要先来看懂题目已经给出代码的含义,再来补全代码
首先我们先来看该类的成员变量以及成员方法
在上图中我们可以看到成员变量有两个:elements数组用来存放元素,size用来记录元素个数
其中有两个构造方法,空参默认调用有参构造创建一个长度为1的数组
然后给出了4个已经写好的方法:一个size,两个检查是否存满,以及一个扩容方法
我们来重点看看扩容方法resize
该方法动态扩容数组的大小,就是创建一个容量更大的新数组并拷贝旧数组,最后再让elements继承新数组
最后我们需要完成添加以及删除的功能的代码
首先我们需要知道该数组添加以及删除的逻辑:也就是数组的移位
当我们要添加新元素,就要将指定位置后的所有元素后移一位,再将新元素添加;当我们要删除元素,就要将指定位置后的所有元素前移一位,将要删除的位置元素覆盖
5.本关答案
package step1;
/**
* Created by zengpeng on 2017/12/25.
*/
public class MyArrayList {
private int[] elements;//元素
private int size;//List中当前的元素个数
public MyArrayList() {
this(1);//List默认大小为1
}
/**
* 按指定大小capacity构造List
*
* @param capacity List初始化时的大小
*/
public MyArrayList(int capacity) {
elements = new int[capacity];
size = 0;
}
/**
* 返回List中元素的个数
*
* @return
*/
public int size() {
return size;
}
/**
* 添加一个元素到末尾
*
* @param item
*/
public void Add(int item) {
int len = elements.length;
if (size == len - 1) {//若数组已满,则扩容
resize(2 * len);
}
/********** Begin *********/
elements[size]=item;
size++;
/********** End *********/
}
/**
* 添加一个元素到指定位置index
*
* @param index
* @param item
*/
public void Add(int index, int item) {
validateRangeForAdd(index);//索引越界,报错
int len = elements.length;
if (size == len - 1) {
resize(2 * len);
}
/********** Begin *********/
//index后的元素后移一位
for(int i=size;i>index;i--){
elements[i]=elements[i-1];
}
//将元素加入到指定位置
elements[index]=item;
size++;
/********** End *********/
}
/**
* 删除指定位置index的元素,并返回被删除的元素
*
* @param index
* @return 被删除的元素
*/
public int remove(int index) {
validateRange(index);//索引越界,报错
/********** Begin *********/
int num=elements[index];
//index后的元素前移覆盖删除的元素
for(int i=index;i<size;i++){
elements[i]=elements[i+1];
}
size--;
return num;
/********** End *********/
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRange(int index) {
if (index >= size || index < 0) {
throw new ArrayIndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRangeForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
/**
* 动态扩展数组大小
*
* @param capacity
*/
private void resize(int capacity) {
assert capacity > size;
int[] tmp = new int[capacity];
for (int i = 0; i < size; i++) {
tmp[i] = elements[i];
}
elements = tmp;
}
}
第2关: 顺序表的实现之查询功能
1.任务描述
本关的任务是在上一关的基础上,实现获取指定位置元素的功能。
2.题目要求
- 返回表中下标为
index
的元素。
3.本关答案
数组优点就是查询方便,我们直接返回index的元素即可
/**
* 返回表中下标为index的元素
*
* @param index 下标
* @return
*/
public int get(int index) {
validateRange(index);
/********** Begin *********/
return elements[index];
/********** End *********/
}
第3关:单链表的实现之增删功能
因为数组的缺点就是不易增删,当我们用数组实现添加或删除时都需要去移动数组;而链表的优点就是增删容易
1.任务描述
本关任务:实现一个带头结点的单链表,并实现基本功能:插入元素,删除元素。
2.相关知识
单链表:单项链表的结点包含两部分:数据域以及指针域
其中数据域就是用来存放数据的,指针域用来指向下一个节点
3.题目要求
- 向链表中添加元素,补全
add(int item)
方法; - 向链表中指定位置添加元素,补全
add(int index, int item)
方法; - 删除链表指定位置的元素并返回其值,补全
remove(int index)
方法。
4.思路分析
本关中要求我们使用带头结点的单向链表实现集合
首先我们来看已给出的代码分析
首先就是节点类Node,给出的Node节点是内部类,我们可以直接引用内部类的方法
//结点内部类
//静态内部类,外部类可直接引用内部类的方法
private static class Node {
int item;//数据域
Node next;//指针域
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
然后我们再来看MyLinkedList类中的成员变量以及方法
其中的成员变量以及构造方法中:头结点first不存放数据,指向集合中的第一个数据;尾节点last指向链表的最后一个节点,表示集合的最后一个数据
private Node first;//头结点,不存数据
private Node last;//指向链表的最后一个节点
private int size;//长度
public MyLinkedList() {
size = 0;
first = new Node(0, null);
last = null;
}
已经给出了两个写好的方法:size集合中数据个数以及检查是否越界
下面要求我们补全添加和删除方法的代码
首先就是向链表尾部添加数据
我们要考虑两种情况:链表为空以及链表不为空。链表为空时,头指针的指针域要变;这两种情况下尾指针是一定会变的
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node newNode=new Node(item,null);//要新添加的节点数据
Node l=last;
last=newNode;//更换尾指针指向
//将新节点加入
if(first.next==null){//集合为空,头指针的指向要变
first.next=newNode;
}else{
l.next=newNode;
}
size++;
/********** End *********/
}
然后就是向指定位置添加索引
在链表中添加元素,我们要知道要添加位置的前一个节点p以及该节点p.next,我们要将新节点的next指向p.next,再将p的next指针指向新节点;这里我们要考虑的是尾指针,若我们添加的是第一个元素,就要指明尾指针,否则在以后得添加中尾指针将会一直是null
/**
* 添加数据item到指定位置index
* index从0开始
* @param index
* @param item
*/
public void add(int index, int item) {
checkPosIndex(index);
/********** Begin *********/
int n=index;
Node p=first;
//获取index处的前一个node
while((n--)>0){
p=p.next;
}
Node newnode=new Node(item,null);
if(first.next==null){//加入的是第一个元素
last=newnode;
}
//更改指针指向
newnode.next=p.next;
p.next=newnode;
size++;
/********** End *********/
}
最后就是删除指定位置的节点
删除链表中指定位置的节点,首先我们要获取到指定位置的前一个节点p以及该节点p.next,将p的指针指向删除元素的下一个节点p.next.next,最后再回收删除的节点;这里我们要考虑的是删除的节点是否是尾节点,因为删除的是尾节点,last就要更换
/**
* 删除指定位置index处的元素并返回, index从0开始
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);//检查索引是否越界
/********** Begin *********/
Node p=first;
//删除位置的前一个节点
while((index--) > 0){
p=p.next;
}
Node del=p.next;
if(del==last){//删除的是最后一个元素,尾节点要变
last=p;
}
//更改指针指向
p.next=del.next;
del.next=null;
size--;
return del.item;
/********** End *********/
}
5.本关答案
package step3;
/**
* Created by zengpeng on 2017/12/25.
*/
public class MyLinkedList {
private Node first;//头结点,不存数据
private Node last;//指向链表的最后一个节点
private int size;//长度
public MyLinkedList() {
size = 0;
first = new Node(0, null);
last = null;
}
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node newNode=new Node(item,null);//要新添加的节点数据
Node l=last;
last=newNode;//更换尾指针指向
//将新节点加入
if(first.next==null){//集合为空,头指针的指向要变
first.next=newNode;
}else{
l.next=newNode;
}
size++;
/********** End *********/
}
/**
* 添加数据item到指定位置index
* index从0开始
* @param index
* @param item
*/
public void add(int index, int item) {
checkPosIndex(index);
/********** Begin *********/
int n=index;
Node p=first;
//获取index处的前一个node
while((n--)>0){
p=p.next;
}
Node newnode=new Node(item,null);
if(first.next==null){//加入的是第一个元素
last=newnode;
}
//更改指针指向
newnode.next=p.next;
p.next=newnode;
size++;
/********** End *********/
}
/**
* 删除指定位置index处的元素并返回, index从0开始
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);//检查索引是否越界
/********** Begin *********/
Node p=first;
//删除位置的前一个节点
while((index--) > 0){
p=p.next;
}
Node del=p.next;
if(del==last){//删除的是最后一个元素,尾节点要变
last=p;
}
//更改指针指向
p.next=del.next;
del.next=null;
size--;
return del.item;
/********** End *********/
}
public int size() {
return size;
}
private void checkPosIndex(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
//结点内部类
//静态内部类,外部类可直接引用内部类的方法
private static class Node {
int item;//数据域
Node next;//指针域
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
}
第4关:单链表的实现之查询功能
1.任务描述
本关任务:在上一关的基础上继续完善单链表的功能,实现获取指定位置元素的功能
2.题目要求
- 获取指定位置
index
处的元素并返回,补全get(int index)
方法。
3.本关答案
链表的优势是增删块,查询慢,在查询元素时我们要挨个遍历节点
/**
* 获取链表中第index个元素
* @param index
* @return
*/
public int get(int index) {
checkPosIndex(index);
/********** Begin *********/
//获取index位置处的节点
Node f=first.next;
while((index--) > 0){
f=f.next;
}
int val=f.item;
return val;
/********** End *********/
}
二.循环链表的设计与实现
第1关:单循环链表的实现—链表的添加、遍历
1.任务描述
本关任务:完成带头结点的单循环链表的添加功能,遍历链表并输出。
2.相关知识
循环链表是一种首尾相接的链表,它与平常的单链表的区别在于它的尾指针指向的是头节点
3.题目要求
- 完成单循环链表的添加功能;
- 遍历单循环链表,并输出元素的值。
4.思路分析
首先代码中已经给出了节点Node类,该类是一个静态内部类,外部类可直接调用其中的方法;其次在外部类中给出的成员变量:haed头指针以及tail尾指针;以及写好的几个成员方法:size链表元素个数和isEmpty是否为空
我们需要重点分析的是构造方法,区别在于尾指针指向的是头结点head,空表时头指针指向的是自己,head 也是tail
public MyCircleLinkedList() {
head = new Node(Integer.MIN_VALUE, null);
head.next = head;//空表时
tail = head;//尾指针指向头结点
size = 0;
}
我们的目标就是补全其中的添加遍历操作,循环链表与平常链表的添加删除操作基本是一样的,区别就是对尾指针的处理
首先是添加操作,这里我们要考虑的是,添加第一个元素时,因为此时的head=tail,因此在对尾指针的指针更改时,头指针也顺带改了,所以我们就不需要考虑添加的位置了
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node node = new Node(item, tail.next);
//更改指针
tail.next = node;
tail = node;
++size;
/********** End *********/
}
然后就是遍历问题,与平常链表的遍历区别就是循环判断的条件变了
/**
* 遍历链表并输出元素
*/
public void output() {
/********** Begin *********/
//指定位置上的元素
Node p = head.next;
while (p != head) {
System.out.println(p.item);
p = p.next;
}
/********** End *********/
}
5.本关答案
package step1;
/**
* Created by sykus on 2018/1/15.
*/
public class MyCircleLinkedList {
private Node head;//头结点, 不存数据
private Node tail;//尾结点, 指向链表的最后一个节点
private int size;
public MyCircleLinkedList() {
head = new Node(Integer.MIN_VALUE, null);
head.next = head;//空表时
tail = head;//尾指针指向头结点
size = 0;
}
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node node = new Node(item, tail.next);
//更改指针
tail.next = node;
tail = node;
++size;
/********** End *********/
}
/**
* 遍历链表并输出元素
*/
public void output() {
/********** Begin *********/
//指定位置上的元素
Node p = head.next;
while (p != head) {
System.out.println(p.item);
p = p.next;
}
/********** End *********/
}
public boolean isEmpty() {
return head.next == head;
}
public int size() {
return size;
}
//结点内部类
private static class Node {
int item;
Node next;
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
}
第2关:单循环链表的实现—链表的删除
1.任务描述
本关任务:删除循环链表中指定位置的结点,并返回其值。
2.题目要求
- 完成删除指定位置
index
处结点,并返回其值。
3.思路分析
我们要考虑的是,当删除的是最后一个节点,尾节点就要改变
/**
* 删除从头结点开始的第index个结点
* index从0开始
*
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);//索引是否越界
/********** Begin *********/
Node p=head;
//指定位置的前一个节点
while(index-- > 0){
p=p.next;
}
Node del=p.next;
if(del==tail){//删除的是最后一个元素,尾节点要变
tail=p;
}
//更改指针
p.next=del.next;
del.next=null;
size--;
return del.item;
/********** End *********/
}
第3关:双向循环链表的实现—链表的插入
1.任务描述
本关任务:实现双向循环链表的添加功能。
2.相关知识
双向链表中的结点由三个域组成,两个链接域一个数据域,如图:
双向链表相比于单向链表优点在于可以在任意一个节点访问到其前一个节点以及其后面的节点
双向循环链表就是在双向链表的基础上,尾指针指向的是头节点
3.题目要求
- 补全
add(int item)
方法,实现双向循环链表的添加功能。
4.思路分析
首先我们来看其已经给出的节点类Node,其为静态内部类,外部类可直接调用其中的方法
成员变量给出的有数据域,以及两个指针域:一个指向后面节点next,一个指向前面节点prev
//结点内部类
private static class Node {
int item;
Node next;//指向后面节点
Node prev;//指向的前面节点
Node(Node prev, int item, Node next) {
this.prev = prev;
this.item = item;
this.next = next;
}
}
然后我们再来看外部类中给出的方法
同单向链表一致,构造方法中尾指针指向的是头结点,空表时头指针指向的是自己
private Node head;//头结点
private Node tail;//指向链表的尾结点
private int size;
public MyDoubleLinkedList() {
head = new Node(null, Integer.MIN_VALUE, null);
head.next = head.prev = head;
tail = head;
size = 0;
}
本关要求我们补全添加操作的功能。在表尾添加元素,与单链表的区别在于其多了一个对于前指针域的操作
/**
* 添加元素到表尾
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node newNode = new Node(null, item, null);
//更改尾节点及其指向
tail.next = newNode;
newNode.prev = tail;
newNode.next = head;
tail = newNode;
//更改头结点的前指针
head.prev = newNode;
++size;
/********** End *********/
}
5.本关答案
package step3;
/**
* Created by sykus on 2018/1/15.
*/
public class MyDoubleLinkedList {
private Node head;//头结点
private Node tail;//指向链表的尾结点
private int size;
public MyDoubleLinkedList() {
head = new Node(null, Integer.MIN_VALUE, null);
head.next = head.prev = head;
tail = head;
size = 0;
}
/**
* 添加元素到表尾
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
Node newNode = new Node(null, item, null);
//更改尾节点及其指向
tail.next = newNode;
newNode.prev = tail;
newNode.next = head;
tail = newNode;
//更改头结点的前指针
head.prev = newNode;
++size;
/********** End *********/
}
/**
* 打印双向链表
*
* @param flag true从左向右顺序打印, false从右向左顺序打印
*/
public void printList(boolean flag) {
Node f = head;
if (flag) {//向右
while (f.next != head) {
f = f.next;
System.out.print(f.item + " ");
}
} else {//向左
while (f.prev != head) {
f = f.prev;
System.out.print(f.item + " ");
}
}
}
public int size() {
return size;
}
//结点内部类
private static class Node {
int item;
Node next;//指向后面节点
Node prev;//指向的前面节点
Node(Node prev, int item, Node next) {
this.prev = prev;
this.item = item;
this.next = next;
}
}
}
第4关:双向循环链表的实现—链表的删除
1.任务描述
本关任务:在上一关的基础上,实现双向循环链表的删除功能。
2.题目要求
- 补全
remove(int index)
方法,实现删除指定位置index
处结点并返回其值的功能
3.思路分析
与单向循环链表的删除操作区别在于多了一个对前指针域的操作,我们要考虑的是删除最后一个元素时尾节点要改变的操作
/**
* 删除指定位置index出的结点,并返回其值
*
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);//检查索引是否越界
/********** Begin *********/
Node p=head.next;
//指定位置的元素
while(index-- > 0){
p=p.next;
}
if(p==tail){//删除的是最后一个,尾节点要变
tail=p.prev;
}
//更改指针
p.prev.next=p.next;
p.next.prev=p.prev;
p=null;
size--;
return p.item;
/********** End *********/
}
三.栈、队列
第1关:实现基于数组的栈
1.任务描述
本关任务:基于数组,利用Java
中泛型实现一个栈,并具有基本的入栈、出栈功能
2.相关知识
栈是线性表的一种,其特点是先进先出
3.题目要求
- 补全
push(T item)
方法,实现入栈功能 - 补全
pop()
方法,实现出栈功能
4.思路分析
首先我们来看给出的成员变量以及方法
可以看到Mystack类定义了泛型,与集合ArrayList<>中要填入的数据类型一样,我们定义了泛型<T>,就可以传递指定类型T的数据;top表示的是栈顶元素下标
public class MyStack<T> {//指定的泛型
private T[] S;
private int top;//栈顶元素下标,初始为-1
public MyStack() {
this(1);//引用下面的有参构造,创建一个容量为1的数组
}
public MyStack(int capacity) {
S = (T[]) new Object[capacity];
top = -1;
}
}
已经写好了的方法:扩展动态数组大小resize以及判断是否为空isEmpty
动态扩容数组的大小,就是创建一个容量更大的新数组并拷贝旧数组,最后再让数组S继承新数组
本关要求我们补全入栈以及出栈的代码
入栈我们直接将top后移,数据存入;出栈将top前移即可
5.本关答案
package step1;
import java.util.NoSuchElementException;
/**
* Created by sykus on 2018/1/26.
*/
public class MyStack<T> {//指定的泛型
private T[] S;
private int top;//栈顶元素下标,初始为-1
public MyStack() {
this(1);//引用下面的有参构造,创建一个容量为1的数组
}
public MyStack(int capacity) {
S = (T[]) new Object[capacity];
top = -1;
}
/**
* 入栈操作,把item压入栈中
*
* @param item
*/
public void push(T item) {
int len = S.length;
if (top == len - 1) {//数组已满,扩容
resize(2 * len);
}
/********** Begin *********/
S[++top]=item;//top后移,数据存入
/********** End *********/
}
/**
* 返回栈顶元素并从栈中移除
*
* @return
*/
public T pop() {
if (isEmpty()) {
throw new NoSuchElementException("栈为空!");
}
/********** Begin *********/
T val=S[top--];//top后移
return val;
/********** End *********/
}
/**
* 判断栈是否为空
*
* @return
*/
public boolean isEmpty() {
if (top < 0)
return true;
else
return false;
}
/**
* 动态扩展数组大小
*
* @param capacity
*/
private void resize(int capacity) {
assert capacity > top;
T[] tmp = (T[]) new Object[capacity];
for (int i = 0; i <= top; i++) {
tmp[i] = S[i];
}
S = tmp;
}
}
第2关:实现基于链表的栈
1.任务描述
本关任务:基于单链表实现一个栈,并具备入栈、出栈功能
2.相关知识
带表头的单链表
3.题目要求
- 补全
push(E item)
方法,实现入栈功能; - 补全
pop()
方法,实现出栈功能,并返回元素的值。
4.思路分析
本关是利用单链表实现的栈
内部类节点Node以及外部类的成员变量和方法与单链表实现时的基本一样,多了个泛型的规范以及栈顶节点指针top,没有尾指针tail,因为我们只考虑表头即可
private Node<E> head;//头结点
private Node<E> top;//栈顶
private int size;//栈中元素个数
public MyStack() {
head = new Node<E>();
head.next = null;
top = null;//栈顶初始化为null
size = 0;
}
首先是入栈操作,就是在链表的表头添加元素
然后是出栈操作,出栈就是删除第一个节点top,这里没有尾指针,因此我们不需要考虑删除最后一个元素时尾指针的指向问题,我们每次删除完成后要重新指定top
5.本关答案
package step2;
import java.util.NoSuchElementException;
/**
* Created by sykus on 2017/12/29.
*/
public class MyStack<E> {
private Node<E> head;//头结点
private Node<E> top;//栈顶
private int size;//栈中元素个数
public MyStack() {
head = new Node<E>();
head.next = null;
top = null;//栈顶初始化为null
size = 0;
}
/**
* 把item压入栈中
*
* @param item
*/
public void push(E item) {
/********** Begin *********/
//在表头添加元素
Node newNode=new Node();
newNode.item=item;
newNode.next=head.next;
//更改head指针
head.next=newNode;
//重新指定top
top=newNode;
size++;
/********** End *********/
}
/**
* 返回它栈顶元素并删除
*/
public E pop() {
if (isEmpty())
throw new NoSuchElementException("栈为空!");
/********** Begin *********/
Node<E> pop=top;//出栈节点
//更改指针
top=pop.next;
head.next=top;
pop.next=null;
size--;
return pop.item;
/********** End *********/
}
/**
* 返回栈中元素个数
*
* @return
*/
public int size() {
return size;
}
/**
* 判断一个栈是否为空
*
* @return
*/
public boolean isEmpty() {
return (null == head);
}
//链表结点内部类
private static class Node<E> {
private E item;
private Node<E> next;
}
}
第3关:基于数组的队列
1.任务描述
本关任务:基于数组实现一个循环队列,并具有基本的添加、删除功能。
2.相关知识
队列也是线性表的一种,其特点为先进先出
本关队列的数组实现采用的是循环数组,循环数组不用像栈数组实现那样,容量满就扩容,会造成内存的浪费,循环数组可以极大的重复利用已经浪费的空间,但其缺点也很明显,当其容量小而存入的数多时,会造成数据的覆盖
3.题目要求
- 补全
enqueue(T item)
方法,实现入队操作; - 补全
dequeue()
方法,实现出队操作。
4.思路分析
首先我们来看给出的成员变量以及方法
其中的head指针表示队首,tail指针表示队尾(要入队的元素索引);当指针越界时,我们就要从头开始循环存入
private T[] Q;
private int head;//头指针
private int tail;//尾指针,表示入队的元素索引
private int size;//元素个数
public MyQueue() {
this(1);//调用下面的有参构造,创建一个容量为1的数组
}
public MyQueue(int capacity) {
Q = (T[]) new Object[capacity];
size = 0;
head = tail = 0;
}
入队,我们直接在tail位置存入元素,然后tail后移若超出则从头循环;出队,我们将head前移,若超出则从头存入
5.本关答案
package step3;
/**
* Created by zengpeng on 2018/1/30.
*/
public class MyQueue<T> {
private T[] Q;
private int head;//头指针
private int tail;//尾指针
private int size;//元素个数
public MyQueue() {
this(1);//调用下面的有参构造,创建一个容量为1的数组
}
public MyQueue(int capacity) {
Q = (T[]) new Object[capacity];
size = 0;
head = tail = 0;
}
/**
* 入队操作
*
* @param item
*/
public void enqueue(T item) {
/********** Begin *********/
Q[tail]=item;
//若越界就从头开始循环
tail=(tail+1)%Q.length;
size++;
/********** End *********/
}
/**
* 出队操作
*
* @return
*/
public T dequeue() {
/********** Begin *********/
T pop=Q[head];
//若越界就从头开始循环
head=(head+1)%Q.length;
size--;
return pop;
/********** End *********/
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty() {
return (head == tail) && (size < Q.length);
}
public int size() {
return size;
}
}
第4关:基于链表的队列
1.任务描述
本关任务:实现链式队列,并具备入队、出队操作
2.相关知识
单向链表
3.题目要求
- 补全
enqueue(T item)
方法,实现入队操作; - 补全
dequeue()
方法,实现出队操作。
4.思路分析
本关的要求是利用单向链表实现队列,这里我们利用一个front指针指向队首,以便于我们的入队操作
相比于单向链表,成员变量多了一个front指针指向队首
private Node<T> head;// 头结点,不存数据
private Node<T> front;//指向队头结点
private Node<T> tail;//指向队尾结点
private int size;
public MyQueue() {
head = new Node<T>();
front = tail = null;
size = 0;
}
其中的出队操作在队首进行,入队操作在队尾进行,其中的操作与单链表的添加删除逻辑基本一致,因为有front指针,因此多了一个对front的指向
首先是入队操作,在表尾添加元素,我们要考虑的是,添加第一个元素时,头指针head要改变的情况
/**
* 入队
*
* @param item
*/
public void enqueue(T item) {
/********** Begin *********/
//在表尾添加数据
Node<T> newNode=new Node();
newNode.item=item;
newNode.next=null;
if(front==null){//为空,指定头结点
head.next=newNode;
front=newNode;
}else{
tail.next=newNode;
}
tail=newNode;
size++;
/********** End *********/
}
然后是出队操作,在表头删除元素,我们要考虑的是删除的最后一个元素时,尾指针tail要改变
/**
* 出队
*
* @return
*/
public T dequeue() {
if (isEmpty())
throw new NoSuchElementException("队列为空!");
/********** Begin *********/
//在表头删除元素
Node<T> pop=front;
//更改指针
head.next=pop.next;
front=pop.next;
pop.next=null;
size--;
if(head.next==null){//队列元素为空,重新指定尾指针
front=tail=null;
}
return pop.item;
/********** End *********/
}
5.本关答案
package step4;
import java.util.NoSuchElementException;
/**
* Created by sykus on 2017/12/29.
*/
public class MyQueue<T> {
private Node<T> head;// 头结点,不存数据
private Node<T> front;//指向队头结点
private Node<T> tail;//指向队尾结点
private int size;
public MyQueue() {
head = new Node<T>();
front = tail = null;
size = 0;
}
/**
* 入队
*
* @param item
*/
public void enqueue(T item) {
/********** Begin *********/
//在表尾添加数据
Node<T> newNode=new Node();
newNode.item=item;
newNode.next=null;
if(front==null){//为空,指定头结点
head.next=newNode;
front=newNode;
}else{
tail.next=newNode;
}
tail=newNode;
size++;
/********** End *********/
}
/**
* 出队
*
* @return
*/
public T dequeue() {
if (isEmpty())
throw new NoSuchElementException("队列为空!");
/********** Begin *********/
//在表头删除元素
Node<T> pop=front;
//更改指针
head.next=pop.next;
front=pop.next;
pop.next=null;
size--;
if(head.next==null){//队列元素为空,重新指定尾指针
front=tail=null;
}
return pop.item;
/********** End *********/
}
/**
* 返回队列中元素数量
*
* @return
*/
public int size() {
return size;
}
/**
* 判断一个队列是否为空
*
* @return
*/
public boolean isEmpty() {
return (front == null);
}
/**
* 链表结点内部类
*/
private static class Node<E> {
private E item;
private Node<E> next;
}
}
如要深入了解,请参考下面的文章
内部类
https://blog.csdn.net/m0_74808313/article/details/132028572
泛型
https://blog.csdn.net/m0_74808313/article/details/132206513
线性表
https://blog.csdn.net/m0_74808313/article/details/130376172
链表
https://blog.csdn.net/m0_74808313/article/details/130295707
https://blog.csdn.net/m0_74808313/article/details/130320890
栈和队列
https://blog.csdn.net/m0_74808313/article/details/130170870
https://blog.csdn.net/m0_74808313/article/details/130236729
双端队列
https://blog.csdn.net/m0_74808313/article/details/130672134
堆
https://blog.csdn.net/m0_74808313/article/details/130535409
哈希表
二叉树
https://blog.csdn.net/m0_74808313/article/details/130103168