数据结构与算法04二叉树|二叉排序树|AVL树

news2024/9/23 1:34:14

目录

一、二叉树(binary tree)

1、二叉树常见术语

2、二叉树常用的操作

2.1、初始化:与链表十分相似,先创建节点,然后构造引用/指针关系.

2.2、插入和删除操作

 3、常见二叉树类型

3.1、满二叉树

 3.2、完全二叉树(complete binary tree)

3.3、完满二叉树(full binary tree)

 3.4平衡二叉树

 4、二叉树的退化

 5、二叉树的遍历

5.1层序遍历

5.1.1代码实现

5.2前序遍历

5.3中序遍历

5.4后序遍历

6、二叉树的数组表示

6.1用数组表示完美二叉树

6.2表示任意的二叉树

6.3代码

二、二叉搜索树

 二叉搜索树的操作

1、查找节点

2、插入结点

3、删除节点

中序遍历有序

二叉搜索树的效率问题 

三、AVL树

 1、AVL(Adelson-Velskii 和Landis)树的常见术语

1.1节点高度

1.2节点平衡因子

2、AVL树旋转

2.1右旋

第一种情况,当节点child无右子节点(记为grand_child)时,

 第二种情况,当child节点有右子节点时,

2.2左旋操作

第一种情况,不平衡节点的child节点无左子节点

第二种情况,child节点有左子节点时,

 3、先左旋后右旋

4、先右旋后左旋

 5、旋转的选择

 6、AVL树的常见操作

6.1插入结点

6.2删除节点


在这里,主要介绍一下二叉树相关的知识。

一、二叉树(binary tree)

二叉树是一种非线性数据结构,代表祖先与后代之间的派生关系,主要体现了一分为二的分治思想。与链表类似,二叉树的基本单元是节点,每个节点包含值、左、右子节点引用。

class TreeNode:
    """二叉树节点类"""
    def __init__(self,val:int):
        self.val:int = val
        self.left:TreeNode|None = None
        self.right:TreeNode|None = None

每个节点都对应有两个引用,分别指向其左子节点(left-child node)右子节点(right-child node),该节点就称为这两个子节点的父节点

在二叉树中,除了叶节点以外,其他所有节点都包含子节点和非空子树。如图1所示,若节点1为父节点,则节点2和3分别对应为节点1的左子节点和右子节点,又分别以节点2和节点3为根节点构成左右子树。

图1

1、二叉树常见术语

  • 根节点(root node):位于二叉树顶层的节点,没有父节点。
  • 叶节点(leaf node):没有子节点的节点,其两个指针均指向 None 。
  • 边(edge):连接两个节点的线段,即节点引用(指针)。
  • 节点所在的层(level):从顶至底递增,根节点所在层为 1 。
  • 节点的度(degree):节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
  • 二叉树的高度(height):从根节点到最远叶节点所经过的边的数量。
  • 节点的深度(depth):从根节点到该节点所经过的边的数量。
  • 节点的高度(height):从距离该节点最远的叶节点到该节点所经过的边的数量。
图2

 注意,通常来讲,高度和宽度都是指从根节点开始到目标节点经过的边的个数,也有其他教材把高度和深度定义为讲过的节点的个数,那样的话,对应的深度和高度都需要加1操作.

2、二叉树常用的操作

2.1、初始化:与链表十分相似,先创建节点,然后构造引用/指针关系.
n1 = TreeNode(1)
n2 = TreeNode(2)
n3 = TreeNode(3)
n4 = TreeNode(4)
n5 = TreeNode(5)

n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
2.2、插入和删除操作

与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现,具体如图所示.

# 插入操作
p = TreeNode(0)
n1.left = p
p.left = n2

# 删除操作
n1.left = n2
图3

 3、常见二叉树类型

3.1、满二叉树

又称为完美二叉树,所有层的节点都被完全填满.叶节点的度为0,其余节点的度均为2;若树的高度为h,则节点总数为2^{h+1}-1,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象.

 3.2、完全二叉树(complete binary tree)

