目录

1. 链表

1.1 单向链表

1.2 单链表增删查改

2.1 带头单链表

2.2 带头单链表增删查改

3.1 双向链表

3.2 双向链表的增删查改


1. 链表

        链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

java 增删改 后置处理链路 java的增删改查是指什么_java

1.1 单向链表

单向链表:每个节点只保存了下一个节点的地址,只能从头节点开始向后遍历,这种链表结构称为单向链表,简称单链表。

我们将单链表类比为火车:火车的不同车厢之间,都是通过一个挂钩连接的,当这两个这厢脱钩后,这两个车厢就没有任何关系了。

java 增删改 后置处理链路 java的增删改查是指什么_链表_02

定义链表类 ——> 类比为火车

public class SingleLinedList {
    Node head;   //头节点(第一个车厢)
    int size;    //当前链表中的节点个数,保存的元素个数(一个Node节点只保存了一个值)
}

给定火车的火车头 Node head ,就能从head开始向后遍历,遍历当前这个链表中所有的节点。

定义节点类 ——> 火车的每节车厢

class Node {
    int val;      //当前节点保存的值
    Node next;    //下一个节点的地址
}

每—列火车由若干个车厢组成,每个车厢就是一个 Node 对象,由多个 Node 对象组成的大实体就是链表对象。

toString方法 —— 用字符串格式化输出链表

public String toString() {
        String ret = "";
        for (Node x = head; x != null; x = x.next) {
            ret += x.val;
            ret += "——>";
            x = x.next;
        }
        ret += "Null";
        return ret;
    }

1.2 单链表增删查改

java 增删改 后置处理链路 java的增删改查是指什么_list_03

public void addFirst(int val):向当前的链表中添加一个新的节点,默认在链表的头部添加
/**
     * 向当前的链表中添加一个新的节点,默认在链表的头部添加
     */
    public void addFirst(int val) {
        Node newNode = new Node();
        newNode.val = val;
        if (head != null) {
            newNode.next = head;
        }
        //无论是否为空,插入后都是链表的新节点
        head = newNode;
        size++;
    }

java 增删改 后置处理链路 java的增删改查是指什么_数据结构_04

public void add(int index,int val):在链表index处插入一个新节点

/**
     * 在链表index处插入一个新节点
     */
    public void add(int index, int val) {
        //判断index插入位置是否非法
        if (index < 0 || index > size) {
            System.err.println("add index illegal!");
        }
        //头节点没有前驱
        if (index == 0) {
            //头插
            addFirst(val);
        } else {
            //当前位置合法且不是在链表头部插入
            Node newNode = new Node();
            newNode.val = val;
            Node prev = head;
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }
            //此时prev一定指向了待插入的前驱
            newNode.next = prev.next;
            prev.next = newNode;
            size++;
        }
    }

java 增删改 后置处理链路 java的增删改查是指什么_数据结构_05

注意:在添加节点的插入index位置判断时,可以使index=size,相当于在最后一个有效元素的后一个位置插入。而在查找、修改、删除时,index不能取到size,因为size索引上没有有效元素。

public void addLast(int val):在链表的尾部插入新元素
/**
     * 尾插
     */
    public void addLast(int val) {
        add(size, val);
    }


相当于在最后一个有效元素的后一个位置插入。

public int getByValue(int val):查找第一个值为val的结点索引是多少
public int getByValue(int val) {
        int index = 0;
        for (Node x = head; x != null; x = x.next) {
            if (x.val == val) {
                return index;
            }
            index++;
        }
        //链表中没有值为val的节点,返回-1
        return -1;
    }
private boolean rangeCheck(int index):判断index位置的合法性
/**
     * 判断index位置的合法性 rangeCheck
     */
    private boolean rangeCheck(int index) {
        //size是当前有效元素的下一个索引
        if (index < 0 || index >= size) {
            return false;
        }
        return true;
    }
public boolean contains(int val):查询当前链表中是否包含值为 val 的元素,若存在返回true,否则返回false
/**
     * 查询当前链表是否含有值为val的元素
     */
    public boolean contains(int val) {
        //第一种写法
//        Node prev = head;
//        while (prev != null) {
//            if (prev.val == val) {
//                return true;
//            }
//            prev = prev.next;
//        }
//        return false;
        //第二种写法
//        int index = getByValue(val);
//        if (index == -1) {
//            return false;
//        }
//        return true;
        //第三种写法
        return getByValue(val) != -1;
    }
public int get(int index):查询索引为index位置的结点值
/**
     * 查询索引为index的元素值为多少
     */
    public int get(int index) {
        //index 合法性判断 另写一个私有方法 rangeCheck(index) 专门判断合法性
        // 1.判断index的合法性
        if (rangeCheck(index)) {
            Node x = head;
            for (int i = 0; i < index; i++) {
                x = x.next;
            }
            return x.val;
        }
        System.err.println("index illegal!set error!");
        return -1;
    }

