第 10 章 树结构的基础部分

news2025/1/23 22:29:43

文章目录

  • 10.1 二叉树
    • 10.1.1 为什么需要树这种数据结构
    • 10.1.2 树示意图
    • 10.1.3 二叉树的概念
    • 10.1.4 二叉树遍历的说明
    • 10.1.5 二叉树遍历应用实例(前序,中序,后序)
    • 10.1.6 二叉树-查找指定节点
    • 10.1.7 二叉树-删除节点
    • 10.1.8 二叉树-删除节点
  • 10.2 顺序存储二叉树
    • 10.2.1 顺序存储二叉树的概念
    • 10.2.2 顺序存储二叉树遍历
  • 10.3 线索化二叉树
    • 10.3.1 先看一个问题
    • 10.3.2 线索二叉树基本介绍
    • 10.3.3 线索二叉树应用案例
    • 10.3.5 线索化二叉树的课后作业:

10.1 二叉树

10.1.1 为什么需要树这种数据结构

  1. 数组存储方式的分析
    优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 [示意图] 画出操作示意图:
    在这里插入图片描述

  2. 链式存储方式的分析
    优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。
    缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) 【示意图】操作示意图:
    在这里插入图片描述

  3. 树存储方式的分析
    能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。【示意图,后面详讲】
    案例: [7, 3, 10, 1, 5, 9, 12]
    在这里插入图片描述

10.1.2 树示意图

在这里插入图片描述
树的常用术语(结合示意图理解):
在这里插入图片描述

10.1.3 二叉树的概念

  1. 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。

  2. 二叉树的子节点分为左节点和右节点

  3. 示意图
    在这里插入图片描述

  4. 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
    在这里插入图片描述

  5. 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
    在这里插入图片描述

10.1.4 二叉树遍历的说明

使用前序,中序和后序对下面的二叉树进行遍历.

  1. 前序遍历: 先输出父节点,再遍历左子树和右子树

  2. 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

  3. 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点

  4. 小结: 看输出父节点的顺序,就确定是前序,中序还是后序

10.1.5 二叉树遍历应用实例(前序,中序,后序)

 应用实例的说明和思路
在这里插入图片描述

 代码实现

 package com.atguigu.tree;

public class BinaryTreeDemo {

	public static void main(String[] args) {
		//先需要创建一颗二叉树
		BinaryTree binaryTree = new BinaryTree();
		//创建需要的结点
		HeroNode root = new HeroNode(1, "宋江");
		HeroNode node2 = new HeroNode(2, "吴用");
		HeroNode node3 = new HeroNode(3, "卢俊义");
		HeroNode node4 = new HeroNode(4, "林冲");
		HeroNode node5 = new HeroNode(5, "关胜");
		
		//说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
		root.setLeft(node2);
		root.setRight(node3);
		node3.setRight(node4);
		node3.setLeft(node5);
		binaryTree.setRoot(root);
		
		//测试
//		System.out.println("前序遍历"); // 1,2,3,5,4
//		binaryTree.preOrder();
		
		//测试 
//		System.out.println("中序遍历");
//		binaryTree.infixOrder(); // 2,1,5,3,4
//		
//		System.out.println("后序遍历");
//		binaryTree.postOrder(); // 2,5,4,3,1
		
		//前序遍历
		//前序遍历的次数 :4 
//		System.out.println("前序遍历方式~~~");
//		HeroNode resNode = binaryTree.preOrderSearch(5);
//		if (resNode != null) {
//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
//		} else {
//			System.out.printf("没有找到 no = %d 的英雄", 5);
//		}
		
		//中序遍历查找
		//中序遍历3次
//		System.out.println("中序遍历方式~~~");
//		HeroNode resNode = binaryTree.infixOrderSearch(5);
//		if (resNode != null) {
//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
//		} else {
//			System.out.printf("没有找到 no = %d 的英雄", 5);
//		}
		
		//后序遍历查找
		//后序遍历查找的次数  2次
//		System.out.println("后序遍历方式~~~");
//		HeroNode resNode = binaryTree.postOrderSearch(5);
//		if (resNode != null) {
//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
//		} else {
//			System.out.printf("没有找到 no = %d 的英雄", 5);
//		}
		
		//测试一把删除结点
		
		System.out.println("删除前,前序遍历");
		binaryTree.preOrder(); //  1,2,3,5,4
		binaryTree.delNode(5);
		//binaryTree.delNode(3);
		System.out.println("删除后,前序遍历");
		binaryTree.preOrder(); // 1,2,3,4
		
		
		
	}

}

//定义BinaryTree 二叉树
class BinaryTree {
	private HeroNode root;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
	
