二叉平衡树(AVL树)Java语言实现

news2025/5/24 20:22:57

一、二叉平衡树

上一章所示的同一组元素按照不同顺序插入到二叉排序树中可能会产生两种形状不同的二叉排序树。
在这里插入图片描述
当出现右边的情况时,树的高度过高,如果要查找值为“70”的节点需要查找7次,其查找次数已经接近于链表了,这样会导致查找效率过低。为了防止右图这种较为极端的情况出现,一种新的二叉树-二叉平衡树AVL树)被提出了。

AVL树得名于它的发明者格奥尔吉·阿杰尔松-韦利斯基(Adelson-Velsky)和叶夫根尼·兰迪斯(E.M. Landis),是他们名字的缩写。Adelson-Velsky和E.M. Landis在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

介绍这一数据结构之前,首先介绍一下平衡因子的概念:
平衡因子=左子树高-右子树高。

AVL树本质上是一棵空树或左右两个子树的高度差的绝对值不超过1的二叉查找树,同时它的左右两个子树也都是一棵平衡二叉树。

查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(log n)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。

其特点如下:
1.它和它的子树必须是一棵空树或左右两个子树的高度差的绝对值不超过1,即平衡因子的绝对值小于等于1。平衡二叉树结点的平衡因子的值只可能是−1、0或1。
2.它必须是一颗二叉查找树
在这里插入图片描述
由平衡二叉树的特点可以得到,上图中左侧为平衡二叉树,右侧不是平衡二叉树。

二、二叉平衡树的操作

1.查找

可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。

    // 查找与val数值相等的节点
    public TreeNode get(int val) {
    	return get(root, val);
    }
    public TreeNode get(TreeNode currentNode, int val) {
    	TreeNode findTreeNode = new TreeNode();
    	while (currentNode != null) {
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
			} else if (currentNode.val == val)  {
				findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。
				System.out.println(findTreeNode.val + " has been found!");
				return findTreeNode;
			}
    	}
    	System.out.println("Error! " + val + " does not exist!");
    	return null;
    }

2.插入

二叉平衡树的插入可以像二叉查找树一样的进行,但是插入新节点后新的二叉树可能会失去平衡。如下图所示,插入节点67后,二叉树进入失衡状态。如何继续保持平衡?
在这里插入图片描述
想要对插入后的二叉树进行平衡,首先要了解以下概念:

最小不平衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过1的结点为根的子树称为最小不平衡子树。
在上图中,以70节点作为根节点的子树,即为最小不平衡子树。

在插入操作中,只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡。如下所示:
在这里插入图片描述

	public void insert(int val) {
	    root = insert(root, val);
	}
	// 将结点插入到AVL树中,并返回根节点
	private TreeNode insert(TreeNode tree, int val) {
		if (tree == null) {
	        // 新建节点
	        tree = new TreeNode(val);
	        if (tree==null) {
	            System.out.println("ERROR: create avltree node failed!");
	            return null;
	        }
		} else {
	
			if (val< tree.val) {    // 应该将key插入到"tree的左子树"的情况
	        tree.left = insert(tree.left, val);
	        // 插入节点后,若AVL树失去平衡,则进行相应的调节。
	        if (height(tree.left) - height(tree.right) == 2) {
	            if (val < tree.left.val)
	                tree = leftLeftRotation(tree);
	            else
	                tree = leftRightRotation(tree);
	        }
	        } else if (val > tree.val) {    // 应该将key插入到"tree的右子树"的情况
	            tree.right = insert(tree.right, val);
	            // 插入节点后,若AVL树失去平衡,则进行相应的调节。
	            if (height(tree.right) - height(tree.left) == 2) {
	                if (val > tree.right.val)
	                    tree = rightRightRotation(tree);
	                else
	                    tree = rightLeftRotation(tree);
	            }
	        } else {    // val == tree.val
	            System.out.println("添加失败: 不允许添加相同的节点!");
	        }
	    }
	    tree.height = Math.max( height(tree.left), height(tree.right)) + 1;
	    return tree;
	}

3.调整最小不平衡子树

