数据结构之链表

235 阅读3分钟

动态数据结构

image.png

链表的特征

image.png image.png

数组和链表的对比

image.png

链表的设计

表头添加元素

image.png

在中间添加元素

关键是找到待添加节点的前一个节点。

如果不设置虚拟头结点,由于head没有钱一个节点,所以就需要对头结点进行特殊的处理。

image.png image.png

删除元素

image.png 这里最主要的是获取当前节点的前一个节点和当前节点。

  • 获取当前节点
/**
 * index = 2
 * 1, 2, 3, 4
 * cur = dummyHead.next = 1, i < index => i < 2
 * i = 0; cur = 2
 * i = 1; cur = 3;
 */
Node cur = dummyHead.next;
for(int i = 0; i < index; i++) {
    cur = cur.next;
}
  • 获取当前节点的前一个节点
/**
 * index = 2
 * 1, 2, 3, 4
 * prev = dummyHead = null, i < index => i < 2
 * i = 0; cur = 1
 * i = 1; cur = 2;
 */
Node prev = dummyHead;
for(int i = 0; i < index; i++) {
   prev = prev.next;
}

代码实现

public class LinkedList<E> {
    private Node dummyHead;
    private int size;
    private class Node {
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.next = next;
            this.e = e;
        }
        public Node(E e) {
            this.e = e;
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }



