JavaDS —— 单链表 与 LinkedList

顺序表和链表区别

ArrayList :
底层使用连续的空间,可以随机访问某下标的元素,时间复杂度为O(1)
但是在插入和删除操作的时候,需要将该位置的后序元素整体往前或者向后移动,时间复杂度为O(N)
增容需要申请新空间,有时候需要拷贝数据释放旧空间,这会有不小的消耗
顺序表的增容一般是2倍增加的,势必会有一定的kong’jian浪费,例如当前容量为100时,需要扩容的话,就是将容量增加到200,如果只是再插入几个数据,就一定会浪费九十几的空间。

既然如此,我们就会思考如何减少空间的浪费,这时候链表就登场了,下面是单链表的示意图:
在这里插入图片描述
链表由两个部分组成,一个是数据域,一个是指针域,数据域是用来存放数据的,指针域是用来存放下一个或者前一个的引用的,这样就把数据给串联起来了,大家也就不难发现,链表的优点就是用多少空间就申请多少空间,做到空间不浪费,并且在下面的内容,你还会感受到链表的插入删除操作效率很高。

链表的分类

链表有8大类,带头和不带头,单向还是双向,循环还是不循环,2^3 = 8种

带头和不带头是指链表有没有一个哨兵节点,就是只是充当头结点的作用,不存放任何有效的数据。
上面的图片就是不带头的,下面的是带头的:
在这里插入图片描述

单向和双向是指:链表的节点是只指向后一个节点的话就是单向的,如果链表的节点即指向前一个结点又指向后一个节点的话就是双向的。
在这里插入图片描述

循环和不循环是指链表是否头尾相连,如果头尾相连就是循环的,否则就是不循环的:
在这里插入图片描述
在这里插入图片描述

实现单链表

下面是自己写的IList接口,会被单链表拓展:

public interface IList {
    //头插法
    public void addFirst(int data);
    
    //尾插法
    public void addLast(int data);
    
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);
    
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key);
    
    //删除第一次出现关键字为key的节点
    public void remove(int key);
    
    //删除所有值为key的节点
    public void removeAllKey(int key);
    
    //得到单链表的长度
    public int size();
	
	//清空链表
    public void clear();
 	
 	//打印链表
    public void display();
}

