数据结构:AVL树的实现和全部图解

news2024/11/22 12:14:22

文章目录

  • 为什么要有AVL树
  • 什么是AVL树
  • AVL树的实现
    • 元素的插入
    • 平衡因子的更新
    • AVL树的旋转
  • AVL树的检查
  • 完整实现

本篇总结的是AVL树中的全部内容,配有详细的图解过程

为什么要有AVL树

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现

什么是AVL树

当一个二叉搜索树形如下面的场景时,进行查找的时候效率会相当低下

在这里插入图片描述

甚至当接近为单支树的时候,查找效率会相当于在顺序表中的查找效率,因此俄罗斯的数学家G.M.Adelson-VelskiiE.M.Landis发明了一个解决方案:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这也就是AVL树的由来

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  1. 它的左右子树都是AVL
  2. 左右子树高度之差的绝对值不超过1

左右子树的高度差也叫做平衡因子,在这样的树中进行搜索,时间复杂度是O(logN)

AVL树的实现

元素的插入

首先定义出节点,由于AVL树对于向上寻找信息有需求,因此在定义节点信息的时候要有该节点指向父亲的指针

// 定义AVL树的节点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(val)
		, bf(0)
	{}
	// 左子树指针 右子树指针 父亲节点指针 节点值 平衡因子
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int bf;
};

下面定义AVL树的插入,AVL树的插入主要是先插入到一个二叉搜索树中,再对于二叉搜索树进行平衡,因此要先插入到二叉搜索树

bool Insert(const pair<K, V> kv)
{
	Node* newnode = new Node(kv);

	if (_root == nullptr)
	{
		_root = newnode;
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (newnode->_kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (newnode->_kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	if (newnode->_kv.first > parent->_kv.first)
	{
		parent->_right = newnode;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = newnode;
		cur->_parent = parent;
	}
}

上面就是二叉搜索树的基本部分,下面开始的是AVL树平衡问题

平衡因子的更新

新节点插入后,AVL树的平衡性可能会遭到破坏,引入平衡因子的意义也就在于此,因此插入后的第一步是要更新平衡因子,看有没有遭到破坏,对于平衡因子的调整有下面的几种情况

  1. 如果插入到节点的左侧,平衡因子-1即可
  2. 如果插入到节点的右侧,平衡因子+1即可

节点插入后会有新的问题,看下面的场景

在这里插入图片描述

依据这个原理,可以写出代码来更新平衡因子

	// 节点的插入
	// 节点插入的逻辑和搜索树基本一致,只是如果不符合要求需要进行旋转
	bool Insert(const pair<K, V> kv)
	{
		Node* newnode = new Node(kv);

		if (_root == nullptr)
		{
			_root = newnode;
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (newnode->_kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (newnode->_kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		if (newnode->_kv.first > parent->_kv.first)
		{
			parent->_right = newnode;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = newnode;
			cur->_parent = parent;
		}

		// 上面就是二叉搜索树的基本部分,下面更新平衡因子
		while (parent)
		{
			// 如果cur是parent的左节点,平衡因子-1,如果是右节点,平衡因子+1
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			// 判断父节点平衡因子绝对值的情况:
			// 1. 如果为0,则证明不需要向上更新
			// 2. 如果为1,则需要向上更新
			// 3. 如果为2,则需要进行旋转
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 开始进行旋转
			}
			else
			{
				// 如果绝对值比2大,说明平衡因子出错了,强制结束
				assert(false);
			}
		}
	}

AVL树的旋转

那当上面的情况已经发生,AVL树应该如何进行平衡?这就引入了旋转的概念

AVL树中,当破坏了AVL树的平衡后,总共有四种会引发的旋转

1. 新节点插入较高左子树的左侧,引发右单旋

在这里插入图片描述
因此下面实现右单旋的代码实现:

	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

2. 新节点插入在较高右子树的右侧,引发左单旋

在这里插入图片描述

	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		// 更新节点
		parent->_right = subRL;
		parent->_parent = subR;

		subR->_left = parent;
		subR->_parent = parentParent;
		if (subRL)
			subRL->_parent = parent;

		//  更新parentParent
		if (_root == parent)
		{
			_root = subR;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
		}

		parent->_bf = subR->_bf = 0;
	}

3. 新节点插入较高右子树的左侧,引发先右单旋再左单旋

对于这种情况来说,总共有下面的两种情况

在这里插入图片描述
代码实现也有了思路,先左单旋再右单旋,最后修改平衡因子的值即可,而修改平衡因子的值,可以通过subRLbf值来判断不同的情况,根据不同的情况修改不同的值即可

	// 先右单旋再左单旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		if (bf == 0)
		{
			// 自己就是新增的节点
			parent->_bf = subR->_bf = 0;
		}
		else if (bf == -1)
		{
			// 在左子树进行的插入
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			// 在右子树进行的插入
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else
		{
			assert(false);
		}
	}

4. 新节点插入较高左子树的右侧,引发先左单旋再右单旋

分析方法和前面类似

	// 先进行左单旋,再进行右单旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(subL);
		RotateR(parent);

		// 更换平衡因子
		if (bf == 0)
		{
			subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			// 插入在右子树
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			// 插入在左子树
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

AVL树的检查

依据上面的实现可以基本实现AVL树,那如何验证AVL树是否正确?其实验证也很简单,只需要看每个节点的平衡因子是否等于对应的右子树减左子树的值即可

	// 用于检查树的高度
	int TreeHeight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		return max(leftheight, rightheight) + 1;
	}

	// 保持树的封装 进行检查AVL树
	bool IsBalance()
	{
		return _IsBalance(root);
	}

	// 进行检查AVL树
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		if (rightheight - leftheight != root->_bf)
			return false;

		return abs(rightheight-leftheight)<2 && _IsBalance(root->_left) && _IsBalance(root->_right);
	}

这样就完成了AVL树的测试

完整实现

#include <iostream>
#include <assert.h>
using namespace std;

// 定义AVL树的节点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
	// 左子树指针 右子树指针 父亲节点指针 节点值 平衡因子
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;
};

// 定义AVL树
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}

	// 节点的插入
	// 节点插入的逻辑和搜索树基本一致,只是如果不符合要求需要进行旋转
	bool Insert(const pair<K, V> kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		// 上面就是二叉搜索树的基本部分,下面更新平衡因子
		while (parent)
		{
			// 如果cur是parent的左节点,平衡因子-1,如果是右节点,平衡因子+1
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			// 判断父节点平衡因子绝对值的情况:
			// 1. 如果为0,则证明不需要向上更新
			// 2. 如果为1,则需要向上更新
			// 3. 如果为2,则需要进行旋转
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 开始进行旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}

				break;
			}
			else
			{
				// 如果绝对值比2大,说明平衡因子出错了,强制结束
				assert(false);
			}
		}
		return true;
	}

	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		// 更新节点
		parent->_right = subRL;
		parent->_parent = subR;

		subR->_left = parent;
		subR->_parent = parentParent;
		if (subRL)
			subRL->_parent = parent;

		//  更新parentParent
		if (_root == parent)
		{
			_root = subR;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
		}

		parent->_bf = subR->_bf = 0;
	}

	// 先右单旋再左单旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		if (bf == 0)
		{
			// 自己就是新增的节点
			parent->_bf = subR->_bf = 0;
		}
		else if (bf == -1)
		{
			// 在左子树进行的插入
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			// 在右子树进行的插入
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else
		{
			assert(false);
		}
	}

	// 先进行左单旋,再进行右单旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(subL);
		RotateR(parent);

		// 更换平衡因子
		if (bf == 0)
		{
			subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			// 插入在右子树
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			// 插入在左子树
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	// 用于检查树的高度
	int TreeHeight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		return max(leftheight, rightheight) + 1;
	}

	// 保持树的封装 进行检查AVL树
	bool IsBalance()
	{
		return _IsBalance(root);
	}

	// 进行检查AVL树
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		if (rightheight - leftheight != root->_bf)
			return false;

		return abs(rightheight-leftheight)<2 && _IsBalance(root->_left) && _IsBalance(root->_right);
	}