调整最小不平衡子树即可使二叉树恢复平衡,可以通过四种旋转操作调整不平衡子树。

1)LL平衡旋转(右单旋转)

LL 是指在节点A 孩子(L)的子树(L)插入了新节点,导致A节点失衡的节点是A节点的子树的子树。

A的平衡因子由1增至2,导致以A为根的子树失去平衡,对于 LL 型,需要执行右旋转操作。

将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

在这里插入图片描述
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。

	// 左旋转
	public TreeNode leftRotation(TreeNode k2) {
		TreeNode k1;

	    k1 = k2.left;
	    k2.left = k1.right;
	    k1.right = k2;

	    k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
	    k1.height = Math.max(height(k1.left), k2.height) + 1;

	    return k1;
	}
	// LL: 左左对应的情况(左单旋转)。
	public TreeNode leftLeftRotation(TreeNode k1) {
	    return leftRotation(k1);
	}
2)RR平衡旋转(左单旋转)

RR是指在节点A 孩子(R)的子树(R)插入了新节点,导致A节点失衡的节点是A节点的子树的子树。

A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,对于 RR 型,需要执行左旋转操作。

将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
在这里插入图片描述
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。

	// 右旋转
	public TreeNode rightRotation(TreeNode k1) {
		TreeNode k2;

	    k2 = k1.right;
	    k1.right = k2.left;
	    k2.left = k1;

	    k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
	    k2.height = Math.max(height(k2.right), k1.height) + 1;

	    return k2;
	}

	// RR: 右右对应的情况(右单旋转)。
	public TreeNode rightRightRotation(TreeNode k1) {
	    return rightRotation(k1);
	}
3)LR平衡旋转(先左后右旋转)

LR 是指在节点A 孩子(L)的子树(R)插入了新节点,导致A节点失衡的节点是A节点的子树的子树。

A的平衡因子由1增至2,导致以A为根的子树失去平衡,对于 LR 型,需要先执行左旋转再执行右旋转操作。

先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
在这里插入图片描述
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。

两次旋转的拆解过程如下:
在这里插入图片描述

	// 左旋转
	public TreeNode leftRotation(TreeNode k2) {
		TreeNode k1;

	    k1 = k2.left;
	    k2.left = k1.right;
	    k1.right = k2;

	    k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
	    k1.height = Math.max(height(k1.left), k2.height) + 1;

	    return k1;
	}
	// 右旋转
	public TreeNode rightRotation(TreeNode k1) {
		TreeNode k2;

	    k2 = k1.right;
	    k1.right = k2.left;
	    k2.left = k1;

	    k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
	    k2.height = Math.max(height(k2.right), k1.height) + 1;

	    return k2;
	}
	// LR: 左右对应的情况(左双旋转)。
	public TreeNode leftRightRotation(TreeNode k1) {
		k1.left = rightRotation(k1.left);
	    return leftRotation(k1);
	}
4)RL平衡旋转(先右后左旋转)

RL 是指在节点A 孩子(R)的子树(L)插入了新节点,导致A节点失衡的节点是A节点的子树的子树。

A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,对于RL 型,需要先执行右旋转再执行左旋转操作。
先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
在这里插入图片描述
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。

两次旋转的拆解过程如下:
在这里插入图片描述

	// 左旋转
	public TreeNode leftRotation(TreeNode k2) {
		TreeNode k1;

	    k1 = k2.left;
	    k2.left = k1.right;
	    k1.right = k2;

	    k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
	    k1.height = Math.max(height(k1.left), k2.height) + 1;

	    return k1;
	}
	// 右旋转
	public TreeNode rightRotation(TreeNode k1) {
		TreeNode k2;

	    k2 = k1.right;
	    k1.right = k2.left;
	    k2.left = k1;

	    k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
	    k2.height = Math.max(height(k2.right), k1.height) + 1;

	    return k2;
	}
	// RL: 右左对应的情况(右双旋转)。
	public TreeNode rightLeftRotation(TreeNode k1) {
		k1.right = leftRotation(k1.right);
	    return rightRotation(k1);
	}

4.删除

