19 树表的查找

news2024/11/13 16:10:26

文章目录

  • 二叉排序树(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.coloradmin.cn/o/512030.html

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

相关文章

非法捕捞识别预警系统 yolov7

非法捕捞识别预警系统通过yolov7网络模型AI视频分析技术&#xff0c;非法捕捞识别预警系统模型算法能够对河道湖泊画面场景中出现的非法捕捞行为进行7*24小时不间断智能检测识别实时告警通知相关人员及时处理。Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测&#xff…

应用网关Nginx+Https证书+内网穿透+图片切割水印+网关登录

一、开源项目简介 Apiumc Gateway 它一个工具等于 Nginx Https证书 内网穿透 图片切割水印 网关登录 Apiumc Gateway 是高性能的Web网关&#xff0c;它从底层Socket原始通信层开始&#xff0c;采用多线程、多任务模式从新构建Web服务&#xff0c;充分发挥当下多核的CPU的…

当代年轻人搞副业有多野?工资6000,兼职1W...

凌晨12:00&#xff0c;我被同做新媒体的闺蜜小冉震了出来。 这是投稿出去&#xff0c;第10086次没有回声。 那种无力感掐着我的脖子&#xff0c;感觉整个人要窒息了。 写稿&#xff0c;真的好难&#xff0c;我好想放弃。 可是&#xff0c;每月被花呗、信用卡、房租支配的恐惧却…

C++【模板进阶】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、非类型模板参数1.1、使用方法1.2、类型要求1.3、实际例子&#xff1a;arr…

详解:三子棋以及N子棋的实现

三子棋以及N子棋的实现 初始化棋盘打印棋盘玩家下棋电脑下棋判断输赢主函数的实现(test.c)game.c的实现game.h的实现 铁汁们~今天给大家分享一篇三子棋以及N子棋的实现&#xff0c;来吧&#xff0c;开造⛳️ 实现流程&#xff1a; 1.游戏不退出&#xff0c;继续玩下一把&#x…

ML之FE:基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本

ML之FE&#xff1a;基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本 目录 基于波士顿房价数据集利用LiR和LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值 # 1、定义数据…

软件架构复习笔记(张友生教材版本)

考纲(张友生版本软件架构 考试题型&#xff1a; 10*3单选 5*3简答题 5*3设计图&#xff08;含画图&#xff09; 10*2 论述题 10*2综合题 复习以课件为主&#xff0c;书为辅 第一章 (软件危机) &#xff1f; &#xff1f; 构造模型与实现 掌握软件结构体系核心模型 第二章 软件体…

K8s之Pod最小调度单元详解

文章目录 一、Pod概念1、Pod是什么&#xff1f;2、Pod网络共享实现方式3、Pod存储共享方式4、创建Pod整体流程 二、使用YAML文件定义Pod资源1、Pod资源清单YAML文件书写技巧1. YAML语法格式&#xff1a;2. 配置Linux tab缩进两个空格3. 使用kubectl explain帮助命令 2、创建Pod…

ChatGPT客服系统产品-利用chatgpt训练企业知识开发个性化客服系统

打造最前沿的AI智能客服系统&#xff0c;基于自有数据语料&#xff0c;充分运用ChatGPT的大模型自然语言生成能力&#xff0c;定制化客服系统为企业提供自主性的客服服务能力。 ChatGPT如何革新智能客服&#xff1f; 根据当前ChatGPT的使用情况&#xff0c;我们发现未来中短期内…

基于 DDR3 的串口传图帧缓存系统设计实现(fifo2mig_axi )

文章目录 前言一、接口转换模块设计二、fifo2mig_axi 模块二、接口转换模块仿真四、fifo2mig_axi_tb五、仿真展示 前言 结合串口接收模块和 tft 显示屏控制模块&#xff0c;设计一个基于 DDR3 的串口传图帧缓存系统。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面…

次世代烘焙 法线贴图 相关知识

一般将低模 高模的法线贴图实现大量细节模型画面的游戏称为次时代游戏。 次世代常用软件 低模&#xff1a;Maya、3Dmax、Topogun 。 中模&#xff1a;Maya、3Dmax 。 高模&#xff1a;Maya、3Dmax、Zbrush。 UV&#xff1a;Maya、Zbrush、Unfold3D、Uvlayout 。 烘焙&#x…

【观察】华为重构分销伙伴体系,坚持“长期主义”做大分销市场

毫无疑问&#xff0c;随着数字化转型的加速&#xff0c;当前不同类型、不同规模的企业&#xff0c;在面临数字化转型时呈现出了不同的困境和特征&#xff0c;同时对合作伙伴也提出了更高的要求&#xff0c;因此唯有通过“精耕细作”的方式才能更好地加速企业数字化转型的步伐。…

AdaSparse: 自适应稀疏网络的多场景CTR预估建模

▐ 摘要 CTR(Click-through rate)预估一直是推荐/广告领域重要技术之一。近年来&#xff0c;通过统一模型来服务多个场景的预估建模已被证明是一种有效的手段。当前多场景预估技术面临的挑战主要来自两方面&#xff1a;1&#xff09;跨场景泛化能力&#xff1a;尤其对稀疏场景&…

【分布式锁】Redisson分布式锁的使用(推荐使用)

文章目录 前言一、常见分布式锁方案对比二、分布式锁需满足四个条件三、什么是Redisson?官网和官方文档Redisson使用 四、Redisson 分布式重入锁用法Redisson 支持单点模式、主从模式、哨兵模式、集群模式自己先思考下,如果要手写一个分布式锁组件&#xff0c;怎么做&#xff…

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-1

深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践-总结-1 Java内存区域与内存溢出异常运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区 OutOfMemoryError异常Java堆溢出 垃圾收集器与内存分配策略对象是否可以被回收引用计数算法可达性分析算法 Java内存区域…

力库华为机试题练习

1、两数之和 arg [2, 3, 6, 5] target 4 for i in range(len(arg)): other target - arg[i] if other in arg[i1:]: print(i, arg[i1:].index(other)i1) else: print(“输入目标数在该列表中不存在”) 2、回文数 方法一&#xff1a; class Solution: def isPalindrome(sel…

抖音小程序怎么压缩图片?教你使用抖音图片压缩助手

图片压缩是将原始图像的数据量进行减少&#xff0c;从而使其文件大小更小&#xff0c;但尽量保持原有图像质量的一种技术。通过对图片进行压缩&#xff0c;可以降低图片在传输过程中所需的带宽和存储空间&#xff0c;提高网站或应用程序的加载速度和响应速度。 此外&#xff0…

亚马逊云科技将帮助GoPlus Security,助力行业健康发展

Gartner 2022年7月发布的技术成熟度曲线分析报告显示&#xff0c;目前Web3技术已经历了第一波创新高峰期&#xff0c;正在从“创新启动阶段”向“创新泡沫阶段”过渡&#xff0c;技术体系逐步成型&#xff0c;市场热度较高&#xff0c;创业投资活跃。高速增长的背后&#xff0c…

浅谈Hutool工具类

一、Hutool简介 Hutool是一个Java工具类库&#xff0c;它封装了很多常用的Java工具类&#xff0c;如加密解密、文件操作、日期时间处理、Http客户端等。它的目标是让Java开发变得更加简单、高效。 二、Hutool的特点 高效&#xff1a;提供了很多高效的工具类和方法。 简单&…

最全的国内chatGPT大模型企业及产品整理

作者 | gongyouliu 编辑 | gongyouliu 自从去年11月30日openAI发布chatGPT以来&#xff0c;chatGPT引爆了新一轮科技革命。最近很多年都没有哪一项科技进步如chatGPT这般吸引全球的目光。除了媒体的大肆报道&#xff0c;国内外各个科技公司、科研机构、高等院校都在跟进&#x…