rbtree C语言TreeMap

* rbtree.h

cpp 复制代码
/**
 * https://opensource.apple.com/source/network_cmds/network_cmds-481.20.1/unbound/util/rbtree.h.auto.html
 * Red black tree. Implementation taken from NSD 3.0.5, adjusted for use
 * in unbound (memory allocation, logging and so on).
 */

#ifndef UTIL_RBTREE_H_
#define	UTIL_RBTREE_H_

#ifndef  uint8_t
typedef unsigned char uint8_t;
#endif

/**
 * This structure must be the first member of the data structure in
 * the rbtree.  This allows easy casting between an rbnode_t and the
 * user data (poor man's inheritance).
 */
typedef struct rbnode_t rbnode_t;
/**
 * The rbnode_t struct definition.
 */
struct rbnode_t {
    /** parent in rbtree, RBTREE_NULL for root */
    rbnode_t   *parent;
    /** left node (smaller items) */
    rbnode_t   *left;
    /** right node (larger items) */
    rbnode_t   *right;
    /** pointer to sorting key */
    const void *key;
    /** colour of this node */
    uint8_t	    color;
};

/** The nullpointer, points to empty node */
#define	RBTREE_NULL &rbtree_null_node
/** the global empty node */
extern	rbnode_t	rbtree_null_node;

/** An entire red black tree */
typedef struct rbtree_t rbtree_t;
/** definition for tree struct */
struct rbtree_t {
    /** The root of the red-black tree */
    rbnode_t    *root;

    /** The number of the nodes in the tree */
    size_t       count;

    /**
     * Key compare function. <0,0,>0 like strcmp.
     * Return 0 on two NULL ptrs.
     */
    int (*cmp) (const void *, const void *);
};

/**
 * Create new tree (malloced) with given key compare function.
 * @param cmpf: compare function (like strcmp) takes pointers to two keys.
 * @return: new tree, empty.
 */
rbtree_t *rbtree_create(int (*cmpf)(const void *, const void *));

/**
 * Init a new tree (malloced by caller) with given key compare function.
 * @param rbtree: uninitialised memory for new tree, returned empty.
 * @param cmpf: compare function (like strcmp) takes pointers to two keys.
 */
void rbtree_init(rbtree_t *rbtree, int (*cmpf)(const void *, const void *));

/**
 * Insert data into the tree.
 * @param rbtree: tree to insert to.
 * @param data: element to insert.
 * @return: data ptr or NULL if key already present.
 */
rbnode_t *rbtree_insert(rbtree_t *rbtree, rbnode_t *data);

/**
 * Delete element from tree.
 * @param rbtree: tree to delete from.
 * @param key: key of item to delete.
 * @return: node that is now unlinked from the tree. User to delete it.
 * returns 0 if node not present
 */
rbnode_t *rbtree_delete(rbtree_t *rbtree, const void *key);

/**
 * Find key in tree. Returns NULL if not found.
 * @param rbtree: tree to find in.
 * @param key: key that must match.
 * @return: node that fits or NULL.
 */
rbnode_t *rbtree_search(rbtree_t *rbtree, const void *key);

/**
 * Find, but match does not have to be exact.
 * @param rbtree: tree to find in.
 * @param key: key to find position of.
 * @param result: set to the exact node if present, otherwise to element that
 *   precedes the position of key in the tree. NULL if no smaller element.
 * @return: true if exact match in result. Else result points to <= element,
 * or NULL if key is smaller than the smallest key.
 */
int rbtree_find_less_equal(rbtree_t *rbtree, const void *key,
                           rbnode_t **result);

/**
 * Returns first (smallest) node in the tree
 * @param rbtree: tree
 * @return: smallest element or NULL if tree empty.
 */
rbnode_t *rbtree_first(rbtree_t *rbtree);

/**
 * Returns last (largest) node in the tree
 * @param rbtree: tree
 * @return: largest element or NULL if tree empty.
 */
rbnode_t *rbtree_last(rbtree_t *rbtree);

/**
 * Returns next larger node in the tree
 * @param rbtree: tree
 * @return: next larger element or NULL if no larger in tree.
 */
rbnode_t *rbtree_next(rbnode_t *rbtree);

