数据结构与算法——Java实现 5.链表

news2024/9/20 6:14:33

目录

一、定义

链表的分类

二、性能

随机访问

插入或删除

三、单向链表

链表内部节点类

① 增加(插入)

1.头插法

2.寻找最后一个节点位置

3.尾插法

4.根据索引位置插入

② 删除

1.删除首个结点

2.获取链表的指定索引节点

3.删除链表指定索引元素节点

4.删除链表中最后一个节点

③ 遍历

1.遍历方式1 loop循环

2.遍历方式2 构造迭代器

3.遍历打印

4.递归遍历

④ 查找

1.根据索引位置查找元素

⑤ 测试方法

四、带哨兵的单向链表

内部节点类

① 插入

1.头插法

2.尾插法

3.根据索引获取链表元素

4.根据索引位置插入

② 删除

1.找到最后一个节点

2.删除第一个节点

3.删除链表中最后一个节点

4.删除链表中指定位置的节点

③ 遍历

1.迭代器循环

2.迭代器循环

3.for循环

④ 接口

⑤ 测试方法

五、带哨兵的双向链表

内部节点类

迭代器

① 查找

1.根据索引查找结点

② 删除

1.按索引删除元素

2.删除链表内第一个元素

3.删除链表的最后一个元素

③ 插入

1.根据索引位置插入元素

2.在链表首位添加元素

3.在链表最后添加元素

④ 遍历

⑤ 测试方法

六、带哨兵的环形链表

迭代器

内部节点类

① 插入

1.在链表开始位置添加元素

2.在链表最后位置添加元素

② 查找

1.根据节点的值查找节点

③ 删除

1.删除第一个元素

2.删除最后一个元素

3.根据结点的值删除

④ 遍历

 1.遍历打印链表元素

⑤ 测试方法


上帝,请赐予我平静,去接受我无法改变的;

给予我勇气,去改变我能改变的

赐我智慧,分辨这两者的区别

                                        —— 24.9.17

中秋佳节快乐!

一、链表的定义

在计算机科学中,链表是根据数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续

链表的分类

1.单向链表:每个元素只知道其下一个元素是谁

2.双向链表:每个元素知道其上一个元素和下一个元素

3.循环链表:通常的链表尾节点tail指向的都是null,而循环链表的tail指向的是头结点head

注:链表的头尾节点不存储数据

二、链表的性能

随机访问

根据index索引查找,时间复杂度O(n)

插入或删除

起始位置:O(1)

结束位置:如果已知 tail 尾节点是O(1),不知道 tail 尾节点是O(n)

中间位置:根据 index 查找时间+O(1)

三、单向链表

链表内部节点类

    // 内部节点类 对外隐藏起来
    private static class Node {
        int value;
        Node next;// 下一个节点指针

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

① 增加(插入)

1.头插法

将元素添加到头结点的位置

    // 1.1 头插法头部添加
    public void addFirst(int value) {
        // 1.链表为空的情况
        // 头结点初始值为null,可以省略一种情况
        // head = new Node(value, null);
        // 2.链表非空
        head = new Node(value,head);
    }

2.寻找最后一个节点位置

寻找链表最后一个元素位置

    // 1.2 找到最后一个结点
    private Node findLast() {
        // 判断是否为空链表
        if (head == null) {
            return null;
        }
        Node p;
        for (p = head; p.next != null ; p = p.next) {
        }
        return p;
    }

3.尾插法

将新元素添加到链表最后一个位置

    // 1.3 尾插法尾部添加
    public void addLast(int value){
        Node last = findLast();

        // 判断链表最后一个元素是否为空
        if (last == null) {
            addFirst(value);
            return;
        }
        last.next = new Node(value,null);
    }

4.根据索引位置插入

    // 1.4 根据索引位置插入
    public void insert(int index, int value){
        // 直接头插法插入在最前
        if(index == 0){
            addFirst(value);
            return;
        }
        Node preNode = findNode(index - 1); // 找到上一个节点
        // 找不到
        if(preNode == null){
            // 抛异常
            throw new IllegalArgumentException(
                    String.format("index [%d] 不合法%n",index));
        }
        preNode.next = new Node(value,preNode.next); // 插入节点的位置等于前一个节点的下一个
    }

② 删除

1.删除首个结点

    // 2.1 删除第一个节点
    public void removeFirst(){
        if(head == null){
            throw new IllegalArgumentException(
                    String.format("链表为空,删除位置不合法"));
        }
        // 头结点指向第二个节点,跳过第一个节点
        head = head.next;
    }

2.获取链表的指定索引节点

    // 2.2 根据索引获取链表的值
    private Node findNode(int value) {
        int i = 0;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head; p != null; p = p.next,i++) {
            if(i == value){
                return p;
            }
        }
        return null;
    }