public int set(int index,int newVal):修改索引为index位置的结点值为newVal,返回修改前的节点值
/**
     * 修改索引为index位置的值为newVal,返回修改前的值
     */
    public int set(int index, int newVal) {
        if (rangeCheck(index)) {
            Node x = head;
            for (int i = 0; i < index; i++) {
                x = x.next;
            }
            int oldVal = x.val;
            x.val = newVal;
            return oldVal;
        }
        System.err.println("index illegal!set error");
        return -1;
    }

public void removeValueOnce(int val):删除链表中第一个值为val的元素
/**
     * 删除第一次出现的值为val的节点
     */
    public void removeValueOnce(int val) {
        if (head == null) {
            System.err.println("链表为空,无法删除");
            return;
        }
        if (head.val == val) {
            // 此时头结点就是第一个值为val的结点
            Node x = head;
            head = head.next;
            x.next = null;
            size--;
        } else {
            // 当前头结点不是待删除的结点,需要遍历
            // 这里同样找到待删除的前驱结点
            // 能否知道前驱结点移动步数?
            Node prev = head;
            while (prev.next != null) {
                // 至少还有后继结点
                if (prev.next.val == val) {
                    // 此时prev.next就是待删除的结点
                    Node node = prev.next;
                    prev.next = node.next;
                    node.next = null;
                    size--;
                    return;
                }
                prev = prev.next;
            }
        }
    }

java 增删改 后置处理链路 java的增删改查是指什么_java_06

java 增删改 后置处理链路 java的增删改查是指什么_java_07

public int remove(int index):删除单链表中索引为index位置的节点,返回删除前的节点值 
 /**
     * 删除索引为index位置的操作,返回删除前的节点值
     */
    public int remove(int index) {
        if (rangeCheck(index)) {
            if (index == 0) {
                //删除头节点
                Node x = head;
                head = head.next;
                size--;
                x.next = null;
                return x.val;
            } else {
                //此时删除的是链表的中间结点
                //找前驱
                Node prev = head;
                for (int i = 0; i < index; i++) {
                    prev = prev.next;
                }
                // prev就是待删除节点的前驱
                // node节点就是待删除的结点
                Node node = prev.next;
                prev.next = node.next;
                node.next = null;
                size--;
                return node.val;
            }
        }
        System.err.println("remove index illegal!");
        return -1;
    }
public void removeAllValue(int val):删除链表中所有值为val的结点
/**
     * 删除链表中 所有值为val 的结点
     */
    public void removeAllValue(int val) {
        while (head != null && head.val == val) {//2,2,2,2极端情况
            // 头节点就是待删除节点
            Node x = head;
            head = head.next;
            x.next = null;
            size --;
        }
        // 头节点一定不是待删除的节点
        // 判空
        if (head == null) {
            // 链表删完了
            return;
        } else {
            Node prev = head;
            while (prev.next != null) {
                // 至少还有后继结点
                if (prev.next.val == val) {
                    // 此时prev.next就是待删除的结点
                    Node node = prev.next;
                    prev.next = prev.next.next;
                    node.next = null;
                    size--;
                } else {
                    // 只有当prev.next.val != val才能移动prev指针!
                    prev = prev.next;
                }
            }
        }
    }

java 增删改 后置处理链路 java的增删改查是指什么_链表_08

递归实现

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足Node.val == val 的节点,并返回新的头节点。

//不带头节点的删除(递归)
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        head.next = removeElements(head.next,val);
        if (head.val == val) {
            return head.next;
        }
        return head;
    }

2.1 带头单链表

虚拟头节点dummyHead,类似于现实中的火车头,只作为链表的头节点使用,不存储有效数据。链表中的其他有效节点我都可以一视同仁,全都有前驱节点。

定义带头链表类 ——> 类比为火车

public class LinkedListWithHead {
    private int size;    //当前链表有效结点的个数,不包括头结点
    private Node dummyHead = new Node();    //虚拟头结点,实实在在存在,不存储数据,就作为火车头
}

给定虚拟头节点dummyHead(火车头),就能从dummyHead开始向后遍历,遍历当前这个链表中所有的节点。

2.2 带头单链表增删查改

java 增删改 后置处理链路 java的增删改查是指什么_数据结构_09

public void addFirst(int val):向当前的链表中添加一个新的节点,默认在链表的头部添加
/**
     * 头插
     */
    public void addFirst(int val) {
        //第一种写法
//        Node node = new Node();
//        node.val = val;
//        node.next = dummyHead.next;
//        dummyHead.next = node;
        //第二种写法
//        Node node = new Node(val, dummyHead.next);
//        dummyHead.next = node;
        //第三种写法
        dummyHead.next = new Node(val,dummyHead.next);
        size++;
    }