完全二叉树只有最底层的节点未被填满,且最底层的节点尽量靠左填充.

3.3、完满二叉树(full binary tree)

 完满二叉树除了叶节点以外,其余所有节点都有两个子节点.

 3.4平衡二叉树

平衡二叉树(balanced binary tree)中任意节点的左子树和右子树的高度之差的绝对值不超过 1 .

 4、二叉树的退化

当二叉树的每层节点都被填满时,达到完美二叉树;而当所有节点都偏向一侧时,二叉树退化为链表.

  • 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
  • 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 O(n).

 5、二叉树的遍历

5.1层序遍历

层序遍历(level-order traversal)指的是从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点.层序遍历本质上是属于广度优先遍历(breadth-first traversal),也成为广度优先搜索(breadth-first search,BFS),它体现了是一圈一圈向外扩展.

5.1.1代码实现

广度优先遍历通常借助duilie来实现,队列遵循先进先出的规则,而广度优先遍历则遵循逐层推进的规则,两者背后的思想是一致的,故实现代码如下:

from collections import deque
def level_order(root:TreeNode|None)->list[int]:
    """层序遍历"""
    queue:deque[TreeNode] = deque()
    queue.append(root)
    result = []
    while queue:
        node = queue.popleft()
        result.append(node.val)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    return result # [1, 2, 3, 4, 5]
5.2前序遍历

相应地,前、中、后序遍历均属于深度优先遍历(depth-first traversal),也称为深度优先搜索(depth-first search,DFS),它体现了一种先走到尽头,再回溯继续的遍历方式.

递则是向下递推;归则是向上回溯.

result = []
def pre_order(root:TreeNode|None)->list[int]|None:
    """先序遍历:根->左->右"""
    if root is None:
        return
    result.append(root.val)
    pre_order(root.left)
    pre_order(root.right)
    return result # [1, 2, 4, 5, 3]
5.3中序遍历
result1 = []
def in_order(root:TreeNode|None)->list[int]|None:
    """中序遍历:左->中->右"""
    if root is None:
        return
    in_order(root.left)
    result1.append(root.val)
    in_order(root.right)
    return result1 # [4, 2, 5, 1, 3]
5.4后序遍历
result2 = []
def post_order(root:TreeNode|None)->list[int]|None:
    """后序遍历:左->右->根"""
    if root is None:
        return
    post_order(root.left)
    post_order(root.right)
    result2.append(root.val)
    return result2 # [4, 5, 2, 3, 1]

6、二叉树的数组表示

在链表表示下,二叉树的存储单元为节点TreeNode且节点之间通过指针相连接。

6.1用数组表示完美二叉树

给定一颗完美二叉树,将所有结点按照层序遍历的顺序存储在一个数组中,则每个节点都对应有一个唯一的数组索引,根据层序遍历的特点,可以推导出父节点和子结点之间的映射公式;若某节点的索引为i,则该节点的左子节点对应的索引为2*i + 1,右子节点对应的索引为2*i + 2。

图6.1

 如图所示,若这样存储给定任意一个节点,都能通过映射公式找到其对应的左子节点和右子节点。

6.2表示任意的二叉树

完美二叉树/完全二叉树是一个特例,在正常的二叉树中,中间层往往会有许多None,但由于层序遍历并不包含这些None,因此无法仅凭序列来推测None的数量和分布位置。这也就意味着存在多种二叉树结构都符合该层的遍历序列。

图6.2.1

为了解决此问题,可以考虑在层序遍历序列中显示的写出所有的None。如图所示,经这样处理后,层序遍历就能唯一的表示一颗二叉树了。

图6.2.2

 对于完全二叉树,None节点一定都是位于最后一层的最右边的位置,即None一定会出现在层序遍历的末尾,因此在使用数组表示完全二叉树时可以省略所有的None,这样就会非常方便。