/**
 * Returns previous smaller node in the tree
 * @param rbtree: tree
 * @return: previous smaller element or NULL if no previous in tree.
 */
rbnode_t *rbtree_previous(rbnode_t *rbtree);

/**
 * Call with node=variable of struct* with rbnode_t as first element.
 * with type is the type of a pointer to that struct.
 */
#define RBTREE_FOR(node, type, rbtree) \
	for(node=(type)rbtree_first(rbtree); \
		(rbnode_t*)node != RBTREE_NULL; \
		node = (type)rbtree_next((rbnode_t*)node))

/**
 * Call function for all elements in the redblack tree, such that
 * leaf elements are called before parent elements. So that all
 * elements can be safely free()d.
 * Note that your function must not remove the nodes from the tree.
 * Since that may trigger rebalances of the rbtree.
 * @param tree: the tree
 * @param func: function called with element and user arg.
 * 	The function must not alter the rbtree.
 * @param arg: user argument.
 */
void traverse_postorder(rbtree_t* tree, void (*func)(rbnode_t*, void*),
                        void* arg);

#endif /* UTIL_RBTREE_H_ */

* rbtree.c

cpp 复制代码
/* https://opensource.apple.com/source/network_cmds/network_cmds-481.20.1/unbound/util/rbtree.c.auto.html */
#include <stdio.h> /* stderr */
#include <stdlib.h> /* malloc */
#include <assert.h>
#include "rbtree.h"

#define log_assert assert

/** Node colour black */
#define	BLACK	0
/** Node colour red */
#define	RED	1

/** the NULL node, global alloc */
rbnode_t	rbtree_null_node = {
        RBTREE_NULL,		/* Parent.  */
        RBTREE_NULL,		/* Left.  */
        RBTREE_NULL,		/* Right.  */
        NULL,			/* Key.  */
        BLACK			/* Color.  */
};

/** rotate subtree left (to preserve redblack property) */
static void rbtree_rotate_left(rbtree_t *rbtree, rbnode_t *node);
/** rotate subtree right (to preserve redblack property) */
static void rbtree_rotate_right(rbtree_t *rbtree, rbnode_t *node);
/** Fixup node colours when insert happened */
static void rbtree_insert_fixup(rbtree_t *rbtree, rbnode_t *node);
/** Fixup node colours when delete happened */
static void rbtree_delete_fixup(rbtree_t* rbtree, rbnode_t* child, rbnode_t* child_parent);

/*
 * Creates a new red black tree, intializes and returns a pointer to it.
 *
 * Return NULL on failure.
 *
 */
rbtree_t *
rbtree_create (int (*cmpf)(const void *, const void *))
{
    rbtree_t *rbtree;

    /* Allocate memory for it */
    rbtree = (rbtree_t *) malloc(sizeof(rbtree_t));
    if (!rbtree) {
        return NULL;
    }

    /* Initialize it */
    rbtree_init(rbtree, cmpf);

    return rbtree;
}

void
rbtree_init(rbtree_t *rbtree, int (*cmpf)(const void *, const void *))
{
    /* Initialize it */
    rbtree->root = RBTREE_NULL;
    rbtree->count = 0;
    rbtree->cmp = cmpf;
}

/*
 * Rotates the node to the left.
 *
 */
static void
rbtree_rotate_left(rbtree_t *rbtree, rbnode_t *node)
{
    rbnode_t *right = node->right;
    node->right = right->left;
    if (right->left != RBTREE_NULL)
        right->left->parent = node;

    right->parent = node->parent;

    if (node->parent != RBTREE_NULL) {
        if (node == node->parent->left) {
            node->parent->left = right;
        } else  {
            node->parent->right = right;
        }
    } else {
        rbtree->root = right;
    }
    right->left = node;
    node->parent = right;
}

/*
 * Rotates the node to the right.
 *
 */
static void
rbtree_rotate_right(rbtree_t *rbtree, rbnode_t *node)
{
    rbnode_t *left = node->left;
    node->left = left->right;
    if (left->right != RBTREE_NULL)
        left->right->parent = node;

    left->parent = node->parent;

    if (node->parent != RBTREE_NULL) {
        if (node == node->parent->right) {
            node->parent->right = left;
        } else  {
            node->parent->left = left;
        }
    } else {
        rbtree->root = left;
    }
    left->right = node;
    node->parent = left;
}