	//删除结点
	public void delNode(int no) {
		if(root != null) {
			//如果只有一个root结点, 这里立即判断root是不是就是要删除结点
			if(root.getNo() == no) {
				root = null;
			} else {
				//递归删除
				root.delNode(no);
			}
		}else{
			System.out.println("空树,不能删除~");
		}
	}
	//前序遍历
	public void preOrder() {
		if(this.root != null) {
			this.root.preOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
	
	//中序遍历
	public void infixOrder() {
		if(this.root != null) {
			this.root.infixOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//后序遍历
	public void postOrder() {
		if(this.root != null) {
			this.root.postOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
	
	//前序遍历
	public HeroNode preOrderSearch(int no) {
		if(root != null) {
			return root.preOrderSearch(no);
		} else {
			return null;
		}
	}
	//中序遍历
	public HeroNode infixOrderSearch(int no) {
		if(root != null) {
			return root.infixOrderSearch(no);
		}else {
			return null;
		}
	}
	//后序遍历
	public HeroNode postOrderSearch(int no) {
		if(root != null) {
			return this.root.postOrderSearch(no);
		}else {
			return null;
		}
	}
}

//先创建HeroNode 结点
class HeroNode {
	private int no;
	private String name;
	private HeroNode left; //默认null
	private HeroNode right; //默认null
	public HeroNode(int no, String name) {
		this.no = no;
		this.name = name;
	}
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public HeroNode getLeft() {
		return left;
	}
	public void setLeft(HeroNode left) {
		this.left = left;
	}
	public HeroNode getRight() {
		return right;
	}
	public void setRight(HeroNode right) {
		this.right = right;
	}
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + "]";
	}
	
	//递归删除结点
	//1.如果删除的节点是叶子节点,则删除该节点
	//2.如果删除的节点是非叶子节点,则删除该子树
	public void delNode(int no) {
		
		//思路
		/*
		 * 	1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
			2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
			3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
			4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
			5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.

		 */
		//2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
		if(this.left != null && this.left.no == no) {
			this.left = null;
			return;
		}
		//3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
		if(this.right != null && this.right.no == no) {
			this.right = null;
			return;
		}
		//4.我们就需要向左子树进行递归删除
		if(this.left != null) {
			this.left.delNode(no);
		}
		//5.则应当向右子树进行递归删除
		if(this.right != null) {
			this.right.delNode(no);
		}
	}
	
	//编写前序遍历的方法
	public void preOrder() {
		System.out.println(this); //先输出父结点
		//递归向左子树前序遍历
		if(this.left != null) {
			this.left.preOrder();
		}
		//递归向右子树前序遍历
		if(this.right != null) {
			this.right.preOrder();
		}
	}
	//中序遍历
	public void infixOrder() {
		
		//递归向左子树中序遍历
		if(this.left != null) {
			this.left.infixOrder();
		}
		//输出父结点
		System.out.println(this);
		//递归向右子树中序遍历
		if(this.right != null) {
			this.right.infixOrder();
		}
	}
	//后序遍历
	public void postOrder() {
		if(this.left != null) {
			this.left.postOrder();
		}
		if(this.right != null) {
			this.right.postOrder();
		}
		System.out.println(this);
	}
	
	//前序遍历查找
	/**
	 * 
	 * @param no 查找no
	 * @return 如果找到就返回该Node ,如果没有找到返回 null
	 */
	public HeroNode preOrderSearch(int no) {
		System.out.println("进入前序遍历");
		//比较当前结点是不是
		if(this.no == no) {
			return this;
		}
		//1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
		//2.如果左递归前序查找,找到结点,则返回
		HeroNode resNode = null;
		if(this.left != null) {
			resNode = this.left.preOrderSearch(no);
		}
		if(resNode != null) {//说明我们左子树找到
			return resNode;
		}
		//1.左递归前序查找,找到结点,则返回,否继续判断,
		//2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
		if(this.right != null) {
			resNode = this.right.preOrderSearch(no);
		}
		return resNode;
	}
	
	//中序遍历查找
	public HeroNode infixOrderSearch(int no) {
		//判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
		HeroNode resNode = null;
		if(this.left != null) {
			resNode = this.left.infixOrderSearch(no);
		}
		if(resNode != null) {
			return resNode;
		}
		System.out.println("进入中序查找");
		//如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
		if(this.no == no) {
			return this;
		}
		//否则继续进行右递归的中序查找
		if(this.right != null) {
			resNode = this.right.infixOrderSearch(no);
		}
		return resNode;
		
	}
	
	//后序遍历查找
	public HeroNode postOrderSearch(int no) {
		
		//判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
		HeroNode resNode = null;
		if(this.left != null) {
			resNode = this.left.postOrderSearch(no);
		}
		if(resNode != null) {//说明在左子树找到
			return resNode;
		}
		
		//如果左子树没有找到,则向右子树递归进行后序遍历查找
		if(this.right != null) {
			resNode = this.right.postOrderSearch(no);
		}
		if(resNode != null) {
			return resNode;
		}
		System.out.println("进入后序查找");
		//如果左右子树都没有找到,就比较当前结点是不是
		if(this.no == no) {
			return this;
		}
		return resNode;
	}
	
}

//

10.1.6 二叉树-查找指定节点

要求

  1. 请编写前序查找,中序查找和后序查找的方法。

  2. 并分别使用三种查找方式,查找 heroNO = 5 的节点

  3. 并分析各种查找方式,分别比较了多少次

  4. 思路分析图解
    在这里插入图片描述

  5. 代码实现

    		package com.atguigu.tree;
    		
    		public class BinaryTreeDemo {
    	
    		public static void main(String[] args) {
    			//先需要创建一颗二叉树
    			BinaryTree binaryTree = new BinaryTree();
    			//创建需要的结点
    			HeroNode root = new HeroNode(1, "宋江");
    			HeroNode node2 = new HeroNode(2, "吴用");
    			HeroNode node3 = new HeroNode(3, "卢俊义");
    			HeroNode node4 = new HeroNode(4, "林冲");
    			HeroNode node5 = new HeroNode(5, "关胜");
    			
    			//说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
    			root.setLeft(node2);
    			root.setRight(node3);
    			node3.setRight(node4);
    			node3.setLeft(node5);
    			binaryTree.setRoot(root);
    			
    			//测试
    	//		System.out.println("前序遍历"); // 1,2,3,5,4
    	//		binaryTree.preOrder();
    			
    			//测试 
    	//		System.out.println("中序遍历");
    	//		binaryTree.infixOrder(); // 2,1,5,3,4
    	//		
    	//		System.out.println("后序遍历");
    	//		binaryTree.postOrder(); // 2,5,4,3,1
    			
    			//前序遍历
    			//前序遍历的次数 :4 
    	//		System.out.println("前序遍历方式~~~");
    	//		HeroNode resNode = binaryTree.preOrderSearch(5);
    	//		if (resNode != null) {
    	//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
    	//		} else {
    	//			System.out.printf("没有找到 no = %d 的英雄", 5);
    	//		}
    			
    			//中序遍历查找
    			//中序遍历3次
    	//		System.out.println("中序遍历方式~~~");
    	//		HeroNode resNode = binaryTree.infixOrderSearch(5);
    	//		if (resNode != null) {
    	//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
    	//		} else {
    	//			System.out.printf("没有找到 no = %d 的英雄", 5);
    	//		}
    			
    			//后序遍历查找
    			//后序遍历查找的次数  2次
    	//		System.out.println("后序遍历方式~~~");
    	//		HeroNode resNode = binaryTree.postOrderSearch(5);
    	//		if (resNode != null) {
    	//			System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName());
    	//		} else {
    	//			System.out.printf("没有找到 no = %d 的英雄", 5);
    	//		}
    			
    			//测试一把删除结点
    			
    			System.out.println("删除前,前序遍历");
    			binaryTree.preOrder(); //  1,2,3,5,4
    			binaryTree.delNode(5);
    			//binaryTree.delNode(3);
    			System.out.println("删除后,前序遍历");
    			binaryTree.preOrder(); // 1,2,3,4
    			
    			
    			
    		}
    	
    	}
    	
    	//定义BinaryTree 二叉树
    	class BinaryTree {
    		private HeroNode root;
    	
    		public void setRoot(HeroNode root) {
    			this.root = root;
    		}
    		
    		//删除结点
    		public void delNode(int no) {
    			if(root != null) {
    				//如果只有一个root结点, 这里立即判断root是不是就是要删除结点
    				if(root.getNo() == no) {
    					root = null;
    				} else {
    					//递归删除
    					root.delNode(no);
    				}
    			}else{
    				System.out.println("空树,不能删除~");
    			}
    		}
    		//前序遍历
    		public void preOrder() {
    			if(this.root != null) {
    				this.root.preOrder();
    			}else {
    				System.out.println("二叉树为空,无法遍历");
    			}
    		}
    		
    		//中序遍历
    		public void infixOrder() {
    			if(this.root != null) {
    				this.root.infixOrder();
    			}else {
    				System.out.println("二叉树为空,无法遍历");
    			}
    		}
    		//后序遍历
    		public void postOrder() {
    			if(this.root != null) {
    				this.root.postOrder();
    			}else {
    				System.out.println("二叉树为空,无法遍历");
    			}
    		}
    		
    		//前序遍历
    		public HeroNode preOrderSearch(int no) {
    			if(root != null) {
    				return root.preOrderSearch(no);
    			} else {
    				return null;
    			}
    		}
    		//中序遍历
    		public HeroNode infixOrderSearch(int no) {
    			if(root != null) {
    				return root.infixOrderSearch(no);
    			}else {
    				return null;
    			}
    		}
    		//后序遍历
    		public HeroNode postOrderSearch(int no) {
    			if(root != null) {
    				return this.root.postOrderSearch(no);
    			}else {
    				return null;
    			}
    		}
    	}
    	
    	//先创建HeroNode 结点
    	class HeroNode {
    		private int no;
    		private String name;
    		private HeroNode left; //默认null
    		private HeroNode right; //默认null
    		public HeroNode(int no, String name) {
    			this.no = no;
    			this.name = name;
    		}
    		public int getNo() {
    			return no;
    		}
    		public void setNo(int no) {
    			this.no = no;
    		}
    		public String getName() {
    			return name;
    		}
    		public void setName(String name) {
    			this.name = name;
    		}
    		public HeroNode getLeft() {
    			return left;
    		}
    		public void setLeft(HeroNode left) {
    			this.left = left;
    		}
    		public HeroNode getRight() {
    			return right;
    		}
    		public void setRight(HeroNode right) {
    			this.right = right;
    		}
    		@Override
    		public String toString() {
    			return "HeroNode [no=" + no + ", name=" + name + "]";
    		}
    		
    		//递归删除结点
    		//1.如果删除的节点是叶子节点,则删除该节点
    		//2.如果删除的节点是非叶子节点,则删除该子树
    		public void delNode(int no) {
    			
    			//思路
    			/*
    			 * 	1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
    				2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    				3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    				4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
    				5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.
    	
    			 */
    			//2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    			if(this.left != null && this.left.no == no) {
    				this.left = null;
    				return;
    			}
    			//3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    			if(this.right != null && this.right.no == no) {
    				this.right = null;
    				return;
    			}
    			//4.我们就需要向左子树进行递归删除
    			if(this.left != null) {
    				this.left.delNode(no);
    			}
    			//5.则应当向右子树进行递归删除
    			if(this.right != null) {
    				this.right.delNode(no);
    			}
    		}
    		
    		//编写前序遍历的方法
    		public void preOrder() {
    			System.out.println(this); //先输出父结点
    			//递归向左子树前序遍历
    			if(this.left != null) {
    				this.left.preOrder();
    			}
    			//递归向右子树前序遍历
    			if(this.right != null) {
    				this.right.preOrder();
    			}
    		}
    		//中序遍历
    		public void infixOrder() {
    			
    			//递归向左子树中序遍历
    			if(this.left != null) {
    				this.left.infixOrder();
    			}
    			//输出父结点
    			System.out.println(this);
    			//递归向右子树中序遍历
    			if(this.right != null) {
    				this.right.infixOrder();
    			}
    		}
    		//后序遍历
    		public void postOrder() {
    			if(this.left != null) {
    				this.left.postOrder();
    			}
    			if(this.right != null) {
    				this.right.postOrder();
    			}
    			System.out.println(this);
    		}
    		
    		//前序遍历查找
    		/**
    		 * 
    		 * @param no 查找no
    		 * @return 如果找到就返回该Node ,如果没有找到返回 null
    		 */
    		public HeroNode preOrderSearch(int no) {
    			System.out.println("进入前序遍历");
    			//比较当前结点是不是
    			if(this.no == no) {
    				return this;
    			}
    			//1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
    			//2.如果左递归前序查找,找到结点,则返回
    			HeroNode resNode = null;
    			if(this.left != null) {
    				resNode = this.left.preOrderSearch(no);
    			}
    			if(resNode != null) {//说明我们左子树找到
    				return resNode;
    			}
    			//1.左递归前序查找,找到结点,则返回,否继续判断,
    			//2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
    			if(this.right != null) {
    				resNode = this.right.preOrderSearch(no);
    			}
    			return resNode;
    		}
    		
    		//中序遍历查找
    		public HeroNode infixOrderSearch(int no) {
    			//判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
    			HeroNode resNode = null;
    			if(this.left != null) {
    				resNode = this.left.infixOrderSearch(no);
    			}
    			if(resNode != null) {
    				return resNode;
    			}
    			System.out.println("进入中序查找");
    			//如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
    			if(this.no == no) {
    				return this;
    			}
    			//否则继续进行右递归的中序查找
    			if(this.right != null) {
    				resNode = this.right.infixOrderSearch(no);
    			}
    			return resNode;
    			
    		}
    		
    		//后序遍历查找
    		public HeroNode postOrderSearch(int no) {
    			
    			//判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
    			HeroNode resNode = null;
    			if(this.left != null) {
    				resNode = this.left.postOrderSearch(no);
    			}
    			if(resNode != null) {//说明在左子树找到
    				return resNode;
    			}
    			
    			//如果左子树没有找到,则向右子树递归进行后序遍历查找
    			if(this.right != null) {
    				resNode = this.right.postOrderSearch(no);
    			}
    			if(resNode != null) {
    				return resNode;
    			}
    			System.out.println("进入后序查找");
    			//如果左右子树都没有找到,就比较当前结点是不是
    			if(this.no == no) {
    				return this;
    			}
    			return resNode;
    		}
    		
    	}
    	
    	//
    

10.1.7 二叉树-删除节点

 要求

  1. 如果删除的节点是叶子节点,则删除该节点

  2. 如果删除的节点是非叶子节点,则删除该子树.

  3. 测试,删除掉 5 号叶子节点 和 3 号子树.

  4. 完成删除思路分析
    在这里插入图片描述

  5. 代码实现

    //递归删除结点
    	//1.如果删除的节点是叶子节点,则删除该节点
    	//2.如果删除的节点是非叶子节点,则删除该子树
    	public void delNode(int no) {
    		
    		//思路
    		/*
    		 * 	1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
    			2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    			3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    			4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
    			5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.
    
    		 */
    		//2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    		if(this.left != null && this.left.no == no) {
    			this.left = null;
    			return;
    		}
    		//3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    		if(this.right != null && this.right.no == no) {
    			this.right = null;
    			return;
    		}
    		//4.我们就需要向左子树进行递归删除
    		if(this.left != null) {
    			this.left.delNode(no);
    		}
    		//5.则应当向右子树进行递归删除
    		if(this.right != null) {
    			this.right.delNode(no);
    		}
    	}
     
