线性表之链表

106 阅读6分钟

链表定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

单链表的实现

定义

单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。

节点类

public class Node<T> { 
	//存储元素 
	public T data; 
	//指向下一个结点 
	public Node next; 
}

API 定义

类名MyLinkedList
成员变量private Node head;//头节点
private int size; //长度
成员内部类private class Node;//节点类
构造方法MyLinkedList();//创建对象
成员方法public void clear();//清空
public boolean isEmpty();//判断线性表是否为null
public int length();//获取线性表的长度
public T get(int i); //获取线性表中第i个元素的值
public void insert(T t);//向线性表插入元素t
public void insert(int i,T t);//向线性表第i个插入元素t
public T remove(int i);//删除并返回线性表中第i个数据元素
public int indexOf(T t);//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1

实现

package com.black.cat.algorithm.linear;


import java.util.Iterator;

/**
 * @Author blackcat
 * @version: 1.0
 * @description:单链表
 */
public class MyLinkedList<T> implements Iterable<T> {

    //头节点
    private Node head;

    //长度
    private int size;


    //节点类
    private class Node {
        //存储元素
        T data;
        //指向下一个结点
        Node next;

        public Node(T data, Node next) {
            this.data = data;
            this.next = next;
        }
    }

    //构造方法
    public MyLinkedList() {
        //初始化头节点
        this.head = new Node(null, null);
        this.size = 0;
    }


    //清空
    public void clear() {
        head.next = null;
        //长度为0
        this.size = 0;
    }

    //判断线性表是否为null
    public boolean isEmpty() {
        return size == 0;
    }

    //获取线性表的长度
    public int length() {
        return size;
    }

    //获取线性表中第i个元素的值
    public T get(int i) {
        if (i < 0 || i >= size) {
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        }
        Node cur = head.next;
        for (int j = 0; j < i; j++) {
            cur = cur.next;
        }
        return cur.data;
    }

    //向线性表插入元素t
    public void insert(T t) {

        Node cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        Node newNode = new Node(t, null);
        cur.next = newNode;
        //size +1
        size++;

    }

    //向线性表第i个插入元素t<br/>
    public void insert(int i, T t) {
        if (i < 0 || i > size)
            throw new IllegalArgumentException("insert failed. Require index >= 0 and index <= size.");

        //找到第i节点
        Node pre = head;
        for (int j = 0; j < i; j++) {
            pre = pre.next;
        }
        //位置i的结点
        Node cur = pre.next;
        //创建新的节点,下一个节点为i位置的节点
        Node newNode = new Node(t, cur);
        //i-1位置的下一个节点为新的节点
        pre.next = newNode;
        //长度++
        size++;

    }