单链表的节点需要一个数据域和一个指针域,我们先来写一个静态内部类来构造节点类:

    static class ListNode {
        public int val;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

除此之外,我们还需要一个头指针来指向第一个节点:

    public ListNode head;

打印

循环遍历链表,打印每一个节点的数据,这个方法有利于我们的测试:

    @Override
    public void display() {
        ListNode cur = head;
        while(cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

头插

在单链表的头部插入一个数据,我们需要将新头节点的next指向原先头部的节点,然后改变head的指向。

    @Override
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }

尾插

循环遍历单链表找到尾节点,然后改变尾节点的指向即可。

这里要注意如果head为空的时候,直接赋值就可以了,不能直接使用null,会报空指针异常,所以在循环前面加多一个判断条件即可。

    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while(cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }

求节点总个数

这个很简单,直接循环遍历即可。

    @Override
    public int size() {
        ListNode cur = head;
        int count = 0;
        while(cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

指定位置插入

先判断指定的位置有没有越界,和之前的顺序表是一样的,这里不赘述:

public class IndexException extends RuntimeException{
    public IndexException(String message) {
        super(message);
    }
}
    private void checkIndexInAdd(int index) throws IndexException {
        if(index < 0 || index > size()) {
            throw new IndexException("下标范围不合法!");
        }
    }

我们要先找到index前一个结点,因为这个插入操作是对三个节点进行操作的,首先先把index的引用放入新结节点的next中,然后再把index前一个结点的next改成新结点的引用,这是一般情况,如果index == 0的话就是头插操作,为什么要做一个判断,因为我们得出的一般规律最后是cur.next = node,这是建立在新结点前面一定有结点的情况下,但是如果是头插的话就不符合了,所以头插需要单独说明。

    @Override
    public void addIndex(int index, int data) {
        try{
            checkIndexInAdd(index);
            if(index == 0) {
                addFirst(data);
                return;
            }
            //找到index前一个的节点
            ListNode cur = head;
            for (int i = 0; i < index - 1; i++) {
                cur = cur.next;
            }
            ListNode node = new ListNode(data);
            node.next = cur.next;
            cur.next = node;
        } catch (IndexException e) {
            System.out.println("index 不合法!");
            e.printStackTrace();
        }
    }

对于插入操作,我们要先处理后面的结点,避免后面的结点丢失。

contains

是否包含某个元素,直接遍历循环即可:

    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

删除第一次出现的key

删除某个结点的时候,由于这是单链表,所以我们最好事先拿到删除节点的前一个结点,然后我们要考虑一些特殊的情况,如果这个链表为空就不需要删除,如果要删除的结点就是头结点,那么我们就需要改变头指针的指向,最后就是一般情况下,我们直接修改删除结点的前一个结点的 next 域 就可以了。

    private ListNode findFrontNodeOfKey(int key) {
        ListNode cur = head;
        while(cur != null) {
            if(cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    @Override
    public void remove(int key) {

        //空链表
        if(head == null) {
            return;
        }

        //头删
        if(head.val == key) {
            head = head.next;
            return;
        }

        ListNode prev = findFrontNodeOfKey(key);
        if(prev == null) {
            return;//不存在key
        }

        ListNode del = prev.next;
        prev.next = del.next;
    }

删除所有出现的key

我们使用两个指针,一个从头结点开始,另一个从头结点的下一个结点开始遍历链表,当第二个指针遇到要删除的结点时,配合第一个指针完成此工作,然后prev不变,cur继续移动,如果没有遇到删除的结点,两个指针是一起继续向后运动。

要注意如果链表为空的话就直接return ,避免发生空指针异常

这时候大家一定知道还差一个结点没有判断,就是第一个结点,所以我们最后还有判断一下头结点。

    @Override
    public void removeAllKey(int key) {
        if(head == null) {
            return;
        }

        ListNode prev = head;
        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == key) {
                prev.next = cur.next;
            } else {
                prev = cur;
            }
            cur = cur.next;
        }
        
        if(head.val == key) {
            head = head.next;
        }
    }

clear

清空链表,你可以直接把头指针赋值为null,由于链表没有被引用,会被JVM自动回收,

    @Override
    public void clear() {
        ListNode cur = head;
        while(cur != null) {
            ListNode tmp = cur.next;
            cur.next = null;
            cur = tmp;
        }
        head = null;
    }

模拟实现LinkedList

LinkedList 是不带头,双向的,循环的链表

构建节点

双向的意味着有两个节点,一个指向前一个结点,一个指向后一个结点,还有一个头指针指向头节点,一个尾指针指向尾节点。

    static class ListNode {
        public int val;
        public ListNode prev;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode head;
    public ListNode last;

打印

    public void display() {
        ListNode cur = head;
        while(cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

头插

要注意如果头指针为null,意味着链表为空,尾指针自然也是null,链表为空的话,插入新数据要改变头尾指针的指向。
正常情况下是链表至少有一个结点,改变原先头节点的prev指向,新结点的next也要改变。

    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if(head == null) {
            head = last = node;
            return;
        }

        head.prev = node;
        node.next = head;
        head = node;
    }

尾插

注意如果尾指针为null时,说明链表为空。和上面的头插一样,要单独讨论说明。

    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if(last == null) {
            head = last = node;
        }

        last.next = node;
        node.prev = last;
        last = node;
    }

求结点个数

    public int size() {
        ListNode cur = head;
        int count = 0;
        while(cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

指定位置插入

先判断index是否合法,不合法还是和之前一样抛异常。

public class IndexOutOfBoundException extends RuntimeException {
    public IndexOutOfBoundException() {
        super();
    }

    public IndexOutOfBoundException(String message) {
        super(message);
    }
}
    private void checkIndexInAdd(int index) throws IndexOutOfBoundException{
        if(index < 0 || index > size()) {
            throw new IndexOutOfBoundException("下标越界!!!");
        }
    }

我们先讨论一般情况,如果待插入的结点正好前后都是由结点的,那么我们需要修改三个结点的指针:
cur.prev.next = node;
node.prev = cur.prev;
node.next = cur;
cur.prev = node;
在这里插入图片描述
现在来注意特殊情况,如果index == 0时,就是头插,不管怎么样,头插就一定要改变头指针,所以要单独讨论。换一种思路,如果是头插的话,cur.prev = null ,所以 cur.prev.next 一定会报空指针异常。所以头插还是要单独讨论。
那如果是尾插呢?尾插意味着 cur == null ,还是和头插思考方式一样,尾节点一定要改变所以要单独讨论,还有cur.prev 一定会报空指针异常。

    public void addIndex(int index,int data) {
        try {
            checkIndexInAdd(index);
            if(index == 0) {
                addFirst(data);
                return;
            }

            ListNode node = new ListNode(data);
            ListNode cur = head;
            for (int i = 0; i < index; i++) {
                cur = cur.next;
            }
            if(cur == null) {
                addLast(data);
                return;
            }

            cur.prev.next = node;
            node.prev = cur.prev;
            node.next = cur;
            cur.prev = node;

        } catch (IndexOutOfBoundException e) {
            e.printStackTrace();
        }
    }

remove

删除第一次出现关键字为key的节点

如果链表为空不能继续删除操作
如果删除头节点,就必须改变头指针,所以要单独说明
一般情况下,需要变动cur前后结点,自然会想到:cur.prev.next = cur.next; cur.next.prev = cur.prev;
那如果是尾删呢?上面两行代码只有前面一行还能继续用,由于是尾删,尾节点就要发生改变,所以last = cur.prev;

public void remove(int key) {
        if(head == null) {
            return;
        }

        if(head.val == key) {
            head = head.next;
            if(head != null) {
                head.prev = null;
            }
            return;
        }

        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == key) {
                cur.prev.next = cur.next;
                if(cur.next == null) {
                    last = cur.prev;
                } else {
                    cur.next.prev = cur.prev;
                }
                return;
            }
            cur = cur.next;
        }
    }

removeAllKey

删除所有值为key的节点

删除所有的key,上面我们写了删除第一次出现key的结点,这里把代码直接帮过来,删掉return就可以继续用,但是一定是对的吗?
前面的链表判空直接返回没有问题,但是头删的话就有问题了,假设头节点是你要删除的结点就意味着头指针要发生改变,那如果新的头节点又要发生改变呢?这里我们选择尽量不改变我们的祖传代码,把头删放在最后面去做即可。

    public void removeAllKey(int key) {
        if(head == null) {
            return;
        }

        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == key) {
                cur.prev.next = cur.next;
                if(cur.next == null) {
                    last = cur.prev;
                } else {
                    cur.next.prev = cur.prev;
                }
            }
            cur = cur.next;
        }

        if(head.val == key) {
            head = head.next;
            if(head != null) {
                head.prev = null;
            }
        }

    }

contains

是否包含key这个元素

    public boolean contains(int key) {
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

clear

你可以直接将head 和last都置为null,这样链表就会被JVM自动回收。
这里模仿源码的写法,源码是一个一个结点都置为null,最后头尾指针再置为null

    public void clear() {
        ListNode cur = head;
        while(cur != null) {
            ListNode tmp = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = tmp;
        }
        head = last = null;
    }

LinkedList 使用

Java集合类中给我们提供了LinkedList,这是一个无头双向循环链表,我们来看一下它里面的方法,方法名字和上面我们模拟实现的差不多。
在这里插入图片描述

LinkedList 的构造方法

在这里插入图片描述
第二个构造方法是可以传入一个对象,和之前ArrayList表示二维数组是一个意思。

LinkedList 的方法

在这里插入图片描述

要注意LinkedList和ArrayList 的subList是一样的原理,截取的list还是原来的对象list,只是范围不同,并没有创建新的对象。

add(默认尾插)

注意LinkedList的add方默认是尾插

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
    }

在这里插入图片描述

addAll

尾插一个对象

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(10);
        list1.add(20);
        list.addAll(list1);
        System.out.println(list);
    }

在这里插入图片描述

遍历链表

直接打印

LinkedList也是重写了toString 方法

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);
    }

在这里插入图片描述

for 循环

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        int size = list.size();
        for (int i = 0; i < size; i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
    }

在这里插入图片描述

for each

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for(int x : list) {
            System.out.print(x + " ");
        }
        System.out.println();
    }

