AVL树的模拟实现

我们上期提到了二叉搜索树,只是简单的讲了一下原理,那么今天我们就讲一下AVL树。

目录

  • AVL树的概念
  • AVL树的实现
    • AVL树的架构
    • insert插入
      • 引用pair对象
      • 引进parent指针
      • 仅插入数据
      • 调节平衡因子
        • 情况1:插入在父亲的右边,父亲的平衡因子++后为0
        • 情况2:插入在父亲的左边,父亲的平衡因子--后为0
        • 情况3:插入左或者右,恰巧父亲不是0,是-1/1
        • 情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。
        • 左边高,右旋
        • 右边高,左旋
        • 双旋转
          • 右左旋转:先右旋然后左旋。
          • 左右双旋:先左旋,在右旋。
    • 中序遍历
    • 判断是否平衡
    • AVL树整体代码

AVL树的概念

其实大家应该很奇怪,难道二叉搜索树不能存储数据吗?为什么要有AVL树呢?二叉搜索树有可能会有畸形的情况。像下图,数据比较分散的话,这棵树很正常,如果我们插入的数相对有序就会变成右边那样畸形的树。
在这里插入图片描述
这个时候就需要人工干预,这里的AVL数就可以更好的控制这个情况,它有自己的平衡规则:左右子树高度之差(平衡因子)绝对不超过1(-1,0,1)。如果不满足这个规则,那么我们就旋转。

AVL树的实现

因为AVL树也是二叉搜索树的一种,所以他也要满足二叉搜索树的条件,然后在满足他自己的平衡规则

AVL树的架构

其实数的节点架构和整体架构并没有变,只是多了一个bf(平衡因子),用来调节平衡。

struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V> kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	pair<K, V> _kv;
	int _bf;
};
template<class K,class V>
class AVLTree
{
	public:
		typedef AVLTreeNode<K,V> Node;
	private:
	Node* _root=nullptr;
}

insert插入

引用pair对象

这里与原来不同,这里我们引入了一个pair对象,那么pair是什么?我们用pair来实现KV结构,在库中的map也是用pair也完成KV结构的,所以这里我们就用这pair。pair对象的first是K值,second是V值。
在这里插入图片描述

引进parent指针

这里引用父指针是因为我们将来要旋转,要不断向上调节平衡因子,因为当我们插入某个值有可能引起平衡因子失衡。

仅插入数据

其实插入部分和我们之前写的一样,只不过要注意的是,我们的值存的是pair对象,要像拿到K值需要 _kv.first拿到K值。一定要遵循二叉搜索树的规律,左子树比根小,右子树比根大

	if (_root == nullptr)
	{
		//插入第一个值
		Node* newNode = new Node(kv);
		_root = newNode;
		return true;
	}
	Node* newNode = new Node(kv);
	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_kv.first < newNode->_kv.first)
		{
			//大于在右边
			parent = cur;
			cur = cur->_right;

		}
		else if (cur->_kv.first > newNode->_kv.first)
		{
			//小于在左边
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//等于,二叉搜索树不允许冗余,所以直接返回false。
			return false;
		}
	}
	if (parent->_kv.first > newNode->_kv.first)
	{
		//在左边
		parent->_left = newNode;
	}
	else
	{
		//在右边
		parent->_right = newNode;
	}
	newNode->_parent = parent;

调节平衡因子

情况1:插入在父亲的右边,父亲的平衡因子++后为0

红色是我插入的数,插入后,它的parent:12从-1加加后变成了0,我们还需要向上更新吗?答案是不需要向上更新,为什么?0表示左右子树的高度差为0,也就说高度没有变,所以我不需要再向上更新
解释:平衡因子原来是1/-1都表示这个树缺了一个节点,当我们插入之后正好填上了这个节点,但是高度并不变。看图!我把15插入,右子树这个高度没有变。
在这里插入图片描述

情况2:插入在父亲的左边,父亲的平衡因子–后为0

