19 树表的查找

文章目录

  • 二叉排序树(BST)
    • 查找操作
        • 二叉排序树的存储结构
        • 查找实现
          • 查找算法分析
          • 二叉排序树的平均查找长度
    • 插入操作
    • 删除操作
    • 代码实现
  • 平衡二叉树(AVL)
    • 插入&旋转操作
      • 插入操作
      • 四种旋转情况
      • 代码实现
    • 删除操作
    • 查找操作

介绍

树表查找是一种在树形数据结构中进行查找的方法。与线性表查找不同,树表查找可以利用树形结构特性更搞笑地进行查找操作。在树表查找中,主要有二叉排序树、线索二叉树、红黑树、B树等实现方法。这些方法都是基于树形数据结构,但在实现和性能方面有所不同。


二叉排序树(BST)

二叉排序树(Binary Search Tree,BST),或是一颗空树,或者是具有下列特征的二叉树:

  1. 对于每个结点,其左子树上的所有结点的值均小于它的值。
  2. 对于每个结点,其右子树上的所有结点的值均大于它的值。
  3. 左、右子树也分别是一颗二叉排序树。

这个性质使得二叉排序树具有非常高的查找效率。

根据二叉排序树的定义,左子树结点值 < 根节点值 < 右子树结点值,因此对二叉树进行中序遍历,可以得到一个递增的有序序列。image-20230505174137577

如图得到二叉排序树的中序遍历序列为 1 2 3 4 6 8

查找操作

二叉排序树的查找是从根节点开始,然后根据待查找值与当前结点值大小关系来决定沿着左子树查找还是右子树进行查找。显然这是一个递归的过程。
假设我们有以下二叉排序树:

    8
   / \
  3   10
 / \     \
1   6     14
   /  \   /
  4    7 13

查找值为7的节点:

  • 从根节点8开始,7 < 8,沿着左子树;
  • 接下来到节点3,7 > 3,沿着右子树;
  • 到达节点6,7 > 6,沿着右子树;
  • 最后到达节点7,查找成功。

二叉排序树的存储结构

定义二叉排序树:

typedef struct {
  	KeyType key;          //关键字项
    InfoType otherinfo;   //其他数据域
};

typedef struct Node{
    ElemType data;
    struct Node* left;
    struct Node* right;
}BSTNode *BSTree;

查找实现

递归算法:

递归算法实现简单,但其缺点是会消耗大量的栈空间,当树的深度非常大时,会出现栈溢出的情况。因此,递归算法更适合树比较小或者树高度较低的情况下使用。

BSTNode* BST_Search(BSTNode*T, ElemType data)
{
    if(T == NULL || T->data == data ){
        return T;
    }
    //递归查找
    if(data<root->data)
    {
        return BST_Search(root->left,data);
    }else{
        return BST_Search(root->right,data);
    }
}

二叉排序树的非递归查找算法:

非递归算法是一种迭代实现的方法,它通过栈或队列来保存未访问的结点,从而避免了递归算法消耗大量的栈空间问题。

非递归算法实现相对复杂一些,但却更加高效,尤其适合大规模树的查找操作。

BSTNode *BST_Search(BSTree T, ElemType key)
{
	while(T!=NULL && key!=T->data)
	{
		if(key<T->data)  T=T->left-child;
		else T=T->right-child;
	}

	return T;
}
查找算法分析

在这里插入图片描述

二叉排序树上查找某关键字等于给定值的结点过程,其实就是走了一条从根到该结点的路径。

  • 比较的值的次数 = 此结点所在的层次数。
  • 最多的比较次数 = 二叉排序树的深度。
二叉排序树的平均查找长度

在二叉排序树中,查找某个结点的平均查找长度(Average Search Length,ASL),是指所有结点所需的比较次数的平均值。平均查找长度是衡量二叉排序树查找性能的重要指标之一。

对于一个具有n个结点的二叉排序树,平均查找长度的计算公式如下:

ASL = ( 深度为1的结点数*1+深度为2的结点数*2+...+深度为h的结点数*h)/n

