LinkedList集合源码分析

LinkedList集合源码分析

文章目录

  • LinkedList集合源码分析
  • 一、字段分析
  • 二、构造函数分析
  • 三、方法分析
  • 四、总结

在这里插入图片描述

  • 看到实现了Deque 就要联想到这个数据结构肯定是属于双端队列了。
  • Queue 表示队列,Deque表示双端队列。

一、字段分析

  • LinkedList 字段很少,就三个。
//表示实际存储的数据个数,即节点个数
transient int size = 0;
//指向最开始的节点
transient Node<E> first;
//指向最末尾的节点
transient Node<E> last;

// Node 是一个静态内部类,从这里也可以看出,LinkedList 是一个双向链表,因为它有一个指向下一个节点
//的 next 和一个指向上一个节点的 prev。
private static class Node<E> {
		//节点值
        E item;
        //上个节点
        Node<E> next;
        //下个节点
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

二、构造函数分析

//就是创建了一个LinkedList 对象,字段值都是默认值。
//即 size = 0; first = null, last = null;
public LinkedList() {
    }

//使用集合作为参数创建 LinkedList,这个参数 c 得是 Collection 的子类才可以
public LinkedList(Collection<? extends E> c) {
        //调用无参构造函数
        this();
        //将 c 的元素全部添加到LinkedList中,接着往下看
        addAll(c);
    }

public boolean addAll(Collection<? extends E> c) {
		//此时的size 为 0
        return addAll(size, c);
    }

//然后这个方法的参数意义是:index 为要插入节点的位置,需要注意是往 index 这个节点的 后面!! 插入(尾差法),
//而不是前面插入,这很关键!!记住这点然后去分析。 参数 c 就是被插入的集合了。
public boolean addAll(int index, Collection<? extends E> c) {
		//检查index越界,检查逻辑也很简单  index >= 0 && index <= size,下面贴出了源码
        checkPositionIndex(index);
		//将被插入集合转换为数组
        Object[] a = c.toArray();
        //被插入集合的长度
        int numNew = a.length;
        //如果被插入集合是空的,则后面也没有元素可插了,到此为止。
        if (numNew == 0)
            return false;
		//创建了两个变量,用来方便操作链表。
		//pred:记录插入位置的上一个节点
		//succ:记录插入位置的下一个节点
		//熟悉双向链表,想必就知道为啥要这个两个变量了,为了方便连接。比如我们向index 位置插入了节点Node
		//则通过 pred.next = node; node.prev = pred; node.next = succ; succ.prev = node,这样双向连接起来。
        Node<E> pred, succ;
        //处理一特殊情况,就是插入位置后面没有节点了,那就是插入到最末尾,所以下个节点succ为空,上个节点pred为最后那个节点。
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        	//处理一般情况了,这里返回的是index处节点的下一个节点,而不是index 处的节点,在联想我上面说的尾差法,
        	//succ 的变量意义是记录新插入节点的上一个节点,所以它现在要成为新节点的下一个节点了。
        	//该处源码看下文,同时贴出了测试结果。
            succ = node(index);
            //pred 才是index 出那个节点。现在充当新插入节点的上个节点
            //效果就是   pred -> <- succ    =>      pred -> <- new -> <- succ 
            pred = succ.prev;
        }
		
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //创建新节点
            Node<E> newNode = new Node<>(pred, e, null);
            //判断山个节点是否为null,即判断插入位置是不是正好插在头部了。
            if (pred == null)
                first = newNode;
            else
            //不是头部,则从上个节点开始,一个一个连接新节点,每次连接完,新节点则作为上个节点。
                pred.next = newNode;
            pred = newNode;
        }
		//新节点查完了,需要把后面的节点在后面补上
        if (succ == null) {
        	//后面没节点了,新的尾结点变成我们最后插入的节点了。
            last = pred;
        } else {
        	//后面还有节点,给连接上。
            pred.next = succ;
            succ.prev = pred;
        }
		//更新 size
        size += numNew;
        //更新版本号,迭代时候用,判断迭代过程中,LinkedList是否被篡改。
        modCount++;
        //插入成功!!!
        return true;
    }

//判断 index 是否越界
private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

