数据结构-基于ArrayList的源码模拟

news2024/11/16 23:37:37

文章目录

      • 继承关系 :
      • 1. 构造方法的模拟
      • 2. 扩容机制的分析
      • 3. 查找方法的模拟
      • 4. 获取,修改元素的方法模拟
      • 5. 添加元素的模拟
      • 6. 删除元素的模拟
      • 7. removeAll与retainAll的模拟
      • 总结: 边缘方法以及总代码

继承关系 :

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 构造方法的模拟

源码中我们的ArrayList的构造方法给出了三种实现
分别是不带参数的,带一个参数的,还有一个参数是类的
模拟实现如下

 /**
     * 在我们ArrayList源码里面,提供了三种构造方法(简单模拟一下)
     */
    public MyArrayList() {
        elementData = EMPTY_ELEMENTDATA;
    }

    public MyArrayList(int ininCapacity) {
        if (ininCapacity < 0) {
            throw new RuntimeException("不是哥们,大小不能是负数啊");
        }
        this.elementData = new Object[ininCapacity];
    }

    public MyArrayList(MyArrayList<? extends T> c) {
        Object[] cArr = c.toArray();
        this.elementData = new Object[cArr.length];
        System.arraycopy(cArr, 0, this.elementData, 0, cArr.length);
        this.size = cArr.length;
    }

2. 扩容机制的分析

有些人很好奇,为什么我创建对象的时候没有容量,但却可以添加元素呢,下面就是我们扩容方法模拟,其实源码底层基于的是grow()函数,其实JDK17关于扩容这一块是比较复杂的(底层又去调用了ArraySupport类中的相关方法),如果想了解的话还是自行查看源码吧
代码实现如下

/**
* 扩容机制解析
* 这个方法getNewLength我们解释一下到底是咋回事,我们想要获得一个最适合的扩容机制
* 第一个参数是原有的容量,第二个参数是最小的容量增长量,第三个参数是最合适的增长量(1.5倍率)
* 那就有问题了,为什么不直接用这个最合适的1.5倍率呢?
* 思考,当我的oldCapacity很小的时候,比如1 , 1>>1 == 0
* 那就意味着此时如果直接用1.5倍率增长是无法完成的
*/

public void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newLength = getNewLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);
        this.elementData = Arrays.copyOf(this.elementData, newLength);
    }

    public void grow() {
        grow(size + 1);
    }

    private int getNewLength(int oldCapacity, int minGrowCapacity, int preferCpacity) {
        int newLength = oldCapacity + Math.max(minGrowCapacity, preferCpacity);
        return newLength;
    }

3. 查找方法的模拟

这个就没什么可说的了,注意一点就是引用类型比较的时候是要用我们的equals方法进行比较的,还有就是源码这里实现的时候是通过先进行null的查找
代码实现如下
从前向后查找和从后向前查找

public int indexOf(Object o) {
        return indexOfRange(o, 0, size);
    }

    private int indexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        //为什么要把这二者区分开的原因就是,引用数据类型不可以直接相等
        if (es == null) {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        return lastIndexOfRange(o, size, 0);
    }

    public int lastIndexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

4. 获取,修改元素的方法模拟

代码实现如下

/**
     * 获取元素,修改元素的方法
     */
    private void checkIndexRange(int index) {
        if (index < 0 || index > size) {
            throw new RuntimeException("下标不合法");
        }
    }

    public T get(int index) {
        checkIndexRange(index);
        return (T) elementData[index];
    }

    public void set(int index, T elem) {
        checkIndexRange(index);
        elementData[index] = elem;
    }

5. 添加元素的模拟

这个有一个比较坑的点就是我们在进行Array.copyOf调用的时候,会修改源数组空间的指向,也就是如果在这之前进行过数组指向的约定,要重写修改指向