    //删除并返回线性表中第i个数据元素
    public T remove(int i) {
        if (i < 0 || i > size)
            throw new IllegalArgumentException("remove failed. Require index >= 0 and index <= size.");
        //找到第i 和 i-1的节点
        Node pre = head;
        for (int j = 0; j < i; j++) {
            pre = pre.next;
        }
        //当前i位置的结点
        Node cur = pre.next;
        //删除当前节点,前一个结点指向删除节点的下一个节点
        pre.next = cur.next;
        //当前节点下一个指向为null
        cur.next = null;
        //长度--
        size--;
        return cur.data;
    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1
    public int indexOf(T t) {
        Node cur = head.next;
        for (int j = 0; j < size; j++) {
            if (cur.data.equals(t)) {
                return j;
            }
            cur = cur.next;
        }
        return -1;
    }


    @Override
    public Iterator<T> iterator() {
        return new LinkedIterator();
    }

    private class LinkedIterator implements Iterator<T> {
        private Node node;

        public LinkedIterator() {
            this.node = head;
        }

        @Override
        public boolean hasNext() {
            return node.next != null;
        }

        @Override
        public T next() {
            node = node.next;
            return node.data;
        }
    }

    public static void main(String[] args) {
        MyLinkedList<String> list = new MyLinkedList<>();
        list.insert(0, "张三");
        list.insert(1, "李四");
        list.insert(2, "王五");
        list.insert("赵六");
        for (String s : list) {
            System.out.println(s);
        }
        //测试length方法
        System.out.println("size----"+list.length());
        System.out.println("-------------------");
        //测试get方法
        System.out.println("get----"+list.get(2));
        System.out.println("------------------------");
        //测试remove方法
        String remove = list.remove(1);
        System.out.println("remove----"+remove);
        System.out.println("size----"+list.length());
        System.out.println("----------------");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

双链表的实现

定义

双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。

节点类

public class Node<T> { 
	//存储元素 
	public T data; 
	//指向下一个结点 
	public Node next; 
    //指向前一个结点 
	public Node pre; 
}

API 定义

类名TwoWayLinkedList
成员变量private Node first;//头节点
private Node last;//尾节点
private int size; //长度
成员内部类private class Node;//节点类
构造方法TwoWayLinkedList();//创建对象
成员方法public void clear();//清空
public boolean isEmpty();//判断线性表是否为null
public int length();//获取线性表的长度
public T get(int i); //获取线性表中第i个元素的值
public void insert(T t);//向线性表插入元素t
public void insert(int i,T t);//向线性表第i个插入元素t
public T remove(int i);//删除并返回线性表中第i个数据元素
public int indexOf(T t);//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1
public T getFirst();//获取第一个元素
public T getLast(); //获取最后一个元素

实现

package com.black.cat.algorithm.linear;

import java.util.Iterator;

/**
 * @Author blackcat
 * @create 2021/12/10 21:37
 * @version: 1.0
 * @description: 双链表的linkedList
 */
public class TwoWayLinkedList<T> implements Iterable<T> {

    //头节点
    private Node first;
    //尾节点
    private Node last;
    //长度
    private int size;

    //节点类
    private class Node {
        //存储元素
        public T data;
        //指向下一个结点
        public Node next;
        //指向前一个结点
        public Node pre;

        public Node(T data, Node next, Node pre) {
            this.data = data;
            this.next = next;
            this.pre = pre;
        }
    }


    TwoWayLinkedList() {
        last = null;
        first = new Node(null, null, null);
        size = 0;
    }

    //清空
    public void clear() {
        last = null;
        first.next = null;
        first.pre = null;
        first.data = null;
        //长度为0
        this.size = 0;
    }

    //判断线性表是否为null
    public boolean isEmpty() {
        return size == 0;
    }

    //获取线性表的长度
    public int length() {
        return size;
    }

    //插入元素t
    public void insert(T t) {
        if (last == null) {
            last = new Node(t, null, first);
            first.next = last;
        } else {
            Node oldLast = last;
            Node node = new Node(t, null, oldLast);
            oldLast.next = node;
            last = node;
        }
        //长度+1
        size++;
    }

    //向指定位置i处插入元素t
    public void insert(int i, T t) {
        if (i < 0 || i > size)
            throw new IllegalArgumentException("insert failed. Require index >= 0 and index <= size.");

        //找到第i节点
        Node pre = first;
        for (int j = 0; j < i; j++) {
            pre = pre.next;
        }
        if (size == i) {
            insert(t);
        } else {
            //位置i的结点
            Node cur = pre.next;
            //创建新的节点,下一个节点为i位置的节点
            Node newNode = new Node(t, cur, pre);
            //i-1位置的下一个节点为新的节点
            pre.next = newNode;
            //位置i的结点的pre 为当前节点
            cur.pre = newNode;
            //长度++
            size++;
        }
    }

    //获取线性表中第i个元素的值
    public T get(int i) {
        if (i < 0 || i >= size) {
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        }
        Node cur = first.next;
        for (int j = 0; j < i; j++) {
            cur = cur.next;
        }
        return cur.data;
    }

    //删除并返回线性表中第i个数据元素
    public T remove(int i) {
        if (i < 0 || i > size)
            throw new IllegalArgumentException("remove failed. Require index >= 0 and index <= size.");
        //找到第i 和 i-1的节点
        Node pre = first;
        for (int j = 0; j < i; j++) {
            pre = pre.next;
        }
        //当前i位置的结点
        Node cur = pre.next;
        //删除当前节点,前一个结点指向删除节点的下一个节点
        pre.next = cur.next;
        //删除节点的下一个节点的pre为pre
        cur.next.pre = pre;
        //当前节点指向为null
        cur.next = null;
        cur.pre = null;
        //长度--
        size--;
        return cur.data;
    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返-1
    public int indexOf(T t) {
        Node cur = first.next;
        for (int i = 0; i < size; i++) {
            if (cur.data.equals(t)) {
                return i;
            }
            cur = cur.next;
        }
        return -1;
    }


    //获取第一个元素
    public T getFirst() {
        if (isEmpty()) {
            return null;
        }
        return first.next.data;
    }

    //获取最后一个元素
    public T getLast() {
        if (isEmpty()) {
            return null;
        }
        return last.data;
    }

    @Override
    public Iterator<T> iterator() {
        return new LinkedIterator();
    }

    private class LinkedIterator implements Iterator<T> {
        private Node node;

        public LinkedIterator() {
            this.node = first;
        }

        @Override
        public boolean hasNext() {
            return node.next != null;
        }

        @Override
        public T next() {
            node = node.next;
            return node.data;
        }
    }

    public static void main(String[] args) {
        TwoWayLinkedList<String> list = new TwoWayLinkedList<>();
        list.insert(0, "张三");
        list.insert(1, "李四");
        list.insert("赵六");
        list.insert(3, "王五");

        for (String s : list) {
            System.out.println(s);
        }
        //测试length方法
        System.out.println("size----" + list.length());
        System.out.println("-------------------");
        //测试get方法
        System.out.println("get----" + list.get(2));
        System.out.println("------------------------");
        //测试remove方法
        String remove = list.remove(1);
        System.out.println("remove----" + remove);
        System.out.println("size----" + list.length());
        System.out.println("----------------");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

LinkedList的源码

数据结构

LinkedList底层数据结构是双向链表

image-20211210234946793.png

 private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
}

关键属性

   /**
     * LinkedList节点个数
     */
    transient int size = 0;
    /**
     * 指向头节点的指针
     */
    transient Node<E> first;

    /**
     * 指向尾节点的指针
     */
    transient Node<E> last;

构造方法

   /**
     * 构造一个空链表.
     */
    public LinkedList() {
    }


    /**
     * 根据指定集合c构造linkedList。先构造一个空linkedlist,在把指定集合c中的所有元素都添加到            *  linkedList中。
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

插入

头插

    /**
     * 在表头添加指定元素e
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        //新建节点,节点的前指针指向null,后指针原来的头节点
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        //如果原来的头结点为null,更新尾指针,否则使原来的头结点f的前置指针指向新的头结点newNode
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

尾插

   /**
     * 在表尾插入指定元素e
     */
    void linkLast(E e) {
        final Node<E> l = last;
        //新建节点newNode,节点的前指针指向l,后指针为null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //如果原来的尾结点为null,更新头指针,否则使原来的尾结点l的后置指针指向新的头结点newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

中间插

  /**
     * 在指定位置添加元素
     */
    public void add(int index, E element) {
        //检查索引是否处于[0-size]之间
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            //node(index) 就是获取
            linkBefore(element, node(index));
    }

   /**
     * 在指定节点succ之前插入指定元素e。指定节点succ不能为null。
     */
    void linkBefore(E e, Node<E> succ) {
        //获得指定节点的前驱
        final Node<E> pred = succ.prev;
        //新建节点newNode,前置指针指向pred,后置指针指向succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        //如果指定节点的前驱为null,将newTouch设为头节点。否则更新pred的后置节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

删除

/**
     * 正向遍历链表,删除出现的第一个值为指定对象的节点
     */
    public boolean remove(Object o) {
        //LinkedList允许存放Null
        //如果删除对象为null
        if (o == null) {
            //从头开始遍历
            for (Node<E> x = first; x != null; x = x.next) {
                //找到元素
                if (x.item == null) {
                    //从链表中移除找到的元素
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * 删除指定节点,返回指定元素的值
     */
    E unlink(Node<E> x) {
        // assert x != null;
        // 保存指定节点的值
        final E element = x.item;
        //得到后继节点
        final Node<E> next = x.next;
        //得到前驱节点
        final Node<E> prev = x.prev;

        if (prev == null) {
            //如果删除的节点是头节点,令头节点指向该节点的后继节点
            first = next;
        } else {
            //将前驱节点的后继节点指向后继节点
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            //如果删除的节点是尾节点,令尾节点指向该节点的前驱节点
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

获取

 /**
     * 返回指定索引处的元素
     */
    public E get(int index) {
        //检查index范围是否在size之内
        checkElementIndex(index);
        //调用node(index)去找到index对应的node然后返回它的值
        return node(index).item;
    }
    
     /**
     * 返回在指定索引处的非空元素
     */
    Node<E> node(int index) {
        // 下标小于长度的一半,从头遍历,否则从尾遍历
        
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

参考资料

segmentfault.com/a/119000001… 我理解的数据结构

www.bilibili.com/video/BV1iJ… 黑马程序员Java数据结构与java算法

segmentfault.com/a/119000001… LinkedList源码分析

小傅哥的java 面经手册第九章