private:
	Node* _root;
};

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

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

相关文章

计算机毕业设计java+springboot+vue的旅游攻略平台

项目介绍 本系统结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。员工管理系统主要包括个人中心、用户管理、攻略管理、审核信息管理、积分…

Go 接口-契约介绍

Go 接口-契约介绍 文章目录 Go 接口-契约介绍一、接口基本介绍1.1 接口类型介绍1.2 为什么要使用接口1.3 面向接口编程1.4 接口的定义 二、空接口2.1 空接口的定义2.2 空接口的应用2.2.1 空接口作为函数的参数2.2.2 空接口作为map的值 2.3 接口类型变量2.4 类型断言 三、尽量定…

Day22力扣打卡

打卡记录 替换子串得到平衡字符串&#xff08;滑动窗口&#xff09; 链接 由于是以后统计替换的子串&#xff0c;不可以直接使用hash表统计的每个次数大于 n / 4 的字符&#xff0c;再将其次数减去平衡数来得到答案&#xff0c;根据字符串的连贯性&#xff0c;使用 滑动窗口 …

MySQL 8.0 如何修改密码安全策略!!!

目录 安全策略参数和常见等级:1.Mysql8.X常见安全策略参数指定密码的强度验证等级validate_password.policy 取值&#xff1a; 解决步骤1.登录mysql2.修改安全策略(1)语法如下:(2)修改完可以看一下&#xff1a; 3.改完密码策略&#xff0c;就可以根据自己修改的策略&#xff0c…

pytorch复现_UNet

什么是UNet U-Net由收缩路径和扩张路径组成。收缩路径是一系列卷积层和汇集层&#xff0c;其中要素地图的分辨率逐渐降低。扩展路径是一系列上采样层和卷积层&#xff0c;其中特征地图的分辨率逐渐增加。 在扩展路径中的每一步&#xff0c;来自收缩路径的对应特征地图与当前特征…

什么是分治算法?