/**
     * 添加元素的方法
     * 请注意这里有一个小点是比较坑的,就是数组完成扩容之后,原来的数组指向是要进行修改的
     */
    private void add(T elem, Object[] elementData, int sz) {
        if (elementData.length == sz) {
            grow();
        }
        this.elementData[size] = elem;
        size++;
    }

    public boolean add(T elem) {
        add(elem, this.elementData, this.size);
        return true;
    }

    public boolean add(int index, T elem) {
        checkIndexRange(index);
        if (this.elementData.length == this.size) {
            grow();
        }
        //这里我们源代码的移动元素的操作是借助System.arraycopy完成的(final不会修改数组指向)
        final Object[] es = this.elementData;
        System.arraycopy(es, index, es, index + 1, this.size - index);
        es[index] = elem;
        this.size++;
        return true;
    }

    public boolean addAll(MyArrayList<? extends T> c) {
        Object[] arr = c.toArray();
        int numsLength = arr.length;
        if (numsLength == 0) {
            return false;
        }
        Object[] es = this.elementData;
        grow(c.size + this.size);
        //重新改变es的指向
        es = this.elementData;
        System.arraycopy(arr, 0, es, this.size, arr.length);
        this.size = this.size + arr.length;
        return true;
    }

6. 删除元素的模拟

源码的这里使用一个fastRemove方法进行操作的,其实也就是System.arraycopy,这个方法底层是cpp/c代码(其实我怀疑是memmove)…
代码实现如下

/**
     * 删除元素的操作(源码里面是借助一个fastRemove的方法完成的,其实也就是arraycopy)
     */
    public T remove(int index) {
        checkIndexRange(index);
        final Object[] es = elementData;
        int newSize = size - 1;
        T oldVal = (T) es[index];
        //这里主要考虑的是尾删除的弊端(尾部删除无法直接进行覆盖)
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return oldVal;
    }

    public boolean remove(Object o) {
        final Object[] es = elementData;
        int index = -1;
        if (o == null) {
            for (int i = 0; i < size; ++i) {
                if (es[i] == null) {
                    index = i;
                    break;
                }
            }
        } else {
            for (int i = 0; i < size; ++i) {
                if (o.equals(es[i])) {
                    index = i;
                    break;
                }
            }
        }
        if (index == -1) {
            return false;
        }
        //到这里说明已经找到了
        int newSize = size - 1;
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return true;
    }

7. removeAll与retainAll的模拟

这两个方法比较特殊所以单拎出来

/**
* 源码中关于下面两个方法的解释可以自己去看,机制相对复杂,但是跟我下面模拟的思路是差不多的
* 这个题的最坑的点就是,切记! ! !,不要在一遍删除元素的同时去改变我们的空间大小界限(一般都会出错)
* 这两个方法就是第一个removeAll是清除掉c中含有的元素
* 第二个方法就是保留下来c中的元素,其他都进行删除
*/
代码实现如下

 public void removeAll(MyArrayList<? extends T> c) {
        final Object[] es = this.elementData;
        int sz = this.size;
        for (int i = 0; i < this.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(es, i + 1, es, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                es[sz] = null;
            }
        }
        this.size = sz;
    }

    public void retainAll(MyArrayList<? extends T> c) {
        Object[] temp = Arrays.copyOf(c.elementData, c.elementData.length);
        final Object[] es = c.elementData;
        int sz = c.size;
        for (int i = 0; i < c.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(c, i + 1, c, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                c.elementData[sz] = null;
            }
        }
        this.size = sz;
        this.elementData = c.elementData;
        c.elementData = temp;
    }

总结: 边缘方法以及总代码

边缘方法都夹在总代码实现里面了,自己查看即可


class MyArrayList<T> {
    private Object[] elementData;
    private int size;
    private static final int DEAFULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 在我们ArrayList源码里面,提供了三种构造方法(简单模拟一下)
     */
    public MyArrayList() {
        elementData = EMPTY_ELEMENTDATA;
    }

    public MyArrayList(int ininCapacity) {
        if (ininCapacity < 0) {
            throw new RuntimeException("不是哥们,大小不能是负数啊");
        }
        this.elementData = new Object[ininCapacity];
    }

    public MyArrayList(MyArrayList<? extends T> c) {
        Object[] cArr = c.toArray();
        this.elementData = new Object[cArr.length];
        System.arraycopy(cArr, 0, this.elementData, 0, cArr.length);
        this.size = cArr.length;
    }