图6.2.3

 即如图6.2.2中的树用数组可以表示为tree = [1, 2, 3, 4, None, 5, 6, 7, 8, None, None, 12, None, None, 14],图6.2.3中的树用数组就可以表示为tree = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],后面的所有NOne就可以不写。

6.3代码
class ArrayBinaryTree:
    """数组表示下的二叉树类"""
    def __init__(self,arr:list[int|None]):
        self._tree = list(arr)

    def size(self):
        return len(self._tree)

    def search(self,i:int)-> int|None:
        """查找指定索引对应的二叉树结点的值"""
        if i < 0 or i >= self.size():
            return None
        return self._tree[i]

    def left(self,i:int)->int|None:
        """返回对应的节点i的左子节点"""
        return 2 * i + 1

    def right(self,i:int)->int|None:
        """返回对应节点i的右子节点"""
        return 2 * i + 2

    def parent(self,i:int)->int|None:
        """返回对应节点为i的父节点的索引"""
        return i // 2 # 稍微有点疑问

    def level_order(self)->list[int]:
        """层序遍历"""
        self.res = []
        for i in range(self.size()):
            if self.search(i) is not None:
                self.res.append(self.search(i))
        return self.res

    def dfs(self,i:int,order:str):
        """深度优先遍历"""
        if self.search(i) is None:
            return
        if order == "pre":
            self.res.append(self.search(i))
        self.dfs(self.left(i),order)

        if order == "in":
            self.res.append(self.search(i))
        self.dfs(self.right(i),order)

        if order == "post":
            self.res.append(self.search(i))

    def pre_order(self)->list[int]:
        """前序遍历"""
        self.res = []
        self.dfs(0,order="pre")
        return self.res

    def in_order(self)->list[int]:
        self.res = []
        self.dfs(0,order="in")
        return self.res

    def post_order(self)->list[int]:
        self.res = []
        self.dfs(0,order="post")
        return self.res

if __name__ == "__main__":
    arr = [1,2,3,4,None,6,None]
    bt = ArrayBinaryTree(arr)
    a = bt.pre_order()
    b = bt.in_order()
    c = bt.post_order()
    print(f"前序遍历为{a}\n中序遍历为{b}\n后序遍历为{c}")
    # 前序遍历为[1, 2, 4, 3, 6]
    # 中序遍历为[4, 2, 1, 6, 3]
    # 后序遍历为[4, 2, 6, 3, 1]

二、二叉搜索树

二叉搜索树满足以下两个条件,

  • 1、对于根节点而言,左子树中所有节点的值<根节点的值<右子树中所有结点的值。
  • 2、任意的左右子树均为二叉排序树,同样满足条件1。

 二叉搜索树的操作

将二叉搜索树封装为一个类BinarySearchTree,并且声明一个成员变量root,指向树的根节点。

​
class TreeNode:
    """二叉树结点类"""
    def __init__(self,val:int):
        self.left:TreeNode|None = None
        self.right:TreeNode|None = None
        self.val = val
class BinarySearchTree:
    """二叉搜索树"""
    def __init__(self):
        # 初始化空树
        self._root = None

​
1、查找节点

与二分查找算法的工作原理相一致,都是每轮排除一般的情况,循环次数最多为二叉树的高度。

    def search(self,num:int)->TreeNode|None:
        """查找方法
        设置一个变量cur用来记录当前遍历到的每个节点,
        从二叉排序树的根节点出发循环比较当前节点值与查找的目标节点num之间的大小关系
        1、若num比当前节点cur.val小,则遍历左子树
        2、若num比当前节点cur.val大,则遍历右子树
        3、若num == cur,val,则说明找到目标节点,跳出循环并返回该节点。
        """
        cur = self._root
        while cur is not None:
            if num < cur.val:
                cur = cur.left
            elif num > cur.val:
                cur = cur.right
            else:
                break
        return cur
2、插入结点

    给定一个待插入的元素num,插入流程应该是按照先找到待插入位置(遍历至None的位置),然后将num元素封装为节点node,将该节点置于None的位置即可插入成功。