3.删除链表指定索引元素节点

    // 2.3 删除链表的指定元素节点
    public void delete(int index){
        // 根据索引获取链表的值
        // index-1:最后一个节点的上一个节点
        Node preNode = findNode(index-1);
        if(preNode == null){
            System.out.println("链表中没有数据");
            return;
        }
        // 前一个指针直接指向被删除节点指向的指针,被删除节点直接丢失
        preNode.next = preNode.next.next;
    }

4.删除链表中最后一个节点

    // 2.4 删除链表中最后一个节点
    public void removeLast(){
        if(head == null){
            throw new IllegalArgumentException(
                    String.format("链表为空,删除位置不合法"));
        }
        Node node = findLast();
        node.next = null;
        // 找到倒数第二个节点
        Node current = head;
        while (current.next.next != null) {
            current = current.next;
        }
        current.next = null;
        return;
    }

③ 遍历

1.遍历方式1 loop循环

    // 3.1 遍历链表方式1 loop 循环
    // 通过consumer外部传参
    public void loop1(Consumer<Integer> consumer){
        // 初始值指向第一个节点head
        Node p = head;
        // 判断下一个节点位置上还有没有元素,若有则继续循环
        while(p != null){
            consumer.accept(p.value);
            p = p.next;
        }
    }

2.遍历方式2 构造迭代器

    @Override
    // 没有名字,被称为匿名内部类 ——> 转换为带名字的内部类
    public Iterator<Integer> iterator() {
        // 创建一个迭代器
        return new NodeIterator();
    }

    private class NodeIterator implements Iterator<Integer> {
        Node p = head;

        @Override
        public boolean hasNext() { // 是否有下一个元素
            return p != null;
        }

        @Override
        public Integer next() { // 返回当前值,并指向下一个元素
            int v = p.value;
            p = p.next;
            return v;
        }
    }

    // 3.2 遍历链表方式2 loop
    public void loop2(Consumer<Integer> consumer) {
        for (Node p = head; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }

3.遍历打印

    public void test(){
        int i = 0;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head; p != null; p = p.next,i++) {
            System.out.println("索引"+i+"的元素是:"+p.value);
        }
    }

4.递归遍历

    // 4.遍历链表方式3 递归遍历
    public void loop3(Consumer<Integer> before,
                      Consumer<Integer> after) {
        // 没有哨兵的单向链表从头指针开始遍历
        recursion(head,before,after);
    }

    // 递归遍历中的递归函数
    private void recursion(Node curr,
                           Consumer<Integer> before, Consumer<Integer> after){// 针对某个节点要进行的操作
        if (curr == null){
            return;
        }
        before.accept(curr.value);
        recursion(curr.next,before,after);
        after.accept(curr.value);
    }

④ 查找

1.根据索引位置查找元素

     // 2.2 根据索引获取链表的值
    private Node findNode(int value) {
        int i = 0;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head; p != null; p = p.next,i++) {
            if(i == value){
                return p;
            }
        }
        return null;
    }

   // 4.1 根据索引查找元素
    public int get(int index) {
        Node p = findNode(index);
        if (p == null) {
            // 抛异常
            throw new IllegalArgumentException(
                    String.format("index [%d] 不合法%n", index));
        } else {
            return p.value;
        }
    }

⑤ 测试方法

package Day3SinglyLinkedList;

import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;

import java.util.List;

public class test {

    // test1() 链表的头插法建立和遍历
    @Test
    public void test1() {
        SinglyLinkedList list = new SinglyLinkedList();
        // 头插法
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.addFirst(4);
        list.addFirst(5);
        list.addFirst(6);
        list.addFirst(7);
        list.addFirst(8);
        list.addFirst(9);

        // 由循环内实现转移到了操作方实现
        // 遍历方式1
        list.loop1(value->{
            System.out.print(value+" ");
        });

        System.out.println();

        // 遍历方式2
        list.loop2(value->{
            System.out.print(value+" ");
        });

        System.out.println();

        // 遍历方式3

    }