从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。

    // 删除结点(z),返回根节点 
    public void remove(int val) {
	    TreeNode z; 
	    if ((z = get(root, val)) != null)
	        root = remove(root, z);
    }
    private TreeNode remove(TreeNode tree, TreeNode z) {
           // 根为空 或者 没有要删除的节点,直接返回null。
        if (tree==null || z==null)
            return null;

        if (z.val < tree.val) {        // 待删除的节点在"tree的左子树"中
            tree.left = remove(tree.left, z);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(tree.right) - height(tree.left) == 2) {
                TreeNode r =  tree.right;
                if (height(r.left) > height(r.right))
                    tree = rightLeftRotation(tree);
                else
                    tree = rightRightRotation(tree);
            }
        } else if (z.val > tree.val) {    // 待删除的节点在"tree的右子树"中
            tree.right = remove(tree.right, z);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(tree.left) - height(tree.right) == 2) {
                TreeNode l =  tree.left;
                if (height(l.right) > height(l.left))
                    tree = leftRightRotation(tree);
                else
                    tree = leftLeftRotation(tree);
            }
        } else {    // tree是对应要删除的节点。
            // tree的左右孩子都非空
            if ((tree.left!=null) && (tree.right!=null)) {
                if (height(tree.left) > height(tree.right)) {
                    // 如果tree的左子树比右子树高;
                    // 则(01)找出tree的左子树中的最大节点(直接前驱)
                    //   (02)将该最大节点的值赋值给tree。
                    //   (03)删除该最大节点。
                    // 这类似于用"tree的左子树中最大节点"做"tree"的替身;
                    TreeNode max = getMaxNode(tree.left);
                    tree.val = max.val;
                    tree.left = remove(tree.left, max);
                } else {
                    // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)
                    // 则(01)找出tree的右子树中的最小节点(直接后继)
                    //   (02)将该最小节点的值赋值给tree。
                    //   (03)删除该最小节点。
                    // 这类似于用"tree的右子树中最小节点"做"tree"的替身;
                    TreeNode min = getMinNode(tree.right);
                    tree.val = min.val;
                    tree.right = remove(tree.right, min);
                }
            } else {
                TreeNode tmp = tree;
                tree = (tree.left!=null) ? tree.left : tree.right;
                tmp = null;
            }
        }
        return tree;
    }
    // 查找最小结点: 返回tree为根结点的AVL树的最小结点。
    private TreeNode getMinNode(TreeNode tree) {
        if (tree == null)
            return null;
        while(tree.left != null)
            tree = tree.left;
        return tree;
    }
 
     // 查找最大结点: 返回tree为根结点的AVL树的最大结点。
    private TreeNode getMaxNode(TreeNode tree) {
        if (tree == null)
            return null;

        while(tree.right != null)
            tree = tree.right;
        return tree;
    }
    	

三、完整代码

package tree;

import java.util.LinkedList;
import java.util.Queue;

public class AVLTree {
	TreeNode root; // 新建根节点
	public class TreeNode {
		int val;
		TreeNode left;
		TreeNode right;
		TreeNode() {}
		int height;
		TreeNode(int val) {
			this.val = val;
		}
		TreeNode(int val, TreeNode left, TreeNode right) {
			this.val = val;
			this.left = left;
			this.right = right;
		}
	}

	
	/*
	 * 获取树的高度
	 */
	private int height(TreeNode tree) {
	    if (tree != null)
	        return tree.height;

	    return 0;
	}
	
	/*
	  * 左旋转
	 *
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode leftRotation(TreeNode k2) {
		TreeNode k1;

	    k1 = k2.left;
	    k2.left = k1.right;
	    k1.right = k2;

	    k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
	    k1.height = Math.max(height(k1.left), k2.height) + 1;

	    return k1;
	}
	/*
	  * 右旋转
	 *
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode rightRotation(TreeNode k1) {
		TreeNode k2;

	    k2 = k1.right;
	    k1.right = k2.left;
	    k2.left = k1;

	    k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
	    k2.height = Math.max(height(k2.right), k1.height) + 1;

	    return k2;
	}
	
	/*
	 * LL: 左左对应的情况(左单旋转)。
	 *
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode leftLeftRotation(TreeNode k1) {
	    return leftRotation(k1);
	}
	/*
	 * RR: 右右对应的情况(右单旋转)。
	 *
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode rightRightRotation(TreeNode k1) {
	    return rightRotation(k1);
	}
	/*
	 * LR: 左右对应的情况(左双旋转)。
	 *
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode leftRightRotation(TreeNode k1) {
		k1.left = rightRotation(k1.left);
	    return leftRotation(k1);
	}
	/*
	 * RL: 右左对应的情况(右双旋转)。
	  * 返回值: 旋转后的根节点
	 */
	public TreeNode rightLeftRotation(TreeNode k1) {
		k1.right = leftRotation(k1.right);
	    return rightRotation(k1);
	}
	