含有n个结点的二叉排序树的平均查找长度和树的形态有关。

  • 树的高度越高,平均查找长度越长,反之越短。
  • 最好的情况的树就是判定树

对于一颗平衡的二叉排序树,其深度为log n,因此平均查找长度为O(log n)。但如果二叉排序树退化为链表,其深度为n-1,此时平均查找长度为O(n)。因此,在实际应用中,为了保证较好的查找性能,应该尽可能保持二叉排序树的平衡。

提高形态不均匀的二叉排序树的查找效率

  • 平衡化(平衡二叉树)处理,尽量让二叉树的形态均衡
    • 如果选择中间数作为根节点,可以保持树左右子树的相对平衡;
    • 关键是根节点选哪个,如果选个小的,自然就有更多的元素跑到右子树,相对来说层次加深。

插入操作

实现步骤:

  1. 如果根节点为空,将新节点作为根节点;
  2. 如果根节点不为空,从根节点开始比较节点值与插入值的大小关系;
  3. 如果插入值小于当前节点值,将新节点插入到左子树中;
  4. 如果插入值大于当前节点值,将新节点插入到右子树中;
  5. 如果插入值等于当前节点值,则不插入重复节点;
  6. 插入节点后,需要保证二叉排序树的性质仍然成立,即对于任意节点,其左子树的值小于当前节点的值,右子树的值大于当前节点的值。
Node* insert(Node *root, int data) {
    // 若根节点为空,创建一个新节点并返回
    if (root == NULL) {
        return createNode(data);
    }

    // 递归插入节点
    if (data < root->data) {
        root->left = insert(root->left, data);
    } else {
        root->right = insert(root->right, data);
    }

    return root;
}

其中createNode()函数用于在根节点为空时,创建一个根节点。

删除操作

删除操作不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从存储二叉树的链表上摘下,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会改变。

删除操作主要包括三种情况:

  1. 删除叶子结点:直接删除,不会破坏二叉排序树的性质
  2. 删除只有一个子树的结点:删除该结点,将其子树提升到被删除结点的位置。
  3. 删除有两个子树的结点:有两种方法实现。
    1. 寻找被删除节点的前驱节点(左子树中最大的节点),用前驱节点的值替换被删除节点的值,然后删除前驱节点。前驱节点要么是叶子节点,要么只有一个左子树。
    2. 寻找被删除节点的后继节点(右子树中最小的节点),用后继节点的值替换被删除节点的值,然后删除后继节点。后继节点要么是叶子节点,要么只有一个右子树。

image-20230505210313988

如图,删除值为78的结点,并用后继节点替换它,在值为78的结点处进行中序遍历得到有序数列为 65 78 81 88 94 ,78的直接后继为81,所以用值为81的结点替换它。

TreeNode* findMin(TreeNode* root) {
    while (root->left != NULL) {
        root = root->left;
    }
    return root;
}

TreeNode* deleteNode(TreeNode* root, int key) {
    if (root == NULL) {
        return NULL;
    }

    if (key < root->val) {
        root->left = deleteNode(root->left, key);
    } else if (key > root->val) {
        root->right = deleteNode(root->right, key);
    } else {
        // Case 1: 删除叶子节点
        if (root->left == NULL && root->right == NULL) {
            free(root);
            root = NULL;
        }
        // Case 2: 删除只有一个子树的节点
        else if (root->left == NULL) {
            TreeNode* temp = root;
            root = root->right;
            free(temp);
        } else if (root->right == NULL) {
            TreeNode* temp = root;
            root = root->left;
            free(temp);
        }
        // Case 3: 删除有两个子树的节点
        else {
            TreeNode* temp = findMin(root->right);
            root->val = temp->val;
            root->right = deleteNode(root->right, temp->val);
        }
    }

    return root;
}

代码实现

#include <stdio.h>
#include <stdlib.h>

// 定义二叉排序树节点结构体
typedef struct BSTNode {
    int key;                    // 节点的值
    struct BSTNode *left;       // 左子节点
    struct BSTNode *right;      // 右子节点
} BSTNode;