当我插入在左边,那我们就需要给父亲的因子bf–。
在这里插入图片描述

情况3:插入左或者右,恰巧父亲不是0,是-1/1

父亲的平衡因子==1/-1,父亲所在子树高度变了。高度变了继续向上更新。
在这里插入图片描述

情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。

这个时候就不满足AVL树的规则了,就需要旋转。
在这里插入图片描述

左边高,右旋

这个树就是左边高的情况,你会发现parent为-2,cur为-1,这个情况就是左边比右边要高,那么我们需要右旋。
在这里插入图片描述
右旋的口诀:把cur的右边给parent的左边,cur的右边链接parent,在让parent的parent链接cur。会发现我们把原来的parent变成了cur的右边。并且现在是平衡的。

其实为什么要把cur的右边给parent呢?是因为cur的右边时当前树最大的值,cur给了parent链接到左边,依然不会破坏二叉搜索树的规则。
在这里插入图片描述

void RotateR(Node* parent )
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	Node* ppNode = parent->_parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;
	}
	parent->_bf = subL->_bf = 0;
}
右边高,左旋

当我们插入红色节点的时候就会导致右边过高,那么我们就需要左旋。
在这里插入图片描述

左旋的口诀:让cur的左边给parent的右边链接,然后让父亲变成cur的左边
解释:为什么要把cur左边的值给parent的右边?cur当前位置是parent的右边,cur的左边也是比parent大的值,给parent的右边依然不影响二叉搜索树的规则。
在这里插入图片描述

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if(subRL)
	subRL->_parent = parent;

	subR->_left = parent;

	Node* pphead = parent->_parent;

	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pphead->_parent == parent)
		{
			pphead->_right = subR;
		}
		else
		{
			pphead->_left = subR;
		}
		subR->_parent = pphead;
	}
	subR->_bf = parent->_bf = 0;
}
双旋转

还有一种情况,它并不是单纯的一边高,用单旋并不能解决问题。当我们仅仅只是单旋会发现他有变成了形态不同,但是问题一样的情况,这个时候就需要双旋
在这里插入图片描述

右左旋转:先右旋然后左旋。

既然我们单旋解决不了问题,我们可以把他变成一边高,然后进行单旋。
如这个图,我们可以对subR进行右旋后,在对parent左旋就可以了。
在这里插入图片描述
右左双旋后:
在这里插入图片描述
但是有个问题,平衡因子我们应该如何更新?你可能会说这种情况不是正好满足左旋/右旋后平衡因子变成0吗?但是如果其他位置都有节点呢?接下来我们抽象图,给大家演示一下。
当我们插入节点的位置不同,你会发现每一个平衡因子也不一样。如图:我插在了右边。这个时候我通过右左双旋就会得到下图。如最右图的红色平衡因子,应该变成这个情况。所以说插入的位置不同,平衡因子也会不同
在这里插入图片描述
如图:假如我插在了左边。会发现平衡因子又不一样了。所以我们并不能用一个例子来概括所有。
在这里插入图片描述
那么我们应该怎么做呢?其实从我们画图,你会发现我们改变的只不过是parent 、subR、sunRL这三个节点的因子,其他的并没有改变。并且,跟subRL的因子有密切关系,所以我们只需要判断subRL的因子是0/1/-1就能修改他们三个因子

		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 =0;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == 1)
			{
				//右边插入的
				subR->_bf = 0;
				subRL->_bf = 0;
				parent->_bf = -1;
			}
			else if(bf==-1)
			{
				//在左边插入的
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}
左右双旋:先左旋,在右旋。

图下是:左右双旋。
在这里插入图片描述

当然了,我们依然需要分析平衡因子,所以我们依然要分析插入在哪个位置。
如图:当插入在右边。
在这里插入图片描述