注意,由于二叉搜索树的定义,故二叉搜索树中不允许存在相同值即重复的节点,故在插入之前应该先循环遍历判断该元素是否已在二叉搜索树中,使用技巧pre是为了寻找到None节点的父节点,这样便可直接使用pre节点操作。

 def insert(self,num:int):
        """插入节点"""
        if self._root is None:
            self._root = TreeNode(num)
            return
        cur,pre = self._root,None
        while cur is not None:
            # 找到重复的节点直接返回
            if num == cur.val:
                return
            pre = cur
            if num < cur.val:
                cur = cur.left
            elif num > cur.val:
                cur = cur.right
        # 插入结点
        node = TreeNode(num)
        if num < pre.val:
            pre.left = node
        else:
            pre.right = node
3、删除节点

     与插入结点一样的是,删除某节点之后还需要继续保持二叉排序树的基本定义(left<root<right),但删除结点与插入结点不同的是,删除结点可以分为三种情况,下面分别对这三种情况进行讨论,

第一种,待删除的节点的度为0,也就是说该节点为叶子节点,则可以直接删除,因为即使删除叶子节点也不会破坏到二叉排序树的基本定义,

第二种,若带删除的节点的度为1,那么直接将叶子节点替换为该节点即可,原因就是 待删除节点为2,以元素2为根节点的树为以4为根节点的左子树,所以4对应的所有左子树的结点的值均小于4,故直接将3替换为4的左子节点即可删除元素2成功。

第三种, 也是最为复杂的一种,若待删除的节点的度为2,那么无法直接删除它,由于删除之后仍需要保持二叉排序树原本的性质,故需要用新的节点替换掉待删除结点,那么我们既可以用左子树的最大节点或右子树的最小节点来替换。

    def remove(self,num:int):
        """删除节点"""
        # 若树为空则直接结束
        if self._root is None:
            return
        cur,pre = self._root,None
        # 1、先找到待删除的节点
        while cur is not None:
            if num == cur.val:
                break
            pre = cur
            if num < cur.val:
                cur = cur.left
            else:
                cur = cur.right
        if cur is None:
            return
        # 2、判断待删除节点的分支个数
        if cur.left is None or cur.right is None:
            child = cur.left or cur.right

            # 删除节点
            if cur != self._root:
                if pre.left == cur:
                    pre.left = child
                else:
                    pre.right = child
            else:
                self._root = child
        else:
            # 这段是通过找当前节点的右子树中最小的元素代码
            tmp:TreeNode = cur.right
            while tmp.left is not None:
                tmp = tmp.left
            self.remove(tmp.val)
            cur.val = tmp.val
中序遍历有序

     二叉树的中序遍历遵循左->根->右的遍历顺序,而二叉搜索树又满足左子节点<根节点<右子节点的值的关系,这也就意味着在对二叉搜索树进行中序遍历时总会优先遍历到下一个最小的节点,故二叉搜索树的中序遍历是升序的。且使用二叉搜索树获取有序的数据仅需要O(n)时间,无需额外排序操作,非常高效。

二叉搜索树的效率问题 

     给定一组数据,我们考虑使用数组或二叉搜索树存储。观察表 7-2 ,二叉搜索树的各项操作的时间复杂度都是对数阶,具有稳定且高效的性能。只有在高频添加、低频查找删除数据的场景下,数组比二叉搜索树的效率更高。在理想情况下,二叉搜索树是“平衡”的,这样就可以在 log⁡ n轮循环内查找任意节点。然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为图 7-23 所示的链表,这时各种操作的时间复杂度也会退化为 O(n) 。

   

三、AVL树

下面先简单介绍一下,为什么要引入AVL树,

第一方面,在二叉搜索树章节中提到过,在多次插入和删除操作之后,二叉搜索树可能退化为链表。在这种情况下,所有操作的时间复杂度将从O(logn)劣化为O(n)。

 第二方面,在满二叉树中连续插入两个节点之后,可能会导致树严重向左倾斜,那么查找的时间复杂度也会随之恶化。