分治算法(divide and conquer algorithm)是指把大问题分割成多个小问 题&#xff0c;然后把每个小问题分割成多个更小的问题&#xff0c;直到问题的规模小到能够 轻易解决。这种算法很适合用递归实现&#xff0c;因为把问题分割成多个与自身相 似的小问题正对应递归情况&#x…

Java —— 类和对象(一)

目录 1. 面向对象的初步认知 1.1 什么是面向对象 1.2 面向对象与面向过程 2. 类定义和使用 2.1 认识类 2.2 类的定义格式 3. 类的实例化(如何产生对象) 3.1 什么是实例化 3.2 访问对象的成员 3.3 类和对象的说明 4. this引用 4.1 为什么要有this引用 4.2 什么是this引用 4.3 th…

无线发射芯片解决方案在智能家居中的应用

随着物联网的发展&#xff0c;智能家居已经成为一个热门话题。智能家居利用无线技术来实现设备之间的互联互通&#xff0c;提供更智能、更便利的生活体验。无线发射芯片解决方案在智能家居中扮演着关键的角色&#xff0c;它们为智能家居设备之间的通信提供了稳定、高效的连接&a…

stm32f103+HC-SR04+ssd1306实现超声波测距

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言HC…

【江协科技-用0.96寸OLED播放知名艺人打篮球视频】

Python进行视频图像处理&#xff0c;通过串口发送给stm32&#xff0c;stm32接收数据&#xff0c;刷新OLED进行显示。 步骤&#xff1a; 1.按照接线图连接好硬件 2.把Keil工程的代码下载到STM32中 3.运行Python代码&#xff0c;通过串口把处理后的数据发送给STM32进行显示 …

Spark 新特性+核心回顾

Spark 新特性核心 本文来自 B站 黑马程序员 - Spark教程 &#xff1a;原地址 1. 掌握Spark的Shuffle流程 1.1 Spark Shuffle Map和Reduce 在Shuffle过程中&#xff0c;提供数据的称之为Map端&#xff08;Shuffle Write&#xff09;接收数据的称之为Reduce端&#xff08;Sh…

Leetcode刷题详解——组合

1. 题目链接&#xff1a;77. 组合 2. 题目描述&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[…

vue3拖拽排序——vuedraggable

文章目录 安装代码效果拖拽前拖拽时拖拽后 vue3 的拖拽排序博主用的是 vuedraggable 安装 安装 npm i vuedraggable4.1.0 --save 引用 import Draggable from vuedraggable;代码 html <van-checkbox-group v-model"dataMap.newsActionChecked"><van-cell…

LazyVim: 将 Neovim 升级为完整 IDE | 开源日报 No.67

curl/curl Stars: 31.5k License: NOASSERTION Curl 是一个命令行工具&#xff0c;用于通过 URL 语法传输数据。 核心优势和关键特点包括&#xff1a; 可在命令行中方便地进行数据传输支持多种协议 (HTTP、FTP 等)提供丰富的选项和参数来满足不同需求 kubernetes/ingress-n…

项目中登录验证码怎么做才合理

唠嗑部分 今天我们来聊聊项目实战中登录验证码如何做比较合理&#xff0c;首先我们聊以下几个问题 1、登录时验证码校验是否必要&#xff1f; 答案当然是很有必要的&#xff0c;因为用户登录行为会直接影响数据库&#xff0c;如果没有某些防范措施&#xff0c;有恶意用户暴力…

NOIP2023模拟12联测33 A. 构造

NOIP2023模拟12联测33 A. 构造 文章目录 NOIP2023模拟12联测33 A. 构造题目大意思路code 题目大意 构造题 思路 想一种构造方法&#xff0c;使得 y y y 能够凑成尽可能多的答案 第一行 x y r y ⋯ r xyry \cdots r xyry⋯r 第二行 r y x y ⋯ x ryxy \cdots x ryxy⋯x …

基于SSM的出租车管理系统

基于SSM的出租车管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 驾驶员界面 摘要 基于SSM&#xff08;Spring、Spring MVC、My…

软考 -- 计算机学习(3)

文章目录 一、软件测试基础1.1 基本概念1.2 软件测试模型1.3 软件测试的分类 二、基于规格说明的测试技术(黑盒)2.1 重要的测试方法1. 等价类划分法2. 边界值法3. 判定表法4. 因果图法 2.2 其他测试方法 三、基于结构的测试技术(白盒)3.1 静态测试3.2 动态测试 一、软件测试基础…

Vue Vuex模块化编码

正常写vuex的index的时候如果数据太多很麻烦&#xff0c;如有的模块是管理用户信息或修改课程等这两个是不同一个种类的&#xff0c;如果代码太多会造成混乱&#xff0c;这时候可以使用模块化管理 原始写法 如果功能模块太多很乱 import Vue from vue import Vuex from vuex …

nodejs卸载和安装教程

一、卸载 1、Win菜单中找到Node.js的卸载程序&#xff0c;运行卸载程序。 3.选择 OK&#xff0c;等待卸载。 4. 删除C:\Users\用户名\AppData\Roaming目录下的npm和npm-cache&#xff1b;删除C:\Users\123\AppData\Local\目录下的npm-cache。 二、安装 傻瓜式安装&#xf…