	public void insert(int val) {
	    root = insert(root, val);
	}
	/* 
	 * 将结点插入到AVL树中,并返回根节点
	 *
	 * 参数说明: 
	*     tree AVL树的根结点
	*     key 插入的结点的键值
	* 返回值: 
	 *     根节点
	 */
	private TreeNode insert(TreeNode tree, int val) {
		if (tree == null) {
	        // 新建节点
	        tree = new TreeNode(val);
	        if (tree==null) {
	            System.out.println("ERROR: create avltree node failed!");
	            return null;
	        }
		} else {
	
			if (val< tree.val) {    // 应该将key插入到"tree的左子树"的情况
	        tree.left = insert(tree.left, val);
	        // 插入节点后,若AVL树失去平衡,则进行相应的调节。
	        if (height(tree.left) - height(tree.right) == 2) {
	            if (val < tree.left.val)
	                tree = leftLeftRotation(tree);
	            else
	                tree = leftRightRotation(tree);
	        }
	        } else if (val > tree.val) {    // 应该将key插入到"tree的右子树"的情况
	            tree.right = insert(tree.right, val);
	            // 插入节点后,若AVL树失去平衡,则进行相应的调节。
	            if (height(tree.right) - height(tree.left) == 2) {
	                if (val > tree.right.val)
	                    tree = rightRightRotation(tree);
	                else
	                    tree = rightLeftRotation(tree);
	            }
	        } else {    // val == tree.val
	            System.out.println("添加失败: 不允许添加相同的节点!");
	        }
	    }
	    tree.height = Math.max( height(tree.left), height(tree.right)) + 1;
	    return tree;
	}
	
	
    // 查找与val数值相等的节点
    public TreeNode get(int val) {
    	return get(root, val);
    }
    public TreeNode get(TreeNode currentNode, int val) {
    	TreeNode findTreeNode = new TreeNode();
    	while (currentNode != null) {
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
			} else if (currentNode.val == val)  {
				findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。
				System.out.println(findTreeNode.val + " has been found!");
				return findTreeNode;
			}
    	}
    	System.out.println("Error! " + val + " does not exist!");
    	return null;
    }

    
    public void remove(int val) {
        TreeNode z; 
        if ((z = get(root, val)) != null)
            root = remove(root, z);
    }
    /* 
         * 删除结点(z),返回根节点
     */
    private TreeNode remove(TreeNode tree, TreeNode z) {
        // 根为空 或者 没有要删除的节点,直接返回null。
        if (tree==null || z==null)
            return null;

        if (z.val < tree.val) {        // 待删除的节点在"tree的左子树"中
            tree.left = remove(tree.left, z);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(tree.right) - height(tree.left) == 2) {
                TreeNode r =  tree.right;
                if (height(r.left) > height(r.right))
                    tree = rightLeftRotation(tree);
                else
                    tree = rightRightRotation(tree);
            }
        } else if (z.val > tree.val) {    // 待删除的节点在"tree的右子树"中
            tree.right = remove(tree.right, z);
            // 删除节点后,若AVL树失去平衡,则进行相应的调节。
            if (height(tree.left) - height(tree.right) == 2) {
                TreeNode l =  tree.left;
                if (height(l.right) > height(l.left))
                    tree = leftRightRotation(tree);
                else
                    tree = leftLeftRotation(tree);
            }
        } else {    // tree是对应要删除的节点。
            // tree的左右孩子都非空
            if ((tree.left!=null) && (tree.right!=null)) {
                if (height(tree.left) > height(tree.right)) {
                    // 如果tree的左子树比右子树高;
                    // 则(01)找出tree的左子树中的最大节点
                    //   (02)将该最大节点的值赋值给tree。
                    //   (03)删除该最大节点。
                    // 这类似于用"tree的左子树中最大节点"做"tree"的替身;
                    // 采用这种方式的好处是: 删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。
                    TreeNode max = getMaxNode(tree.left);
                    tree.val = max.val;
                    tree.left = remove(tree.left, max);
                } else {
                    // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)
                    // 则(01)找出tree的右子树中的最小节点
                    //   (02)将该最小节点的值赋值给tree。
                    //   (03)删除该最小节点。
                    // 这类似于用"tree的右子树中最小节点"做"tree"的替身;
                    // 采用这种方式的好处是: 删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。
                    TreeNode min = getMinNode(tree.right);
                    tree.val = min.val;
                    tree.right = remove(tree.right, min);
                }
            } else {
                TreeNode tmp = tree;
                tree = (tree.left!=null) ? tree.left : tree.right;
                tmp = null;
            }
        }
        return tree;
    }

    // 查找最小结点: 返回tree为根结点的AVL树的最小结点。
    private TreeNode getMinNode(TreeNode tree) {
        if (tree == null)
            return null;
        while(tree.left != null)
            tree = tree.left;
        return tree;
    }
 
     // 查找最大结点: 返回tree为根结点的AVL树的最大结点。
    private TreeNode getMaxNode(TreeNode tree) {
        if (tree == null)
            return null;

        while(tree.right != null)
            tree = tree.right;
        return tree;
    }
    	
	// 树的先序遍历
	public void preOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		System.out.print(root.val + " "); //先输出当前节点(初始的时候是root节点)
		preOrderTraversal(root.left); // 如果左子节点不为空,则递归继续前序遍历
		preOrderTraversal(root.right); // 如果右子节点不为空,则递归继续前序遍历
	}
	
	// 树的中序遍历
	public void inOrderTraversa (TreeNode root) {
		if (root == null) {
			return;
		}
		inOrderTraversa(root.left); // 如果当前节点的左子节点不为空,则递归中序遍历
		System.out.print(root.val + " "); // 输出当前节点
		inOrderTraversa(root.right); // 如果当前的右子节点不为空,则递归中序遍历		
	}
	
	// 树的后序遍历
	public void postOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		postOrderTraversal(root.left); // 如果当前节点的左子节点不为空,则递归后序遍历
		postOrderTraversal(root.right); // 如果当前节点的右子节点不为空,则递归后序遍历
		System.out.print(root.val + " "); // 输出当前节点		
	}

    // 广度优先遍历,即树的层次遍历,借用队列实现
    public void levelOrderTraversal(TreeNode root) {
    	if(root == null) {
    		return;
    	}    	
    	Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放每层操作的根节点
        queue.offer(root);        
        while (!queue.isEmpty()) {
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) { // 用for循换可以隔离开每一层的遍历
            	TreeNode rootNode = queue.poll(); // 开始操作后将其从队列移除
            	System.out.print(rootNode.val + " ");
                if (rootNode.left != null) {
    	            TreeNode leftNode = rootNode.left; // 左节点存入队列,下一层遍历它就成了新根节点	            
    	            queue.offer(leftNode);
                }
                if (rootNode.right != null) {
                	TreeNode rightNode = rootNode.right; // 右节点存入队列,下一层遍历它就成了新根节点
                	queue.offer(rightNode);
                }
            }
        }
    }
    public static void main(String[] args) {
		AVLTree avlTree = new AVLTree();
		int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};
		for (int i = 0; i < arr.length; i++) {
			avlTree.insert(arr[i]);
		}
		avlTree.preOrderTraversal(avlTree.root);
		System.out.println();
		avlTree.inOrderTraversa(avlTree.root);
		System.out.println();
		avlTree.postOrderTraversal(avlTree.root);
		System.out.println();
		avlTree.levelOrderTraversal(avlTree.root);
		System.out.println();
		avlTree.get(3);
		System.out.println();
		avlTree.remove(1);
		System.out.println();
		avlTree.inOrderTraversa(avlTree.root);
		
    }
}

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

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