static void
rbtree_insert_fixup(rbtree_t *rbtree, rbnode_t *node)
{
    rbnode_t	*uncle;

    /* While not at the root and need fixing... */
    while (node != rbtree->root && node->parent->color == RED) {
        /* If our parent is left child of our grandparent... */
        if (node->parent == node->parent->parent->left) {
            uncle = node->parent->parent->right;

            /* If our uncle is red... */
            if (uncle->color == RED) {
                /* Paint the parent and the uncle black... */
                node->parent->color = BLACK;
                uncle->color = BLACK;

                /* And the grandparent red... */
                node->parent->parent->color = RED;

                /* And continue fixing the grandparent */
                node = node->parent->parent;
            } else {				/* Our uncle is black... */
                /* Are we the right child? */
                if (node == node->parent->right) {
                    node = node->parent;
                    rbtree_rotate_left(rbtree, node);
                }
                /* Now we're the left child, repaint and rotate... */
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                rbtree_rotate_right(rbtree, node->parent->parent);
            }
        } else {
            uncle = node->parent->parent->left;

            /* If our uncle is red... */
            if (uncle->color == RED) {
                /* Paint the parent and the uncle black... */
                node->parent->color = BLACK;
                uncle->color = BLACK;

                /* And the grandparent red... */
                node->parent->parent->color = RED;

                /* And continue fixing the grandparent */
                node = node->parent->parent;
            } else {				/* Our uncle is black... */
                /* Are we the right child? */
                if (node == node->parent->left) {
                    node = node->parent;
                    rbtree_rotate_right(rbtree, node);
                }
                /* Now we're the right child, repaint and rotate... */
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                rbtree_rotate_left(rbtree, node->parent->parent);
            }
        }
    }
    rbtree->root->color = BLACK;
}

#define fatal_exit(fmt, ...) do { \
    fprintf(stderr, fmt, ##__VA_ARGS__); \
    exit(0);  \
} while (0);

/*
 * Inserts a node into a red black tree.
 *
 * Returns NULL on failure or the pointer to the newly added node
 * otherwise.
 */
rbnode_t *
rbtree_insert (rbtree_t *rbtree, rbnode_t *data)
{
    /* XXX Not necessary, but keeps compiler quiet... */
    int r = 0;

    /* We start at the root of the tree */
    rbnode_t	*node = rbtree->root;
    rbnode_t	*parent = RBTREE_NULL;

    /* fptr_ok(fptr_whitelist_rbtree_cmp(rbtree->cmp)); */
    /* Lets find the new parent... */
    while (node != RBTREE_NULL) {
        /* Compare two keys, do we have a duplicate? */
        if ((r = rbtree->cmp(data->key, node->key)) == 0) {
            return NULL;
        }
        parent = node;

        if (r < 0) {
            node = node->left;
        } else {
            node = node->right;
        }
    }

    /* Initialize the new node */
    data->parent = parent;
    data->left = data->right = RBTREE_NULL;
    data->color = RED;
    rbtree->count++;

    /* Insert it into the tree... */
    if (parent != RBTREE_NULL) {
        if (r < 0) {
            parent->left = data;
        } else {
            parent->right = data;
        }
    } else {
        rbtree->root = data;
    }

    /* Fix up the red-black properties... */
    rbtree_insert_fixup(rbtree, data);

    return data;
}

/*
 * Searches the red black tree, returns the data if key is found or NULL otherwise.
 *
 */
rbnode_t *
rbtree_search (rbtree_t *rbtree, const void *key)
{
    rbnode_t *node;

    if (rbtree_find_less_equal(rbtree, key, &node)) {
        return node;
    } else {
        return NULL;
    }
}

/** helpers for delete: swap node colours */
static void swap_int8(uint8_t* x, uint8_t* y)
{
    uint8_t t = *x; *x = *y; *y = t;
}

/** helpers for delete: swap node pointers */
static void swap_np(rbnode_t** x, rbnode_t** y)
{
    rbnode_t* t = *x; *x = *y; *y = t;
}

