链表中的基本操作

定义节点:

public class Node {
    public int val;
    public Node next = null;
    //java中只能靠引用来保存地址,你的地址指向什么类型的对象,引用的类型也是一样的
    public Node(int val) {
        this.val = val;
    }
    @Override
    public String toString() {
        return "[" + val + "]";
    }
}

链表的遍历查找操作:

public class Main {
    // 通过这个方法, 创建出一个固定内容的链表.
    // 使用头结点来代指整个链表.
    // 让方法把头结点返回回去就行了
    public static Node createList() {
        Node a = new Node(1);
        Node b = new Node(2);
        Node c = new Node(3);
        Node d = new Node(4);
        a.next = b;
        b.next = c;
        c.next = d;
        d.next = null;
        return a;
    }

    public static void main(String[] args) {
        Node head = createList();

        //1.遍历链表,把各个元素打印出来
        System.out.print("遍历链表,打印所有元素:");
        for (Node cur1 = head; cur1 != null; cur1 = cur1.next) {
            System.out.print(cur1.val);
        }

        //2.通过遍历,找到链表的最后一个结点。
        //head = null;
        // 链表的头节点是 null, 此时就表示一个空的链表(一个节点都没有的链表)
        System.out.print("\n"+"遍历链表,找到最后一个节点:");
        Node cur2 = head;
        while (cur2 != null && cur2.next != null) {
            cur2 = cur2.next;
        }
        // 一旦循环结束,cur 就指向了链表的最后一个节点.
        System.out.println(cur2.val);

        //3.通过遍历,找到链表的倒数第二个结点。
        // 特点就是 cur.next.next 为 null
        System.out.print("遍历链表,找到倒数第二个节点:");
        Node cur3 = head;
        while (cur3 != null && cur3.next != null && cur3.next.next != null) {
            cur3 = cur3.next;
        }
        System.out.print(cur3.val);

        //4.通过遍历,找到链表的第 n 个结点。(链表的长度 >= n)
        System.out.print("\n"+"遍历链表,找到第3个节点:");
        int N = 3;
        Node cur4 = head;
        for (int i = 1; i < N; i++) {
            cur4 = cur4.next;
        }
        // 此时 cur 指向的元素, 就是正数第 N 个元素.(这儿不是在算下标)
        System.out.print(cur4.val);

        //5.通过遍历,计算链表中元素的个数。
        System.out.print("\n"+"遍历链表,计算链表元素总数:");
        int count = 0;
        for (Node cur5 = head; cur5 != null; cur5 = cur5.next) {
            count++;
        }
        System.out.print(count);

        /*
        结合第4,5代码就可以实现“获取链表中倒数第N个节点的元素”,
        倒数第N个节点就是正数“链表元素总个数+1-N”个元素。
         */

        //6.通过遍历,找到链表中是否包含某个元素。
        System.out.print("\n"+"遍历链表,判断是否包含元素值为3的元素:");
        int toFind = 3;
        Node cur6 = head;
        for (; cur6 != null; cur6 = cur6.next) {
            if (cur6.val == toFind) {
                break;
            }
        }
        if (cur6 != null) {
            System.out.print("找到了元素值为"+toFind+"的元素!");
        } else {
            System.out.print("没找到元素值为"+toFind+"的元素!");
        }
    }
}

运行结果:

java 链表具有的特点 java中链表操作_java

不带傀儡节点的链表的增删操作:

public class Main_Normal {

        public static Node createList(){
        Node a = new Node(1);
        Node b = new Node(2);
        Node c = new Node(3);
        Node d = new Node(4);
        Node e = new Node(5);
        Node f = new Node(6);
        Node g = new Node(7);
        Node h = new Node(8);
        Node i = new Node(9);
        Node j = new Node(10);
        a.next = b;
        b.next = c;
        c.next = d;
        d.next = e;
        e.next = f;
        f.next = g;
        g.next = h;
        h.next = i;
        i.next = j;
        j.next = null;
        return a;
    }

        //1)遍历一个不带傀儡节点的链表.
    public static void print(Node head) {
        for (Node cur = head; cur != null; cur = cur.next) {
            System.out.print(cur.val+" ");
        }
    }

        //2)在链表末尾插一个节点(这个就不需要区分有没有傀儡节点的存在).
    public static Node insertTail(Node head, int val) {
        Node newNode = new Node(val);
        if (head == null) {
            return newNode;
        }
        // 1. 先找到末尾节点.
        Node prev = head;
        while (prev.next != null) {
            prev = prev.next;
        }
        // 循环结束,  prev 就是最后一个节点了.
        newNode.next = prev.next;
        prev.next = newNode;
        return head;
    }