    public void trimToSize() {
        //注意,通过System.arraycopy拷贝是不能直接改变空间大小的
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

    /**
     * 扩容机制解析
     * 这个方法getNewLength我们解释一下到底是咋回事,我们想要获得一个最适合的扩容机制
     * 第一个参数是原有的容量,第二个参数是最小的容量增长量,第三个参数是最合适的增长量(1.5倍率)
     * 那就有问题了,为什么不直接用这个最合适的1.5倍率呢?
     * 思考,当我的oldCapacity很小的时候,比如1 , 1>>1 == 0
     * 那就意味着此时如果直接用1.5倍率增长是无法完成的
     */
    public void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newLength = getNewLength(oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1);
        this.elementData = Arrays.copyOf(this.elementData, newLength);
    }

    public void grow() {
        grow(size + 1);
    }

    private int getNewLength(int oldCapacity, int minGrowCapacity, int preferCpacity) {
        int newLength = oldCapacity + Math.max(minGrowCapacity, preferCpacity);
        return newLength;
    }

    public int getSize() {
        return this.size;
    }

    public boolean isEmpty() {
        return this.size == 0;
    }

    /**
     * 查找方法的分析
     * 查找方法就是找到从前或者从后开始的第一个满足查找条件的数据类型的下标
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        return indexOfRange(o, 0, size);
    }

    private int indexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        //为什么要把这二者区分开的原因就是,引用数据类型不可以直接相等
        if (es == null) {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex; i < toIndex; i++) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        return lastIndexOfRange(o, size, 0);
    }

    public int lastIndexOfRange(Object o, int fromIndex, int toIndex) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = fromIndex - 1; i >= toIndex; --i) {
                if (es[i].equals(o)) {
                    return i;
                }
            }
        }
        return -1;
    }

    //toArray方法就是将这个东西转换为数组
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * 获取元素,修改元素的方法
     */
    private void checkIndexRange(int index) {
        if (index < 0 || index > size) {
            throw new RuntimeException("下标不合法");
        }
    }

    public T get(int index) {
        checkIndexRange(index);
        return (T) elementData[index];
    }

    public void set(int index, T elem) {
        checkIndexRange(index);
        elementData[index] = elem;
    }

    /**
     * 添加元素的方法
     * 请注意这里有一个小点是比较坑的,就是数组完成扩容之后,原来的数组指向是要进行修改的
     */
    private void add(T elem, Object[] elementData, int sz) {
        if (elementData.length == sz) {
            grow();
        }
        this.elementData[size] = elem;
        size++;
    }

    public boolean add(T elem) {
        add(elem, this.elementData, this.size);
        return true;
    }

    public boolean add(int index, T elem) {
        checkIndexRange(index);
        if (this.elementData.length == this.size) {
            grow();
        }
        //这里我们源代码的移动元素的操作是借助System.arraycopy完成的(final不会修改数组指向)
        final Object[] es = this.elementData;
        System.arraycopy(es, index, es, index + 1, this.size - index);
        es[index] = elem;
        this.size++;
        return true;
    }

    public boolean addAll(MyArrayList<? extends T> c) {
        Object[] arr = c.toArray();
        int numsLength = arr.length;
        if (numsLength == 0) {
            return false;
        }
        Object[] es = this.elementData;
        grow(c.size + this.size);
        //重新改变es的指向
        es = this.elementData;
        System.arraycopy(arr, 0, es, this.size, arr.length);
        this.size = this.size + arr.length;
        return true;
    }

    /**
     * 删除元素的操作(源码里面是借助一个fastRemove的方法完成的,其实也就是arraycopy)
     */
    public T remove(int index) {
        checkIndexRange(index);
        final Object[] es = elementData;
        int newSize = size - 1;
        T oldVal = (T) es[index];
        //这里主要考虑的是尾删除的弊端(尾部删除无法直接进行覆盖)
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return oldVal;
    }

    public boolean remove(Object o) {
        final Object[] es = elementData;
        int index = -1;
        if (o == null) {
            for (int i = 0; i < size; ++i) {
                if (es[i] == null) {
                    index = i;
                    break;
                }
            }
        } else {
            for (int i = 0; i < size; ++i) {
                if (o.equals(es[i])) {
                    index = i;
                    break;
                }
            }
        }
        if (index == -1) {
            return false;
        }
        //到这里说明已经找到了
        int newSize = size - 1;
        if (newSize > index) {
            System.arraycopy(es, index + 1, es, index, newSize - index);
        }
        es[size = newSize] = null;
        return true;
    }

