链表

链表

链表(Linked List)

动态数组有个明显得缺点

  • 可能造成内存空间的大量浪费

能否用到多少就申请多少内存?

  • 链表可以办到这一点

链表是一种链式存储的线性表, 所有元素的内存地址不一定是连续的

链表(Linked List)接口设计

链表的大部分接口和动态数组是一致的

int size();//元素的数量
boolean isEmpty();//是否为空
boolean contains(E element);//是否包含某个元素
void add(E element);//添加元素到最后面
E get(int index);//返回index位置对应的元素
E set(int index, E element);//设置index位置的元素
void add(int index, E element);//往index位置添加元素
E remove(int index);//删除index位置对应的元素
int indexOf(E element);//查看元素的位置
void clear();//清除所有元素

由于链表的大部分接口和动态数组一致,我们抽取出一个共同的 List 接口

/**
 * list 接口供动态数组和链表实现
 * @param <E> 泛型
 */
public interface List<E> {
    // 表示元素不存在
    static final int ELEMENT_NOT_FOUND = -1;
    /**
     * 清除所有元素
     */
    void clear();

    /**
     * 元素的数量
     * @return
     */
    int size();

    /**
     * 是否为空
     * @return
     */
    boolean isEmpty();

    /**
     * 是否包含某个元素
     * @param element
     * @return
     */
    boolean contains(E element);

    /**
     * 添加元素到尾部
     * @param element
     */
    void add(E element);

    /**
     * 获取index位置的元素
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return 原来的元素ֵ
     */
    E set(int index, E element);

    /**
     * 在index位置插入一个元素
     * @param index
     * @param element
     */
    void add(int index, E element);

    /**
     * 删除index位置的元素
     * @param index
     * @return
     */
    E remove(int index);

    /**
     * 查看元素的索引
     * @param element
     * @return
     */
    int indexOf(E element);
}

再将一些通用的字段与方法放到一个抽象类中,无论是动态数组还是链表只需要继承这个抽象类

/**
 * 动态数组和链表公共的方法 其他方法有各自的实现
 * @param <E> 泛型
 */
public abstract class AbstractList<E> implements List<E>{
    /**
     * 元素数量
     */
    protected int size;

    /**
     * 元素的数量
     * @return
     */
    public int size(){
        return size;
    }
    /**
     * 是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }
    /**
     * 是否包含某个元素
     * @param element 元素
     * @return
     */
    public boolean contains(E element){
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }
    /**
     * 添加元素到最后面
     * @param element
     */
    public void add(E element){
        add(size,element);
    }

    /**
     * 越界异常提示
     * @param index
     */
    protected void outOfBounds(int index){
        throw new IndexOutOfBoundsException("Index:"+index+", Size:"+size);
    }

    /**
     * 边界判断
     * @param index
     */
    protected void rangeCheck(int index){
        if (index < 0 || index >= size){
            this.outOfBounds(index);
        }
    }

    /**
     * 添加时边界判断
     * @param index
     */
    protected void rangeCheckForAdd(int index){
        if (index < 0 || index > size){
            this.outOfBounds(index);
        }
    }
}
创建 LinkedList 继承 AbstractList
  • size属性记录存储数据的数量,创建first属性引用链表的第0个元素,来管理链表的数据
  • 创建私有类Node,其中的element属性用于存储元素,next属性用于指向链表中的下一个结点
public class LinkedList<E> extends AbstractList<E>{
    /**
     * 元素所在的结点
     */
    private Node<E> first;

    /**
     * 内部类 结点
     * @param <E>
     */
    private static class Node<E>{
        E element;//元素
        Node<E> next;//下个元素的地址
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }
}
默认构造方法

链表的创建与动态数组不同,动态数组在构造时需要传入一个空间属性,来决定这个数组的容量。但链表元素是在添加时才创建的,内存地址不一定是连续的。所以链表不需要在单独设计构造方法,使用默认构造方法

清空元素 - clear()

first指向null,释放链表所有node,同时size置为0

public class LinkedList<E> extends AbstractList<E>{
    ......
    
    /**
     * 清除所有元素
     */
    public void clear(){
        size = 0;
        first = null;
    }    
        