        //3)删除节点, 此处是按照目标节点的值来删除.
    public static Node remove_Value(Node head, int value) {
        if (head==null){
            return head;
        }
        if (head.val == value){
            //这就表示要删除的节点是头结点.
            head=head.next;
            return head;
        }
        // 1. 先找到 val 这个值对应的位置,同时也要找到 val 的前一个位置.
        Node prev = head;
        while (prev != null
                && prev.next != null
                && prev.next.val != value) {
            prev = prev.next;
        }
        // 循环结束之后, prev 就指向待删除节点的前一个节点了.
        if (prev == null || prev.next == null) {
            // 没有找到值为 val 的节点.
            System.out.print("遍历该链表后,没有找到你想删除的元素!");
            return head;
        }
        // 2. 真正进行删除了, toDelete 指向要被删除的节点.
        Node toDelete = prev.next;
        prev.next = toDelete.next;
        return head;
    }

        //4)删除节点, 按照目标节点位置来删除.
    public static Node remove_Location(Node head, Node toDelete) {
        if (head==null){
            return head;
        }
        if (head==toDelete){
            //说明要删除的就是头结点.
            head=head.next;
            return head;
        }
        // 1. 先需要找到 toDelete 的前一个节点.
        Node prev = head;
        while (prev != null && prev.next != toDelete) {
            prev = prev.next;
        }
        if (prev == null) {
            // 没找到.
            return head;
        }
        // 2. 进行删除.
        prev.next = toDelete.next;
        return head;
    }

        //5)给定节点下标来删除.(类似数组下标也是从0开始).
        //size()函数用来获取链表的大小.
    public static int size(Node head) {
        int size = 0;
        for (Node cur = head; cur != null; cur = cur.next) {
            size++;
        }
        return size;
    }

    public static Node remove_Subscript(Node head, int index) {
        if (index < 0 || index >= size(head)) {
            return head;
        }
        // 如果 index 为 0, 意味着要删除头结点.
        if (index == 0) {
            head = head.next;
            return head;
        }
        // 1. 还是要先找到待删除节点的前一个位置. index - 1 这个节点就是前一个位置.
        Node prev = head;
        for (int i = 0; i < index - 1; i++) {
            prev = prev.next;
        }
        // 循环结束之后, prev 就指向了待删除节点的前一个位置.
        // 2. 真正进行删除.
        Node toDelete = prev.next;
        prev.next = toDelete.next;
        return head;
    }

        //6)不带傀儡节点的链表,进行尾删操作
    public static Node remove_Tail(Node head){
        if (head==null){
            return null;
        }
        if (head.next==null){
            //链表中只有一个节点,尾删的节点就是这个节点的本身.
            //此时删除该节点后,这个链表就变成了空表.
            return null;
        }
        //要想进行尾删操作,一般需要找到尾部节点的前一个节点.
        Node prev = head;

        /*这段代码不容易理解可以用下面的代码等价替换.
        while(prev!=null && prev.next!=null && prev.next.next!=null){
            prev=prev.next;
        }
         */
        Node toDelete = prev.next;
        while(prev!=null && prev.next!=null){
            toDelete=prev.next;
            if (toDelete.next==null){
                break;
            }
            prev=prev.next;
        }
        //接下来就是进行删除操作,由于toDelete节点已经是最后一个节点了,它的next一定是null.
        prev.next = toDelete.next;//prev.next=null;
        return head;
    }
    
