对于链式存储结构,在上一篇文章已经对单链表进行了讲解,文章链接(https://blog.csdn.net/master_1997/article/details/101024943)。今天来给大家讲解,采用链式存储结构的栈和队列,以及循环链表。
链式栈
对于栈,就是采用先进后出,后进先出的结构,之前已经讲过采用顺序存储结构的栈,本次要说的是采用链式存储的栈。
**通过LinkedList来进行进栈,出栈操作。**内容较简单,具体操作看如下代码。
类图
public class LinkedStack<E> implements Stack<E> {
private LinkedList<E> list; //通过链表来实现栈
public LinkedStack() { //创建无参构造
list = new LinkedList<E>();
}
@Override
public int getSize() { //获取栈里的有效元素个数
return list.getSize(); //通过链表的getSize()方法来获取
}
@Override
public boolean isEmpty() { //判断栈是否为空
return list.isEmpty(); //直接调用链表的判空方法
}
@Override
public void push(E e) { //进栈
list.addFirst(e); //依据栈的特点,可以采用链表的头插法
}
@Override
public E pop() { //出栈
return list.removeFirst(); //依据栈的特点,可以采用链表的删头法
}
@Override
public E peek() { //获取栈顶元素
return list.getFirst(); //依据栈的先进后出的特点,说明栈顶元素在第一个
}
@Override
public void clear() { //清空栈
list.clear();
}
//重写toString方法,便于数据的输出,直观对比
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedStack: size=" + getSize() + "\n");
if(isEmpty()) {
sb.append("[]");
} else {
sb.append('[');
for(int i = 0; i < getSize(); i++) {
sb.append(list.get(i));
if(i == getSize() - 1) {
sb.append(']');
} else {
sb.append(']');
}
}
}
return sb.toString();
}
//重写equals方法
@Override
public boolean equals(Object obj) {
if(obj == null) { //如果是空,直接false
return false;
}
if(obj == this) { //如果是自己,直接输出true
return true;
}
if(obj instanceof LinkedStack) { //判断obj是否属于LinkedStack的子类或实例
LinkedStack<E> stack = (LinkedStack<E>)obj; //对obj进行强转
if(getSize() == stack.getSize()) { //判断两个对象的有效容量大小是否相等
for(int i = 0; i < getSize(); i++) { //对两个对象进行遍历
if(list.get(i) != stack.list.get(i)) { //如果有一个不一样则返回false
return false;
}
}
return true; // 如果整个遍历都没有返回false,说明相同
} else {
return false;
}
}
return false;
}
}
链式队列
链式队列也是先进先出,链式队列只允许在表的一端进行插入(入队)、删除(出队)操作。允许插入的一端称为队尾,允许删除的一端称为队头。 以上这些也同样适用于采用顺序存储结构的队列。
链式队列使用链表来实现,链表中的数据域用来存放队列中的元素,指针域用来存放队列中下一个元素的地址,同时使用队头指针指向队列的第一个元素和最后一个元素。
从下面的类图,可以清楚的看到链式队列(LinkedQueue)是基于LinkedList实现的。
类图
public class LinkedQueue<E> implements Queue<E> {
private LinkedList<E> list; //定义一个链表,来实现队列的操作
public LinkedQueue() { //创建一个无参构造
list = new LinkedList<E>(); //对链表实例化
}
@Override
public int getSize() { //获取当前队列的元素个数
return list.getSize(); //直接获取链表的getSIze()方法进行获取
}
@Override
public boolean isEmpty() { //判断队列是否为空
return list.isEmpty(); //直接调用链表的判空方法
}
@Override
public void clear() { //清空队列
list.clear();
}
@Override
public void enqueue(E e) { //进队
list.addLast(e); //队列只能从一端进,一端出,等同于链表的尾插
}
@Override
public E dequeue() { //出队
return list.removeFirst(); //依据队列的特性,等同于链表的删除头元素
}
@Override
public E getFront() { //获取头元素
return list.getFirst();
}
@Override
public E getRear() { //获取队尾元素
return list.getLast(); //队列的队尾,等同于链表的表尾,所以采用链表的getLast方法
}
//重写toString方法,方便数据输出,更加的直观
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedQueue: size=" + getSize() + "\n");
if(isEmpty()) {
sb.append("[]");
} else {
sb.append('[');
for(int i = 0; i < getSize(); i++) {
sb.append(list.get(i));
if(i == getSize() - 1) {
sb.append(']');
} else {
sb.append(',');
}
}
}
return sb.toString();
}
//重写equals方法
@Override
public boolean equals(Object obj) {
if(obj == null) { //判断传入的对象是否为空
return false;
}
if(obj == this) { //判断传入的对象是否是自己
return true; //如果是自己就肯定顶相等
}
if(obj instanceof LinkedQueue) { //判断obj是否属于LinkedQueue的子类或实例
LinkedQueue queue = (LinkedQueue)obj; //对传入的对象进行强转,进行对比
if(this.list.equals(queue.list)){ //调用LinkedList的 equals方法进行判断
return true;
} else {
return false;
}
}
return false;
}
}
单向循环链表
将单链表中的尾节点的指针域由NULL改为指向头结点,使整个单链表形成一个环,这种头尾相接的单链表就可以称之为单循环链表,简称单向循环链表。
采用虚拟头结点的单向循环链表
一个结点
多个结点
采用真实头结点
具体代码
public class LoopSingle<E> implements List<E> {
private class Node{
E data; //数据域
Node next; //指针域
public Node() {
this(null,null);
}
public Node(E data,Node next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return data.toString();
}
}
private Node head; //创建头指针
private Node rear; //创建尾指针
private int size; //创建元素个数
public LoopSingle() { //无参构造
head = null; //默认为空
rear = null;
size = 0; //元素个数默认为0
}
public LoopSingle(E[] arr) { //有参构造
this();
for(E e: arr) { //对数组遍历
addLast(e); //调用addLast方法尾插元素
}
}
public Node getHead() { //获取头指针
return head;
}
public Node getRear() { //获取尾指针
return rear;
}
@Override
public int getSize() { //获取元素个数
return size;
}
@Override
public boolean isEmpty() { //判断是否为空
return size == 0 && head == null && rear == null; //元素个数为0且头指针和尾指针同时为空
}
插入方法
单向循环链表插入元素分四种情况
- 链表为空,让头指针指向新插入的元素,然后后移尾指针
- 从头插入元素,插入角标为0,新插入元素指向头指针,再将头指针前移,最后将尾指针指向新头指针
- 从尾插入元素,插入角标尾size ,先让新插入元素指向头指针,然后让尾指针指向新插入元素,最后后移尾指针
- 其他情况,一般插入,先循环遍历找到插入角标处前一个元素,新插入元素的next指向前一个元素的next,最后将前一个元素的next指向新插入元素。
@Override
public void add(int index, E e) {
if(index < 0 && index > size) { //判断角标是否合法
throw new IllegalArgumentException("插入角标非法");
}
Node n = new Node(e,null);
if(isEmpty()) { //特殊情况
head = n;
rear = n;
rear.next = head;
} else if(index == 0) { //头插
n.next = head;
head = n;
rear.next = head;
} else if(index == size) { //尾插
n.next = head;
rear.next = n;
rear = rear.next;
} else { //其他情况
Node p = head;
for(int i = 0; i < index - 1; i++) {
p = p.next;
}
n.next = p.next;
p.next = n;
}
size++;
}
@Override
public void addFirst(E e) { //表头插入元素
add(0,e); //角标0处插入
}
@Override
public void addLast(E e) { //表尾插入元素
add(size,e); //角标size(元素个数)插入
}
删除方法
分四种情况
- 删除链表就一个元素
- 删除链表表头元素
- 删除链表表尾元素
- 删除中间元素
@Override
public E remove(int index) {
if(index < 0 || index >= size) {
throw new IllegalArgumentException("删除角标非法");
}
E res = null;
if(size == 1) { //如果就一个元素,则直接置空
res = head.data;
head = null;
rear = null;
} else if(index == 0) { //下标为0,则将头指针后移一位,尾指针指向新的头指针
res = head.data;
head = head.next;
rear.next = head;
} else if(index == size - 1) { //小标为最后一个,循环得到倒数第二个元素,将它指向头指针
Node p = head;
res = rear.data;
while(p.next != rear) {
p = p.next;
}
p.next = rear.next;
rear = p;
} else { //删除中间元素
Node p = head;
for(int i = 0; i < index - 1; i++) { //遍历得到当前指定下标元素
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
}
size--;
return res;
}
@Override
public E removeFirst() { //删除表头元素
return remove(0);
}
@Override
public E removeLast() { //删除表尾元素
return remove(size - 1);
}
@Override
public void removeElement(E e) { //删除指定元素
remove(find(e)); //find方法找到下标remove方法通过下标删除
}
查找方法
分三种情况
- 表头元素
- 表尾元素
- 中间元素
@Override
public E get(int index) {
if(index < 0 || index >= size) { //判断角标是否合法
throw new IllegalArgumentException("获取角标非法");
}
if(index == 0) { //表头位置
return head.data; //返回头指针数据
} else if(index == size - 1) { //表尾位置
return rear.data; //返回表尾数据
} else {
Node p = head; //创建新指针
for(int i = 0; i < index; i++) { //遍历到指定角标元素
p = p.next; //此时p指针指向index位置的元素
}
return p.data;
}
}
@Override
public E getFirst() { //获取表头元素
return get(0); //调用get方法,传入index = 0
}
@Override
public E getLast() { //获取表尾元素
return get(size - 1); //传入size - 1获取表尾元素
}
//通过元素找角标
@Override
public int find(E e) {
if(isEmpty()) { //判断链表是否为空
return -1;
}
Node p = head;
int index = 0;
while(p.data != e) { //对链表遍历
p = p.next;
index++;
if(p == head) { //如果遍历了一圈还没找到,说明无此元素,返回-1
return -1;
}
}
return index;
}
//判断指定元素是否存在于链表中
@Override
public boolean contains(E e) {
int index = find(e); //调用上面的find方法,返回-1,说明无,否则说明有
if(index == 0) {
return true;
} else {
return false;
}
}
以上代码非Java内置的方法。