//返回第 index + 1 处的节点
Node<E> node(int index) {
		//这里分情况讨论了,因为linkedList 查找元素只能一个一个的遍历,效率低下,为了提高效率,判断这个位置距离头部近,
		//还是尾部近,哪边近则从哪边开始遍历查找,从而减少遍历次数,提高效率。
        // assert isElementIndex(index);
        if (index < (size >> 1)) {
            Node<E> x = first;
            //这里从 i = 0,开始的,相当于是从fist开始(即第一个节点),往后数index 下,不就是index + 1 处的节点嘛
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

在这里插入图片描述

三、方法分析

  • 添加元素。LinkedList 添加方法的API还挺多,,核心逻辑都差不多。
//方法名看起来好像是和把 e 和头结点相连、事实上也确实差不多,是用 e 创建新的节点作为头结点,
//而原先的头结点就作为新头结点的下一个节点
private void linkFirst(E e) {
		//获取头结点
        final Node<E> f = first;
        //使用 e 创建新的节点,并且 pre = null, next = f
        final Node<E> newNode = new Node<>(null, e, f);
        //更新 fist变量 为 新的头结点
        first = newNode;
        //如果链表原先是没有节点的,那么头结点当然也是尾结点了。
        if (f == null)
            last = newNode;
        else
        	//否则尾结点不变,并且将老的头结点连接上新的头结点,因为前面效果是  new -> old,这里之后就是 new -> <- old
            f.prev = newNode;
        //节点个数+1    
        size++;
        //更新版本号,看到这个字段,应该想到一定是用在迭代器中了,并且是用来防止迭代过程中,
        //校验集合是否被修改,实时也确实如此。
        modCount++;
    }

    
    //前面分析了,那这个也一目了然了,使用e 创建新的节点作为尾结点。
    void linkLast(E e) {
    	//获取尾结点
        final Node<E> l = last;
        //使用e创建新的节点,并且pre = last , next = null,效果就是  l <- newNode -> null
        final Node<E> newNode = new Node<>(l, e, null);
        //更新 last 变量,指向新的尾结点
        last = newNode;
        //如果原先 链表中没有节点,那尾结点也是头结点。
        if (l == null)
            first = newNode;
        else
        	//否则,将老的尾结点连接上新的尾结点,效果变成 l -> <- newNode -> null
            l.next = newNode;
        //节点个数 + 1
        size++;
        //版本 + 1
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
     //使用 e 创建新的节点,并且将该节点插入到 succ 节点之前。
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //获取 succ 的上个节点
        final Node<E> pred = succ.prev;
        //使用 e 创建新的节点,并且是 prev = pred, next = succ, 效果就是   pred  <- newNode -> succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //变成 pred  <- newNode -> <- succ
        succ.prev = newNode;
        //如果succ就是头结点,那么新的节点插到它前面,那么新插入的节点不就是新的头结点了。
        if (pred == null)
            first = newNode;
        else
        	//succ不是头结点,效果是  pred  -> <- newNode -> <- succ
            pred.next = newNode;
        //节点个数 + 1    
        size++;
        //版本 + 1
        modCount++;
    }

//所有的添加相关私有方法说完了,我们常调用的api 就是基于这些操作来的。

//向链表中添加新节点,可见该方法是想链表的尾部添加新的节点
public boolean add(E e) {
		//上面分析过了,向尾部添加新节点。
        linkLast(e);
        return true;
    }

//向 index 处添加新节点,是向index处节点的尾部添加新的节点,尾差发。
public void add(int index, E element) {
		//检查 index 是否越界
        checkPositionIndex(index);
		//如果index处就是尾结点,那么直接在尾部插入新的节点
        if (index == size)
            linkLast(element);
        else
        	//处理不是尾结点的情况。node(index) 在构造方法时说过了,是返回 index + 1 出的节点,在index + 1的前面插入,
        	//不就是往 index 出的后面插入吗,所以是我们分析的没问题,逻辑一致的。
            linkBefore(element, node(index));
    }
    
//插入新的头节点
public void addFirst(E e) {
        linkFirst(e);
    }
//插入新的尾结点    
public void addLast(E e) {
        linkLast(e);
    }

//还有addAll 方法,在构造方法说过了。

//LinkedList 实现了 List 和 Queue 两个接口,前面是属于List集合相关操作的,LinkedList 也可以作为 队列使用,下面分析作为队列
//时相关的添加元素的方法

//向队列尾部添加元素
public boolean offer(E e) {
        return add(e);
    }
    
//向队列头部添加元素
public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

//向队列尾部添加元素    
public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

//向队列头部添加元素
public void push(E e) {
        addFirst(e);
    }
  • 获取元素方法。
//获取第index + 1 个节点,这个方法基本没用过。。
public E get(int index) {
		//检查是否越界
        checkElementIndex(index);
        return node(index).item;
    }

//获取头结点 (只是获取不删除),链表为空,则报错
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    
//获取头结点 (只是获取不删除),链表为空,则报错
public E element() {
        return getFirst();
    }
    
//获取尾结点 (只是获取不删除)链表为空,则报错
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

//获取头结点 (只是获取不删除),支持链表为空,返回null
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }


//获取头结点 (获取且删除),支持链表为空,返回null
public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
  • 扩容方法:链表存储数据所需的内存空间不需要连续的,所以并不需要扩容。添加新元素时,直接连接上即可。

  • 移除元素方法:

//这个方法名看起来像是将头结点first断开,可是如果是这样的话,为啥要传参数呢,因为直接可以拿到变量 first。
//实时上,该方法作用就是移除 头结点first,并且这里的参数f就是头结点,这是个私有方法,内部调用之前,这个 f 就是
//被赋值 first,并且做了非空的判断,所以到这里的f 必不为空,且f 必为 First。这样简化了方法里的逻辑。不需要做其他额外判断。
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //获取 节点f 的值
        final E element = f.item;
        //获取 节点f 的下一个节点
        final Node<E> next = f.next;
        //将 节点f 的值设为 null
        f.item = null;
        //将 节点f 和 下一个节点断开, 效果为   f <- next
        f.next = null; // help GC
        //将 节点f 设置next,这样设置是为了将 prev 和 next 连接,从而剔除掉 节点f。
        first = next;
        //如果要删除的节点f是尾结点的话,即当前就只有一个节点,被删除了后,last 也要背设置为null 了。
        if (next == null)
            last = null;
        else
        	//还有其他节点的情况。  next节点 断开了指向 first。
            next.prev = null;
        //节点数量 -1    
        size--;
        //版本 + 1
        modCount++;
        //返回被删除的头结点的值
        return element;
    }

    /**
     * Unlinks non-null last node l.
     */
    //和上面同样的道理,作用:删除尾结点。且这里的 l 就是 last 尾结点。且必不为空。
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        //获取尾结点的值
        final E element = l.item;
        //获取尾结点 的上一个节点,该节点后续要作为新的尾结点了
        final Node<E> prev = l.prev;
        //尾结点值设为null
        l.item = null;
        //尾结点断开指向上个节点
        l.prev = null; // help GC
        //设置新的尾结点,即prev
        last = prev;
        //如果链表只有一个节点,那么删除了,头结点也要设置为null了,因为整个链表没有节点了
        if (prev == null)
            first = null;
        else
        	//不止一个节点的情况。将新的尾结点的next 设置null
            prev.next = null;
        //节点个数 - 1
        size--;
        //版本 + 1
        modCount++;
        //返回要删除的节点值
        return element;
    }

    /**
     * Unlinks non-null node x.
     */
    //删除节点 x ,且 x 比不为null
    E unlink(Node<E> x) {
        // assert x != null;
        //x 节点的值
        final E element = x.item;
        //x 节点的上一个节点
        final Node<E> next = x.next;
        //x 节点的下一个节点
        final Node<E> prev = x.prev;
		//如果 x 节点就是头结点的话,那么新的头结点就是next了。
        if (prev == null) {
            first = next;
        } else {
        	//节点x节点不是头结点,所以上个节点指向 x节点 的下一个节点。
        	//prev -> <- x -><- next  =>    prev-> next  且    prev <- x -> <- next
            prev.next = next;
            //断开 x节点 指向上个节点, 变成  	prev-> next  且    x -> <- next
            x.prev = null;
        }
		//同样的如果删除的 x节点 是尾结点,那么上个 节点prev 就是新的尾结点了。
        if (next == null) {
            last = prev;
        } else {
        	//x 节点不是尾结点的情况。 变成  prev-> <- next  且    x ->  next
            next.prev = prev;
            //变成 prev-> <- next  且    x ->  null ,这样  节点x 就彻底断开了,并且prev 和 next连接上了。
            x.next = null;
        }
		//设置 x节点 的值为null。方便 gc
        x.item = null;
        //元素个数 - 1
        size--;
        //版本 + 1
        modCount++;
        //返回 被删除节点x 的值
        return element;
    }