    // test2() 迭代器遍历
    @Test
    public void test2(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.addFirst(4);

        for (Integer value : list) {
            System.out.print(value+" ");
        }
    }

    // test3、链表的尾插法建立和遍历
    // 根据索引值查找对应位置的元素
    @Test
    public void test3(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);

        list.addLast(6);
        list.addLast(7);
        list.addLast(8);
        list.addLast(9);

        list.test();
        // 断言
        Assertions.assertIterableEquals(List.of(1,2,3,4,6,7,8,9), list);
        int i = list.get(4);
        System.out.println("根据索引4查找到的元素是:"+i);
    }

    // test4.根据索引位置插入
    @Test
    public void test4(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.addLast(9);
        list.addLast(8);
        list.addLast(7);
        list.insert(3,4);
        list.insert(0,0);
        for (Integer i : list) {
            System.out.print(i+" ");
        }
    }

    // test5.删除链表第一个元素
    @Test
    public void test5(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);

        list.removeFirst();
        for (Integer i : list) {
            // 2 3 4
            System.out.print(i+" ");
        }
        System.out.println();
        list.removeFirst();
        System.out.println("====================");
        for (Integer i : list) {
            // 3 4
            System.out.print(i+" ");
        }
    }

    // test6.删除链表指定索引的元素
    @Test
    public void test6(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);
        list.addLast(5);
        list.addLast(6);
        list.addLast(7);
        list.addLast(8);
        list.addLast(9);

        list.delete(5);
        list.test();
    }

    // test7 删除链表中最后一个元素
    @Test
    public void test7(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);
        list.addLast(5);
        list.addLast(6);
        list.addLast(7);
        list.addLast(8);
        list.addLast(9);


        list.removeLast();
        list.removeLast();
        list.test();

        // 检验空链表的检验
        // SinglyLinkedList list2 = new SinglyLinkedList();
        // list2.removeLast();
    }

    // test8 用递归对链表进行遍历
    @Test
    @DisplayName("递归方式遍历")
    public void test8(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);

        list.loop3(value->{
                System.out.println("before:"+value);
            }, value->{
                System.out.println("after:"+value);
        });
    }
}

四、带哨兵的单向链表

内部节点类

    // 内部节点类 对外隐藏起来
    private static class Node {
        int value;
        Node next;// 下一个节点指针

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

① 插入

1.头插法

    // 1.1 功能1头插法头部添加
    public void addFirst(int value) {
        // 链表不为空
        insert(0,value);
    }

2.尾插法

    // 1.2 尾插法尾部添加
    public void addLast(int value){
        // 寻找最右一个节点,链表为空最后一个节点是哨兵节点
        Node last = findLast();
        // 判断链表最后一个元素是否为空
        last.next = new Node(value,null);
    }

3.根据索引获取链表元素

    // 1.3 根据索引获取链表的值
    private Node findNode(int value) {
        int i = -1;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head; p != null; p = p.next,i++) {
            if(i == value){
                return p;
            }
        }
        return null;
    }
    public int get(int index){
        Node p = findNode(index);
        if (p == null){
            // 抛异常
            throw new IllegalArgumentException(
                    String.format("index [%d] 不合法%n",index));
        }else{
            return p.value;
        }
    }

4.根据索引位置插入

    // 1.4 根据索引位置插入
    public void insert(int index, int value) throws IllegalArgumentException {
        Node preNode = findNode(index - 1); // 找到上一个节点
        if(preNode == null){
            // 抛异常
            throw new IllegalArgumentException(
                    String.format("index [%d] 不合法%n",index));
        }
        preNode.next = new Node(value,preNode.next); // 插入节点的位置等于前一个节点的下一个
    }

② 删除

1.找到最后一个节点

    // 2.1 找到最后一个结点
    private Node findLast() {
        Node p;
        // 一直遍历直到找到最后一个节点
        for (p = head; p.next != null ; p = p.next) {

        }
        return p;
    }

2.删除第一个节点

    // 2.2 删除第一个节点
    public void removeFirst(){
        delete(0);
    }