    2.	如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回
    (结束递归删除)
    3.	如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回 (结束递归删除)
    4.	如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
    5.	如果第 4 步也没有删除结点,则应当向右子树进行递归删除.
    
    
    */
    //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回(结束递归删除)
    if(this.left != null && this.left.no == no) { this.left = null;
    return;
    
    }
    //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结束递归删除)
    if(this.right != null && this.right.no == no) { this.right = null;
    return;
    
    }
    //4.我们就需要向左子树进行递归删除 if(this.left != null) {
    this.left.delNode(no);
    
    }
    //5.则应当向右子树进行递归删除 if(this.right != null) {
     
    this.right.delNode(no);
    }
    }
    
    
    //在 BinaryTree 类增加方法
    
    
    //删除结点
    public void delNode(int no) { if(root != null) {
    //如果只有一个 root 结点, 这里立即判断 root 是不是就是要删除结点
    if(root.getNo() == no) { root = null;
    } else {
    //递归删除 root.delNode(no);
    }
    }else{
    System.out.println("空树,不能删除~");
    }
    }
    
    
    //在 BinaryTreeDemo 类增加测试代码:
    
    
    //测试一把删除结点
    
    
    System.out.println("删除前,前序遍历");
    

10.1.8 二叉树-删除节点

思考题(课后练习)

  1. 如果要删除的节点是非叶子节点,现在我们不希望将该非叶子节点为根节点的子树删除,需要指定规则, 假如规定如下:
  2. 如果该非叶子节点 A 只有一个子节点 B,则子节点 B 替代节点 A
  3. 如果该非叶子节点 A 有左子节点 B 和右子节点 C,则让左子节点 B 替代节点 A。
  4. 请大家思考,如何完成该删除功能, 老师给出提示.(课后练习)
  5. 后面在讲解 二叉排序树时,在给大家讲解具体的删除方法
    在这里插入图片描述