在这里插入图片描述

迭代器

在这里插入图片描述
ListIterator 是继承 Iterator 的,这两个都可以来遍历链表打印数据。

迭代器的使用可以类似下面的图:
在这里插入图片描述

while(it.hasNext())hasNext表示是否由下一个数据,通过next()方法打印下一个数据,之后 it 一直向后移动。

Iterator
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        
        System.out.println("===== Iterator ====");
        Iterator<Integer> it = list.iterator();
        while (it.hasNext()) {
            System.out.print(it.next()+" ");
        }
        System.out.println();
    }

在这里插入图片描述

ListIterator
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        ListIterator<Integer> lit =  list.listIterator();
        while (lit.hasNext()) {
            System.out.print(lit.next()+" ");
        }
        System.out.println();
    }

在这里插入图片描述

ListIterator(逆向遍历)
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        System.out.println("===== ListIterator ====");
        ListIterator<Integer> lit2 =  list.listIterator(list.size());
        while (lit2.hasPrevious()) {
            System.out.print(lit2.previous()+" ");
        }
        System.out.println();
    }

在这里插入图片描述

listIterator(int n) ,可以指定从哪个下标开始遍历链表

ArrrayList 和 LinkedLisrt 的总结

在这里插入图片描述

配套练习:
http://t.csdnimg.cn/nmObG

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录二刷7.22|977.有序数组的平方