3.删除链表中最后一个节点

    // 2.3 删除链表中最后一个元素
    public void removeLast(){
        if(head == null){
            throw new IllegalArgumentException(
                    String.format("链表为空,删除位置不合法"));
        }
        Node node = findLast();
        node.next = null;
        // 找到倒数第二个节点
        Node current = head;
        while (current.next.next != null) {
            current = current.next;
        }
        current.next = null;
        return;
    }

4.删除链表中指定位置的节点

    // 2.4 删除指定链表节点
    public void delete(int index){
        // 根据索引获取链表的值
        // index-1:最后一个节点的上一个节点
        Node preNode = findNode(index-1);
        if(preNode == null){
            System.out.println("链表中没有数据");
            return;
        }
        Node removeNode = preNode.next;
        if (removeNode == null){
            throw new IllegalArgumentException();
        }
        // 前一个指针直接指向被删除节点指向的指针,被删除节点直接丢失
        preNode.next = removeNode.next;
    }

③ 遍历

1.迭代器循环

    // 3.1遍历链表方式1 loop 循环
    @Override
    // 没有名字,被称为匿名内部类 ——> 转换为带名字的内部类
    public Iterator<Integer> iterator() {
        // 创建一个迭代器
        return new NodeIterator();
    }

    private class NodeIterator implements Iterator<Integer> {
        Node p = head.next;

        @Override
        public boolean hasNext() { // 是否有下一个元素
            return p != null;
        }

        @Override
        public Integer next() { // 返回当前值,并指向下一个元素
            int v = p.value;
            p = p.next;
            return v;
        }
    }
    
    // 通过consumer外部传参
    public void loop1(Consumer<Integer> consumer){
        // 初始值指向第一个节点head
        Node p = head.next;
        // 判断下一个节点位置上还有没有元素,若有则继续循环
        while(p != null){
            consumer.accept(p.value);
            p = p.next;
        }
    }

2.迭代器循环

    // 3.2 遍历链表方式2 loop
    public void loop2(Consumer<Integer> consumer) {
        for (Node p = head.next; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }

3.for循环

    // 3.3 遍历打印
    public void test(){
        int i = 0;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head.next; p != null; p = p.next,i++) {
            System.out.println("索引"+i+"的元素是:"+p.value);
        }
    }

④ 接口

package Day4GuardSingleList;

import java.util.Iterator;

public interface NodeIterator {
    // 没有名字,被称为匿名内部类 ——> 转换为带名字的内部类
    Iterator<Integer> iterator();
}

⑤ 测试方法

package Day4GuardSingleList;

import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;

import java.util.List;
import java.util.function.Consumer;

public class test {

    @Test
    @DisplayName("测试 addLast")
    public void test1() {
        GuardMethod list = getLinkedList();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5),list);
    }

    @Test
    @DisplayName("测试,get")
    public void test2() {
        GuardMethod list = getLinkedList();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5),list);
        Assertions.assertEquals(3,list.get(2));
        Assertions.assertThrows(IllegalArgumentException.class,() -> list.get(10));
    }

    @Test
    @DisplayName("测试 insert")
    public void test3() {
        GuardMethod list = getLinkedList();
        list.insert(2,5);
        list.test();

        System.out.println("————————————————————");
        GuardMethod list2 = getLinkedList();
        list2.addFirst(1);
        list2.test();

        System.out.println("————————————————————");
        list2.addFirst(5);
        list2.test();
    }

    @Test
    @DisplayName("测试 删除")
    public void test4() {
        GuardMethod list = getLinkedList();
        list.test();
        System.out.println("————————————————————");
        list.removeLast();
        list.test();
        System.out.println("————————————————————");
        list.delete(3);
        list.test();
        System.out.println("————————————————————");
        list.delete(1);
        list.test();
    }

    // 9.测试创建类对象
    private GuardMethod getLinkedList(){
        GuardMethod gm = new GuardMethod();
        gm.addLast(1);
        gm.addLast(2);
        gm.addLast(3);
        gm.addLast(4);
        gm.addLast(5);
        return gm;
    }
}

五、带哨兵的双向链表

内部节点类

    static class Node{
        Node prev; // 上一个节点指针
        int val; // 节点值
        Node next; // 下一个节点指针

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

    private Node head; // 头部哨兵
    private Node tail; // 尾部哨兵

    public DoubleGuardMethod(){
        head = new Node(11,null,null);
        tail = new Node(04,null,null);
        head.next = tail;
        tail.prev = head;
    }

迭代器

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = head.next;
            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public Integer next() {
                int value = p.val;
                p = p.next;
                return value;
            }
        };
    }

    @Override
    public void forEach(Consumer<? super Integer> action) {
        Iterable.super.forEach(action);
    }

    @Override
    public Spliterator<Integer> spliterator() {
        return Iterable.super.spliterator();
    }