10.2 顺序存储二叉树

10.2.1 顺序存储二叉树的概念

 基本说明
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,看右面的示意图。
在这里插入图片描述

 要求:

  1. 右图的二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 6]
  2. 要求在遍历数组 arr 时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历

 顺序存储二叉树的特点:

  1. 顺序二叉树通常只考虑完全二叉树
  2. 第 n 个元素的左子节点为 2 * n + 1
  3. 第 n 个元素的右子节点为 2 * n + 2
  4. 第 n 个元素的父节点为 (n-1) / 2
  5. n : 表示二叉树中的第几个元素(按 0 开始编号如图所示)

10.2.2 顺序存储二叉树遍历

需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为
1,2,4,5,3,6,7
代码实现:

package com.atguigu.tree;

public class ArrBinaryTreeDemo {

	public static void main(String[] args) {
		int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
		//创建一个 ArrBinaryTree
		ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
		arrBinaryTree.preOrder(); // 1,2,4,5,3,6,7
	}

}

//编写一个ArrayBinaryTree, 实现顺序存储二叉树遍历

class ArrBinaryTree {
	private int[] arr;//存储数据结点的数组

	public ArrBinaryTree(int[] arr) {
		this.arr = arr;
	}
	
	//重载preOrder
	public void preOrder() {
		this.preOrder(0);
	}
	