相关文章

短短几日连发数案,艺术家Jennifer Le Feuvre插画版权维权

案件基本情况起诉时间&#xff1a;2024-10-7、2024-10-8、2024-10-9案件号&#xff1a;24-cv-09629、24-cv-09636、24-cv-09640、24-cv-09688、24-cv-09697、24-cv-09709、24-cv-09712、24-cv-09757、24-cv-09775、24-cv-09794原告&#xff1a;Jennifer Le Feuvre原告律所&…

【安装教程】Windows10环境下Pytorch(GPU版)的安装与配置

目录 Pytorch的概念安装前要求一、NVIDIA驱动查看二、Anaconda的安装2.1 Anaconda的安装2.2 创建虚拟环境2.3 激活虚拟环境 三、CUDA ToolKit的安装&#xff08;选做&#xff0c;CPU版本可跳过&#xff09;3.1 CUDA安装包的下载&#xff08;以CUDA11.6.0为例&#xff09;3.2 CU…

架构设计笔记-19-大数据架构设计理论与实践

知识要点 案例分析 1.Lambda架构优缺点 2.web架构设计 3.web系统架构设计相关技术 论文

面向对象的继承性

目录 1、继承的概念 2、方法的重写 3、重载和重写的区别 4、super关键字 5、this和super的区别 6、final关键字 7、抽象类 8、接口 1、继承的概念 在程序中&#xff0c;继承描述的是事物之间的所属关系&#xff0c;通过继承可以使很多事物之间形成一种关系体系。 在Java…