有虚拟头节点之后,所有节点都是该节点的"后继"节点。无论当前是否存在有效元素,我们的插入步骤完全一致,省去了判断头的情况。

java 增删改 后置处理链路 java的增删改查是指什么_java 增删改 后置处理链路_10

java 增删改 后置处理链路 java的增删改查是指什么_链表_11

public void add(int index,int val):在链表index处插入一个新节点
/**
     * 在索引为index的位置插入新元素val
     */
    public void add(int index, int val) {
        if (index < 0 || index > size) {
            System.err.println("index illegal");
            return;
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        //此时prev位置就是待插入位置
        //1.普通插入
//        Node node = new Node();
//        node.val = val;
//        node.next = prev.next;
//        prev.next = node;
        // 2.使用构造方法在产生新节点的同时给node赋值以及链接后继节点
//        Node node = new Node(val,prev.next);
//        prev.next = node;
        // 3.使用匿名对象
        prev.next = new Node(val, prev.next);
        size++;
    }


索引是从第一个有效元素开始计数的
dummyHead ——>不算索引。不存储有效元素
因为prev从dummyHead开始走,prev指针走index步恰好就是待插入位置的前驱。

java 增删改 后置处理链路 java的增删改查是指什么_list_12

public int get(int val):查找第一个值为val的结点索引是多少
/**
     * 查找索引为index位置的值
     */
    public int get(int index) {
        if (index < 0 || index >= size) {
            System.err.println("get index illegal!");
            return -1;
        }
        Node prev = dummyHead.next;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        return prev.val;
    }

public int set(int index,int newVal):修改索引为index位置的结点值为newVal,返回修改前的节点值
/**
     * 修改索引为index位置值为newVal,返回修改之前的值
     */
    public int set(int index, int newVal) {
        if (index < 0 || index >= size) {
            System.err.println("set index illegal!");
            return -1;
        }
        Node prev = dummyHead.next;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        int oldVal = prev.val;
        prev.val = newVal;
        return oldVal;
    }

public int remove(int index):删除单链表中索引为index位置的节点,返回删除前的节点值
/**
     * 删除单链表中索引为index位置的节点值,返回删除前的值
     */
    public int remove(int index) {
        //index合法性
        if (rangeCheck(index)) {
            Node prev = dummyHead;
            for (int i = 0; i < index; i++) {
                prev = prev.next;
            }
            Node node = prev.next;
            int val = node.val;
            prev.next = node.next;
            node.next = node = null;
            size--;
            return val;
        }
        System.err.println("index illegal!");
        return -1;
    }

java 增删改 后置处理链路 java的增删改查是指什么_java_13

public void removeAllValue(int val):删除链表中所有值为val的结点
/**
     * 删除所有值为val的元素
     */
    public void removeAllValue(int val) {
        Node prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val) {
//                Node node = prev.next;
//                prev.next = node.next;
//                node.next = null;
                prev.next = prev.next.next;
                size--;
            } else {
                prev = prev.next;
            }
        }
    }


此时不用再关心头节点的情况
prev都是从dummyHead开始向后判断,其他的逻辑和单链表的删除所有值完全一样,无需处理头节点的情况!

java 增删改 后置处理链路 java的增删改查是指什么_list_14

3.1 双向链表

双向链表:对于该链表中的任意节点,即可通过该节点向后走,也可以通过该节点向前走。
双向链表的节点:每个节点既保存了下一个节点的地址,也保存了上—个节点的地址。这样的话,就可以通过任意的节点从前向后或者从后向前。

java 增删改 后置处理链路 java的增删改查是指什么_数据结构_15

定义双向链表类

public class DoubleLinkedList {
    //有效节点的个数
    private int size;
    //当前头结点
    private DoubleNode head;
    //当前尾节点
    private DoubleNode tail;
}
定义双向链表的节点类
class DoubleNode {
    //前驱节点
    DoubleNode prev;
    //当前节点值
    int val;
    //后继节点
    DoubleNode next;

    public DoubleNode() {
    }
    public DoubleNode(int val) {
        this.val = val;
    }
    public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
        this.prev = prev;
        this.val = val;
        this.next = next;
    }
}

3.2 双向链表的增删查改

java 增删改 后置处理链路 java的增删改查是指什么_list_16

public void addFirst(int val):向当前的链表中添加一个新的节点,默认在链表的头部添加
/**
     * 头插
     */
    public void addFirst(int val) {
        //创建新节点
        // 这个节点就是以后的头结点
        // 构造方法为对象属性进行初始化
        DoubleNode node = new DoubleNode(null, val, head);
        if (head == null) {
            tail = node;
        } else {
            head.prev = node;
        }
        // 对于头插来说,最终无论链表是否为空。head = node
        head = node;
        size++;
    }