① 查找

1.根据索引查找结点

    // 1.根据索引查找结点 val:索引
    private Node findNode(int val){
        int i = -1;
        // 遍历到尾哨兵时结束
        for (Node p = head; p != tail ; p=p.next,i++) {
            // 索引找到时
            if (i == val)
                return p;
        }
        return null;
    }

② 删除

1.按索引删除元素

    // 2.1 按索引删除元素
    public void remove(int val){
        Node pre = findNode(val-1);
        if (pre == null){
            System.out.println("Node not found");
            return;
        }
        Node node = pre.next;
        if (node == tail){
            System.out.println("Node not found");
        }
        pre.next = node.next;
        pre.next.prev = pre;
    }

2.删除链表内第一个元素

    // 2.2 删除链表内第一个元素
    public void removeFirst(){
        remove(0);
    }

3.删除链表的最后一个元素

    // 2.3 删除最后一个元素
    public void removeLast(){
        // 删除最后一个节点位置在尾节点的前一个节点
        Node removed = tail.prev;
        if (removed == head){
            System.out.println("您想要删除的链表中没有元素");
            return;
        }
        // 删除的节点之前的节点
        Node prev = removed.prev;
        prev.next = tail;
        tail.prev = prev;
    }

③ 插入

1.根据索引位置插入元素

    // 3.1 根据索引位置插入元素
    public void insert(int index, int value){
        // pre 新节点的上一个节点
        Node pre = findNode(index-1);
        if (pre == null){
            System.out.println("您输入的index不合法");
            return;
        }
        Node next = pre.next;
        Node node = new Node(value, pre, next);
        pre.next = node;
        next.prev = node;
    }

2.在链表首位添加元素

    // 3.2 在链表首位添加元素
    public void addFirst(int value){
        insert(0,value);
    }

3.在链表最后添加元素

    // 3.3 在链表最后一个位置添加元素
    public void addLast(int value){
        Node last = tail.prev;
        Node newNode = new Node(value,last,tail);
        last.next = newNode;
        tail.prev = newNode;
    }

④ 遍历

    // 4.遍历打印
    public void test(){
        int i = 0;
        // for循环的第一个部分只能定义一句,for循环的第三个部分可以定义多个语句
        for (Node p = head.next; p != tail; p = p.next,i++) {
            System.out.println("索引"+i+"的元素是:"+p.val);
        }
    }

⑤ 测试方法

package Day5GuardDoubleList;

import org.junit.Test;

public class test {
    @Test
    public void test1() {
        DoubleGuardMethod list = getList();
        System.out.println("[1,2,3,4,5]");
        list.test();
        System.out.println("————————————————————————————————");

        list.addLast(3);
        System.out.println("[1,2,3,4,5,3]");
        list.test();
        System.out.println("————————————————————————————————");

        System.out.println("[1,2,3,4,5,3,10]");
        list.addLast(10);
        list.test();
        System.out.println("————————————————————————————————");

        list.insert(1,15);
        System.out.println("[1,15,2,3,4,5,3,10]");
        list.test();
        System.out.println("————————————————————————————————");

        list.addFirst(0);
        System.out.println("[0,1,15,2,3,4,5,3,10]");
        list.test();
        System.out.println("————————————————————————————————");

        list.removeFirst();
        System.out.println("[1,15,2,3,4,5,3,10]");
        System.out.println("");
        list.test();
        System.out.println("————————————————————————————————");

        list.removeLast();
        System.out.println("[1,15,2,3,4,5,3]");
        list.test();
        System.out.println("————————————————————————————————");

        System.out.println("[1,15,3,4,5,3,10]");
        list.remove(2);
        list.test();
    }

    private DoubleGuardMethod getList(){
        DoubleGuardMethod list = new DoubleGuardMethod();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        list.addLast(5);
        return list;
    }

}

六、带哨兵的环形链表

迭代器

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            // 从哨兵节点的next节点开始遍历
            Node p = sentinel.next;
            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public Integer next() {
                int val = p.value;
                p = p.next;
                return val;
            }
        };
    }