    /**
     * 源码中关于下面两个方法的解释可以自己去看,机制相对复杂,但是跟我下面模拟的思路是差不多的
     * 这个题的最坑的点就是,切记! ! !,不要在一遍删除元素的同时去改变我们的空间大小界限(一般都会出错)
     * 这两个方法就是第一个removeAll是清除掉c中含有的元素
     * 第二个方法就是保留下来c中的元素,其他都进行删除
     */
    public void removeAll(MyArrayList<? extends T> c) {
        final Object[] es = this.elementData;
        int sz = this.size;
        for (int i = 0; i < this.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(es, i + 1, es, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                es[sz] = null;
            }
        }
        this.size = sz;
    }

    public void retainAll(MyArrayList<? extends T> c) {
        Object[] temp = Arrays.copyOf(c.elementData, c.elementData.length);
        final Object[] es = c.elementData;
        int sz = c.size;
        for (int i = 0; i < c.size; ++i) {
            while (c.contains(es[i])) {
                System.arraycopy(c, i + 1, c, i, this.size - 1 - i);
                sz--;
                if (sz < 0) {
                    break;
                }
                c.elementData[sz] = null;
            }
        }
        this.size = sz;
        this.elementData = c.elementData;
        c.elementData = temp;
    }
}

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

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

相关文章

【Java基础】19.继承(面向对象的三大特征:封装、继承、多态)

文章目录 前言一、继承的概念二、继承的步骤1.类的继承格式2.继承的实例3.继承类型 三、继承的特性四、继承的关键字1.extends关键字2.implements关键字3.super 与 this 关键字4.final 关键字 五、构造器 前言 一、继承的概念 继承是java面向对象编程技术的一块基石&#xff…

Git - 在PyCharm/Idea中集成使用Git

文章目录 Git - 在PyCharm/Idea中集成使用Git1.新建GitHub仓库2.将仓库与项目绑定3.在PyCharm中使用Git4.新建Gitee仓库5.将仓库与项目绑定6.在IDEA中使用Git Git - 在PyCharm/Idea中集成使用Git 本文详细讲解了如何在 PyCharm 或 Idea 中配置 Gitee 或 GitHub 仓库&#xff0…

【算法】深度优先遍历(DFS)算法详解与实现

文章目录 1.算法原理2. 常见的深度优先遍历方式3. 代码实现总结 深度优先遍历&#xff08;DFS&#xff09;是一种常用的树或图的遍历算法&#xff0c;它通过尽可能深地搜索树或图的分支&#xff0c;直到路径上的所有节点都被访问完毕&#xff0c;然后再回溯到上一层&#xff0c…

win10 系统怎么开启 guest 账户?

win10 系统怎么开启 guest 账户&#xff1f; 段子手168 前言&#xff1a; guest 账户即所谓的来宾账户&#xff0c;我们可以通过该账户访问计算机&#xff0c;如打印机共享等&#xff0c;但会在一定程度上受到限制。下面分享 WIN10 系统开启 guest 来宾账户的几种方法。 方法…

袁庭新ES系列15节|Elasticsearch客户端基础操作

前言 上一章节我们介绍了搭建Elasticsearch集群相关的知识。那么又该如何来操作Elasticsearch集群呢&#xff1f;在ES官网中提供了各种语言的客户端&#xff0c;我们在项目开发过程中有多种Elasticsearch版本和连接客户端可以选择&#xff0c;那么他们有什么区别&#xff1f;这…

Clion 2023.1.5 最新详细破解安装教程

CLion 最大的优点是跨平台&#xff0c;在Linux、Mac、Windows 上都可以运行。CLion 还同时支持 GCC、Clang、MSVC 这 3 种编译器&#xff0c;使用 CLion 编写程序&#xff0c;程序员可以根据需要随意切换使用的编译器。 第一步: 下载最新的 Clion 2023.1.5 版本安装包 我们先…

(2022级)成都工业学院数据库原理及应用实验五: SQL复杂查询

写在前面 1、基于2022级软件工程/计算机科学与技术实验指导书 2、成品仅提供参考 3、如果成品不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 Navicat Premium 16 Mysql 8.0.36 实验要求 在实验三的基础上完成下列查询&#xff1a; 1、查询医生…

项目管理-项目开发计划介绍

