链表头的时间复杂度分析满足O(1)
- 如果只对链表头进行增删的话,它的时间复杂度是O(1)的
- 只查链表头的元素,它的时间复杂度也是O(1)的
满足以上两个条件的数据结构是栈,对于栈是后进先出的, 只对栈的一端,栈顶进行操作。无论是添加元素,删除元素,还是查看元素都在栈顶进行。
所以可以把链表头当作栈顶,用链表底层实现一个栈。
栈的接口
Stack.java
public interface Stack<E> {
void push(E e); // 向栈中添加一个元素,入栈
E pop(); // 拿出栈顶的元素,出栈
E peek(); // 看一下栈顶的元素是谁
int getSize(); // 看一下栈里面一共有多少个元素
boolean isEmpty(); // 看一下栈是否为空
}
复用链表类LinkedList
关于链表类,在我的另一篇文章《实现一个属于自己的链表类》进行了底层实现,直接将第七节的完整代码拿来复用。
LinkedList.java
// 支持泛型
public class LinkedList<E> {
// 节点设计成链表类中的内部类
// 设计私有类,只有在链表数据结构内才可以访问到Node
private class Node {
// 设计成public 在LinkedList中可以随意访问操作 不需要设置get,set方法
public E e; // 存放元素
public Node next; // 指向Node的引用
public Node(E e, Node next) {
// 将用户传来的数据交给节点
this.e = e;
this.next = next;
}
// 用户只传来e
public Node(E e) {
this(e, null);
}
// 用户什么都不传
public Node() {
this(null, null);
}
// 对每一个节点设置toString方法
@Override
public String toString() {
// 每一个节点,直接打印e所对应的toString
return e.toString();
}
}
// 修改为dummyHead设置虚拟头节点
private Node dummyHead;
// 用户不能在外部直接修改size
private int size; // 来记录链表中有多少个元素
// 链表构造函数
public LinkedList() {
// 对于一个空的链表来说,它是存在一个节点的,这个节点就是唯一的虚拟头节点
dummyHead = new Node(null, null);
size = 0;
}
// 获取链表中元素的个数
public int getSize() {
return size;
}
// 返回链表是否为空
public boolean isEmpty() {
// size是否为0
return size == 0;
}
// 使用虚拟头节点添加元素
public void add(int index, E e) {
// 需要先判断index的合法性
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Illegal index.");
}
// 此时dummyHead指向的是0元素之前一个的位置的节点
Node prev = dummyHead;
// 只需要遍历到index就可以了,因为是从dummyHead开始遍历的
for (int i = 0; i < index; i++) {
// 当前prev存的节点的下一个节点放进prev中
// 遍历一直挪动prev位置存放下一个节点
prev = prev.next;
}
prev.next = new Node(e, prev.next);
// 插入之后维护size
size++;
}
// 在链表头添加新的元素
public void addFirst(E e) {
add(0, e);
}
// 在链表的末尾添加一个新的元素e
public void addLast(E e) {
// 复用add(),只需要在size添加即可
add(size, e);
}
// 查询操作
public E get(int index) {
// get之前先判断合法性
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Add failed.Illegal index.");
}
// 遍历链表,是从索引为0开始的,从当前的开始遍历
Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
// 最终cur里存储的e就是需要查找的元素
return cur.e;
}
// 获取链表第一个元素
public E getFirst() {
return get(0);
}
// 获取链表最后第一个元素
public E getLast() {
return get(size - 1);
}
// 链表的更新,修改
public void set(int index, E e) {
// 判断合法
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Add failed.Illegal index.");
}
// 进行一次遍历,找到第index元素进行替换
Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
// 最终cur里存储的e等于新的e
cur.e = e;
}
// 查找链表中是否存在元素e
public boolean contains(E e) {
// 设置cur从第一个节点开始
Node cur = dummyHead.next;
// 不知道循环多少次使用while
// cur 节点不等他null的话,意味着当前cur节点是一个有效节点
while (cur != null) {
// cur.e是用户传来的e,返回true
if (cur.e.equals(e))
return true;
// 否则就看下一个节点
cur = cur.next;
// 直到cur为空,说明把整个链表遍历了一遍
}
return false;
}
// 删除链表中index位置元素 返回删除的元素
public E remove(int index) {
// 判断合法
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Add failed.Illegal index.");
}
// prev存了待删除节点之前的节点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
// 将删除的节点保存进retNode;
Node retNode = prev.next;
// 将当前的前一个元素的节点指向当前后一个节点
prev.next = retNode.next;
// 让删除元素的节点的指向为空
retNode.next = null;
// 维护size
size--;
// 将删除的元素返回
return retNode.e;
}
// 删除第一个元素,返回删除的元素
public E removeFirst() {
return remove(0);
}
// 删除第一个元素,返回删除的元素
public E removeLast() {
return remove(size - 1);
}
@Override
public String toString() {
// 需要遍历一边整个链表中的所有的元素
StringBuilder res = new StringBuilder();
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
res.append(cur.e + "->");
}
// 最后跟一个空表示达到了链表的结尾
res.append("NULL");
return res.toString();
}
}
通过链表实现栈
通过实现接口,实现栈LinkedListStack<E>
LinkedListStack.java
public class LinkedListStack<E> implements Stack<E> {
// 私有链表对象
private LinkedList<E> list;
// 由于是链表栈,底层实现链表没有容基的概念,一个构造函数就够了
public LinkedListStack() {
list = new LinkedList<>();
}
// 栈里面一共有多少个元素
@Override
public int getSize() {
return list.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 String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack: top [");
res.append(list);
res.append("]");
return res.toString();
}
}
测试类Main.java
public class Main {
public static void main(String[] args) {
LinkedListStack<Integer> stack = new LinkedListStack<>();
// 入栈测试
for (int i = 0; i < 5; i++) {
stack.push(i);
System.out.println(stack);
}
// 进行一次出栈
stack.pop();
System.out.println(stack);
}
}
测试结果
栈类中toString()中添加了top来标记栈顶的位置,直观结果如下:
Stack: top [0->NULL]
Stack: top [1->0->NULL]
Stack: top [2->1->0->NULL]
Stack: top [3->2->1->0->NULL]
Stack: top [4->3->2->1->0->NULL]
Stack: top [3->2->1->0->NULL]