	//编写一个方法,完成顺序存储二叉树的前序遍历
	/**
	 * 
	 * @param index 数组的下标 
	 */
	public void preOrder(int index) {
		//如果数组为空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("数组为空,不能按照二叉树的前序遍历");
		}
		//输出当前这个元素
		System.out.println(arr[index]); 
		//向左递归遍历
		if((index * 2 + 1) < arr.length) {
			preOrder(2 * index + 1 );
		}
		//向右递归遍历
		if((index * 2 + 2) < arr.length) {
			preOrder(2 * index + 2);
		}
	}
	
}

作业:
课后练习:请同学们完成对数组以二叉树中序,后序遍历方式的代码.
10.2.3 顺序存储二叉树应用实例
八大排序算法中的堆排序,就会使用到顺序存储二叉树, 关于堆排序,我们放在<<树结构实际应用>> 章节讲解。

10.3 线索化二叉树

10.3.1 先看一个问题

将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7
在这里插入图片描述

问题分析:

  1. 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
  2. 但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.
  3. 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
  4. 解决方案-线索二叉树

10.3.2 线索二叉树基本介绍

  1. n 个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")

  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种

  3. 一个结点的前一个结点,称为前驱结点

  4. 一个结点的后一个结点,称为后继结点

10.3.3 线索二叉树应用案例

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}
在这里插入图片描述

思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}
在这里插入图片描述

 说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:

  1. left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
  2. right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向的是后继节点.

 代码实现:

10.3.4 遍历线索化二叉树

  1. 说明:对前面的中序线索化的二叉树, 进行遍历

  2. 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。

  3. 代码:

    package com.atguigu.tree.threadedbinarytree;
    
    import java.util.concurrent.SynchronousQueue;
    
    public class ThreadedBinaryTreeDemo {
    
    	public static void main(String[] args) {
    		//测试一把中序线索二叉树的功能
    		HeroNode root = new HeroNode(1, "tom");
    		HeroNode node2 = new HeroNode(3, "jack");
    		HeroNode node3 = new HeroNode(6, "smith");
    		HeroNode node4 = new HeroNode(8, "mary");
    		HeroNode node5 = new HeroNode(10, "king");
    		HeroNode node6 = new HeroNode(14, "dim");
    		
    		//二叉树,后面我们要递归创建, 现在简单处理使用手动创建
    		root.setLeft(node2);
    		root.setRight(node3);
    		node2.setLeft(node4);
    		node2.setRight(node5);
    		node3.setLeft(node6);
    		
    		//测试中序线索化
    		ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
    		threadedBinaryTree.setRoot(root);
    		threadedBinaryTree.threadedNodes();
    		
    		//测试: 以10号节点测试
    		HeroNode leftNode = node5.getLeft();
    		HeroNode rightNode = node5.getRight();
    		System.out.println("10号结点的前驱结点是 ="  + leftNode); //3
    		System.out.println("10号结点的后继结点是="  + rightNode); //1
    		
    		//当线索化二叉树后,能在使用原来的遍历方法
    		//threadedBinaryTree.infixOrder();
    		System.out.println("使用线索化的方式遍历 线索化二叉树");
    		threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6
    		
    	}
    
    }
    
    
    
    
    //定义ThreadedBinaryTree 实现了线索化功能的二叉树
    class ThreadedBinaryTree {
    	private HeroNode root;
    	
    	//为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
    	//在递归进行线索化时,pre 总是保留前一个结点
    	private HeroNode pre = null;
    
    	public void setRoot(HeroNode root) {
    		this.root = root;
    	}
    	
    	//重载一把threadedNodes方法
    	public void threadedNodes() {
    		this.threadedNodes(root);
    	}
    	
    	//遍历线索化二叉树的方法
    	public void threadedList() {
    		//定义一个变量,存储当前遍历的结点,从root开始
    		HeroNode node = root;
    		while(node != null) {
    			//循环的找到leftType == 1的结点,第一个找到就是8结点
    			//后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化
    			//处理后的有效结点
    			while(node.getLeftType() == 0) {
    				node = node.getLeft();
    			}
    			
    			//打印当前这个结点
    			System.out.println(node);
    			//如果当前结点的右指针指向的是后继结点,就一直输出
    			while(node.getRightType() == 1) {
    				//获取到当前结点的后继结点
    				node = node.getRight();
    				System.out.println(node);
    			}
    			//替换这个遍历的结点
    			node = node.getRight();
    			
    		}
    	}
    	
    	//编写对二叉树进行中序线索化的方法
    	/**
    	 * 
    	 * @param node 就是当前需要线索化的结点
    	 */
    	public void threadedNodes(HeroNode node) {
    		
    		//如果node==null, 不能线索化
    		if(node == null) {
    			return;
    		}
    		
    		//(一)先线索化左子树
    		threadedNodes(node.getLeft());
    		//(二)线索化当前结点[有难度]
    		
    		//处理当前结点的前驱结点
    		//以8结点来理解
    		//8结点的.left = null , 8结点的.leftType = 1
    		if(node.getLeft() == null) {
    			//让当前结点的左指针指向前驱结点 
    			node.setLeft(pre); 
    			//修改当前结点的左指针的类型,指向前驱结点
    			node.setLeftType(1);
    		}
    		
    		//处理后继结点
    		if (pre != null && pre.getRight() == null) {
    			//让前驱结点的右指针指向当前结点
    			pre.setRight(node);
    			//修改前驱结点的右指针类型
    			pre.setRightType(1);
    		}
    		//!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
    		pre = node;
    		
    		//(三)在线索化右子树
    		threadedNodes(node.getRight());
    		
    		
    	}
    	
    	//删除结点
    	public void delNode(int no) {
    		if(root != null) {
    			//如果只有一个root结点, 这里立即判断root是不是就是要删除结点
    			if(root.getNo() == no) {
    				root = null;
    			} else {
    				//递归删除
    				root.delNode(no);
    			}
    		}else{
    			System.out.println("空树,不能删除~");
    		}
    	}
    	//前序遍历
    	public void preOrder() {
    		if(this.root != null) {
    			this.root.preOrder();
    		}else {
    			System.out.println("二叉树为空,无法遍历");
    		}
    	}
    	
    	//中序遍历
    	public void infixOrder() {
    		if(this.root != null) {
    			this.root.infixOrder();
    		}else {
    			System.out.println("二叉树为空,无法遍历");
    		}
    	}
    	//后序遍历
    	public void postOrder() {
    		if(this.root != null) {
    			this.root.postOrder();
    		}else {
    			System.out.println("二叉树为空,无法遍历");
    		}
    	}
    	
    	//前序遍历
    	public HeroNode preOrderSearch(int no) {
    		if(root != null) {
    			return root.preOrderSearch(no);
    		} else {
    			return null;
    		}
    	}
    	//中序遍历
    	public HeroNode infixOrderSearch(int no) {
    		if(root != null) {
    			return root.infixOrderSearch(no);
    		}else {
    			return null;
    		}
    	}
    	//后序遍历
    	public HeroNode postOrderSearch(int no) {
    		if(root != null) {
    			return this.root.postOrderSearch(no);
    		}else {
    			return null;
    		}
    	}
    }
    
    //先创建HeroNode 结点
    class HeroNode {
    	private int no;
    	private String name;
    	private HeroNode left; //默认null
    	private HeroNode right; //默认null
    	//说明
    	//1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
    	//2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点
    	private int leftType;
    	private int rightType;
    	
    	
    	
    	public int getLeftType() {
    		return leftType;
    	}
    	public void setLeftType(int leftType) {
    		this.leftType = leftType;
    	}
    	public int getRightType() {
    		return rightType;
    	}
    	public void setRightType(int rightType) {
    		this.rightType = rightType;
    	}
    	public HeroNode(int no, String name) {
    		this.no = no;
    		this.name = name;
    	}
    	public int getNo() {
    		return no;
    	}
    	public void setNo(int no) {
    		this.no = no;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public HeroNode getLeft() {
    		return left;
    	}
    	public void setLeft(HeroNode left) {
    		this.left = left;
    	}
    	public HeroNode getRight() {
    		return right;
    	}
    	public void setRight(HeroNode right) {
    		this.right = right;
    	}
    	@Override
    	public String toString() {
    		return "HeroNode [no=" + no + ", name=" + name + "]";
    	}
    	
    	//递归删除结点
    	//1.如果删除的节点是叶子节点,则删除该节点
    	//2.如果删除的节点是非叶子节点,则删除该子树
    	public void delNode(int no) {
    		
    		//思路
    		/*
    		 * 	1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
    			2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    			3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    			4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
    			5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.
    
    		 */
    		//2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    		if(this.left != null && this.left.no == no) {
    			this.left = null;
    			return;
    		}
    		//3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    		if(this.right != null && this.right.no == no) {
    			this.right = null;
    			return;
    		}
    		//4.我们就需要向左子树进行递归删除
    		if(this.left != null) {
    			this.left.delNode(no);
    		}
    		//5.则应当向右子树进行递归删除
    		if(this.right != null) {
    			this.right.delNode(no);
    		}
    	}
    	
    	//编写前序遍历的方法
    	public void preOrder() {
    		System.out.println(this); //先输出父结点
    		//递归向左子树前序遍历
    		if(this.left != null) {
    			this.left.preOrder();
    		}
    		//递归向右子树前序遍历
    		if(this.right != null) {
    			this.right.preOrder();
    		}
    	}
    	//中序遍历
    	public void infixOrder() {
    		
    		//递归向左子树中序遍历
    		if(this.left != null) {
    			this.left.infixOrder();
    		}
    		//输出父结点
    		System.out.println(this);
    		//递归向右子树中序遍历
    		if(this.right != null) {
    			this.right.infixOrder();
    		}
    	}
    	//后序遍历
    	public void postOrder() {
    		if(this.left != null) {
    			this.left.postOrder();
    		}
    		if(this.right != null) {
    			this.right.postOrder();
    		}
    		System.out.println(this);
    	}
    	
    	//前序遍历查找
    	/**
    	 * 
    	 * @param no 查找no
    	 * @return 如果找到就返回该Node ,如果没有找到返回 null
    	 */
    	public HeroNode preOrderSearch(int no) {
    		System.out.println("进入前序遍历");
    		//比较当前结点是不是
    		if(this.no == no) {
    			return this;
    		}
    		//1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
    		//2.如果左递归前序查找,找到结点,则返回
    		HeroNode resNode = null;
    		if(this.left != null) {
    			resNode = this.left.preOrderSearch(no);
    		}
    		if(resNode != null) {//说明我们左子树找到
    			return resNode;
    		}
    		//1.左递归前序查找,找到结点,则返回,否继续判断,
    		//2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
    		if(this.right != null) {
    			resNode = this.right.preOrderSearch(no);
    		}
    		return resNode;
    	}
    	
    	//中序遍历查找
    	public HeroNode infixOrderSearch(int no) {
    		//判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
    		HeroNode resNode = null;
    		if(this.left != null) {
    			resNode = this.left.infixOrderSearch(no);
    		}
    		if(resNode != null) {
    			return resNode;
    		}
    		System.out.println("进入中序查找");
    		//如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
    		if(this.no == no) {
    			return this;
    		}
    		//否则继续进行右递归的中序查找
    		if(this.right != null) {
    			resNode = this.right.infixOrderSearch(no);
    		}
    		return resNode;
    		
    	}
    	
    	//后序遍历查找
    	public HeroNode postOrderSearch(int no) {
    		
    		//判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
    		HeroNode resNode = null;
    		if(this.left != null) {
    			resNode = this.left.postOrderSearch(no);
    		}
    		if(resNode != null) {//说明在左子树找到
    			return resNode;
    		}
    		
    		//如果左子树没有找到,则向右子树递归进行后序遍历查找
    		if(this.right != null) {
    			resNode = this.right.postOrderSearch(no);
    		}
    		if(resNode != null) {
    			return resNode;
    		}
    		System.out.println("进入后序查找");
    		//如果左右子树都没有找到,就比较当前结点是不是
    		if(this.no == no) {
    			return this;
    		}
    		return resNode;
    	}
    	
    }
    