内部节点类

    private static class Node{
        Node prev;
        Node next;
        int value;

        public Node(Node prev,Node next,int value){
            this.prev = prev;
            this.next = next;
            this.value = value;
        }
    }

    public AnnualListMethod(){
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

    // 创建哨兵节点
    private Node sentinel = new Node(null,null,-1);

① 插入

1.在链表开始位置添加元素

    // 1.1 在链表开始位置添加元素
    // 双向链表考虑四个指针
    public void addFirst(int value){
        Node a = sentinel;
        Node b = sentinel.next;
        Node added = new Node(sentinel, sentinel.next, value);
        a.next = added;
        b.prev = added;
    }

2.在链表最后位置添加元素

    // 1.2 在链表最后位置添加元素
    // 双向链表考虑四个指针
    public void addLast(int value){
        Node a = sentinel.prev;
        Node b = sentinel;
        Node node = new Node(a,b,value);
        a.next = node;
        node.next = b;
        b.prev = node;
    }

② 查找

1.根据节点的值查找节点

    // 2.1 根据节点的值查找节点
    private Node findByValue(int value){
        Node p = sentinel.next;
        while (p != sentinel){
            if (p.value == value){
                return p;
            }
            p = p.next;
        }
        return null;
    }

③ 删除

1.删除第一个元素

    // 3.1.删除第一个元素
    public void removeFirst(){
        Node node = sentinel.next;
        if (node == sentinel){
            System.out.println("链表为空");
            return;
        }
        Node a = sentinel;
        Node b = node.next;
        a.next = b;
        b.prev = a;
    }

2.删除最后一个元素

    // 3.2 删除最后一个元素
    public void removeLast(){
        Node node = sentinel.prev;
        if (node == sentinel){
            System.out.println("删除位置不合法");
            return;
        }
        Node a = node.prev;
        Node b = sentinel;
        a.next = b;
        b.prev = a;
    }

3.根据结点的值删除

    // 3.3 根据结点的值删除
    public void removeByValue(int value){
        Node node = findByValue(value);
        if (node == null){
            System.out.println("链表中不含这个值");
            return;
        }
        Node a = node.prev;
        Node b = node.next;
        a.next = b;
        b.prev = a;
    }

④ 遍历

 1.遍历打印链表元素

    // 4.1 遍历打印链表元素
    public void print(){
        Node node = new Node(sentinel,sentinel.next,sentinel.next.value);
        if (node == sentinel){
            System.out.println("链表为空");
            return;
        }
        while (node.next != sentinel){
            node = node.next;
            System.out.print(node.value+" ");
        }
        System.out.println();
    }

⑤ 测试方法

package Day6AnnularList;

import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;

import java.util.List;

public class test {

    @Test
    @DisplayName("在头部添加元素")
    public void addFirst(){
        AnnualListMethod list = new AnnualListMethod();
        list.addFirst(1);
        list.addFirst(2);
        list.addFirst(3);
        list.addFirst(4);
        list.addFirst(5);

        // 断言,比较链表是否相等
        Assertions.assertIterableEquals(List.of(5,4,3,2,1), list);
    }

    @Test
    @DisplayName("在尾部添加元素")
    public void addLast(){
        AnnualListMethod list = new AnnualListMethod();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        list.addLast(5);

        // 断言,比较链表是否相等
        Assertions.assertIterableEquals(List.of(1,2,3,4,5), list);
    }

    @Test
    @DisplayName("删除第一个元素")
    public void removeFirst(){
        AnnualListMethod list = new AnnualListMethod();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        list.addLast(5);

        Assertions.assertIterableEquals(List.of(1,2,3,4,5), list);
        list.removeFirst();
        Assertions.assertIterableEquals(List.of(2,3,4,5), list);
        list.removeFirst();
        Assertions.assertIterableEquals(List.of(3,4,5), list);
        list.removeFirst();
        Assertions.assertIterableEquals(List.of(4,5), list);
        list.removeFirst();
        Assertions.assertIterableEquals(List.of(5), list);
        list.removeFirst();
        Assertions.assertIterableEquals(List.of(),list);
        list.removeFirst();
    }

    @Test
    @DisplayName("删除最后一个元素")
    public void removeLast(){
        AnnualListMethod list = new AnnualListMethod();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        list.addLast(5);

        Assertions.assertIterableEquals(List.of(1,2,3,4,5), list);
        list.removeLast();
        Assertions.assertIterableEquals(List.of(1,2,3,4), list);
        list.removeLast();
        Assertions.assertIterableEquals(List.of(1,2,3),list);
        list.removeLast();
        Assertions.assertIterableEquals(List.of(1,2),list);
        list.removeLast();
        Assertions.assertIterableEquals(List.of(1),list);
        list.removeLast();
        Assertions.assertIterableEquals(List.of(),list);
        list.removeLast();
    }

    @Test
    @DisplayName("通过节点的值删除链表中的节点")
    public void removeByValue(){
        AnnualListMethod list = new AnnualListMethod();
        list.addLast(1);
        list.print();
        list.addLast(2);
        list.print();
        list.addLast(3);
        list.print();
        list.addLast(4);
        list.print();
        list.addLast(5);
        list.print();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5), list);

        list.addLast(3);
        list.print();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5,3), list);
        list.addLast(2);
        list.print();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5,3,2), list);
        list.addLast(5);
        list.print();
        Assertions.assertIterableEquals(List.of(1,2,3,4,5,3,2,5), list);
        list.removeByValue(2);
        list.print();
        Assertions.assertIterableEquals(List.of(1,3,4,5,3,2,5), list);
        list.removeByValue(7);
        list.print();
    }
}