故在引入AVL之后,确保在持续增加和删除结点之后,AVL不会退化,从而使得各种操作的时间复杂度都会保持在O(log n)级别。

 1、AVL(Adelson-Velskii 和Landis)树的常见术语

    AVL树既是二叉搜索树,又是平衡二叉树,同时满足这两类二叉树的所有性质,因此是一种平衡二叉搜索树(balanced binary search tree)。   一颗AVL树是其每一个节点的左子树和右子树的高度最多差1的二叉查找树/排序树。

1.1节点高度

    由于AVL树的相关操作需要获取节点高度,因此我们为节点类添加height变量,需要获取它的值,并且要有更新AVL树高度的函数。

“节点的高度”指的是从该节点到距离它最远的叶子节点的边的数量。需要特殊指明的是,叶节点的高度为0,而空节点的高度为-1。

1.2节点平衡因子

    节点的平衡因子(balance factor)被定义为左子树的高度减去右子树的高度,同时规定空节点的平衡因子为0,若平衡因子为f,则有-1=<f<=1,获取平衡因子的功能封装为函数后,如下,

2、AVL树旋转

    AVL树的特点就在于其旋转操作,它能够在不影响二叉树的中序遍历的前提下,使得平衡节点重新恢复平衡。换句话说,旋转操作既能保持二叉搜索树的性质,也能使得树重新变为平衡二叉树。

若节点的平衡因子的绝对值|f|大于1,则称该节点为失衡节点。根据节点失衡情况的不同,旋转操作可以分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。

在这里,给出AVL树的节点类,获取/更新树的高度,平衡因子的方法。

class TreeNode:
    def __init__(self,val:int):
        self.val = val
        self.height:int = 0
        self.left:TreeNode|None = None
        self.right:TreeNode|None = None

def height(self,node:TreeNode|None)->int:
    """获取节点的高度"""
    if node is not None:
        return node.height
    return -1

def update_height(self,node:TreeNode|None):
    """更新节点高度"""
    # 结点的高度等于最高的子树高度加1
    node.height = max([self.height(node.left),self.height(node.right)]) + 1

def balance_factor(self,node:TreeNode|None)->int:
    if node is None:
        return 0
    # balance factor = 左子树高度 - 右子树高度
    return self.height(node.left) - self.height(node.right)
2.1右旋

如下图中所示,可以确定的是它是一棵二叉排序树,但并不能满足AVL的另一个条件,计算出各个节点的平衡因子之后,可以发现,只有值为3和4的节点不能满足平衡条件,从底向上看,二叉排序树中的首个失衡节点为3,因为值为4的节点虽然也为失衡节点,但是,它的不平衡归根结底也是由它的子树所决定的。所以,我们关注以节点3为根节点的子树,将该节点记为node,其左子节点记为child,子树右旋后达到平衡,并仍保持二叉排序树性质。

第一种情况,当节点child无右子节点时3为node节点,1为child节点,
右旋前
右旋后

 第二种情况,当child节点有右子节点时记为grand_child,

这种情况下,如图所示,只有值为5的节点失衡,这也仍然满足一颗二叉排序树的性质,故这种情况下该如何调整呢?

整体也是需要先右旋,然后把grand_child节点设置为原本node节点的左子节点即可。因为本来child子树就是node节点的左子树,其上面对应的所有节点肯定都是小于node节点对应的元素的值,所以在右旋把node节点所连接的节点转化为右子树之后,grand_child一定能成为node的左子节点。

总结一下,,向右旋转是一种形象化的说法,实际上也是需要修改节点的指针来实现,代码如下: 

def right_rotate(self,node:TreeNode|None)->TreeNode|None:
    """右旋操作"""
    child = node.left
    grand_child = child.right
    # 以child为原点,将node向右旋转
    child.right = node
    node.left = grand_child
    # 更新节点高度
    self.update_height(node)
    self.update_height(child)
    # 返回旋转后子树的根节点
    return child