/** Update parent pointers of child trees of 'parent' */
static void change_parent_ptr(rbtree_t* rbtree, rbnode_t* parent, rbnode_t* old, rbnode_t* new)
{
    if(parent == RBTREE_NULL)
    {
        log_assert(rbtree->root == old);
        if(rbtree->root == old) rbtree->root = new;
        return;
    }
    log_assert(parent->left == old || parent->right == old
               || parent->left == new || parent->right == new);
    if(parent->left == old) parent->left = new;
    if(parent->right == old) parent->right = new;
}
/** Update parent pointer of a node 'child' */
static void change_child_ptr(rbnode_t* child, rbnode_t* old, rbnode_t* new)
{
    if(child == RBTREE_NULL) return;
    log_assert(child->parent == old || child->parent == new);
    if(child->parent == old) child->parent = new;
}

rbnode_t*
rbtree_delete(rbtree_t *rbtree, const void *key)
{
    rbnode_t *to_delete;
    rbnode_t *child;
    if((to_delete = rbtree_search(rbtree, key)) == 0) return 0;
    rbtree->count--;

    /* make sure we have at most one non-leaf child */
    if(to_delete->left != RBTREE_NULL && to_delete->right != RBTREE_NULL)
    {
        /* swap with smallest from right subtree (or largest from left) */
        rbnode_t *smright = to_delete->right;
        while(smright->left != RBTREE_NULL)
            smright = smright->left;
        /* swap the smright and to_delete elements in the tree,
         * but the rbnode_t is first part of user data struct
         * so cannot just swap the keys and data pointers. Instead
         * readjust the pointers left,right,parent */

        /* swap colors - colors are tied to the position in the tree */
        swap_int8(&to_delete->color, &smright->color);

        /* swap child pointers in parents of smright/to_delete */
        change_parent_ptr(rbtree, to_delete->parent, to_delete, smright);
        if(to_delete->right != smright)
            change_parent_ptr(rbtree, smright->parent, smright, to_delete);

        /* swap parent pointers in children of smright/to_delete */
        change_child_ptr(smright->left, smright, to_delete);
        change_child_ptr(smright->left, smright, to_delete);
        change_child_ptr(smright->right, smright, to_delete);
        change_child_ptr(smright->right, smright, to_delete);
        change_child_ptr(to_delete->left, to_delete, smright);
        if(to_delete->right != smright)
            change_child_ptr(to_delete->right, to_delete, smright);
        if(to_delete->right == smright)
        {
            /* set up so after swap they work */
            to_delete->right = to_delete;
            smright->parent = smright;
        }

        /* swap pointers in to_delete/smright nodes */
        swap_np(&to_delete->parent, &smright->parent);
        swap_np(&to_delete->left, &smright->left);
        swap_np(&to_delete->right, &smright->right);

        /* now delete to_delete (which is at the location where the smright previously was) */
    }
    log_assert(to_delete->left == RBTREE_NULL || to_delete->right == RBTREE_NULL);

    if(to_delete->left != RBTREE_NULL) child = to_delete->left;
    else child = to_delete->right;

    /* unlink to_delete from the tree, replace to_delete with child */
    change_parent_ptr(rbtree, to_delete->parent, to_delete, child);
    change_child_ptr(child, to_delete, to_delete->parent);

    if(to_delete->color == RED)
    {
        /* if node is red then the child (black) can be swapped in */
    }
    else if(child->color == RED)
    {
        /* change child to BLACK, removing a RED node is no problem */
        if(child!=RBTREE_NULL) child->color = BLACK;
    }
    else rbtree_delete_fixup(rbtree, child, to_delete->parent);

    /* unlink completely */
    to_delete->parent = RBTREE_NULL;
    to_delete->left = RBTREE_NULL;
    to_delete->right = RBTREE_NULL;
    to_delete->color = BLACK;
    return to_delete;
}