    public static void main(String[] args) {

        //创建没有傀儡节点的链表.
        Node head1 = createList();
        System.out.print("创建一个普通链表1,遍历打印其中所有元素:");
        print(head1);

        //创建新的节点
        Node newNode = new Node(100);

        //把节点插入到第1和第2个元素之间
        Node prev = head1;
        //1).先把newNode的next指向prev的next.
        newNode.next = prev.next;
        //2).再把prev的next指向newNode.
        prev.next = newNode;

        System.out.print("\n"+"在链表1的第1和第2个元素之间插入值为100的新节点,遍历打印新链表:");
        print(head1);

        //需要注意的是:1和2代码片段的顺序一定不可以颠倒!!!
        System.out.println("\n"+"==========分===================割====================线==========");

        //把节点插入到链表的头部(会影响到head引用的指向).
        Node head2 = createList();
        System.out.print("\n"+"创建一个普通链表2,遍历打印其中所有元素:");
        print(head2);
        //1)让newNode的next指向链表的第一个节点.
        newNode.next = head2;
        //2)让head指向新的节点.
        head2 = newNode;
        System.out.print("\n"+"在链表2的头部插入值为100的新节点,遍历打印新链表:");
        print(head2);
        System.out.println("\n"+"==========分===================割====================线==========");

        Node head3 = createList();
        System.out.print("\n"+"创建一个普通链表3,遍历打印其中所有元素:");
        print(head3);

        System.out.print("\n"+"在链表3的末尾插入一个值为200的新节点,遍历打印链表3:");
        insertTail(head3,200);
        print(head3);

        System.out.print("\n"+"在链表3中删除一个值为2的节点,遍历打印链表3:");
        remove_Value(head3,2);
        print(head3);

        System.out.print("\n"+"在链表3中删除一个值为2000的节点,遍历打印链表3:");
        remove_Value(head3,2000);
        print(head3);

        System.out.print("\n"+"在链表3中删除第4个节点,遍历打印链表3:");
        Node toDel = head3.next.next.next;
        remove_Location(head3,toDel);
        print(head3);

        System.out.print("\n"+"在链表3中删除下标为5的节点,遍历打印链表3:");
        remove_Subscript(head3,5);
        print(head3);

        System.out.print("\n"+"在链表3中删除末尾节点,遍历打印链表3:");
        remove_Tail(head3);
        print(head3);
        }
}

运行结果:

java 链表具有的特点 java中链表操作_java 链表具有的特点_02


带傀儡节点的链表的删除操作:

public class Main_Dummy {
    // 带傀儡节点的链表,
    // 本质的区别就在于傀儡节点中的值是不使用的,
    // 该链表认为长度是 4。
    public static Node createListWithDummy() {
        Node dummy = new Node(0);
        Node a = new Node(1);
        Node b = new Node(2);
        Node c = new Node(3);
        Node d = new Node(4);
        dummy.next = a;
        a.next = b;
        b.next = c;
        c.next = d;
        d.next = null;
        return dummy;
    }

     /*
     带傀儡节点和不带傀儡节点, 是两种风格迥异的链表,
     不要尝试用一套代码来解决两种链表的问题,
     必须要分别实现。
     */

        // 遍历一个带傀儡节点的链表.
    public static void printWithDummy(Node head) {
        for (Node cur = head.next; cur != null; cur = cur.next) {
            System.out.print(cur.val+"  ");
        }
    }

        //带有傀儡节点的链表进行删除操作.
    public static Node removeWithDummy(Node head,int val){
        //此时我们就不用考虑到head引用修改的问题,也不用考虑删除第一个节点与删除后面节点存在的差异.
        Node prev =head;
        while(prev!=null && prev.next!=null && prev.next.val!=val){
            prev=prev.next;
        }
        //当这个循环结束,意味着要么prev到达了链表尾部,要么找到了val匹配的值.
        if (prev==null || prev.next==null){
            //认为没有找到这样的节点
            return head;
        }
        //找到节点.
        Node toDelete = prev.next;
        prev.next = toDelete.next;
        return head;
    }

    public static void main(String[] args) {

        // 创建带傀儡节点的链表
        Node head1 = createListWithDummy();//此处的head就是指的傀儡节点
        System.out.print("创建一个带傀儡节点的链表1,遍历打印其中所有元素:");
        printWithDummy(head1);
        Node newNode1 = new Node(100);
        // 1. 往中间某个位置插入. 需要知道待插入位置的前一个位置.
        // 例如, 还是往 1 和 2 之间插入prev1 就是指向 1 的位置. prev1 名字的由来, 表示前一个元素。
        Node prev1 = head1.next;
        newNode1.next = prev1.next;
        prev1.next = newNode1;
        System.out.print("\n"+"在链表1第1和第2个节点之间插入一个值为100的新的节点,遍历打印链表:");
        printWithDummy(head1);

        System.out.println("\n"+"==========分===================割====================线==========");

        Node head2 = createListWithDummy();
        System.out.print("\n"+"创建一个带傀儡节点的链表2,遍历打印其中所有元素:");
        printWithDummy(head2);
        Node newNode2 = new Node(200);
        // 2. 往链表头部插入. (由于是带傀儡节点, 其实是插入到 head 的后面)
        Node prev2 = head2;
        newNode2.next = prev2.next;
        prev2.next = newNode2;
        System.out.print("\n"+"在链表2的头部插入一个值为200的新的节点,遍历打印链表:");
        printWithDummy(head2);

        System.out.print("\n"+"从链表2中删除节点值为3的节点,遍历打印链表:");
        removeWithDummy(head2,3);
        printWithDummy(head2);
    }
}

运行结果:

java 链表具有的特点 java中链表操作_数据结构_03