2.2左旋操作
第一种情况,不平衡节点的child节点无左子节点

如图所示,从底向上看,首个发生不平衡的节点是4,进而导致节点1也不平衡,满足二叉排序树的性质。由于原本就是二叉排序树,故位于根节点1右子树上的节点值肯定是递增的,故只需要把4节点左旋,让节点5成为以1为根节点的右子树的根节点即可,这样既是二叉排序树,又保持住了平衡。

第二种情况,child节点有左子节点时,

 如图所示,满足二叉排序树但不平衡的二叉树的失衡节点为值为1的节点。当child节点有左子节点时(记为grand_child),只需要在左旋中添加一步,将grand_child作为node的右子节点即可,和右旋,child节点有右子树是一个道理的。

可以观察到,右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的。基于对称性,我们只需将右旋的实现代码中的所有的 left 替换为 right ,将所有的 right 替换为 left ,即可得到左旋的实现代码:

def left_rotate(self,node:TreeNode|None)->TreeNode|None:
    """左旋操作"""
    child = node.right
    grand_child = child.left
    # 以child为原点,将node向左旋转
    child.left = node
    node.right = grand_child
    # 更新节点高度
    self.update_height(node)
    self.update_height(child)
    # 返回旋转后子树的根节点
    return child
 3、先左旋后右旋

像这种情况,仅旋转一次(左旋或右旋)是不够的,都无法使得子树平衡,需要先对child执行左转,再对node执行右转即可平衡。

4、先右旋后左旋

像这种情况,则是需要先对child右旋,再对node执行左旋即可保持平衡。

 5、旋转的选择

 下面,我们通过判断失衡节点的平衡因子balance factor以及较高一侧子节点的平衡因子的正负号,来确定失衡节点究竟是属于下面图中的哪种情况。

def rotate(self,node:TreeNode|None)->TreeNode|None:
    """执行旋转操作,使得子树重新平衡"""
    balance_factor = self.balance_factor(node)
    # 左偏树
    if balance_factor > 1:
        if self.balance_factor(node.left) >= 0:
            # 右旋
            return self.right_rotate(node)
        else:
            # 先左旋后右旋
            node.left = self.left_rotate(node.left)
            return self.right_rotate(node)
    # 右偏树
    elif balance_factor < -1:
        if self.balance_factor(node.right) <= 0:
            # 左旋
            return self.left_rotate(node)
        else:
            # 先右旋后左旋
            node.right = self.right_rotate(node.right)
            return self.left_rotate(node)
    # 否则的话,平衡因子为1,则为平衡树,无需旋转
    return node
 6、AVL树的常见操作
6.1插入结点

AVL树的节点插入操作与二叉排序/搜索树在主体上类似,但区别是,按照二叉排序树插入结点之后从该节点到根节点的路径上可能会出现一些列失衡的节点,因此,我们需要从这个节点开始,自底向上执行旋转操作,使得所有失衡节点都能够恢复平衡,

def insert(self, val):
    """插入节点"""
    self._root = self.insert_helper(self._root, val)

def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:
    """递归插入节点(辅助方法)"""
    if node is None:
        return TreeNode(val)
    # 1. 查找插入位置并插入节点
    if val < node.val:
        node.left = self.insert_helper(node.left, val)
    elif val > node.val:
        node.right = self.insert_helper(node.right, val)
    else:
        # 重复节点不插入,直接返回
        return node
    # 更新节点高度
    self.update_height(node)
    # 2. 执行旋转操作,使该子树重新恢复平衡
    return self.rotate(node)
6.2删除节点

同样地,删除节点也是基于二叉排序树的基础上,需要从底至顶执行旋转操作,使得左右失衡的节点恢复平衡。

def remove(self, val: int):
    """删除节点"""
    self._root = self.remove_helper(self._root, val)