10.3.5 线索化二叉树的课后作业:

我这里讲解了中序线索化二叉树,前序线索化二叉树和后序线索化二叉树的分析思路类似,同学们作为课后作业完成.

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

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

相关文章

行业分享----dbaplus174期:美团基于Orchestrator的MySQL高可用实践

记录 MySQL高可用方案-MMM、MHA、MGR、PXC https://blog.csdn.net/jycjyc/article/details/119731980 美团数据库高可用架构的演进与设想 https://tech.meituan.com/2017/06/29/database-availability-architecture.html

数据结构(c)冒泡排序

本文除了最下面的代码是我写的&#xff0c;其余是网上抄写的。 冒泡排序 什么是冒泡排序&#xff1f; 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交…

相对原子质量和质子数和原子数的关系。

问题描述&#xff1a; 相对原子质量和质子数和原子数的关系。 问题解答&#xff1a; 相对原子质量&#xff08;相对原子质量单位&#xff0c;通常用amu表示&#xff09;和质子数、原子数之间存在一定的关系。这关系可以通过以下公式表示&#xff1a; 其中&#xff0c;是相对…

NLP论文阅读记录 - 2021 | 使用深度强化模型耦合上下文单词表示和注意机制的自动文本摘要

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作2.1 单词表示2.2 文本摘要方法 三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Automatic text summarization us…

并发编程之并发容器

目录 并发容器 CopyOnWriteArrayList 应用场景 常用方法 读多写少场景使用CopyOnWriteArrayList举例 CopyOnWriteArrayList原理 CopyOnWriteArrayList 的缺陷 扩展迭代器fail-fast与fail-safe机制 ConcurrentHashMap 应用场景 常用方法 并发场景下线程安全举例 Con…

51-11 多模态论文串讲—VLMo 论文精读

VLMo: Unified Vision-Language Pre-Training with Mixture-of-Modality-Experts (NeurIPS 2022) VLMo 是一种多模态 Transformer 模型&#xff0c;从名字可以看得出来它是一种 Mixture-of-Modality-Experts (MoME)&#xff0c;即混合多模态专家。怎么理解呢&#xff1f;主流 …

