链表

2019-01-25  本文已影响0人  珍惜丶现在的

链表和数组都是线性结构。不同的是,数组需要一块连续的内存空间来存储数据,而链表则对空间是否连续没有要求。所以这一点差异体现了两种数据结构的不同特性。数组支持随机访问,效率高;而添加、删除操作效率低,因为每一次添加、删除都会造成数据的搬动。链表通过指针指向下一个节点,所以不支持随机访问,只能从头结点遍历;而添加、删除操作比数组效率高,因为不会有数据的搬动。

链表有很多种,单向链表、双向链表、循环链表等。


在Java中,提供了双向链表的实现LinkedList。接下来我们实现一个单链表。能够熟练的实现了之后,剩下的几种形式也就没什么难度了。

public class LinkedList<E> {
    // 定义单链表的节点
    private class Node {
        private E e; // 存储的数据
        private Node next; // 指向下一个节点的引用
        public Node() {
            this(null, null);
        }
        public Node(E e) {
            this(e, null);
        }
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }
        @Override
        public String toString() {
            return e.toString();
        }
    }
    
    private int size; // 链表中节点数量
    private Node dummyHead; // 指向头结点的引用
  
    public LinkedList() {
        this.size = 0;
        this.dummyHead = new Node(); // 虚拟头结点,不存储任何数据
    }
}

为什么要有一个不存储数据的头结点?在添加、删除方法的实现上,使用不存储数据的头结点能使代码更加的简洁。有兴趣的可以自己实现一个存储数据的头结点,体会一下添加、删除方法的实现。

add() 方法

public void add(E e, int index) {
    if (index < 0 || index > size)
        throw new IllegalArgumentException("illegal index: " + index);
    Node prev = dummyHead;
    for (int i = 0; i < index; i++) {
        prev = prev.next;
    }
    /*
        prev.next = new Node(e, prev.next); 相当于 
        Node node = new Node(e, null);
        node.next = prev.next;
        prev.next = node;
    */
    prev.next = new Node(e, prev.next);
    size++; // 维护变量size
}
public void addFirst(E e) {
    add(e, 0);
}
public void addLast(E e) {
    add(e, size);
}

get() 方法

public Node get(int index) {
    if (index < 0 || index >= size)
        throw new IllegalArgumentException("illegal index: " + index);
    Node cur = dummyHead.next; // 因为头结点不存储数据,所以从 dummyHead.next 开始
    for (int i = 0; i < index; i++) {
        cur = cur.next;
    }
    return cur;
}
public Node getFirst() {
    return get(0);
}
public Node getLast() {
    return get(size - 1);
}

remove() 方法

// 删除指定位置的节点
public E remove(int index) {
    if (index < 0 || index >= size)
        throw new IllegalArgumentException("illegal index: " + index);
    Node prev = dummyHead;
    for (int i = 0; i < index; i++) {
        prev = prev.next;
    }
    Node delNode = prev.next;
    prev.next = delNode.next;
    delNode.next = null;
    size --;
    return delNode.e;
}
public E removeFirst() {
    return remove(0);
}
public E removeLast() {
    return remove(size - 1);
}
// 删除链表中值为 e 的第一个节点
public void removeElement(E e) {
    Node prev = dummyHead;
    // 找到待删除节点的前驱节点
    for (int i = 0; i < size; i++) {
        if (prev.next.e.equals(e))
            break;
        prev = prev.next;
    }
    // 判断是否为空(当 e 在链表中不存在,会出现为空的情况)
    if (prev.next != null) {
        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null;
        size --;
    }
}

链表和数组一样,都是基础的数据结构,队列、栈都是基于数组或者链表来实现的。关于链表的面试题目也有很多,就比如单链表翻转、检测链表中是否有环等,可以作为练习。

上一篇下一篇

猜你喜欢

热点阅读