static void rbtree_delete_fixup(rbtree_t* rbtree, rbnode_t* child, rbnode_t* child_parent)
{
    rbnode_t* sibling;
    int go_up = 1;

    /* determine sibling to the node that is one-black short */
    if(child_parent->right == child) sibling = child_parent->left;
    else sibling = child_parent->right;

    while(go_up)
    {
        if(child_parent == RBTREE_NULL)
        {
            /* removed parent==black from root, every path, so ok */
            return;
        }

        if(sibling->color == RED)
        {	/* rotate to get a black sibling */
            child_parent->color = RED;
            sibling->color = BLACK;
            if(child_parent->right == child)
                rbtree_rotate_right(rbtree, child_parent);
            else	rbtree_rotate_left(rbtree, child_parent);
            /* new sibling after rotation */
            if(child_parent->right == child) sibling = child_parent->left;
            else sibling = child_parent->right;
        }

        if(child_parent->color == BLACK
           && sibling->color == BLACK
           && sibling->left->color == BLACK
           && sibling->right->color == BLACK)
        {	/* fixup local with recolor of sibling */
            if(sibling != RBTREE_NULL)
                sibling->color = RED;

            child = child_parent;
            child_parent = child_parent->parent;
            /* prepare to go up, new sibling */
            if(child_parent->right == child) sibling = child_parent->left;
            else sibling = child_parent->right;
        }
        else go_up = 0;
    }

    if(child_parent->color == RED
       && sibling->color == BLACK
       && sibling->left->color == BLACK
       && sibling->right->color == BLACK)
    {
        /* move red to sibling to rebalance */
        if(sibling != RBTREE_NULL)
            sibling->color = RED;
        child_parent->color = BLACK;
        return;
    }
    log_assert(sibling != RBTREE_NULL);

    /* get a new sibling, by rotating at sibling. See which child
       of sibling is red */
    if(child_parent->right == child
       && sibling->color == BLACK
       && sibling->right->color == RED
       && sibling->left->color == BLACK)
    {
        sibling->color = RED;
        sibling->right->color = BLACK;
        rbtree_rotate_left(rbtree, sibling);
        /* new sibling after rotation */
        if(child_parent->right == child) sibling = child_parent->left;
        else sibling = child_parent->right;
    }
    else if(child_parent->left == child
            && sibling->color == BLACK
            && sibling->left->color == RED
            && sibling->right->color == BLACK)
    {
        sibling->color = RED;
        sibling->left->color = BLACK;
        rbtree_rotate_right(rbtree, sibling);
        /* new sibling after rotation */
        if(child_parent->right == child) sibling = child_parent->left;
        else sibling = child_parent->right;
    }

    /* now we have a black sibling with a red child. rotate and exchange colors. */
    sibling->color = child_parent->color;
    child_parent->color = BLACK;
    if(child_parent->right == child)
    {
        log_assert(sibling->left->color == RED);
        sibling->left->color = BLACK;
        rbtree_rotate_right(rbtree, child_parent);
    }
    else
    {
        log_assert(sibling->right->color == RED);
        sibling->right->color = BLACK;
        rbtree_rotate_left(rbtree, child_parent);
    }
}

int
rbtree_find_less_equal(rbtree_t *rbtree, const void *key, rbnode_t **result)
{
    int r;
    rbnode_t *node;

    log_assert(result);

    /* We start at root... */
    node = rbtree->root;

    *result = NULL;
    /* fptr_ok(fptr_whitelist_rbtree_cmp(rbtree->cmp)); */

    /* While there are children... */
    while (node != RBTREE_NULL) {
        r = rbtree->cmp(key, node->key);
        if (r == 0) {
            /* Exact match */
            *result = node;
            return 1;
        }
        if (r < 0) {
            node = node->left;
        } else {
            /* Temporary match */
            *result = node;
            node = node->right;
        }
    }
    return 0;
}

/*
 * Finds the first element in the red black tree
 *
 */
rbnode_t *
rbtree_first (rbtree_t *rbtree)
{
    rbnode_t *node;

    for (node = rbtree->root; node->left != RBTREE_NULL; node = node->left);
    return node;
}

rbnode_t *
rbtree_last (rbtree_t *rbtree)
{
    rbnode_t *node;

    for (node = rbtree->root; node->right != RBTREE_NULL; node = node->right);
    return node;
}

/*
 * Returns the next node...
 *
 */
rbnode_t *
rbtree_next (rbnode_t *node)
{
    rbnode_t *parent;

    if (node->right != RBTREE_NULL) {
        /* One right, then keep on going left... */
        for (node = node->right; node->left != RBTREE_NULL; node = node->left);
    } else {
        parent = node->parent;
        while (parent != RBTREE_NULL && node == parent->right) {
            node = parent;
            parent = parent->parent;
        }
        node = parent;
    }
    return node;
}