def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:
    """递归删除节点(辅助方法)"""
    if node is None:
        return None
    # 1. 查找节点并删除
    if val < node.val:
        node.left = self.remove_helper(node.left, val)
    elif val > node.val:
        node.right = self.remove_helper(node.right, val)
    else:
        if node.left is None or node.right is None:
            child = node.left or node.right
            # 子节点数量 = 0 ,直接删除 node 并返回
            if child is None:
                return None
            # 子节点数量 = 1 ,直接删除 node
            else:
                node = child
        else:
            # 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点
            temp = node.right
            while temp.left is not None:
                temp = temp.left
            node.right = self.remove_helper(node.right, temp.val)
            node.val = temp.val
    # 更新节点高度
    self.update_height(node)
    # 2. 执行旋转操作,使该子树重新恢复平衡
    return self.rotate(node)

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

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

相关文章

跳跃游戏Ⅱ - vector

55. 跳跃游戏 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool canJump(vector<int>& nums) {int n nums.size();int reach 0;for(int i 0; i < n; i){if(i > reach){return false;}reach max(inums[i], reach);}return true;} }; …

SpringBoot3 + Vue3 学习 Day 2

登入接口 和 获取用户详细信息的开发 学习视频登入接口的开发1、登入主逻辑2、登入认证jwt 介绍生成 JWT① 导入依赖② 编写代码③ 验证JWT 登入认证接口的实现① 导入 工具类② controller 类实现③ 存在的问题及优化① 编写拦截器② 注册拦截器③ 其他接口直接提供服务 获取用…

JVM(day4)类加载机制

类加载过程 加载 通过一个类的全限定名来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 在内存中生成一个代表这个类的java.lang.Class对象&#xff0c;作为方法区这个类的各种数据的访问入口。 验证 文件格式验证 元数…

LeetCode做题记录(第二天)647. 回文子串

题目&#xff1a; 647. 回文子串 标签&#xff1a;双指针 字符串 动态规划 题目信息&#xff1a; 思路一&#xff1a;暴力实现 我们直接for套for分割成一个个子串再判断&#xff0c;如果子串是回文子串&#xff0c;就1&#xff0c;最后得出结果 代码实现&#xff1a; cl…

C语言实例-约瑟夫生者死者小游戏

问题&#xff1a; 30个人在一条船上&#xff0c;超载&#xff0c;需要15人下船。于是人们排成一队&#xff0c;排队的位置即为他们的编号。报数&#xff0c;从1开始&#xff0c;数到9的人下船&#xff0c;如此循环&#xff0c;直到船上仅剩15人为止&#xff0c;问都有哪些编号…

Missing script:‘dev‘

场景&#xff1a; npm run dev 原因&#xff1a;没有安装依赖&#xff0c;可用镜像安装&#xff08;详见下图ReadMe 蓝色字体&#xff09;&#xff0c;没安装依赖可从package-lock.json文件是否存在看出&#xff0c;存在则有依赖 解决&#xff1a;

KMP算法(算法篇)

算法之KMP算法 KMP算法 概念&#xff1a; KMP算法是用于解决字符串匹配的问题的算法&#xff0c;也就是有一个文本串和一个模式串&#xff0c;求解这个模式串是否在文本串中出现或者匹配。相对于暴力求解&#xff0c;KMP算法使用了前缀表来进行匹配&#xff0c;充分利用了之…

【Vue3】从零开始编写项目

【Vue3】从零开始编写项目 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

神经网络模型实现(训练、测试)

目录 一、神经网络骨架&#xff1a;二、卷积操作&#xff1a;三、卷积层&#xff1a;四、池化层&#xff1a;五、激活函数&#xff08;以ReLU为例&#xff09;&#xff1a;六、模型搭建&#xff1a;七、损失函数、梯度下降&#xff1a;八、模型保存与加载&#xff1a;九、模型训…

Linux下安装JDK、Tomact、MySQL以及Nginx的超详细步骤