51单片机的智能空调【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器继电器按键等模块构成。适用于空调温度控制等相似项目。 可实现功能: 1、LCD1602实时显示室内温湿度、运行模式、设定温度和定时时间 2、首先选择空调的运行模式&#xff0c;如加热、制冷、除湿&…

@RequestMapping对不同参数的接收方式

1、简单参数 1、参数名与形参变量名相同&#xff0c;定义形参即可接收参数&#xff0c;且会自动进行类型转换。 RequestMapping("/simple")public String simpleParam(String name,int age){String username name;int userAge age;System.out.println(username&…

芝法酱学习笔记(0.7)——harbor与SpringBoot容器化docker部署

前言 之前我们主要讲的jar包部署。使用jar包部署可能导致不同服务互相争抢资源&#xff08;隔离性&#xff09;&#xff0c;不同服务可能需要不同的jdk环境&#xff0c;有时也会造成困扰。故在微服务时代&#xff0c;我们通常使用docker部署 一、docker安装 docke相关的知识…

Dev-C++萌新学习福利3

朝鲜球作品原创https://blog.csdn.net/2401_86502594?spm1011.2124.3001.5343 清北互联地址https://www.17ac.cn/#/ 萌新福利 作品成本6999元&#xff01;&#xff01;&#xff01; 清北互联团队编写课程&#xff0c;本人不收费。亏本买卖&#xff0c;良心服务&#xff0c;同嫂…

ASP.NET Core8.0学习笔记(二十)——EFCore导航属性与外键

一、什么是实体间关系 数据库表&#xff08;实体&#xff09;之间的关系&#xff1a;一对一&#xff08;学生-成绩&#xff09;、一对多&#xff08;学生-科目&#xff09;、多对多&#xff08;教师-班级&#xff09;。数据库中&#xff0c;每一个实体可以由主键唯一标识&…

自学1个月拿金奖!北交大学子分享昇腾AI原生创新算子挑战赛金奖之路

近年来在人工智能领域&#xff0c;算子开发的价值日益凸显&#xff0c;算子开发也受到越来越多年轻开发者的青睐。对于高校开发者&#xff0c;如何从零开始学习算子开发&#xff0c;提升软硬结合的AI开发能力&#xff1f;成功已举办两个赛季的昇腾AI原生创新算子挑战赛&#xf…

IDEA中的快捷键大全--超详细

