链表概述
真正的动态数据结构,也是最简单的动态数据结构,可以更深入的理解引用(或者指针),更深入的理解递归操作。
优点:真正的动态,不需要处理固定容量的问题
缺点:丧失了随机访问的能力
实现链表
添加元素的过程
在指定位置进行插入元素时,需要辅助节点prev进行维护。
public class LinkedList<E> {
//节点类
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) { this(e, null); }
public Node() { this(null, null); }
@Override
public String toString() {
return e.toString();
}
}
private Node head;//头结点
private int size;
public LinkedList() {
head = null;
size = 0;
}
public int getSize() { return size; }
public boolean isEmpty() { return size == 0;}
/**
* 头插法插入元素
* @param e
*/
public void addFirst(E e) {
// Node node = new Node(e);
// node.next = head;
// head = node;
head = new Node(e, head);
size++;
}
/**
* 在链表指定位置插入新元素
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("插入位置有误");
}
if (index == 0) {//处理从首元素插入,因为首元素没有prev节点
addFirst(e);
} else {
Node prev = head;//prev初识指向head
for (int i = 0; i < index - 1; i++) {//移动到指定位置
prev = prev.next;
}
//Node newNode = new Node(e);
//newNode.next=prev.next;
//prev.next=newNode;
prev.next = new Node(e, prev.next);
}
size++;
}
/**
* 在链表尾部插入元素
* @param e
*/
public void addLast(E e) {
add(size, e);
}
}
在链表头添加元素和在链表其他位置添加元素的逻辑上有所差别(addFirst不能复用add() ):在给链表添加元素时,需要找到待添加位置之前的元素,但是对于链表头来说,他没有那个节点,所以在逻辑上有点点不同,所以引入一个虚拟头结点dummyHead
,让添加操作统一起来,dummyHead对用户来说没有任何意义,类似于在循环队列中有意识的浪费一个空间一样,只是方便编写逻辑。
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) { this(e, null);}
public Node() { this(null, null); }
@Override
public String toString() { return e.toString(); }
}
private Node dummyHead;//虚拟头结点
private int size;
public LinkedList() {
//这样看来:初始时链表中有一个节点
dummyHead = new Node(null, null);
size = 0;
}
/**
* 数组转为链表
*/
public LinkedList(E[] arr){
if(arr.length == 0 && arr == null) {
throw new IllegalArgumentException("arr can not be empty");
}
this.val=arr[0];
ListNode current = this;
for(int i = 1;i < arr.length ; i++){
current.next=new ListNode(arr[i]);
current=current.next;
}
}
public int getSize() { return size; }
public boolean isEmpty() { return size == 0; }
/**
* 在链表指定位置插入新元素
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("插入位置有误");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {//移动到指定位置
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
/**
* 头插法插入元素
* @param e
*/
public void addFirst(E e) { add(0, e);}
/**
* 在链表尾部插入元素
* @param e
*/
public void addLast(E e) { add(size, e); }
/**
* 获取指定下标的元素
* @param index
* @return
*/
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引非法!");
}
Node current = dummyHead.next;
for (int i = 0; i < index; i++) {
current = current.next;
}
return current.e;
}
public E getFirst() { return get(0); }
public E getLast() { return get(size); }
/**
* 是否包含指定元素
* @param e
* @return
*/
public boolean contains(E e) {
for (Node node = dummyHead.next; node != null; node = node.next) {
if (node.e.equals(e)) {
return true;
}
}
return false;
}
/**
* 给指定位置修改元素值
* @param index
* @param e
*/
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引非法!");
}
Node current = dummyHead.next;
for (int i = 0; i < index; i++) {
current = current.next;
}
current.e = e;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
Node current = dummyHead.next;
while (current != null) {
result.append(current + "->");
current = current.next;
}
result.append("NULL");
return result.toString();
}
/**
* 删除指定位置的元素
* @param index
* @return
*/
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引非法!");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode.next = null;
size--;
return retNode.e;
}
public E removeFirst(){ return remove(0); }
public E removeLast(){ return remove(size-1); }
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 5; i++) {
linkedList.addFirst(i);
System.out.println(linkedList);
}
linkedList.add(2, 666);
System.out.println(linkedList);
linkedList.remove(2);
System.out.println(linkedList);
}
}
这样一来在添加元素时,就统一起来,addFirst()和addLast()都能复用add()方法了。
链表实现栈
由于链表在头部操作时时间复杂度为O(1),所以符合栈(只在一端进行操作)的结构。
栈接口
public interface Stack<E> {
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
这种栈的底层是一个链表,链表的头部为栈顶,所以操作链头部即可
。
public class LinkedListStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkedListStack() { list = new LinkedList<E>(); }
public LinkedListStack(LinkedList<E> list) { this.list=list; }
@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);
return res.toString();
}
public static void main(String[] args) {
LinkedListStack<Integer> linkedListStack=new LinkedListStack<>();
for (int i=0;i<5;i++){
linkedListStack.push(i);
System.out.println(linkedListStack);
}
linkedListStack.pop();
System.out.println(linkedListStack);
}
}
链表实现队列
回顾之前使用数组实现队列时,在出队操作时要进行数组元素的移位导致复杂度为O(n),所以引入了循环数组解决此问题。
链表的时间复杂度如下,可以看出如果只对链表头部操作是时间复杂度为O(1)的,而队列则是对链表头和尾都需要进行操作,所以不能像实现栈那样只对对首元素操作,所以要对链表结构做细微的改变。
我们的希望是在链表的尾部操作也是O(1)级别的,只需设立一个类似head的节点tail进行维护链表尾部即可,tail节点即为待添加元素的前一个节点,此时在tail和head节点处添加节点都是很容易的。但是删除元素时,我们知道需要找到待删除元素的前一个节点,但是在删除队尾元素(出队操作)时,就算引入了tail,还是需要从头遍历一遍链表,也无法通过O(1)的复杂度找到待删除元素的前一个节点,
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
public class LinkedListQueue<E> implements Queue<E> {
class Node {
public E e;
public Node next;
public Node() { this(null, null); }
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) { this(e, null); }
@Override
public String toString() { return e.toString(); }
}
private int size;//队列元素个数
private Node tail, head;//队首、队尾
public LinkedListQueue() {
size = 0;
head = null;
tail = null;
}
@Override
public int getSize() { return size; }
@Override
public boolean isEmpty() { return size == 0;}
/**
* 入队(尾插法)
* @param e
*/
@Override
public void enqueue(E e) {
if (tail == null) {//队列没有元素时(但凡队列中有元素那么tail就不为null)
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
/**
* 出队(头出法)
* @return
*/
@Override
public E dequeue() {
if (isEmpty()) {
throw new RuntimeException("队列为空!");
}
Node retNode = head;
head = head.next;
retNode.next = null;
if (head == null) {
tail = null;
}
size--;
return retNode.e;
}
@Override
public E getFront() { return head.e; }
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Queue front: ");
Node current = head;
while (current != null) {
result.append(current + "->");
current = current.next;
}
result.append("null tail ");
return result.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for (int i = 0; i < 5; i++) {
queue.enqueue(i);
System.out.println(queue.toString());
}
queue.dequeue();
System.out.println(queue.toString());
}
}