如图:当我们插在左边。
在这里插入图片描述

		void RotateLR(Node* parent)
		{
			//左右双旋
			//平衡因子需要单独调节
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋在右旋
			RotateL(parent->_left);
			RotateR(parent);
			//重新计算调节因子
			if (bf == 0)
			{
				//当且节点就是新插的
				subL->_bf = 0;
				parent->_bf = 0;
				subLR->_bf = 0;
			}
			else if(bf==1)
			{
				 //在当前节点的右边插入的
				parent->_bf = 0;
				subL->_bf = -1;
				subLR->_bf = 0;
			}
			else if(bf == -1)
			{
				subL->_bf = 0;
				parent->_bf = 1;
				subLR->_bf = 0;
			}
			else
			{
				assert(false);

			}
		}

中序遍历

因为二叉搜索树我们都是中序遍历,因为中序遍历更接近有序。

void _Inorder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_Inorder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second<<endl;
	_Inorder(root->_right);
}

判断是否平衡

什么时候不平衡?是不是因子大于等于2的时候,所以我们需要算左右树的高度相减,如果超过2,那就是不平衡。

		bool _isBalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			if (abs(left - right) >= 2) return false;//因子大于等于2的时候不平衡
			return _isBalance(root->_left) && _isBalance(root->_right);
		}
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			return max(left, right) + 1;
		}

AVL树整体代码

以下是整个AVL树所有的代码?

问题:为什么没有像库一样写删除呢?答:我们模拟实现其实是为了了解底层,并不是要超过底层,因为现有的库已经很好了,我们没必要写一个。二叉搜索树很少用删除接口。所以这里没有实现

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<assert.h>

using namespace std;
namespace KV
{
	template<class K,class V>
	struct AVLTreeNode
	{
		AVLTreeNode(const pair<K, V> kv)
			:_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_kv(kv)
			,_bf(0)
		{}
		AVLTreeNode* _left;
		AVLTreeNode* _right;
		AVLTreeNode* _parent;
		pair<K, V> _kv;
		int _bf;
	};
	template<class K,class V>
	class AVLTree
	{
	public:
		typedef AVLTreeNode<K,V> Node;
		bool insert(const pair<K, V> kv)
		{
			if (_root == nullptr)
			{
				//插入第一个值
				Node* newNode = new Node(kv);
				_root = newNode;
				return true;
			}
			Node* newNode = new Node(kv);
			Node* cur = _root;
			Node* parent = cur;
			while (cur)
			{
				if (cur->_kv.first < newNode->_kv.first)
				{
					//大于在右边
					parent = cur;
					cur = cur->_right;

				}
				else if (cur->_kv.first > newNode->_kv.first)
				{
					//小于在左边
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//等于,二叉搜索树不允许冗余,所以直接返回false。
					return false;
				}
			}
			if (parent->_kv.first > newNode->_kv.first)
			{
				//在左边
				parent->_left = newNode;
			}
			else
			{
				//在右边
				parent->_right = newNode;
			}
			newNode->_parent = parent;
			cur = newNode;
			while (parent)
			{
				if (parent->_left == cur)
				{
					cur->_parent->_bf--;
				}
				else if (parent->_right == cur)
				{
					cur->_parent->_bf++;
				}
				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)
					{
						RotateR(parent);
					}
					//右边高,左旋转
					else if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotateL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						//左右双旋
						RotateLR(parent);
					}
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						//右左双旋
						RotateRL(parent);
					}
					else
					{
						assert(false);
					}
				}
				else
				{
					//按道理说不可能有这种情况,但是保不准会有bug
					assert(false);
					break;
				}
			}
			return true;
		}

		void Inorder()
		{
			_Inorder(_root);
		}
		int Height()
		{
			return _Height(_root);
		}
		bool isBalance()
		{
			return _isBalance(_root);
		}
	private:
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			return max(left, right) + 1;
		}
		bool _isBalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}
			int left = _Height(root->_left);
			int right = _Height(root->_right);
			if (abs(left - right) >= 2) return false;
			return _isBalance(root->_left) && _isBalance(root->_right);
		}
		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 =0;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == 1)
			{
				//右边插入的
				subR->_bf = 0;
				subRL->_bf = 0;
				parent->_bf = -1;
			}
			else if(bf==-1)
			{
				//在左边插入的
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}
		void RotateLR(Node* parent)
		{
			//左右双旋
			//平衡因子需要单独调节
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			//先左旋在右旋
			RotateL(parent->_left);
			RotateR(parent);
			//重新计算调节因子
			if (bf == 0)
			{
				//当且节点就是新插的
				subL->_bf = 0;
				parent->_bf = 0;
				subLR->_bf = 0;
			}
			else if(bf==1)
			{
				 //在当前节点的右边插入的
				parent->_bf = 0;
				subL->_bf = -1;
				subLR->_bf = 0;
			}
			else if(bf == -1)
			{
				subL->_bf = 0;
				parent->_bf = 1;
				subLR->_bf = 0;
			}
			else
			{
				assert(false);

			}
		}
		void RotateR(Node* parent )
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;
			subL->_right = parent;
			Node* ppNode = parent->_parent;
			parent->_parent = subL;
			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (ppNode->_left == parent)
				{
					ppNode->_left = subL;
				}
				else
				{
					ppNode->_right = subL;
				}
				subL->_parent = ppNode;
			}
			parent->_bf = subL->_bf = 0;
		}
		void RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if(subRL)
			subRL->_parent = parent;

			subR->_left = parent;

			Node* pphead = parent->_parent;

			parent->_parent = subR;
			if (parent == _root)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (pphead->_parent == parent)
				{
					pphead->_right = subR;
				}
				else
				{
					pphead->_left = subR;
				}
				subR->_parent = pphead;
			}
			subR->_bf = parent->_bf = 0;
		}

		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_Inorder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second<<
				" 因子:" <<root->_bf<< endl;
			_Inorder(root->_right);
		}
		Node* _root=nullptr;
	};
}

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

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