    ......
}
查找结点 - node(int index)

此时需要添加node()方法获取索引(index)位置对应的结点

public class LinkedList<E> extends AbstractList<E>{
    ......
    
     /**
     * 获取index位置对应的结点对象
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }
        
    ......
}
添加元素 - add(int index, E element)
  • 添加数据结点时,需要创建一个结点存储数据,并将该结点拼接到最后结点的后面,然后size加1
  • 需要区分当前链表没有数据,新结点拼接到first当前链表有数据,新结点拼接到最后的结点
  • 添加元素尤其要注意 0 位置,给空链表添加first结点是个特殊情况

public class LinkedList<E> extends AbstractList<E>{
    ......
    
     /**
     * 返回index位置对应的元素
     * @param index
     * @return
     */
    public E get(int index){
        return node(index).element;
    }
    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element){
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    /**
     * 往index位置添加元素
     * @param index
     * @param element
     */
    public void add(int index, E element){
        //边界判断
        rangeCheckForAdd(index);
        if (index == 0){// 给空链表添加第一个元素的情况
            //创建新结点,first结点指向这个新结点
            first = new Node<>(element,first);
        }else {
            //index上一个结点
            Node<E> prev = node(index - 1);
            //创建新结点,prev结点指向这个新结点
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }
        
    ......
}
删除元素 - remove(int index)
  • 首先找到删除结点前一个结点(prev),然后通过变更(prev)结点next指针指向删除结点的下一个结点,然后size减1
  • 需要判断是否删除的第0个元素,如果是,则使用first结点指向第1个结点

public class LinkedList<E> extends AbstractList<E>{
    ......
    
     /**
     * 删除index位置对应的元素
     * @param index
     * @return
     */
    public E remove(int index){
        //边界判断
        rangeCheck(index);
        //记录结点
        Node<E> node = first;
        if (index == 0){ // 删除第一个元素是特殊情况
            first = first.next;
        }else {
            //index上一个结点
            Node<E> prev = node(index - 1);
            //记录删除的结点
            node = prev.next;
            //node结点下一个结点,prev结点指向这个结点
            prev.next = node.next;
        }
        size --;
        return node.element;
    }
        
    ......
}
查找元素位置 - indexOf(E element)
  • 查找指定元素的索引,需要遍历所有结点,找到结点对应的元素与执行元素相等

    如果需要支持结点element为null,则需要分情况处理

public class LinkedList<E> extends AbstractList<E>{
    ......
    
     /**
     * 查看元素的位置
     * @param element
     * @return
     */
    public int indexOf(E element){
        // 取出头结点
        Node<E> node = first;
        if(element ==null){// 当element为null时的处理
            // 遍历结点, 找到存储为null的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        }else {
            // 遍历结点, 找到存储的元素与指定元素相等的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }

        return ELEMENT_NOT_FOUND;
    }
        
    ......
}
打印元素 - toString()
  • 重写toString方法
  • toString 方法中将元素拼接成字符串
  • 字符串拼接建议使用StringBuilder
public class LinkedList<E> extends AbstractList<E>{
    ......
    
     /**
     * 打印
     * @return
     */
    @Override
    public String toString() {
        //Size = ? , [1, 2, 3]
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
        
    ......
}
完整代码 --- 单向链表
/**
 * 单链表的实现
 * @param <E> 泛型
 */
public class LinkedList<E> extends AbstractList<E>{
    /**
     * 元素所在的结点
     */
    private Node<E> first;

    /**
     * 内部类 结点
     * @param <E>
     */
    private static class Node<E>{
        E element;//元素
        Node<E> next;//下个元素的地址
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    /**
     * 返回index位置对应的元素
     * @param index
     * @return
     */
    public E get(int index){
        return node(index).element;
    }
    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element){
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    /**
     * 往index位置添加元素
     * @param index
     * @param element
     */
    public void add(int index, E element){
        //边界判断
        rangeCheckForAdd(index);
        if (index == 0){// 给空链表添加第一个元素的情况
            //创建新结点,first结点指向这个新结点
            first = new Node<>(element,first);
        }else {
            //index上一个结点
            Node<E> prev = node(index - 1);
            //创建新结点,prev结点指向这个新结点
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }
    /**
     * 删除index位置对应的元素
     * @param index
     * @return
     */
    public E remove(int index){
        //边界判断
        rangeCheck(index);
        //记录结点
        Node<E> node = first;
        if (index == 0){ // 删除第一个元素是特殊情况
            first = first.next;
        }else {
            //index上一个结点
            Node<E> prev = node(index - 1);
            //记录删除的结点
            node = prev.next;
            //node结点下一个结点,prev结点指向这个结点
            prev.next = node.next;
        }
        size --;
        return node.element;
    }

    /**
     * 查看元素的位置
     * @param element
     * @return
     */
    public int indexOf(E element){
        // 取出头结点
        Node<E> node = first;
        if(element ==null){// 当element为null时的处理
            // 遍历结点, 找到存储为null的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        }else {
            // 遍历结点, 找到存储的元素与指定元素相等的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }

        return ELEMENT_NOT_FOUND;
    }
    /**
     * 清除所有元素
     */
    public void clear(){
        size = 0;
        first = null;
    }

    /**
     * 获取index位置对应的结点对象
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    /**
     * 打印
     * @return
     */
    @Override
    public String toString() {
        //Size = ? , [1, 2, 3]
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }


}

虚拟头结点 --- 存在内存问题

有时候为了让代码更加精简,统一所有结点的处理逻辑,可以在最前面增加一个虚拟头结点(不存储数据)

带虚拟头结点的单向链表与普通单向链表代码类似:但是 addremove略有不同;

/**
 * 增加一个虚拟头结点
 * @param <E> 泛型
 */
public class VirtualHeadLinkedList<E> extends AbstractList<E>{
    /**
     * 元素所在的结点
     */
    private Node<E> first;

    /**
     * 内部类 结点
     * @param <E>
     */
    private static class Node<E>{
        E element;//元素
        Node<E> next;//下个元素的地址
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    /**
     * 构造函数 创建一个空结点 --- 增加
     */
    public VirtualHeadLinkedList() {
        this.first = new Node<>(null,null);
    }

    /**
     * 返回index位置对应的元素
     * @param index
     * @return
     */
    public E get(int index){
        return node(index).element;
    }
    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element){
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    /**
     * 往index位置添加元素 --- 修改
     * @param index
     * @param element
     */
    public void add(int index, E element){
        //边界判断
        rangeCheckForAdd(index);
        Node<E> prev = index ==0 ? first : node(index - 1);
        prev.next = new Node<>(element, prev.next);
        size++;
    }
    /**
     * 删除index位置对应的元素 --- 修改
     * @param index
     * @return
     */
    public E remove(int index){
        //边界判断
        rangeCheck(index);
        Node<E> prev = index == 0 ? first : node(index - 1);
        Node<E> node = prev.next;
        prev.next = node.next;
        size --;
        return node.element;
    }

    /**
     * 查看元素的位置
     * @param element
     * @return
     */
    public int indexOf(E element){
        // 取出头结点
        Node<E> node = first;
        if(element ==null){// 当element为null时的处理
            // 遍历结点, 找到存储为null的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        }else {
            // 遍历结点, 找到存储的元素与指定元素相等的结点, 返回索引
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }

        return ELEMENT_NOT_FOUND;
    }
    /**
     * 清除所有元素
     */
    public void clear(){
        size = 0;
        first = null;
    }

    /**
     * 获取index位置对应的结点对象 --- 修改
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        //first的下一个结点
        Node<E> node = first.next;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    /**
     * 打印 --- 修改
     * @return
     */
    @Override
    public String toString() {
        //Size = ? , [1, 2, 3]
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first.next;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }

}

动态数组、链表复杂度分析

数组的随机访问速度非常快:elements[n]的效率与 n 是多少无关;

双向链表

  • 单向链表只能通过Node中next属性从头遍历链表,完成搜索
  • 双向链表中的Node增加prev属性,指向该结点上一个结点
  • 双向链表查找元素可以从first或last两个方向开始查找

因此双向链表可以提升链表的综合性能

双向链表只有一个结点(元素)

双向链表接口设计

相较于单向链表,双向链表需要重写查找结点插入结点删除结点清空结点四个方法

  • 新增一个last元素,最后一个元素
  • 私有结点Node 中的prev 元素用于指向链表中的上一个结点
public class LinkedList<E> extends AbstractList<E> {
    /**
     * 元素所在的结点
     */
    private Node<E> first;
     //增加last结点
    private Node<E> last;

    /**
     * 内部类 结点
     * @param <E>
     */
    private static class Node<E>{
        E element;//元素
        Node<E> prev;//增加上一个元素地址
        Node<E> next;//下个元素的地址
        public Node(E element, Node<E> prev,Node<E> next) {
            this.element = element;
            this.prev = prev;
            this.next = next;
        }
    }
}
查找结点 - node(int index)

通过判断查找的节点在链表的前半段或后半段,选择first结点从前往后遍历或last结点从后往前遍历

public class LinkedList<E> extends AbstractList<E>{
    ......
    
    /**
     * 获取index位置对应的节点对象
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        //往后
        if (index < (size >> 1)){
            Node<E> node = first;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        }else {//往前
            Node<E> node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
            return node;
        }
    }
        
    ......
}
清空元素 - clear()

需将新增属性last置为null

public class LinkedList<E> extends AbstractList<E>{
    ......
    
    /**
     * 清除所有元素
     */
    public void clear(){
        size = 0;
        first = null;
        last = null;
    }
        
    ......
}
添加元素 - add(int index, E element)
  • 假定插入位置原结点为oldNode(下图2号结点),上一个结点为prevNode(下图1号结点),新结点为node

  • prevNodenext现在为node

  • oldNodeprev现在为node

  • nodeprev即为oldNodeprev

  • nodenextoldNode

  • 特殊情况,插入在最前面:

    • 如果oldNodeprev为null,那么oldNode为原头结点。那么双向链表的first属性需改为node
  • 插入在最后面:

    • nodeprev为双向链表的lastnextnull,同时last需改为node
    • 如果原链表为空,则双向链表的firstlast都为node

public class LinkedList<E> extends AbstractList<E>{
    ......
    
    /**
     * 往index位置添加元素
     * @param index
     * @param element
     */
    public void add(int index, E element){
        //边界判断
        rangeCheckForAdd(index);
        // size == 0
        // index == 0
        if (index == size){//往最后添加元素
            Node<E> oldLast = last;
            last = new Node<>(element,oldLast,null);
            if (oldLast == null){//这是链表的第一个元素
                first = last;
            }else {
                oldLast.next = last;
            }
        }else {// 正常添加元素
            //插入位置的原结点,即为新节点的next节点
            Node<E> next = node(index);
            //新添加结点的上一个节点,即为该位置原节点的上一个节点
            Node<E> prev = next.prev;
            //创建新结点
            Node<E> node = new Node<>(element,prev,next);
            //原结点的上一个结点,为新添加结点
            next.prev = node;
            if (prev == null){//index == 0 即添加的是第一个
                first = node;
            }else {
                //原结点上一个结点的next,即为新添加节点
                prev.next = node;
            }
        }

        size++;
    }
        
    ......
}
删除元素 - remove(int index)
  • 删除结点, 只需要让被删除结点的前一个结点与后一个结点之间链接, 同时去掉被删除结点引用
  • 需要注意的是, 第0个结点和最后一个结点要特殊处理
public class LinkedList<E> extends AbstractList<E>{
    ......
    
    /**
     * 删除index位置对应的元素
     * @param index
     * @return
     */
    public E remove(int index){
        //边界判断
        rangeCheck(index);
        //删除的结点
        Node<E> node = node(index);
        //删除结点的上一个结点
        Node<E> prev = node.prev;
        //删除结点的下一个结点
        Node<E> next = node.next;
        //删除结点, 只需去掉对这个结点的引用
        if (prev == null){//index == 0 即删除的是第一个
            first = next;
        }else {
            prev.next = next;
        }
        if (next == null){//index == size - 1 即删除的是最后一个
            last = prev;
        }else {
            next.prev = prev;
        }
        size --;
        return node.element;
    }
        
    ......
}
完整代码 --- 双向链表
/**
 * 双向链表的实现
 * @param <E> 泛型
 */
public class LinkedList<E> extends AbstractList<E> {
    /**
     * 元素所在的结点
     */
    private Node<E> first;
    //增加last节点
    private Node<E> last;

    /**
     * 内部类 结点
     * @param <E>
     */
    private static class Node<E>{
        E element;//元素
        Node<E> prev;//增加上一个元素地址
        Node<E> next;//下个元素的地址
        public Node(E element, Node<E> prev,Node<E> next) {
            this.element = element;
            this.prev = prev;
            this.next = next;
        }
    }

    /**
     * 返回index位置对应的元素
     * @param index
     * @return
     */
    public E get(int index){
        return node(index).element;
    }
    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element){
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }

    /**
     * 往index位置添加元素
     * @param index
     * @param element
     */
    public void add(int index, E element){
        //边界判断
        rangeCheckForAdd(index);
        // size == 0
        // index == 0
        if (index == size){//往最后添加元素
            Node<E> oldLast = last;
            last = new Node<>(element,oldLast,null);
            if (oldLast == null){//这是链表的第一个元素
                first = last;
            }else {
                oldLast.next = last;
            }
        }else {// 正常添加元素
            //插入位置的原结点,即为新节点的next节点
            Node<E> next = node(index);
            //新添加结点的上一个节点,即为该位置原节点的上一个节点
            Node<E> prev = next.prev;
            //创建新结点
            Node<E> node = new Node<>(element,prev,next);
            //原结点的上一个结点,为新添加结点
            next.prev = node;
            if (prev == null){//index == 0 即添加的是第一个
                first = node;
            }else {
                //原结点上一个结点的next,即为新添加节点
                prev.next = node;
            }
        }

        size++;
    }
    /**
     * 删除index位置对应的元素
     * @param index
     * @return
     */
    public E remove(int index){
        //边界判断
        rangeCheck(index);
        //删除的结点
        Node<E> node = node(index);
        //删除结点的上一个结点
        Node<E> prev = node.prev;
        //删除结点的下一个结点
        Node<E> next = node.next;
        //删除结点, 只需去掉对这个结点的引用
        if (prev == null){//index == 0 即删除的是第一个
            first = next;
        }else {
            prev.next = next;
        }
        if (next ==null){//index == size - 1 即删除的是最后一个
            last = prev;
        }else {
            next.prev = prev;
        }
        size --;
        return node.element;
    }

    /**
     * 查看元素的位置
     * @param element
     * @return
     */
    public int indexOf(E element){
        // 取出头结点
        Node<E> node = first;
        if(element ==null){// 当element为null时的处理
            // 遍历节点, 找到存储为null的节点, 返回索引
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        }else {
            // 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }

        return ELEMENT_NOT_FOUND;
    }
    /**
     * 清除所有元素
     */
    public void clear(){
        size = 0;
        first = null;
        last = null;
    }

    /**
     * 获取index位置对应的节点对象
     * @param index
     * @return
     */
    private Node<E> node(int index){
        rangeCheck(index);
        //往后
        if (index < (size >> 1)){
            Node<E> node = first;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        }else {//往前
            Node<E> node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
            return node;
        }
    }

    /**
     * 打印
     * @return
     */
    @Override
    public String toString() {
        //Size = ? , [1, 2, 3]
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0){
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

双向链表和单向链表对比

粗略对比一下删除的操作数量:操作数量缩减了近一半

有了双向链表,单向链表是否就没有任何用处了?

  • 并非如此,在哈希表的设计中就用到了单链表
  • 至于原因,在哈希表中会详细讲解

双向链表 vs 动态数组

动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容解决)
双向链表:开辟、销毁内存空间的次数相对较多,但不会造成内存空间的浪费

  • 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择
  • 如果频繁在头部进行添加、删除操作,建议选择使用双向链表
  • 如果有频繁的 (在任意位置)添加、删除操作,建议选择使用双向链表
  • 如果有频繁的查询操作(随机访问操作),建议选择使用动态数组
原文地址:https://www.cnblogs.com/netu/p/14999189.html