rbnode_t *
rbtree_previous(rbnode_t *node)
{
    rbnode_t *parent;

    if (node->left != RBTREE_NULL) {
        /* One left, then keep on going right... */
        for (node = node->left; node->right != RBTREE_NULL; node = node->right);
    } else {
        parent = node->parent;
        while (parent != RBTREE_NULL && node == parent->left) {
            node = parent;
            parent = parent->parent;
        }
        node = parent;
    }
    return node;
}

/** recursive descent traverse */
static void
traverse_post(void (*func)(rbnode_t*, void*), void* arg, rbnode_t* node)
{
    if(!node || node == RBTREE_NULL)
        return;
    /* recurse */
    traverse_post(func, arg, node->left);
    traverse_post(func, arg, node->right);
    /* call user func */
    (*func)(node, arg);
}

void
traverse_postorder(rbtree_t* tree, void (*func)(rbnode_t*, void*), void* arg)
{
    traverse_post(func, arg, tree->root);
}

* main.c

cpp 复制代码
#include <stdio.h>
#include <string.h> /* strncpy */
#include <stdlib.h> /* malloc */
#include "rbtree.h"

typedef struct {
    char s[32];
    int d;
} pair_si_t;

int str_cmp(const void *p1, const void *p2) {
    const pair_si_t *t1 = (pair_si_t *)p1;
    const pair_si_t *t2 = (pair_si_t *)p2;
    const char *s1, *s2;

    for (s1 = t1->s, s2 = t2->s; *s1 != '\0' && *s2 != '\0'; s1++, s2++) {
        if (*s1 - *s2 != 0) {
            return *s1-*s2;
        }
    }
    if (*s1 != 0x00) { return *s1; }
    if (*s2 != 0x00) { return 0-*s2;}
    return 0;
}

void traverse_handler(rbnode_t *node, void *arg) {
    pair_si_t *t = (pair_si_t *)node->key;
    free(t);
    free(node);
}

int main() {
    rbnode_t *n1, *n2, *np;
    pair_si_t *pair;
    rbtree_t *treeMap = NULL;
    pair_si_t key;
    rbnode_t *node;

    treeMap = rbtree_create(str_cmp);
    rbtree_init(treeMap, str_cmp);

    n1 = (rbnode_t *)malloc(sizeof(rbnode_t));
    pair = (pair_si_t *)malloc(sizeof(pair_si_t));
    strncpy(pair->s, "Rajib Sarma", 32);
    pair->d = 100;
    n1->key = pair;
    rbtree_insert(treeMap, n1);

    n2 = (rbnode_t *)malloc(sizeof(rbnode_t));
    pair = (pair_si_t *)malloc(sizeof(pair_si_t));
    strncpy(pair->s, "Sazid Ahmed", 32);
    pair->d = 200;
    n2->key = pair;
    rbtree_insert(treeMap, n2);

    np = (rbnode_t *)malloc(sizeof(rbnode_t));
    pair = (pair_si_t *)malloc(sizeof(pair_si_t));
    strncpy(pair->s, "v-zhanm", 32);
    pair->d = 247;
    np->key = pair;
    rbtree_insert(treeMap, np);

    strncpy(key.s, "Sazid Ahmed", 32);
    node = rbtree_search(treeMap, &key);
    printf("%d\n", ((pair_si_t *)node->key)->d); /* 200 */

    traverse_postorder(treeMap, traverse_handler, NULL);

    return 0;
}

编译运行

bash 复制代码
mkdir cmake-build-debug
cd cmake-build-debug
cmake ..
make
./rbtree

TreeMap中放入了键值对

"Rajib Sarma" => 100

"Sazid Ahmed" => 200

"v-zhanm" => 247

所以根据"Sazid Ahmed" 找到值为200

相关推荐
lb36363636361 小时前
分享一下arr的意义(c基础)(必看)(牢记)
c语言·知识点
Swift社区2 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht2 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht2 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20242 小时前
Swift 数组
开发语言
南东山人3 小时前
一文说清:C和C++混合编程
c语言·c++
stm 学习ing3 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
LNTON羚通4 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
湫ccc4 小时前
《Python基础》之字符串格式化输出
开发语言·python