java 增删改 后置处理链路 java的增删改查是指什么_java_17

public void addLast(int val):向当前的链表中添加一个新的节点,默认在链表的尾部添加
/**
     * 尾插法
     */
    public void addLast(int val) {
        //这个节点就是插入后的尾节点
        DoubleNode node = new DoubleNode(tail, val, null);
        if (tail == null) {
            head = node;
        } else {
            tail.next = node;
        }
        tail = node;
        size++;
    }

java 增删改 后置处理链路 java的增删改查是指什么_链表_18

public void add(int index,int val):在链表index处插入一个新节点
/**
     * index 位置插入节点 (重点)
     */
    public void add(int index, int val) {
        if (index < 0 || index > size) {
            System.err.println("add index illegal!");
            return;
        }
        if (index == 0) {
            addFirst(val);
        } else if (index == size) {
            addLast(val);
        } else {
            DoubleNode prev = node(index - 1);
            DoubleNode next = prev.next;
            DoubleNode node = new DoubleNode(prev, val, next);
            prev.next = node;
            next.prev = node;
            size++;
        }
    }


java 增删改 后置处理链路 java的增删改查是指什么_java_19

此处的代码先后顺序无所谓。

private DoubleNode node(int index):根据索引值找节点
假设链表有100个节点,我想在97号索引位置插入新元素,从尾节点向前遍历就会快得多!
 index < size / 2,从前向后找,插入位置在前半部分
 index > size / 2,从后向前找,插入位置就在后半部分链表/**
     * 根据索引值找节点
     */
    private DoubleNode node(int index) {
        DoubleNode x = null;
        if (index < size / 2) {
            x = head;
            for (int i = 0; i < index; i++) {
                x = x.next;
            }
        } else {
            x = tail;
            for (int i = size - 1; i > index; i--) {
                x = x.prev;
            }
        }
        return x;
    }
public int get(int index):查询索引为index位置的结点值
/**
     * 查找索引为index位置的值
     */
    public int get(int index) {
        if (index < 0 || index >= size) {
            System.err.println("add index illegal!");
            return -1;
        }
        DoubleNode cur = node(index);    //见“查”部分的代码
        return cur.val;
    }

public int set(int index, int newVal):修改索引为index位置值为newVal,返回修改之前的值
/**
     * 修改索引为index位置值为newVal,返回修改之前的值
     */
    public int set(int index, int newVal) {
        if (index < 0 || index >= size) {
            System.err.println("add index illegal!");
            return -1;
        }
        DoubleNode cur = node(index);
        int oldVal = cur.val;
        cur.val = newVal;
        return oldVal;
    }

private void unlink(DoubleNode node):删除当前双向链表中的node节点

分治思想

先处理前驱节点,完全不管后继,等前驱部分全部处理完毕,再单独处理后继情况。
/**
     * 删除当前双向链表中的node节点
     */
    private void unlink(DoubleNode node) {
        //1.前空后空
        //2.前不空后空
        //3.前空后不空
        //4.前不空后不空
        DoubleNode prev = node.prev;
        DoubleNode successor = node.next;
        //先处理前半部分
        if (prev == null) {
            head = successor;
        } else {
            prev.next = successor;
            node.prev = null;
        }
        //处理后半部分
        if (successor == null) {
            tail = prev;
        } else {
            successor.prev = prev;
            node.next = null;
        }
        size--;
    }

java 增删改 后置处理链路 java的增删改查是指什么_java_20

public void removeIndex(int index):删除索引为index的节点
/**
     * 删除索引为index的节点
     */
    public void removeIndex(int index) {
        if (index < 0 || index >= size) {
            System.err.println("remove index illegal!");
            return;
        }
        DoubleNode cur = node(index);
        unlink(cur);
    }
public void removeFirst():删除头节点
/**
     * 删头
     */
    public void removeFirst() {
        removeIndex(0);
    }
public void removeLast():删除尾节点
/**
     * 删尾
     */
    public void removeLast() {
        removeIndex(size - 1);
    }
public void removeValueOnce(int val):删除第一个值为val的节点
/**
     * 删除第一个值为val的节点
     */
    public void removeValueOnce(int val) {
        for (DoubleNode x = head; x != null; x = x.next) {
            if (x.val == val) {
                unlink(x);
                break;
            }
        }
    }
public void removeAllValue(int val):删除全部值为val的节点
/**
     * 删除全部值为val的节点
     */
    public void removeAllValue(int val) {
        for (DoubleNode x = head; x != null; ) {
            if (x.val == val) {
                DoubleNode successor = x.next;
                unlink(x);
                x = successor;
            } else {
                x = x.next;
            }
        }
    }

如有Bug,望指正,感谢浏览💖