[学习笔记]刘知远团队大模型技术与交叉应用L1-NLPBig Model Basics

本节主要介绍NLP和大模型的基础知识。提及了词表示如何从one-hot发展到Word Embedding。语言模型如何从N-gram发展成预训练语言模型PLMs。然后介绍了大模型在NLP任务上的表现&#xff0c;以及它遵循的基本范式。最后介绍了本课程需要用到的编程环境和GPU服务器。 一篇NLP方向的…

【java】创建打印数组的方法并调用

java的数组和Python不一样&#xff0c;不能直接用print打印&#xff0c;而如果每次都用循环的方法&#xff0c;比较麻烦&#xff0c;就直接创建一个类&方法&#xff0c;每次用的时候直接调用。 public class list_deal {public static void printArray(int[] arr){for (in…

Asp .Net Core 系列:基于 Swashbuckle.AspNetCore 包 集成 Swagger

什么是 Swagger? Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。它提供了一种规范的方式来定义、构建和文档化 RESTful Web 服务&#xff0c;使客户端能够发现和理解各种服务的功能。Swagger 的目标是使部署管理和使用功…

JVM实战(15)——Full GC调优

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

智能寻迹避障清障机器人设计(电路图附件+代码)

附 录 智能小车原理图 智能小车拓展板原理图 智能小车拓展板PCB 智能小车底板PCB Arduino UNO原理图 Arduino UNO PCB 程序部分 void Robot_Traction() //机器人循迹子程序{//有信号为LOW 没有信号为HIGHSR digitalRead(SensorRight);//有信号表明在白…

外部ADC之AD7949——14bit、8通道、250k

前言 在实际项目中&#xff0c;仅靠单片机内部的ADC采样&#xff0c;很有可能达不到实际采样精度&#xff0c;这个时候就需要外接外部ADC芯片进行采样&#xff0c;这些外部ADC一般都是SPI接口或者是并口。 单片机通过SPI接口或并口读写芯片内部寄存器&#xff0c;配置参考极性…

2023 年全国职业院校技能大赛(高职组) “云计算应用”赛项赛卷 B部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】第一场&#xff1a;模块一 私有云、模块二 容器云【任务 1】私有云服务搭建[5 分]【题目 1】1.1.1 基础环境配置[0.2 分]【题目 2】1.1.2 Yum 源配置[0.2 分]【题目 3】1.1.3 配置无秘钥 ssh[0.2 分]【题…

javacv和opencv对图文视频编辑-裸眼3D图片制作

通过斗鸡眼&#xff0c;将左右两张相似的图片叠加到一起看&#xff0c;就会有3D效果。 3D图片&#xff0c;3D眼镜&#xff0c;3D视频等原理类似&#xff0c;都是通过两眼视觉差引起脑补产生3D效果。 图片&#xff1a; 图片来源&#xff1a; 一些我拍摄的真*裸眼3D照片 - 哔哩…

[Docker] 基本名词

镜像(iamge)&#xff1a; Docker 镜像就好比是一个模板&#xff0c;可以通过这个模板来创建容器服务&#xff0c; 容器&#xff08;container&#xff09;: Docker利用容器技术&#xff0c;独立运行一个或则多个应用&#xff0c;通过镜像来创建的。 启动&#xff0c;停止&a…

LeetCode-1672/1572/54/73

1.最富有客户的资产总量&#xff08;1672&#xff09; 题目描述&#xff1a; 给你一个 m x n 的整数网格 accounts &#xff0c;其中 accounts[i][j] 是第 i​​​​​​​​​​​​ 位客户在第 j 家银行托管的资产数量。返回最富有客户所拥有的 资产总量 。 客户的 资产总…

鸿蒙Harmony-线性布局(Row/Column)详解

人生的下半场&#xff0c;做个简单的人&#xff0c;少与人纠缠&#xff0c;多看大自然&#xff0c;在路上见世界&#xff0c;在途中寻自己。往后余生唯愿开心健康&#xff0c;至于其他&#xff0c;随缘就好&#xff01; 目录 一&#xff0c;定义 二&#xff0c;基本概念 三&am…

Linux实操学习

Linux常用操作 一、帮助命令1. man1.1 基本语法1.2 快捷键1.3 注意事项 2. help2.1 基本语法2.2 注意事项 3. 常用快捷键 二、文件目录类1. 常规操作1.1 pwd1.2 cd1.3 ls 2. 文件夹操作2.1 mkdir2.2 rmdir 3. 文件操作3.1 touch3.2 cp3.3 rm3.4 mv 4. 文件查看4.1 cat4.2 more4…

浏览器进程模型和JS的事件循环

一、浏览器的进程模型 1、什么是进程&#xff1f; 程序运行所需要的专属内存空间 2、什么是线程&#xff1f; ​​​​​运行​代码的称为线程&#xff08;同一个进程中的线程共享进程的资源&#xff09; ⼀个进程⾄少有⼀个线程&#xff0c;所以在进程开启后会⾃动创建⼀个线…

软件测试|Pydantic BaseModel使用详解

简介 当我们在Python中编写应用程序时&#xff0c;通常需要处理和验证数据。Pydantic 是一个流行的库&#xff0c;它可以帮助我们定义数据模型并自动进行数据验证。在Pydantic中&#xff0c;BaseModel是一个核心概念&#xff0c;它用于定义数据模型和验证输入数据。在这篇文章…