暴力解法&#xff1a; ——如果想暴力解决这个问题的话&#xff0c;可以像题目那样&#xff0c;先将每一个元素平方&#xff0c;然后再排序 双指针&#xff1a; ——从题目中找到的信息&#xff1a;这是一个非递减顺序的整数数组&#xff0c;从例子中&#xff0c;可以容易看…

excel 百分位函数 学习

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、函数说明PERCENTILE 函数PERCENTILE.inc 函数PERCENTILE.exc 函数QUARTILE.EXC 函数 二、使用步骤总结 前言 excel 百分位函数 Excel提供了几个函数用于…

数据库MySQL下载安装

MySQL下载安装地址如下&#xff1a; MySQL :: Download MySQL Community Server 1、下载界面 2、点击下载 3、解压记住目录 4、配置my.ini文件 未完..

vector 介绍

1.简述vector 首先我们要大致弄明白vector是一个什么东西,其实vector就是之前我们学过的顺序表,这里直接使用就行了. 定义vector-------->vector<typename> arr 此时的这种定义vector可以理解成为一个数组,而typename可以是各种数据类型,比如string,int,double....…

QT实现自定义带有提示信息的透明环形进度条

1. 概述 做界面开发的童鞋可能都会遇到这样的需求&#xff0c;就是有一些界面点击了之后比较耗时的操作&#xff0c;需要界面给出一个环形进度条的进度反馈信息. 如何来实现这样的需求呢&#xff0c;话不多说&#xff0c;上效果 透明进度条 2. 代码实现 waitfeedbackprogressba…

3-2 多层感知机的从零开始实现

import torch from torch import nn from d2l import torch as d2lbatch_size 256 # 批量大小为256 train_iter, test_iter d2l.load_data_fashion_mnist(batch_size) # load进来训练集和测试集初始化模型参数 回想一下&#xff0c;Fashion-MNIST中的每个图像由 28 28 784…

一文了解5G新通话技术演进与业务模型

5G新通话简介 5G新通话&#xff0c;也被称为VoNR&#xff0c;是基于R16及后续协议产生的一种增强型语音通话业务。 它在IMS网络里新增数据通道&#xff08;Data Channel&#xff09;&#xff0c;承载通话时的文本、图片、涂鸦、菜单等信息。它能在传统话音业务基础上提供更多服…

红日靶场----(三)1.漏洞利用

上期已经信息收集阶段已经完成&#xff0c;接下来是漏洞利用。 靶场思路 通过信息收集得到两个吧靶场的思路 1、http://192.168.195.33/phpmyadmin/&#xff08;数据库的管理界面&#xff09; root/root 2、http://192.168.195.33/yxcms/index.php?radmin/index/login&am…

练习 6.7:⼈们 在为练习 6.1 编写的程序中,再创建两个表⽰⼈的字典,然后将这三个字典都存储在⼀个名为 people 的列表中。

练习 6.7&#xff1a;⼈们 在为练习 6.1 编写的程序中&#xff0c;再创建两个表⽰⼈的字典&#xff0c;然后将这三个字典都存储在⼀个名为 people 的列表中。 要求 遍历这个列表&#xff0c;将其中每个⼈的所有信息都打印出来。 代码 human {shuicc: {first_name: shui,la…

linux下安装cutecom串口助手;centos安装cutecom串口助手;rpm安装包安装cutecom串口助手

在支持apt-get的系统下安装 在终端命令行中输入&#xff1a; sudo apt-get install cutecom 安装好后输入 sudo cutecom 就可以了 关于如何使用&#xff0c;可以看这个https://www.cnblogs.com/xingboy/p/14388610.html 如果你的电脑不支持apt-get。 那我们就通过安装包…