相关文章

如何应对Android面试官 -> 玩转 Fragment

前言 本章主要讲解下 Framgent 的核心原理&#xff1b; 基础用法 线上基础用法&#xff0c;其他的可以自行百度 FragmentManager manager getSupportFragmentManager(); FragmentTransaction transaction manager.beginTransaction(); transaction.add(R.id.contentlayout,…

100个投资者99个选择使用这款EA,WeTrade发现1个事实

为什么100个投资者会有99个选择使用这款EA&#xff0c;是因为这款EA能提供两个版本吗?是因为能控制风险吗?都不是&#xff0c;WeTrade发现1个事实才是这么多投资者选择的原因&#xff0c;那就是能实现100%的盈利率。 我们都知道外汇狙击手EA提供两种版本&#xff0c;分别是标…

2018 年山东省职业院校技能大赛高职组“信息安全管理与评估”赛项任务书

2018年山东省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书 赛项时间 8:30-13:00&#xff0c;共计4小时30分钟&#xff0c;含赛题发放、收卷时间。 赛项信息 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 平台搭建与安全设备配置防护 …

云原生网关 MSE-Higress

云原生网关 MSE-Higress 什么是云原生网关MSEMSE测评产品文档产品能力产品控制台 MSE与其他网关 什么是云原生网关MSE 在体验云原生网关 MSE-Higress功能之前&#xff0c;先了解一下什么是云原生网关 MSE&#xff0c;简单的说就是MSE就是遵循开源 Ingress/Gateway API 标准的下…

kubernetes(Jenkins、kubernetes核心、K8s实战-KubeSphere、)

文章目录 1. Jenkins1.1. 概述1.1.1. 简单部署1.1.2. 自动化部署1.1.3. DevOps概述1.1.4. CI/CD概述 1.2. jenkins介绍及安装1.2.1. 安装1.2.2. 解锁jenkins1.2.3. 安装推荐插件1.2.4. 创建管理员用户1.2.5. 升级jenkins版本1.2.6. 安装额外插件blue ocean1.2.7. jenkins界面说…

[vue3后台管理二]首页和登录测试