七、链表——递归遍历

    // 遍历链表方式3 递归遍历 定义两个泛型
    public void loop3(Consumer<Integer> before,
                      Consumer<Integer> after) {
        // 没有哨兵的单向链表从头指针开始遍历
        recursion(head,before,after);
    }

    // 递归遍历中的递归函数
    private void recursion(Node curr,
                           Consumer<Integer> before, Consumer<Integer> after){// 针对某个节点要进行的操作
        if (curr == null){
            return;
        }
        before.accept(curr.value);
        recursion(curr.next,before,after);
        after.accept(curr.value);
    }
    // test8 用递归对链表进行遍历
    @Test
    @DisplayName("递归方式遍历")
    public void test8(){
        SinglyLinkedList list = new SinglyLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);

        list.loop3(value->{
                System.out.println("before:"+value);
            }, value->{
                System.out.println("after:"+value);
        });
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2143881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】【力扣】83.删除排序链表中的重复元素

题目 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2]示例 2&#xff1a; 输入&#xff1a;head [1,1,2,3,3] 输出&#…

js 控制台自动 debugger

在全局文件中加入以下代码&#xff0c;即可打开控制台自动调试 setInterval(function() {check() }, 1000); var check function() {function doCheck() {(function() {}["constructor"]("debugger")())doCheck()}try {doCheck()} catch (err) {} }; che…

Flask-WTF的使用

组织一个 Flask 项目通常需要遵循一定的结构&#xff0c;以便代码清晰、可维护。下面是一个典型的 Flask 项目结构&#xff1a; my_flask_app/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── forms.py │ ├── templat…

T3打卡-天气识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 1.导入数据&#xff1a; #设置GPU import tensorflow as tf gpustf.config.list_physical_devices("GPU") if gpus:gpu0gpus[0]tf.config.experime…

Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发

目录 一、为什么要使用OkHhttp? 在不使用OkHhttp之前&#xff0c;我们都是在使用什么&#xff1f;使用HttpURLConnection&#xff0c;那么我们看看HttpURLConnection发起一次请求&#xff0c;两次请求要花多长时间&#xff0c;而OkHttp花多长时间。HttpURLConnection会比okht…

【银河麒麟高级服务器操作系统实例】tcp_mem分析处理全过程内核参数调优参考

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 现象描述 系统中出现大量的TCP: out of memory…

Mina protocol - 体验教程

Mina protocol - 体验教程 一、零知识证明( Zero Knowledge Proof )1、零知识证明&#xff08;ZKP&#xff09;的基本流程工作流程&#xff1a; 2、zkApp 的优势&#xff1a;3、zkApp 每个方法的编译过程&#xff1a; 二、搭建第一个zkapp先决条件1、下载或者更新 zkApp CLI​2…

基于Springboot美食推荐小程序的设计与实现(源码+数据库+文档)

一.项目介绍 pc端&#xff1a; 支持用户、餐厅老板注册 支持管理员、餐厅老板登录 管理员&#xff1a; 管理员模块维护、 餐厅管理模块维护、 用户管理模块维护、 商品管…