druid(德鲁伊)数据线程池连接MySQL数据库

文章目录 1、druid连接MySQL2、编写JDBCUtils 工具类 1、druid连接MySQL 初学JDBC时&#xff0c;连接数据库是先建立连接&#xff0c;用完直接关闭。这就需要不断的创建和销毁连接&#xff0c;会消耗系统的资源。 借鉴线程池的思想&#xff0c;数据连接池就这么被设计出来了。…

【Unity2D 2022:NPC】制作任务系统

一、接受任务 1. 编辑NPC对话脚本&#xff1a; &#xff08;1&#xff09;创建静态布尔变量用来判断ruby是否接受到任务 public class NPCDialog : MonoBehaviour {// 创建全局变量用来判断ruby是否接到任务public static bool receiveTask false; } &#xff08;2&#xff…

git自动pull同步远程若干分支与本地若干分支

git自动pull同步远程若干分支与本地若干分支 假设远程代码仓库有100个分支&#xff0c;而本地只有10个本地分支与远程分支一一对应&#xff0c;现在要保持本地的这个10个分支与远程一致&#xff0c;最笨的方法是checkout到每个分支&#xff0c;然后一个一个的 git pull origin…

展望未来:在【PyCharm】中结合【机器学习】实现高效的图形化处理

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言一、PyCharm简介与配置1.1 PyCharm基础1.2 图形化库配置 二、机器学习项目中的数据可视化2.1 数据加载与预处理2.2 数据探索性可视化2.3 模型训练与结果可视化 三、PyCharm中的图形化调试四、高级…

【香橙派 Orange pi AIpro】| 开发板深入使用体验

目录 一. &#x1f981; 写在前面二. &#x1f981; 愉快的安装流程2.1 安装前准备2.2 流程准备2.2.1 烧录镜像2.2.2 开机2.2.3 连网2.2.4 SSH远程连接开发板 2.3 体验 AI 应用样例 三. &#x1f981; 写在最后 一. &#x1f981; 写在前面 大家好&#xff0c;我是狮子呀&…

【kubernetes】Helm包管理器基本概念与Chart实战

概念&#xff1a;基础架构与常用命令 三个重要概念&#xff1a; 1.chart 创建Kukernetes应用程序所必需的一组信息。 2.config 包含了可以合并到打包的chart中的配置信息&#xff0c;用于创建一个可发布的对象。 3.release 是一个与特走配置相结合的chart的运行实例。 常用命…

二叉搜索树大冒险:寻找-插入-删除

OK&#xff0c;看我们题目就可知道啦&#xff0c;今天要分享学习的一种数据结构就是二叉搜索树。 内容题目也说了三个大概的&#xff0c;分别是寻找、插入、删除。 讲这个之前呢&#xff0c;那么就先讲讲这个二叉搜索树是何方神圣呢&#xff1f; 二叉搜索树&#xff1a; 又…

包成功安装tiny-cuda-nn,记录安装过程中的问题解决,附带pytorch3d安装【踩坑指南】

tiny-cuda-nn安装过程中的问题解决&#xff0c;附带pytorch3d安装【踩坑指南】 前言tiny-cuda-nn第一种下载方法&#xff1a;命令行安装tiny-cuda-nn第二种下载方法&#xff1a;本地编译 pytorch3d安装 前言 official repo: https://github.com/NVlabs/tiny-cuda-nn 该包可以显…

酷克数据亮相第13届PostgreSQL中国技术大会,获数据库杰出贡献奖

7 月 12 日&#xff0c;第 13 届 PostgreSQL 中国技术大会在杭州盛大开幕。本次大会以“聚焦云端创新&#xff0c;汇聚智慧共享”为主题&#xff0c;邀请了国内外 PG 领域众多行业大咖、学术精英及技术专家&#xff0c;共同探讨数据库领域的发展趋势、技术创新和实践经验。酷克…

计算机的错误计算(二十九)

摘要 &#xff08;1&#xff09;讨论近似值的错误数字个数。有时&#xff0c;遇到数字9或0, 不太好确认近似值的错误数字个数。&#xff08;2&#xff09;并进一步解释确认计算机的错误计算&#xff08;二十八&#xff09;中一个函数值的错误数字个数。 理论上&#xff0c;我…