[vue3后台管理二]首页和登录测试 1 修改main.js import ./assets/main.cssimport { createApp } from vue import App from ./App.vue import router from ./router createApp(App).use(router).mount(#app)2 路由创建 import {createRouter, createWebHistory} from vue-ro…

装机必备——截图工具Snipaste安装教程

装机必备——截图工具Snipaste安装教程 软件下载 软件名称&#xff1a;Snipaste2.7 软件语言&#xff1a;简体中文 软件大小&#xff1a;15.37M 系统要求&#xff1a;Windows7或更高&#xff0c; 32/64位操作系统 硬件要求&#xff1a;CPU2GHz &#xff0c;RAM2G或更高 下载通…

论文笔记 Explicit Visual Prompting for Low-Level Structure Segmentations

通俗地解释视觉中的prompt 在视觉中的“prompt”&#xff08;提示&#xff09;可以用一种比较通俗的方式来理解&#xff1a; 什么是视觉中的提示&#xff1f; 想象一下&#xff0c;你有一个已经接受过大量训练的超级助手&#xff08;类似于预训练的模型&#xff09;&#xf…

vscode:如何解决”检测到include错误,请更新includePath“

vscode:如何解决”检测到include错误&#xff0c;请更新includePath“ 前言解决办法1 获取includePath路径2 将includePath路径添加到指定文件3 保存 前言 配置vscode是出现如下错误&#xff1a; 解决办法 1 获取includePath路径 通过cmd打开终端&#xff0c;输入如下指令&a…

C++之vector

1、标准库的vector类型 2、vector对象的初始化 3、vector常用成员函数 #include <vector> #include <algorithm> #include <iostream> using namespace std;typedef vector<int> INTVEC;// 普通方法 //void showVec(const INTVEC& vec) // 这边如…

⌈ 传知代码 ⌋ YOLOv9最新最全代码复现

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

一款高级管理控制面板主题!【送源码】

AdminLTE是一个完全响应的管理模板。基于Bootstrap5框架和JavaScript插件。高度可定制&#xff0c;易于使用。适用于从小型移动设备到大型桌面的多种屏幕分辨率。AdminLTE 是一个基于Bootstrap 3.x的免费高级管理控制面板主题。 https://github.com/almasaeed2010/AdminLTE —…

43-3 应急响应 - WebShell查杀工具

一、WebShell 简介 WebShell是一种以asp、php、jsp等网页文件形式存在的代码执行环境,通常用于网站管理、服务器管理和权限管理等操作。然而,如果被入侵者利用,它也可以用于控制网站服务器。具有完整功能的WebShell通常被称为"大马",而功能简单的则称为"小马…

运维开发.MySQL.范式与反范式化

运维开发 MySQL.三大范式 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/artic…

【C语言训练题库】杨辉三角(下三角型和金字塔型)

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 题目&#xff1a;打印杨辉三角 1. 下三角型 1.1 图例: 1.2. 解析: 1.3. 代码: 1.4. 运行&#xff1a; 2. 金字塔型 2.1 图例 2.2. 解析 2.2.1. 打印金…

[测试开发]如何让IDEA实时显示内存

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Django 解决 CSRF 问题

在 Django 出现 CSRF 问题 要解决这个问题&#xff0c;就得在 html 里这么修改 <!DOCTYPE html> <html><head></head><body><form action"/login/" method"post">{% csrf_token %}</form></body> </…

【window 安装 service bus explorer】

安装ServiceBusExplorer 首先需要安装Chocolatey安装 service bus explorer 首先需要安装Chocolatey 参考&#xff1a; https://chocolatey.org/install#install-step2 以管理员身份运行powershell输入Get-ExecutionPolicy回车&#xff0c;若显示 Restricted输入Set-Executio…

Android笔记--应用安装

这一节了解一下普通应用安装app的方式&#xff0c;主要是唤起系统来安装&#xff0c;直接上代码: 申请权限 <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name"android.permission.WRITE_EXT…