目录 1、为什么安装这些软件 2、安装软件的方式 3、安装JDK 3.1 下载Linux版本的JDK 3.2 将压缩包拖拽到Linux系统下 3.3 解压jdk文件 3.4 修改文件夹名字 3.5 配置环境变量 4、安装Tomcat 4.1 下载Tomcat 4.2 将Tomcat放入Linux系统并解压&#xff0c;步骤如上面的…

MenuToolButton自绘控件,带下拉框的QToolButton,附源码

MenuToolButton自绘控件&#xff0c;带下拉框的QToolButton 效果 下拉样式可自定义 跟随QToolButton的Qt::ToolButtonStyle属性改变图标文字样式 使用示例 正常UI文件创建QToolButton然后提升&#xff0c;或者直接代码创建都可以。 // 创建一个 QList 对象来存储 QPixm…

JDK、JRE、JVM的区别java的基本数据类型

说一说JDK、JRE、JVM的区别在哪&#xff1f; JDK&#xff1a; Java Delopment kit是java工具包&#xff0c;包含了编译器javac&#xff0c;调试器&#xff08;jdb&#xff09;以及其他用于开发和调试java程序的工具。JDK是开发人员在开发java应用程序时候所需要的的基本工具。…

10道JVM经典面试题

1、 JVM中&#xff0c;new出来的对象是在哪个区&#xff1f; 2、 说说类加载有哪些步骤&#xff1f; 3、 JMM是什么&#xff1f; 4、 说说JVM内存结构&#xff1f; 5、 MinorGC和FullGC有什么区别&#xff1f; 6、 什么是STW? 7、 什么情况下会发生堆/栈溢出&#xff1f…

【高中数学/对数函数】log_x_x+1与(x+1)/x,log_x+1_x与x/(x+1)的图线有着惊人的相似性

【图像】 褐线与蓝线&#xff0c;黄线与绿线&#xff0c;只是像左右平移了一样。 【生成图像的代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head>…

大模型学习笔记十二:AI产品部署

文章目录 一、如何选择GPU和云服务器厂商&#xff0c;追求最高性价比1&#xff09;根据场景选择GPU2&#xff09;训练或微调所需显卡&#xff08;以Falcon为例子&#xff09;3&#xff09;服务器价格计算器 二、全球大模型了解1&#xff09;llm所有模型2&#xff09;模型综合排…

基于Python+Django,开发的一个在线教育系统

一、项目简介 使用Python的web框架Django进行开发的一个在线教育系统&#xff01; 二、所需要的环境与组件 Python3.6 Django1.11.7 Pymysql Mysql pure_pagination DjangoUeditor captcha xadmin crispy_forms 三、安装 1. 下载项目后进入项目目录cd Online-educ…

企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充(Vue项目版)。。。

引子 关于企业微信PC版应用跳转到默认浏览器&#xff0c;我之前写过一篇文章&#xff1a;企业微信PC版应用跳转到默认浏览器&#xff0c;避坑指南&#xff0c;欢迎补充。。。 以前的文章里用的前后端一体的Jsp项目&#xff0c;这次我使用的是前后端分离的Vue项目&#xff0c;…

数据库——单表查询

一、建立数据库mydb8_worker mysql> use mydb8_worker; 二、建立表 1.创建表 mysql> create table t_worker(department_id int(11) not null comment 部门号,-> worder_id int(11) primary key not null comment 职工号,-> worker_date date not null comment…

Git安装教程 | Git配置教程 | Github

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本片教程是分享的Git教程的第1️⃣期&#xff1a;Git的安装与配置✈️ 文章目录 1.前言&#x1f347;2.Git下载&#x1f34e;3.Git 的安装&#x1f95d…

Python数据风险案例54——人工智能热门概念股爬虫分析其价值(三因子模型)

案例背景 人工智能概念如火如荼的夏天&#xff0c;在这个2024年&#xff0c;我觉得需要提早布局一下这个概念。所以我们找一下A股里面人们的人工智能概念股&#xff0c;然后分析他们的数据应用三因子模型&#xff0c;也就是最经典的资本资产定价模型的衍生版去研究他们各自的投…