目录 一、通用类型 1.1 图示 1.2 表格化 二、编写速度提升 2.1 图示 2.1.1 表格化 2.2 图示 2.2.1 表格化: 三、类结构,查找和查看源码 3.1 图示 3.2 表格化 四、查找,替换和关闭 4.1图示 4.2 表格化 五、调整格式 5.1 图示 5.2 表格化 六、快捷键的自主定义…

docker login 命令登录harbor镜像仓库(含报错)

作者&#xff1a;程序那点事儿 日期&#xff1a;2024/02/02 14:10 执行登录命令&#xff1a;docker login -uadmin 192.168.43.106:8880 报错&#xff1a; Error response from daemon: Get "https://192.168.43.106:8880/v2/": http: server gave HTTP response t…

计组-CPU构成(运算器与控制器的组成)

整个计算机&#xff0c;由主机和外设2部分构成 计算机结构中&#xff1a; 主机&#xff08;这里的主机包含的部分远比我们主机箱里的部件要少&#xff09;&#xff1a;只包括2大部件&#xff0c;一个是CPU&#xff0c;一个是主存储器&#xff08;即我们平时说的内存&#xff…

专题1:方向导数与梯度

一、回忆偏导数 多元函数&#xff08;比如有x、y两个变量&#xff09;在某个点有两个偏导数&#xff0c;一个是关于x的偏导数&#xff0c;一个是关于y的偏导数。如下所示&#xff1a; 所谓偏导数&#xff0c;其实就是某点处函数在x的正方向或y的正方向上的变化率。从图像上来看…

JavaSE--全盘拿下数组的关键要领

嗨嗨大家~我来啦&#xff01;今天我们来进入数组的学习吧。 目录 一 数组的定义 1 创建数组 2 初始化数组 二 数组的使用 1 数组的访问 2 数组的遍历 2.1 for 循环打印 2.2 for-each 打印数组 三 数组是引用类型 3.1 JVM内存分布 3.2 区分基本类型与引用类型变…

线程相关知识点

一、线程 1.1 线程的概念 线程是轻量级的进程。 进程是分配资源的最小单位&#xff0c;线程是调度的最小单位。 线程不会单独分配内存空间&#xff0c;线程共用进程的资源。 线程之间通信比较方便&#xff0c;但是不安全。 多线程没有多进程安全。 多线程效率比较高。线程创建…

嵌入式学习-I/O-Day01

嵌入式学习-I/O-Day01 IO介绍 IO分类 文件IO 标准IO 标准IO的调用逻辑 标准IO缓存机制 标准IO的特点 * 流 定义 流的分类 流指针FILE * 缓存区的分类 * 全缓存—》基于文件 行缓存-》基于终端stdin/stdout 不缓存&#xff1a;stderr 标准IO的函数接口 ​​​​…

小白都来用这款AI绘画神器,IDEOGRAM2.0,轻松画出高质量图片

大家好&#xff01;我是宇航&#xff0c;一位喜欢AI绘画的10年技术专家&#xff0c;专注于输出AI绘画与视频内容 今天给大家介绍一款绝对的生图神器——Ideogram2.0! 不论你是AI小白&#xff0c;手残党还是资深玩家&#xff0c;无论你是做网页设计&#xff0c;电商&#xff0c…

React路由 基本使用 嵌套路由 动态路由 获取路由参数 异步路由 根据配置文件来生成路由

文章目录 React-router的三个版本react-router使用嵌套路由动态路由 获取路由参数Params参数Query参数Location信息 控制跳转地址异步路由根据配置文件生成路由 React-router的三个版本 React-router 服务端渲染使用React-router-dom 浏览器端渲染使用React-router-native Rea…

【STM32CubeMX开发】-2.2-TIM_输出一个PWM信号

目录 1 Tim定时器的时钟源 2 Tim定时器的配置 2.1 PWM配置 2.2 中断配置 3 生成代码 4 测试结果 结尾 1 Tim定时器的时钟源 TIM3的时钟来源自APB1 Timer clocks&#xff0c;时钟树上所有总线频率均设置为了STM32F0能达到的最高频率&#xff0c;此时APB1 Timer clocks …