目录 一、内容总览 二、项目开发计划概述 2.1 概述 2.2 项自开发计划的目的和作用 2.3 项目开发计划的内容 2.3.1 工作计划 2.3.2 人员组织计划 2.3.3 设备采购和资源供应计划 2.3.4 配置管理计划 2.3.5 进度安排计划 2.3.6 成本投资计划 2.3.7 质量保证计划 2.3.8…

Unity Editor编辑器扩展之创建脚本

前言 既然你看到这篇文章了&#xff0c;你是否也有需要使用代码创建脚本的需求&#xff1f;使用编辑器扩展工具根据不同的表格或者新增的内容去创建你想要的脚本。如果不使用工具&#xff0c;那么你只能不断去修改某个脚本&#xff0c;这项工作既繁琐也浪费时间。这个时候作为程…

Electron+Vue3整合 - 开发时状态整合

说明 本文介绍一下 Electron Vue3 的整合的基本操作。实现的效果是 &#xff1a; 1、一个正常的Vue3项目&#xff1b; 2、整合加入 Electron 框架 &#xff1a;开发时 Electron 加载的是开发的vue项目&#xff1b;步骤一&#xff1a;创建vue3项目 常规操作&#xff0c;不再赘…

盲人购物指南:智能化辅助引领超市购物新体验

作为一名资深记者&#xff0c;我有幸见证了一位盲人朋友借助一款名为蝙蝠避障的高科技辅助应用&#xff0c;独立完成超市购物之旅&#xff0c;这一过程充分展示了盲人购物指南新时代的到来。 在前往超市的路上&#xff0c;这款应用犹如一位贴心的“电子向导”&#xff0c;实时为…

Vue【路由】

1&#xff1a;什么是单页应用程序&#xff08;single page application&#xff09; 所有得功能在一个html页面上实现 2&#xff1a;单页面应用程序的优缺点 优点&#xff1a;按需更新性能高&#xff0c;开发效率也高&#xff0c;用户的体验较好 缺点&#xff1a;学习成本高…

学习-官方文档编辑方法

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

高效编程工具 JetBrains CLion 2024 中文激活 mac/win

在追求编程高效与精准的道路上&#xff0c;JetBrains CLion 2024 for Mac无疑是您的最佳伙伴。这款专为Mac用户打造的C/C集成开发环境&#xff0c;凭借其强大的功能和出色的性能&#xff0c;赢得了广大开发者的青睐。 CLion 2024拥有智能的代码编辑器和强大的代码分析工具&…

超越GPT-4V,苹果多模态大模型上新,神经形态计算加速MLLM(一)

4月8日&#xff0c;苹果发布了其最新的多模态大语言模型&#xff08;MLLM &#xff09;——Ferret-UI&#xff0c;能够更有效地理解和与屏幕信息进行交互&#xff0c;在所有基本UI任务上都超过了GPT-4V&#xff01; 苹果开发的多模态模型Ferret-UI增强了对屏幕的理解和交互&am…

解锁棋盘之谜:探索N皇后问题的全方位解决策略【python 力扣51题】

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作 作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打怪升级之旅 python数据分析…

【Qt 学习笔记】Qt常用控件 | 显示类控件Label的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 显示类控件Label的使用及说明 文章编号&#xff1a;Qt 学…

AOP基础-动态代理

文章目录 1.动态代理1.需求分析2.动态代理的核心3.代码实例1.Vehicle.java2.Car.java3.Ship.java4.VehicleProxyProvider.java(动态代理模板)5.测试使用 2.动态代理深入—横切关注点1.需求分析2.四个横切关注点3.代码实例1.Cal.java2.CalImpl.java3.VehicleProxyProvider02.jav…

iOS重签名-超详细,附排错

文章目录 重签名步骤步骤 1: 准备必要的材料步骤 2: 解压 .ipa 文件步骤3:将 Provisioning Profile 复制到 Payload 目录步骤 4: 移除原来的签名步骤 5: 使用新的证书和 Provisioning Profile 进行重签名步骤 6: 重新打包 .ipa 文件步骤 7: 安装和测试得到provisioning file和…

语音转换中的扩散模型——DDDM-VC

DDDM-VC: Decoupled Denoising Diffusion Models with Disentangled Representation and Prior Mixup for Verifed Robust Voice Conversion https://ojs.aaai.org/index.php/AAAI/article/view/29740https://ojs.aaai.org/index.php/AAAI/article/view/29740 1.概述 首先,语…