Qt:NULL与nullptr的区别(手写nullptr)

前言 发现还是有人不知道NULL 与nullptr的区别&#xff0c;故写此文章。 正文 对于NULL 先看NULL的源码 我们可以看出这段代码是一个典型的预处理器宏定义块&#xff0c;用于处理 NULL 宏的定义。 先看开头 #if defined (_STDDEF_H) || defined (__need_NULL)这行代码检…

git报错,error: bad signature 0x00000000fatal: index file corrupt

报错 git -c diff.mnemonicprefixfalse -c core.quotepathfalse --no-optional-locks checkout daily --progress error: bad signature 0x00000000 fatal: index file corrupt 原因 git 仓库中索引文损坏 处理 1.该备份的先备份 2.删除索引并重置 rm -f .git/index git r…

医学数据分析实训 项目五 分类分析--乳腺癌数据分析与诊断

文章目录 项目六&#xff1a;分类分析实践目的实践平台实践内容&#xff08;一&#xff09;数据理解及准备&#xff08;二&#xff09;模型建立、预测及优化任务一&#xff1a;使用 KNN算法进行分类预测任务二&#xff1a;使用贝叶斯分类算法进行分类预测任务三&#xff1a;使用…

Linux基础3-基础工具4(git,冯诺依曼计算机体系结构)

上篇文章&#xff1a;Linux基础3-基础工具3&#xff08;make,makefile,gdb详解&#xff09;-CSDN博客 本章重点&#xff1a; 1. git简易使用 2. 冯诺依曼计算机体系结构介绍 一. git使用 1.1 什么是git? git是用于管理代码版本的一种工具&#xff0c;我们在如GitHub&#xf…

C++ | (二)类与对象(上)

燕子去了&#xff0c;有再来的时候&#xff1b;杨柳枯了&#xff0c;有再青的时候&#xff1b;桃花谢了&#xff0c;有再开的时候。但是&#xff0c;聪明的&#xff0c;你告诉我&#xff0c;我们的假期为什么一去不复返呢&#xff1f; 目录 一、初识类 1.1 类的定义 1.2 C中…

面试真题-TCP的三次握手

TCP的基础知识 TCP头部 面试题&#xff1a;TCP的头部是多大&#xff1f; TCP&#xff08;传输控制协议&#xff09;的头部通常是固定的20个字节长&#xff0c;但是根据TCP选项&#xff08;Options&#xff09;的不同&#xff0c;这个长度可以扩展。TCP头部包含了许多关键的字…

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生 什么是幽灵依赖 (幻影依赖) 形成原因 幽灵依赖是指node_modules中存在 而package.json中没有声明过的依赖 但却能够在项目的依赖树中找到并使用的模块 Node.js 的模块解析规则&#xff1a; Node.js 采用了一种非传统的模…

C++速通LeetCode简单第20题-多数元素

方法一&#xff1a;暴力解法&#xff0c;放multiset中排序&#xff0c;然后依次count统计&#xff0c;不满足条件的值erase清除。 class Solution { public:int majorityElement(vector<int>& nums) {int ans 0;multiset<int> s;for(int i 0;i < nums.s…

「iOS」viewController的生命周期

iOS学习 ViewController生命周期有关方法案例注意 ViewController生命周期有关方法 init - 初始化程序&#xff1b;loadView - 在UIViewController对象的view被访问且为空的时候调用&#xff1b;viewDidLoad - 视图加载完成后调用&#xff1b;viewWillAppear - UIViewControll…

给大模型技术从业者的建议,入门转行必看!!

01—大模型技术学习建议‍‍‍ 这个关于学习大模型技术的建议&#xff0c;也可以说是一个学习技术的方法论。 首先大家要明白一点——(任何)技术都是一个更偏向于实践的东西&#xff0c;具体来说就是学习技术实践要大于理论&#xff0c;要以实践为主理论为辅&#xff0c;而不…

换个手机IP地址是不是不一样?

在当今这个信息爆炸的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。而IP地址&#xff0c;作为手机连接网络的桥梁&#xff0c;也时常引起我们的关注。你是否曾经好奇&#xff0c;换个手机&#xff0c;IP地址会不会也跟着变呢&#xff1f;本文将深入探讨这个问题&a…

Android15之编译Cuttlefish模拟器(二百三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…