// 创建新节点
BSTNode *newNode(int key) {
    BSTNode *node = (BSTNode *)malloc(sizeof(BSTNode));
    node->key = key;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 向二叉排序树插入新节点
BSTNode *insert(BSTNode *root, int key) {
    if (root == NULL) { // 如果根节点为空,创建新节点
        return newNode(key);
    }

    if (key < root->key) { // 如果插入值小于根节点值,插入到左子树
        root->left = insert(root->left, key);
    } else if (key > root->key) { // 如果插入值大于根节点值,插入到右子树
        root->right = insert(root->right, key);
    }

    return root;
}

// 寻找二叉排序树中的最小值节点
BSTNode *findMin(BSTNode *root) {
    while (root->left != NULL) {
        root = root->left;
    }
    return root;
}

// 从二叉排序树删除节点
BSTNode *deleteNode(BSTNode *root, int key) {
    if (root == NULL) {
        return root;
    }

    if (key < root->key) {
        root->left = deleteNode(root->left, key);
    } else if (key > root->key) {
        root->right = deleteNode(root->right, key);
    } else {
        // 情况1:删除节点没有子节点
        if (root->left == NULL && root->right == NULL) {
            free(root);
            root = NULL;
        }
            // 情况2:删除节点只有一个子节点(右子节点)
        else if (root->left == NULL) {
            BSTNode *temp = root->right;
            free(root);
            return temp;
        }
            // 情况3:删除节点只有一个子节点(左子节点)
        else if (root->right == NULL) {
            BSTNode *temp = root->left;
            free(root);
            return temp;
        }
            // 情况4:删除节点有两个子节点
        else {
            BSTNode *temp = findMin(root->right);
            root->key = temp->key;
            root->right = deleteNode(root->right, temp->key);
        }
    }

    return root;
}

// 在二叉排序树中搜索指定值的节点
BSTNode *search(BSTNode *root, int key) {
    if (root == NULL || root->key == key) {
        return root;
    }

    if (key < root->key) {
        return search(root->left, key);
    } else {
        return search(root->right, key);
    }
}

// 更新二叉排序树中的节点值
BSTNode *updateNode(BSTNode *root, int oldKey, int newKey) {
// 先删除原来的节点
    root = deleteNode(root, oldKey);
// 再插入新的节点
    return insert(root, newKey);
}

// 中序遍历二叉排序树
void inorder(BSTNode *root) {
    if (root != NULL) {
        inorder(root->left);
        printf("%d ", root->key);
        inorder(root->right);
    }
}
int main() {
    BSTNode *root = NULL;

    // 向二叉排序树插入节点
    root = insert(root, 50);
    root = insert(root, 30);
    root = insert(root, 20);
    root = insert(root, 40);
    root = insert(root, 70);
    root = insert(root, 60);
    root = insert(root, 80);

    // 打印二叉排序树
    printf("Binary Search Tree: \n");
    printf("   50\n");
    printf("  /  \\\n");
    printf("30    70\n");
    printf("  \\   / \\ \n");
    printf("  40 60  80\n");
    printf("  /\n");
    printf("20\n\n");

    // 删除节点 20
    printf("Delete 20\n");
    root = deleteNode(root, 20);
    printf("Inorder traversal of the modified tree: \n");
    inorder(root);
    printf("\n\n");

    // 删除节点 30
    printf("Delete 30\n");
    root = deleteNode(root, 30);
    printf("Inorder traversal of the modified tree: \n");
    inorder(root);
    printf("\n\n");

    // 删除节点 50
    printf("Delete 50\n");
    root = deleteNode(root, 50);
    printf("Inorder traversal of the modified tree: \n");
    inorder(root);
    printf("\n\n");

    // 更新节点 60 的值为 55
    printf("Update 60 to 55\n");
    root = updateNode(root, 60, 55);
    printf("Inorder traversal of the modified tree: \n");
    inorder(root);
    printf("\n");

    return 0;
}

平衡二叉树(AVL)

平衡二叉树(Balanced Binary Tree),AVL树,平衡二叉树的出现是为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除结点时,保证任意结点的左右子树高度差不超过1。

因此,平衡二叉树可定义为或者是一颗空树,或者是具有下列性质的二叉排序树:

  1. 它的左子树和右子树都是平衡二叉树
  2. 左右子树的高度差绝对值不超过1

为了方便起见,给每个结点附加一个数字,给出该结点左子树与右子树的高度差。这个数字成为结点的平衡因子(BF)。

  • 平衡因子 = 左子树的高度 - 右子树的高度
  • 平衡因子可以取三个值:-1 0 1 分别对应于左子树高度比右子树低一层、左右子树高度相等、左子树高度比右子树高一层。
  • 平衡二叉树的定义中要求平衡因子的绝对值不超过 1。当平衡因子的绝对值超过 1 时,就需要通过旋转等操作来重新平衡二叉树,以保证其性质。

image-20230510171231223

插入&旋转操作

二叉平衡树保证平衡的基本思想如下:每当在二叉平衡树中插入(或删除)一个结点时,首先检查其插入路径上的结点是否因为此次操作而导致了不平衡。若导致了不平衡,则先找到插入路径上离插入结点最近的平衡因子的绝对值大于1的结点A,再对以A为根的子树,在保证二叉平衡树的特性的前提下,调整各节点的位置关系,使之重新达到平衡。

注意:每次调整的对象都是最小不平衡子树,即以插入路径上离插入结点最近的平衡因子的绝对值大于1的结点作为根的子树。

插入操作

平衡二叉树的插入操作包括两个部分:插入新结点和平衡调整。

  1. 首先,将新结点插入到二叉排序树中的正确位置上,与普通的二叉排序树的插入操作一样。
  2. 然后,从新结点开始向上逐层更新每个结点的平衡因子,并检查是否导致了当前结点的不平衡。
  3. 如果当前结点的平衡因子绝对值大于1,则需要进行平衡调整。为了找到最小的平衡子树,我们需要在向上逐层更新平衡因子的过程中,记录下第一个平衡因子绝对值大于1的结点作为当前子树的根节点。
  4. 对以该根节点为根的子树进行平衡调整,可采用四种旋转操作之一。
  5. 最后,对于每个被更新了平衡因子的结点,需要检查其父节点的平衡因子是否需要更新,并进行相应的平衡调整,知道根节点为止。

image-20230510173137776

四种旋转情况

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

由于在结点A的做孩子(L)的左子树(L)上插入了新结点,A的平衡因子有1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根节点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

       |                   |
       A                   B
      / \                 / \
     B   T3   ----->     C   A
    / \                     / \
   C   T2                  T2  T3

image-20230510212637186

2. RR平衡旋转(左单旋转)

在 RR 旋转中,我们假设新结点插入在结点 A 的右孩子 B 的右子树®上。这时,结点 A 的平衡因子从 -1 变为 -2,导致以 A 为根节点的子树失去平衡。

为了恢复平衡,我们需要进行一次向左的旋转操作。具体来说,我们需要将结点 B 向左上旋转代替 A 成为根节点,将 A 结点向左下旋转成为 B 的左子树的根节点,而 B 的原左子树则作为 A 结点的右子树。

      A(-1)              B(0)
      / \               /   \
    T1  B(1)    -->   A(0)   T3
       / \           /   \
     T2  T3         T1   T2

image-20230510213349483

3. LR平衡旋转(先左后后双旋转)

由于我们在A的左子树L的右子树R上插入新结点,导致A的平衡因子由1增至2,导致以A为根的子树失去平衡,此时需要进行两次旋转操作,先左旋转后右旋转。

第一次旋转将结点 B 的右子树 C 向左上旋转代替 B 成为根节点,将 B 向左下旋转成为 C 的左子树的根节点,而 C 的原左子树则作为 B 的右子树。

第二次旋转将结点 C 向右上旋转代替 A 成为根节点,将 A 向右下旋转成为 C 的右子树的根节点,而 C 的原右子树则作为 A 的左子树。

       |                     |                      |
       A(2)                  A(0)                   C(0)
      / \                   /  \                   /   \
     B(0) T4        -->   C(0)  T4        -->     B(-1) A(0)
    / \                  /  \                   / \    /  \
   T1  C(1)             B(-1) T3               T1 T2  T3  T4
      / \              / \
     T2 T3           T1   T2

image-20230510215021110

4. RL 平衡旋转(先右后左双旋转)

由于在A的右孩子®的左子树(L)上插入新结点, A的平衡因子由-1减至-2, 导致以A为根的子树失去平衡,需要进行两次旋转操作,先 右旋转后左旋转。

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

第二次旋转将结点 C 向左上旋转代替 B 成为根节点,将 B 向左下旋转成为 C 的左子树的根节点,而 C 的原左子树则作为 B 的右子树。

       |                     |                       |
       A(-2)                 A(0)                    C(0)
      / \                   /  \                    /   \
     T1  B(0)        -->  T1   C(0)       -->     A(0)  B(1)
        / \                / \                  / \    /  \
      C(-1) T4           T2  B(0)              T1 T2  T3  T4
      / \                   / \
     T2 T3                T3  T4

image-20230510215327446

例:

以关键字序列{15,3, 7, 10,9, 8}构造一棵平衡二叉树的过程为例,插入7后导致不平衡,最小不平衡子树的根为15,插入位置为其左孩子的右子树,故执行LR旋转,先左后右双旋转。再插入9后导致不平衡,最小不平衡子树的根为15,插入位置为其左孩子的左子树,故执行LL旋转,右单旋转。再插入8后导致不平衡,最小不平衡子树的根为7,插入位置为其右孩子的左子树,故执行RL旋转,先右后左双旋转,最后结果如图:

image-20230510220932197

代码实现

#include <stdio.h>
#include <stdlib.h>

typedef struct AVLNode {
    int key;
    int height;
    struct AVLNode* left;
    struct AVLNode* right;
} AVLNode;

// 获取AVL树高度
int getHeight(AVLNode* node) {
    if (node == NULL) {
        return 0;
    }
    return node->height;
}

// 更新结点高度
void updateHeight(AVLNode* node) {
    int leftHeight = getHeight(node->left);
    int rightHeight = getHeight(node->right);
    node->height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

// 获取AVL树平衡因子
int getBalanceFactor(AVLNode* node) {
    if (node == NULL) {
        return 0;
    }
    return getHeight(node->left) - getHeight(node->right);
}

// 右单旋转(LL)
AVLNode* rightRotate(AVLNode* A) {
    AVLNode* B = A->left;
    A->left = B->right;
    B->right = A;
    updateHeight(A);
    updateHeight(B);
    return B;
}

// 左单旋转(RR)
AVLNode* leftRotate(AVLNode* A) {
    AVLNode* B = A->right;
    A->right = B->left;
    B->left = A;
    updateHeight(A);
    updateHeight(B);
    return B;
}

// 插入新结点并保持平衡
AVLNode* insertNode(AVLNode* root, int key) {
    if (root == NULL) {
        AVLNode* newNode = (AVLNode*)malloc(sizeof(AVLNode));
        newNode->key = key;
        newNode->height = 1;
        newNode->left = NULL;
        newNode->right = NULL;
        return newNode;
    }

    if (key < root->key) {
        root->left = insertNode(root->left, key);
    } else if (key > root->key) {
        root->right = insertNode(root->right, key);
    } else {
        return root; // 不允许插入重复值
    }

    updateHeight(root);
    int balanceFactor = getBalanceFactor(root);

    // LL旋转
    if (balanceFactor > 1 && getBalanceFactor(root->left) > 0) {
        return rightRotate(root);
    }
    // RR旋转
    if (balanceFactor < -1 && getBalanceFactor(root->right) < 0) {
        return leftRotate(root);
    }
    // LR旋转
    if (balanceFactor > 1 && getBalanceFactor(root->left) < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }
    // RL旋转
    if (balanceFactor < -1 && getBalanceFactor(root->right) > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    return root;
}

// 前序遍历
void preorderTraversal(AVLNode* root) {
    if (root == NULL) {
        return;
    }
    printf("%d ", root->key);
    preorderTraversal(root->left);
    preorderTraversal(root->right);
}

int main() {
    AVLNode* root = NULL;
    int keys[] = {15, 3, 7, 10, 9, 8};
    int n = sizeof(keys) / sizeof(int);

    // 插入所有键值
    for (int i = 0; i < n; i++) {
        root = insertNode(root, keys[i]);
    }

    // 前序遍历打印
    printf("Preorder traversal: ");
    preorderTraversal(root);
    printf("\n");

    return 0;
}

删除操作

与插入操作类似,删除结点时可能导致不平衡,那么我们又需要进行适当的旋转来恢复平衡。

// 删除指定节点中的最小节点并返回更新后的子树。
AVLNode* deleteMinNode(AVLNode *node) {
    // 如果节点没有左子树,那么这个节点就是最小节点,用右子树替换它并释放内存。
    if (node->left == NULL) {
        AVLNode *rightNode = node->right;
        free(node);
        return rightNode;
    }
    // 递归地查找并删除左子树中的最小节点。
    node->left = deleteMinNode(node->left);
    // 对节点进行平衡调整。
    return balance(node);
}

// 删除具有指定键值的节点。
AVLNode* removeNode(AVLNode *node, int key) {
    // 如果节点为空,直接返回。
    if (node == NULL) {
        return node;
    }

    // 如果要删除的键值小于当前节点的键值,则递归地在左子树中删除。
    if (key < node->key) {
        node->left = removeNode(node->left, key);
    } 
    // 如果要删除的键值大于当前节点的键值,则递归地在右子树中删除。
    else if (key > node->key) {
        node->right = removeNode(node->right, key);
    } 
    // 如果找到了要删除的节点。
    else {
        // 情况 1: 节点没有左子树,直接用右子树替换当前节点。
        if (node->left == NULL) {
            AVLNode *rightNode = node->right;
            free(node);
            return rightNode;
        } 
        // 情况 2: 节点没有右子树,直接用左子树替换当前节点。
        else if (node->right == NULL) {
            AVLNode *leftNode = node->left;
            free(node);
            return leftNode;
        } 
        // 情况 3: 节点同时具有左子树和右子树。
        else {
            // 找到右子树中的最小节点,并用它的键值替换当前节点的键值。
            AVLNode *minNode = findMinNode(node->right);
            node->key = minNode->key;
            // 删除右子树中的最小节点。
            node->right = deleteMinNode(node->right);
        }
    }

    // 对节点进行平衡调整。
    return balance(node);
}

// balance operation
AVLNode* balance(AVLNode *node) {
    if (node == NULL) {
        return node;
    }

    int balanceFactor = getBalanceFactor(node);

    if (balanceFactor > 1) {
        if (getBalanceFactor(node->left) >= 0) {
            return rightRotate(node);   // LL旋转
        } else {
            node->left = leftRotate(node->left);
            return rightRotate(node);   // LR旋转
        }
    } else if (balanceFactor < -1) {
        if (getBalanceFactor(node->right) <= 0) {
            return leftRotate(node);    // RR旋转
        } else {
            node->right = rightRotate(node->right);
            return leftRotate(node);    // RL旋转
        }
    }

    return node;
}

在删除节点时,需要根据被删除节点的情况进行不同的操作:

  • 如果被删除节点的左子树为空,则将其右子树提升为当前节点的位置。
  • 如果被删除节点的右子树为空,则将其左子树提升为当前节点的位置。
  • 如果被删除节点的左右子树均不为空,则需要找到其右子树中最小的节点,将其值赋给当前节点,并删除右子树中的最小节点。

查找操作

平衡二叉树的查找操作与二叉排序树的查找操作类似,从根节点开始递归查找:

  1. 如果当前节点为空,则返回 NULL。
  2. 如果当前节点的 key 等于查找的值,则返回当前节点。
  3. 如果当前节点的 key 大于查找的值,则递归查找左子树。
  4. 如果当前节点的 key 小于查找的值,则递归查找右子树。
AVLNode* search(AVLNode *node, int key) {
    if (node == NULL || node->key == key) {
        return node;
    }

    if (key < node->key) {
        return search(node->left, key);
    } else {
        return search(node->right, key);
    }
}

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

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

相关文章

React antd Form item「受控组件与非受控组件」子组件 defaultValue 不生效等问题总结

一、为什么 Form.Item 下的子组件 defaultValue 不生效&#xff1f; 当你为 Form.Item 设置 name 属性后&#xff0c;子组件会转为受控模式。因而 defaultValue 不会生效。你需要在 Form 上通过 initialValues 设置默认值。name 字段名&#xff0c;支持数组 类型&#xff1a;N…

Cocos Creator 3.7.3 正式上线,渲染管线和算法持续更新

Cocos Creator 3.7.3 正式发布。该版本对近日用户反馈的一系列关键性问题进行了集中修复&#xff0c;也对一部分性能进行了优化&#xff0c;提升了用户体验&#xff0c;建议所有 v3.x 用户升级。 Engine Features Render Graph 自定义渲染管线支持 GLES 后端Deprecate addRast…

十分钟教你搭建ChatGPT 图片生成的安卓应用

十分钟教你搭建ChatGPT 图片生成的安卓应用 大家好&#xff0c;我是易安&#xff01; 今天&#xff0c;我们将集成 OpenAI API (ChatGPT)来构建一个简单的类似 ChatGPT 的 android 应用程序&#xff0c;让它返回我们想要的图片&#xff0c;本文是上一篇的姊妹篇。 详细步骤 第…

Linux安装使用PostgreSQL

安装PostgreSQL 开源数据库&#xff1a;PostgreSQL 在官网选择对应版本的安装包 https://www.postgresql.org/download/ 我的Linux系统是CentOS7 选择对应的系统 选择安装的版本、平台、架构 复制粘贴安装脚本运行 初始化后会创建一个用户postgres&#xff0c;一般开始…

IDEA开发实现Maven+Servlet+Mybatis实现CRUD管理系统-Mapper代理开发

Mapper代理开发概述 之前我们写的代码是基本使用方式&#xff0c;它也存在硬编码的问题&#xff0c;如下&#xff1a; 这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式&#xff08;如下图&…

MapReduce框架

TextInputFormat 1&#xff09;FileInputFormat实现类 思考&#xff1a;在运行MapReduce程序时&#xff0c;输入的文件格式包括&#xff1a;基于行的日志文件、二进制格式文件、数据库表等。那么&#xff0c;针对不同的数据类型&#xff0c;MapReduce是如何读取这些数据的呢&…

解密《永恒之塔私服》龙界要塞全貌

《永恒之塔2.0&#xff1a;进军龙界》将于12月29日14:00正式开放测试&#xff0c;全区全服29日起陆续更新&#xff0c;并将在元旦假期期间完成所有区服更新工作。12月27日&#xff0c;2.0新区抢先免费体验已经开始&#xff0c;凭特权激活码抢先免费体验无外挂《永恒之塔2.0》。…

1.环境搭建

1.Windows 系统GolangVisual Studio CodeMicrosoft Windows (x86-64)go1.20.1.windows-amd64.msihttps://code.visualstudio.com/DownloadMicrosoft Windows (x86-i386)go1.20.1.windows-386.msihttps://code.visualstudio.com/DownloadMicrosoft Windows (ARM64)go1.20.1.win…

【操作系统】内存管理

文章目录 内存的基础知识什么是内存进程的工作原理指令的工作原理逻辑地址&#xff08;相对地址&#xff09;vs物理地址&#xff08;绝对地址&#xff09;从写程序到程序运行—编译、链接、装入装入的三种方式链接的三种方式 什么是内存管理&#xff1f;内存空间的分配与回收内…

基于51单片机的简易电子秤

首先看看题目要求&#xff1a; 1.方案论证 &#xff08;1&#xff09;压力传感器的论证与选择 方案一&#xff1a;采用惠更斯电桥&#xff0c;当电阻应变片承受载荷产生变形时&#xff0c;其阻值将发生变化。从而使电桥失去平衡&#xff0c;产生相应的差动信号&#xff0c;但…

Java中线程的状态

Java中线程的状态分为6种。 初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;ready&#xff09;和运行中&#xff08;running&#xff09;两种状态笼统的称为“运行”。 线程对象创建…

gateway的使用

什么是Spring Cloud Gateway 网关作为流量的入口&#xff0c;常用的功能包括路由转发&#xff0c;权限校验&#xff0c;限流等。 Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架&#xff0c;定位于取代 Netflix Zuul。相比 Zuul 来说&#xff0c;Spring Cloud …

ADV7391BCPZ-ASEMI代理亚德诺ADV7391BCPZ原厂芯片

编辑-Z ADV7391BCPZ参数描述&#xff1a; 型号&#xff1a;ADV7391BCPZ VDD&#xff1a;1.8V VAA&#xff1a;3.3V 全驱动输出电流&#xff1a;34.6 mA 低驱动输出电流&#xff1a;4.3 mA 输出电容&#xff1a;10 pF 模拟输出延迟&#xff1a;6 ns DAC模拟输出倾斜&am…

文件一直处于修改状态 git checkout 无法还原的问题解决方法

问题描述 最近在 RT-Thread 时&#xff0c;使用 Git 回退版本验证问题&#xff0c;后来 git pull 拉取最新代码后&#xff0c;发现里面有几个文件&#xff0c;一直为【修改】状态&#xff0c;并且无法还原&#xff0c;git checkout xxx git reset --hard 都用了&#xff0c;依旧…

简单两步创建nVisual建筑场景

nVisual是一款网络基础设施可视化管理软件&#xff0c;通过模型可规划即将建设的机房效果&#xff0c;或者将已有的机房场景复刻至系统中&#xff0c;便于运维管理者清晰的了解机房设备及线缆路由连接关系。 数据初始我们需要将数据中心场景复刻至系统中&#xff0c;建筑场景也…

【Java】抽象类与接口

文章目录 1.抽象类1.1抽象方法 2.接口3.抽象类和接口的区别3.1代码中区分&#xff1a;3.2设计层面区分 在面向对象编程中&#xff0c;抽象是非常重要的一个特征。在Java中可以通过抽象类或接口的形式实现这一特性。 1.抽象类 Java关键字 abstract对应抽象类的使用 1.1抽象方法…

Calico的BGP打通Kubernetes网络和局域网

1、项目背景 随着云原生技术的不断发展&#xff0c;容器化应用已成为企业构建云原生架构的重要方式。而随着集群规模不断扩大&#xff0c;跨主机通信的需求也越来越重要。在 Kubernetes 集群中&#xff0c;Pod 是最小的调度和管理单位&#xff0c;而网络也是 Kubernetes 中最重…

二叉树OJ

文章目录 二叉树OJ根据二叉树创建字符串思路示例代码 二叉树的层序遍历思路示例代码 二叉树的层序遍历 II思路示例代码 二叉树的最近公共祖先思路1示例代码1思路2示例代码2 二叉搜索树与双向链表思路1示例代码1思路2示例代码2 迭代实现二叉树的三种遍历前序遍历思路示例代码 中…

哪些蓝牙耳机戴久不疼?长时间佩戴不疼的蓝牙耳机推荐

现在的真无线耳机已经成为了人们的标配之一了&#xff0c;各个厂家也紧随其后&#xff0c;生产出了多种无线耳机&#xff0c;不少人的选购蓝牙耳机一般都是对音质、佩戴和连接&#xff0c;但通常人们佩戴蓝牙耳机都是在半天左右&#xff0c;小编专门整理了一期舒适度高的耳机&a…

ElasticSeach 集成 springboot

声明是ElasticSearch? ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c; 基于RESTful web接口。Elasticsearch是用Java开发的&#xff0c;并作为Apache许可条款下的开放源码发布&#xff0c;是 当前流行的企业级搜索引擎…
最新文章