//将头结点删除
public E removeFirst() {
		//获取头结点
        final Node<E> f = first;
        //头结点为空则报错
        if (f == null)
            throw new NoSuchElementException();
        //将头结点断开
        return unlinkFirst(f);
    }

//删除尾结点
public E removeLast() {
		//获取尾结点
        final Node<E> l = last;
        //尾结点为空则报错
        if (l == null)
            throw new NoSuchElementException();
       	//将尾结点断开    
        return unlinkLast(l);
    }

//根据对象删除节点
public boolean remove(Object o) {
		//从这里也可以看出,LinkedList 是支持存储null和删除null 的。
		//如果链表中又重复的值,删除的是靠近头结点的那一个值,而不是都删除
		//处理 null 的情况,从头结点开始遍历
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                	//断开节点x
                    unlink(x);
                    return true;
                }
            }
        } else {
        	//处理非 null 的前框,从头结点开始遍历
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                	//断开节点x
                    unlink(x);
                    return true;
                }
            }
        }
        //如果没找到这个值,则返回 false,删除失败
        return false;
    }

//删除 第 index+ 1 个节点 
public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

//最为队列的相关方法 -------------------------------

//删除头结点
public E remove() {
        return removeFirst();
    }

//获取头结点值并删除,如果链表为空,则返回null    
public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