    public LinkedList() {
//        链表中本身存在一个空节点,虚拟节点。
         this.dummyHead = new Node(null, null);
         this.size = 0;
    }

//    获取元素个数
    public int getSize() {
        return size;
    }
//    判断是否为空
    public boolean isEmpty() {
        return size == 0;
    }

//    头部添加元素
    public void addFirst(E e) {
//        先创建一个node节点
//        Node node = new Node(e);
//        node.next = head;
//        head = node;
//        head = new Node(e, head);
        add(0, e);
    }
//    在中间添加节点
    public void add(int index, E e) {
        if(index > size && index < 0) {
            throw new Error("add fail, index > size && index < 0");
        }

//        先创建一个prev节点,来查找预添加节点的前一个节点。
        Node prev = dummyHead;
        // 这里的边界是index。因为prev开始是等于第一个节点之前的空节点的。
        for(int i = 0; i < index; i++) {
            prev = prev.next;
        }
        Node node = new Node(e);
        node.next = prev.next;
        prev.next = node;
        size++;
    }
//    从尾部添加节点
    public void addLast(E e) {
        add(size - 1, e);
    }

//    查找元素
    public E get(int index) {
        /**
         * index = 2
         * 1, 2, 3, 4
         * cur = dummyHead.next = 1, i < index => i < 2
         * i = 0; cur = 2
         * i = 1; cur = 3;
         */
        Node cur = dummyHead.next;
        for(int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.e;
    }

//    确定是否有该元素
    public boolean contains(E e) {
        Node cur = dummyHead.next;
        for(int i = 0; i < size; i++) {
            if(cur.e == e) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
//    修改元素
    public void set(int index, E e) {
        if(index < 0 || index >= size) {
            throw new Error("set fail, index < 0 || index >= size");
        }

        Node cur = dummyHead;
        for(int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.e = e;
    }

//    删除元素
    public E remove(int index) {
        if(index > size && index < 0) {
            throw new Error("remove fail, index > size && index < 0");
        }
//        找到待删除节点之前的节点。
        /**
         * index = 2
         * 1, 2, 3, 4
         * prev = dummyHead = null, i < index => i < 2
         * i = 0; cur = 1
         * i = 1; cur = 2;
         */
        Node prev = dummyHead;
        for(int i = 0; i < index; i++) {
           prev = prev.next;
        }
//        将prev的next指向下下一个元素。
        Node cur = prev.next;
        prev.next = cur.next;
        size--;
        return cur.e;
    }

    public E removeFirst() {
        return remove(0);
    }
    public E removeLast() {
        return remove(size - 1);
    }
    
 
    // 通过指定元素删除元素
    public void removeElement(E e) {
        Node prev = dummyHead; // 从头开始遍历
        while(prev.next != null) {
            if(e.equals(prev.next.e)) {
                prev.next = prev.next.next;
                prev.next.next = null;
                size--;
            }
            prev = prev.next;
        }
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("LinkedList:    ");
        Node cur = dummyHead.next;
        while(cur != null) {
            res.append(cur.e + "=>");
            cur = cur.next;
        }
        return res.toString();
    }
}

复杂度分析

image.png image.png image.png image.png

用链表设计栈结构

我们可以使用链表头部作为栈顶结构。 image.png

public class StackByLinkedList<E> implements StackInterface<E> {
//    我们可以使用链表头部作为栈顶结构。
    private LinkedList<E> list;

    public StackByLinkedList() {
        list = new LinkedList();
    }
    @Override
    public void push(E e) {
        list.addFirst(e);
    }

    @Override
    public E pop() {
        return list.removeFirst();
    }

    @Override
    public int getSize() {
        return list.getSize();
    }

    @Override
    public E peek() {
        return list.get(0);
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("linkedListStack top: ");
        res.append(list);
        return res.toString();
    }
}

用链表设计队列结构

这里需要注意的是,当队列中只有一个元素时,删除了元素,需要将tail指针指向null。 image.png

public class LinkedListQueue<E> implements QueueInterface<E> {
//    节点总数
    private int size;
//    头部节点
    private Node head;
//    尾部节点
    private Node tail;

    private class Node {
        public E e;
        public Node next;

        public Node(E e) {
            this.e = e;
        }

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    public LinkedListQueue() {
        this.size = 0;
        this.head = null;
        this.tail = null;
    }

    //    入队
    @Override
    public void enqueue(E e) {
//        需要先判断队列是否为空
        if(tail == null) {
            tail = new Node(e);
            head = tail;
        }else {
//            改变尾节点指向。
            tail.next = new Node(e);
            tail = tail.next;
        }
        size++;
    }

    @Override
    public E dequeue() {
        if(head == null) {
            throw new Error("linkedListQueue is empty");
        }
        E cur = head.e;
        head = head.next;
//        如果队列中只有一个元素,那么删除后需要改变tail指向
       if(head == null) {
            tail = null;
        }
        size--;
        return cur;
    }

    @Override
    public E getFront() {
        return head.e;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("LinkedListQueue : ");
        Node cur = head;
        while(cur != null) {
            res.append(cur.e + "=>");
            cur = cur.next;
        }
        return res.toString();
    }
}

移除链表元素 LeetCode-203

  • 方式一:删除头结点时另做考虑
class Solution {
    public ListNode removeElements(ListNode head, int val) {
       // 查看头部节点是否为val元素。可能删除后下一个节点依旧是val。
       while(head != null && head.val == val) {
           head = head.next;
       }

    //    当删除头部节点时,链表为空,则返回false.
        if(head == null) {
            return null;
        }
        // 判断中间元素是否是val
        ListNode prev = head;
        while(prev.next != null) {
            // 查找到了,将跳跃一个元素
            if(prev.next.val == val) {
                prev.next = prev.next.next;
            }else {
            // 没有查到,将指向下一个元素继续。
                prev = prev.next;
            }
        }
        return head;
    }
}
  • 方式二: 创建一个虚拟头结点。 注意: 这个虚拟节点的next始终指向头结点。给出虚拟头结点的作用是为了可以统一处理头结点和中间节点。因为我们需要找到的是待删除元素的前一个元素。
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 创建一个虚拟节点。并且next始终指向头结点。
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        // 创建一个查询的前一个节点
        ListNode prev = dummyHead;
        while(prev.next != null) {
            if(prev.next.val == val) {
                prev.next = prev.next.next;
            }else {
                prev = prev.next;
            }
        }
        return dummyHead.next;
    }
}