//获取尾结点值并删除,如果链表为空,则返回null
public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

//获取头结点的值,并删除头结点
public E pop() {
        return removeFirst();
    }
  • 迭代器:LinkedList 只提供了一个 ListItr 迭代器,并提供了迭代过程中操作集合的方法,LinkedList 的迭代器使用非常非常少,一般LinkedList 被用来作为队列,操作头部和尾部,迭代器部分的代码有兴趣的可以自己看看。

四、总结

  • LinkedList 使用双向链表来存储元素,不需要连续的内存空间来存储元素,所以不需要扩容。
  • 一般作为队列,直接操作头部和尾部。支持存储或删除相同的元素,且支持存储null,存储的元素有序。存在相同多个值是,会删除靠近头结点的值。
  • 查找元素只能遍历查找,效率慢O(n),删除,插入快O(1)。根据元素值查找,只能从头结点开始遍历。如果根据 位置查找,会从距离头结点或尾结点近的一方开始遍历。
  • 不是线程安全的。

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

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

相关文章

Python影像分析-根据卫星图像估算土壤湿度

在这篇文章中,我将讨论用于估计土壤湿度的最流行的模型,称为梯形模型。该模型源自卫星,这些卫星以一致的比例和分辨率提供地表温度和植被指数,例如归一化植被指数 (NDVI)。” 目录 🌟简介📥下载 Sentinel-3 表面温度和 NDVI 图像📉绘制 NDVI 与 LST(梯形空间)的图�…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

【C++专栏】C++入门 | 命名空间、输入输出、缺省参数

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;C专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家 点赞&#x1f44d;收藏⭐评论✍ C入门 | 命名空间、输入输出、缺省参数 文章编号&#xff1a;C入门 / 0…

基于stm32的电压采样与质量检测系统

150基于stm32的电压采样与质量检测系统[proteus仿真] 电压检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于stm32的电压采样与质量检测系统 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xf…

【递归搜索回溯专栏】专题一递归:汉诺塔

本专栏内容为&#xff1a;递归&#xff0c;搜索与回溯算法专栏。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;递归搜索回溯专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代…

java性能调优面试,程序员Java视频

前言 很多人在打算自学Java的时候或许都没有思考过Java的应用方向&#xff0c;市场需要什么样的人才&#xff0c;企业对你有什么要求等等一系列问题&#xff1b;或许你只听说这个行业薪资高…然后懵懵懂懂的上路&#xff0c;不得要害。 对于零基础来学习Java&#xff0c;你或…

Go 简单设计和实现可扩展、高性能的泛型本地缓存

相信大家对于缓存这个词都不陌生&#xff0c;但凡追求高性能的业务场景&#xff0c;一般都会使用缓存&#xff0c;它可以提高数据的检索速度&#xff0c;减少数据库的压力。缓存大体分为两类&#xff1a;本地缓存和分布式缓存&#xff08;如 Redis&#xff09;。本地缓存适用于…

正向代理和反向代理区别

正向代理和反向代理的区别&#xff1a; 特点正向代理反向代理位置位于客户端和目标服务器之间位于目标服务器和客户端之间代理对象代理服务器代表客户端发送请求到目标服务器代理服务器代表目标服务器接收客户端的请求配置客户端需要手动配置代理服务器客户端不需要知道代理服…

阻塞队列、生产者消费者模型、阻塞队列的模拟实现等干货

文章目录 &#x1f490;生产者消费者模型&#x1f490;模拟实现阻塞队列&#x1f4a1;注意点一&#x1f4a1;注意点二 阻塞队列是一种“特殊”的数据结构&#xff0c;但是也遵循队列的“先进先出”特性&#xff0c;它的特殊在于&#xff1a; 阻塞队列的两个特性&#xff1a; 1…

Java精品项目--第7期基于SpringBoot的驾校后台管理系统的设计分析与实现

项目使用技术栈 JavaSpringBootMavenMySQLMyBatisShiroHTMLVue.js&#xff08;非前后端分离&#xff09;… 项目截图

vue3组件通信有哪几种方式?

文章目录 一、父子通信1、props2、模板引用ref和defineExpose 二、跨层级传递数据provid和inject 一、父子通信 1、props 父组件中给子组件绑定属性子组件内通过props选项接收 子传父&#xff0c;通过defineEmits,先声明事件&#xff0c;再emit触发 2、模板引用ref和define…

LabVIEW高精度天线自动测试系统

LabVIEW高精度天线自动测试系统 系统是一个集成了LabVIEW软件的自动化天线测试平台&#xff0c;提高天线性能测试的精度与效率。系统通过远程控制测试仪表&#xff0c;实现了数据采集、方向图绘制、参数计算等功能&#xff0c;特别适用于对天线辐射特性的精确测量。 在天线的…

HashMap 源码解读

文章目录 一、什么是HashMap HashMap 是一种快速的查找并且插入、删除性能都良好的一种 K/V键值对的数据结构&#xff0c;key唯一&#xff0c;value允许重复它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是非线程安全的。 二、HashMap的数据结…

使用MockJS模拟数据,如何获取入参?

场景描述 在使用MockJS进行模拟数据的时候&#xff0c;会遇到一种场景&#xff0c;当参数1时&#xff0c;展示A类数据&#xff0c;当参数B时&#xff0c;展示B类数据&#xff0c;为了实现这场景&#xff0c;那就要在模拟数据时拿到请求参数&#xff1f; 实现逻辑 mock方法的…

【详识C语言】自定义类型之三:联合

本章重点 联合 联合类型的定义 联合的特点 联合大小的计算 联合&#xff08;共用体&#xff09; 联合类型的定义 联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09;…

【自然语言处理】【大模型】BitNet:用1-bit Transformer训练LLM

BitNet&#xff1a;用1-bit Transformer训练LLM 《BitNet: Scaling 1-bit Transformers for Large Language Models》 论文地址&#xff1a;https://arxiv.org/pdf/2310.11453.pdf 相关博客 【自然语言处理】【大模型】BitNet&#xff1a;用1-bit Transformer训练LLM 【自然语言…

OBS插件开发(二)推流实时曲线

不发视频了&#xff0c;截个图算了&#xff0c;嫌麻烦 1&#xff0c;自定义QWidget图表绘制 &#xff0c;动态更新 2&#xff0c;OBS直播帧率&#xff0c;码率监控 3&#xff0c;主要用于前端推流状况可视化&#xff0c;异常报警&#xff0c;及时性&#xff0c;无人值守直播

MySQL基础-----SQL语句之DDL数据定义语句

目录 前言 开启登录数据库 一、数据库操作 1.查询所有数据库 2.切换使用数据库 3.查询当前使用的数据库 4.创建数据库 创建一个hello数据库, 使用数据库默认的字符集。 创建一个itheima数据库&#xff0c;并且指定字符集 5.删除数据库 二、表操作 1.查询当前数据库所有…

Java项目:39 springboot007大学生租房平台的设计与实现

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 系统有管理员、房东和用户 【主要功能】 1、后台&#xff1a;房源管理、信息审批管理、订单信息管理、房东管理、用户管理 2、前台&#xff1a;注册登…

【数学建模】层次分析

1.建立递阶层次结构模型 2.构造出各层次中的所有判断矩阵 对指标的重要性进行两两比较&#xff0c;构造判断矩阵&#xff0c;科学求出权重 矩阵中元素aij的意义是&#xff0c;第i个指标相对第j个指标的重要程度 对角线1&#xff0c;aijaji1 矛盾——>一致性检验