红黑树(附完整C代码)

版权声明:原创不易,转载请注明转自weewqrer 红黑树


红黑树简介

首先红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK。通过对一条从根节点到NIL叶节点(指空结点或者下面说的哨兵)的简单路径上各个结点在颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。

用途

红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。对于查找、插入、删除、最大、最小等动态操作的时间复杂度为O(lgn).常见的用途有以下几种:

  • STL(标准模板库)中在set map是基于红黑树实现的。
  • Java中在TreeMap使用的也是红黑树。
  • epoll在内核中的实现,用红黑树管理事件块。
  • linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块

红黑树 VS AVL树

常见的平衡树有红黑树和AVL平衡树,为什么STL和linux都使用红黑树作为平衡树的实现?大概有以下几个原因:

  1. 从实现细节上来讲,如果插入一个结点引起了树的不平衡,AVL树和红黑树都最多需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度
  2. 从两种平衡树对平衡的要求来讲,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
  3. 总体来说,RB-tree的统计性能是高于AVL的。

关于参考书

《算法导论》和《数据结构与算法分析》是大家常用的两本算法书,针对红黑树这一章,这两本书上也都有,但是二者从数据结构到使用的方法上都不一样,这里我推荐使用《算法导论》。有以下几个原因:

  • 《数据结构与算法分析》中使用的数据结构没有保存父亲结点,所以在调用旋转函数时需要用函数返回值来保持上下结点的连接,这在avl树中显得很简洁,因为在avl中的情况比较简单,但是在红黑树中涉及到了祖祖父、祖父、父亲、儿子、四代结点,按照这本书上的方法得保存GGP、GP、P、X四个全局变量的值,在更新它们的指向时非常容易搞混。
  • 《数据结构与算法分析》没有实现删除操作,只是描述了实现的方法,按照这上面的方法我也做了实现,最后发现代码非常长,至少150+,不如《算法导论》中的简洁。
  • 另外,《算法导论》中描述的方法比较完整,思路严谨。

红黑树详解

红黑树性质

红黑树是每个结点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 列表项结点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL结点)。
  4. 每个红色结点必须有两个黑色的子结点。(从每个叶子到根的所有路径上不能有两个连续的红色结点。)
  5. 从任一结点到其每个叶子的所有简单路径都包含相同数目的黑色结点。

    为了便于处理红黑树中的边界情况,使用一个哨兵来代表所有的NIL结点,也就是说所有指向NIL的指针都指向哨兵T.nil。

红黑树数据结构

typedef enum ColorType {RED, BLACK} ColorType;
typedef struct rbt_t{
    int key;
    rbt_t * left;
    rbt_t * right;
    rbt_t * p;
    ColorType color;
}rbt_t;

typedef struct rbt_root_t{
    rbt_t* root;
    rbt_t* nil;
}rbt_root_t;

/*
*@brief rbt_init 初始化
*/
rbt_root_t* rbt_init(void){
    rbt_root_t* T;

    T = (rbt_root_t*)malloc(sizeof(rbt_root_t));
    assert( NULL != T);

    //用一个哨兵代表NIL。
    T->nil = (rbt_t*)malloc(sizeof(rbt_t));
    assert(NULL != T->nil);
    T->nil->color = BLACK;
    T->nil->left = T->nil->right = NULL;
    T->nil->p = NULL;

    T->root = T->nil;
    return T;
}

红黑树旋转

搜索树操作inert和delete在含有n个关键字的红黑树上,运行花费时间为O(lgn)。由于这两个操作对树做了修改,所以有可能违反上面的性质,所以需要改变树中某些结点的颜色以及指针的结构。

指针结构的修改是通过旋转来完成的,这是一种能保持二叉搜索树性质的局部操作。一种有两种旋转操作,如下图所示,都在O(1)时间内完成。

这里要求x,y都不是T.nil。

c代码:

/*
*@brief rbt_left_rotate
*@param[in] T 树根
*@param[in] x 要进行旋转的节点
*/
void rbt_left_rotate( rbt_root_t* T, rbt_t* x){
    rbt_t* y = x->right;

    x->right = y->left;
    if(x->right != T->nil)//更新某结点的父亲时,要确定此结点不是T.nil
        x->right->p = x;

    y->p = x->p;
    if(x->p == T->nil){//如果x以前是树根,那么现在树根易主了
        T->root = y;
    }else if(y->key < y->p->key)
        y->p->left = y;
    else
        y->p->right = y;

    y->left = x;
    x->p = y;
}
/*
*@brief rbt_right_rotate
*@param[in] 树根
*@param[in] 要进行旋转的节点
*/
void rbt_right_rotate( rbt_root_t* T, rbt_t* x){
    rbt_t * y = x->left;
    x->left = y->right;

    if(T->nil != x->left)
        x->left->p = x;

    y->p = x->p;
    if(y->p == T->nil)
        T->root = y;
    else if(y->key < y->p->key)
        y->p->left= y;
    else
        y->p->right = y;

    y->right = x;
    x->p = y;
}

红黑树插入

在红黑树中插入一个元素,跟在二叉搜索树中插入一个元素一样,只是插入一个元素之后,有可能使得这个树不再平衡,所以要再处理一下,使之重新回到平衡状态。

图中N为新插入的结点,U为它的叔叔。

插入操作的关键在于以下几点:

  1. 新插入的节点一定是红色的。(如果是黑色的,会破坏条件5)
  2. 如果新插入的节点的父亲是黑色的,则没有破坏任何性质,那么插入完成。
  3. 如果插入节点的父节点是红色, 破坏了性质4. 故插入算法就是通过重新着色或旋转, 来维持性质

插入一个红色节点要处理这么几种情况:

此时要记住一件事事情,插入时总是要考虑它的叔叔,删除时总要考虑它的兄弟。而且插入时维护的主要是颜色(性质4),而删除时维护的主要是黑色结点数量(性质5)

情况1:

N为红,P为红(GP一定为黑),U为红。

下面会说明我们可以通过一种特殊的处理把这种情况避免掉。

那为什么要避免这种情况呢?因为这种情况一般是通过颜色翻转来处理的,也就是把P U换成黑色,把GP抱成红色,但是GP的父亲如果是红色的话又会违反红黑树的性质。

情况2:

N,P都为红(GP一定为黑),U为黑

根据境像,情况2可细分为4种情况,如下:

但是这四种具体情况的处理手法是一样的,都是通过颜色翻转与旋转来处理的。下面我们通过情况2.1和2.2来说明一下处理方法:

情况2.2通过调用left_rotate(T,p)变成情况2.1;

情况2.1通过交换GP与P的颜色,然后调用right_rotate(T,GP),此时不再违反任何性质。

情况2.3和2.4分别是2.1和2.2的境像。

如何避免情况1

令X = T.root,在向下遍历的过程中,我们如果遇到X.right.color == x.left.color == RED时我们将x与它孩子的颜色翻转,即把x涂成红色,把x.right和x.left涂成黑色。

如果x的父亲为黑色,没有违反性质;如果x的父亲为红色,那么可以把x当成新插入的红色结点N,那么只需要处理情况2即可。

至此,插入完成,具体实现可以看完整代码部分,代码也有必要的注释。

红黑树删除

还是上面同样那句话,插入时总是要考虑它的叔叔,删除时总要考虑它的兄弟。而且插入时维护的主要是颜色(性质4),而删除时维护的主要是黑色结点数量(性质5)。

写删除的代码花费了我大概一天多的时间,因为我总是试图找出一种比《数据结构与算法分析》上更清晰,比《算法导论》中更简单的方法,但是失败了(⊙▽⊙).

实际上删除操作也没有那么难,如果要删除 z 结点,那么就让 z 的后继来代替 z 的位置即可。 如果z是红色的,那么操作便完成了,删除一个红色结点没有违反任何性质。但如果z是黑色的,那么我们删除一个黑色结点,便违反了性质5,造成黑色结点数量的左右不平衡。只要分析出删除一个黑色结点会遇到哪些情况即可。

首先找到要删除的结点,我们定义它为 z 。

如果 z 的两个孩子都不是T.nil,那么我们在 z 的右子树中找出最小的结点 m,把 m 结点的值赋给 z (而不是把m移植到z的位置,也就不用考虑颜色问题,这一点是比《算法导论》中要简单的),那么我们要删除的结点就成为 m 了。m 肯定没有左孩子。令 z 重新指向 m .

找到要删除的结点 z 之后,我们用 z 的孩子(记作 x )来取代 z的位置(即使z.right == T.nil) 。rbt_transplant(T,z,z.right);

此时用到下面一段代码,实现用v代替u

void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v){
    if(u->p == T->nil)
        T->root = v;
    else if(u == u->p->left)
        u->p->left =v;
    else
        u->p->right = v;
    v->p = u->p;//即使v是T.nil也可以执行这一行
}

到目前为止,如果要被删除的 z 结点是红色的,那么程序就结束了。但是如果 z 是黑色的,所以删除z之后z这边少了一个黑色结点,会违反性质5,此时分为4种情况(x 是左孩子 和 x 是右孩子分别有4种情况,现在只讨论x是左孩子的情况):

情况1

x的兄弟w是红色的,那么它们的父亲、w的孩子都是黑色的。

这种情况下只能做一种无损的操作,通过交换颜色再旋转,对树的性质不会产生影响,所以从根到x结点的路径上少的一个黑色结点也不会补上。

交换p与w的颜色,再对p进行左旋操之后,x的新兄弟就为黑色,情况变成了2 3 4中的一种.

图中x为白色,表示我们不关心x的颜色。

情况2

x的兄弟w是黑色,而且w的两个孩子都是黑色。

此时可以细分为2种情况,但无论哪种情况,我们要进行的操作都是一样的,都是将w涂成红色,将p涂成黑色。

如果是情况2.1(有可能由情况1发展过来的),由于上述操作为x那边补上了一个黑色(从根到x在路径上多了一个黑色结点),此时红黑树性质5得到满足,程序结束。

如果是情况2.2, 经过上述操作后,P的右子树也少了一个黑色结点,令P作为新的X继续循环。

情况3

W是黑色有,w在左孩子是红色的,W的右孩子是黑色的。

通过交换L与W的颜色,再对W进行右旋操作。这种操作也不会对红黑树性质产生影响,此时进入情况4,我们会看到通过情况4中的操作最终使红黑树性质得到满足,结束程序。

图中最后边的R结点没有画出来,因为我们不关心它了

情况4

w是黑色的,w的右孩子是红色的。

把w涂成p的颜色,把P涂成黑色,R涂成黑色,左旋P。此时从根到x在路径上多了一个黑色结点,程序结束。

具体实现代码见下面。

完整代码(C)

#include<stdafx.h>
#include<malloc.h>
#include <assert.h>

//版权声明:原创不易,转载请注明转自[weewqrer 红黑树](http://blog.csdn.net/weewqrer/article/details/51866488)

//红黑树
typedef enum ColorType {RED, BLACK} ColorType;
typedef struct rbt_t{
    int key;
    rbt_t * left;
    rbt_t * right;
    rbt_t * p;
    ColorType color;
}rbt_t;

typedef struct rbt_root_t{
    rbt_t* root;
    rbt_t* nil;
}rbt_root_t;

//函数声明
rbt_root_t* rbt_init(void);
static void rbt_handleReorient(rbt_root_t* T, rbt_t* x, int k);
rbt_root_t* rbt_insert(rbt_root_t* &T, int k);
rbt_root_t* rbt_delete(rbt_root_t* &T, int k);

void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v);

static void rbt_left_rotate( rbt_root_t* T, rbt_t* x);
static void rbt_right_rotate( rbt_root_t* T, rbt_t* x);

void rbt_inPrint(const rbt_root_t* T, rbt_t* t);
void rbt_prePrint(const rbt_t * T, rbt_t* t);
void rbt_print(const rbt_root_t* T);

static rbt_t* rbt_findMin(rbt_root_t * T, rbt_t* t);
static rbt_t* rbt_findMax(rbt_root_t * T, rbt_t* t);

static rbt_t* rbt_findMin(rbt_root_t * T, rbt_t* t){
    if(t == T->nil) return T->nil;

    while(t->left != T->nil)
        t = t->left;
    return t;
}
static rbt_t* rbt_findMax(rbt_root_t * T, rbt_t* t){
    if(t == T->nil) return T->nil;

    while(t->right != T->nil)
        t = t->right;
    return t;
}
/*
*@brief rbt_init 初始化
*/
rbt_root_t* rbt_init(void){
    rbt_root_t* T;

    T = (rbt_root_t*)malloc(sizeof(rbt_root_t));
    assert( NULL != T);

    T->nil = (rbt_t*)malloc(sizeof(rbt_t));
    assert(NULL != T->nil);
    T->nil->color = BLACK;
    T->nil->left = T->nil->right = NULL;
    T->nil->p = NULL;

    T->root = T->nil;

    return T;
}

/*
*@brief rbt_handleReorient  内部函数 由rbt_insert调用
*      在两种情况下调用这个函数:
* 1 x有连个红色儿子
* 2 x为新插入的结点
*
*/
void rbt_handleReorient(rbt_root_t* T, rbt_t* x, int k){

    //在第一种情况下,进行颜色翻转; 在第二种情况下,相当于对新插入的x点初始化
    x->color = RED;
    x->left->color = x->right->color = BLACK;

    //如果x.p为红色,那么x.p一定不是根,x.p.p一定不是T.nil,而且为黑色
    if(  RED == x->p->color){
        x->p->p->color = RED;//此时x, p, x.p.p都为红

        if(x->p->key < x->p->p->key){
            if(k > x->p->key){
                x->color = BLACK;//小心地处理颜色
                rbt_left_rotate(T,x->p);
                rbt_right_rotate(T,x->p);
            }else{
                x->p->color = BLACK;//小心地处理颜色
                rbt_right_rotate(T,x->p->p);
            }

        }else{
            if(k < x->p->key){
                x->color = BLACK;
                rbt_right_rotate(T,x->p);
                rbt_left_rotate(T,x->p);
            }else{
                x->p->color = BLACK;
                rbt_left_rotate(T,x->p->p);
            }

        }
    }

    T->root->color = BLACK;//无条件令根为黑色
}
/*
*@brief brt_insert 插入
*1 新插入的结点一定是红色的,如果是黑色的,会破坏条件4(每个结点到null叶结点的每条路径有同样数目的黑色结点)
*2 如果新插入的结点的父亲是黑色的,那么插入完成。 如果父亲是红色的,那么做一个旋转即可。(前提是叔叔是黑色的)
*3 我们这个插入要保证其叔叔是黑色的。也就是在x下沉过程中,不允许存在两个红色结点肩并肩。
*/
rbt_root_t* rbt_insert(rbt_root_t* &T, int k){

    rbt_t * x, *p;
    x = T->root;
    p = x;

    //令x下沉到叶子上,而且保证一路上不会有同时为红色的兄弟
    while( x != T->nil){
        //
        //保证没有一对兄弟同时为红色, 为什么要这么做?
        if(x != T->nil)
            if(x->left->color == RED && x->right->color == RED)
                rbt_handleReorient(T,x,k);

        p = x;
        if(k<x->key)
            x = x->left;
        else if(k>x->key)
            x = x->right;
        else{
            printf("\n%d已存在\n",k);
            return T;
        }

    }

    //为x分配空间,并对其进行初始化
    x = (rbt_t *)malloc(sizeof(rbt_t));
    assert(NULL != x);
    x->key = k;
    x->color = RED;
    x->left = x->right = T->nil;
    x->p = p;

    //让x的父亲指向x
    if(T->root == T->nil)
        T->root = x;
    else if(k < p->key)
        p->left = x;
    else
        p->right = x;

    //因为一路下来,如果x的父亲是红色,那么x的叔叔肯定不是红色了,这个时候只需要做一下翻转即可。
    rbt_handleReorient(T,x,k);

    return T;
}
void rbt_transplant(rbt_root_t* T, rbt_t* u, rbt_t* v){
    if(u->p == T->nil)
        T->root = v;
    else if(u == u->p->left)
        u->p->left =v;
    else
        u->p->right = v;
    v->p = u->p;
}
/*
*@brief rbt_delete 从树中删除 k
*
*
*/
rbt_root_t* rbt_delete(rbt_root_t* &T, int k){
    assert(T != NULL);
    if(NULL == T->root) return T;

    //找到要被删除的叶子结点
    rbt_t * toDelete = T->root;
    rbt_t * x;

    //找到值为k的结点
    while(toDelete != T->nil && toDelete->key != k){
        if(k<toDelete->key)
            toDelete = toDelete->left;
        else if(k>toDelete->key)
            toDelete = toDelete->right;
    }

    if(toDelete == T->nil){
        printf("\n%d 不存在\n",k);
        return T;
    }

    //如果两个孩子,就找到右子树中最小的代替, alternative最多有一个右孩子
    if(toDelete->left != T->nil && toDelete->right != T->nil){
        rbt_t* alternative = rbt_findMin(T, toDelete->right);
        k = toDelete->key = alternative->key;
        toDelete = alternative;
    }

    if(toDelete->left == T->nil){
        x = toDelete->right;
        rbt_transplant(T,toDelete,toDelete->right);
    }else if(toDelete->right == T->nil){
        x = toDelete->left;
        rbt_transplant(T,toDelete,toDelete->left);
    }

    if(toDelete->color == BLACK){
        //x不是todelete,而是用于代替x的那个
        //如果x颜色为红色的,把x涂成黑色即可, 否则 从根到x处少了一个黑色结点,导致不平衡
        while(x != T->root && x->color == BLACK){
            if(x == x->p->left){
                rbt_t* w = x->p->right;

                //情况1 x的兄弟是红色的,通过
                if(RED == w->color){
                    w->color = BLACK;
                    w->p->color = RED;
                    rbt_left_rotate(T,x->p);
                    w = x->p->right;
                }//处理完情况1之后,w.color== BLACK , 情况就变成2 3 4 了

                //情况2 x的兄弟是黑色的,并且其儿子都是黑色的。
                if(w->left->color == BLACK && w->right->color == BLACK){
                    if(x->p->color == RED){
                        x->p->color = BLACK;
                        w->color = RED;

                        break;
                    }else{
                        w->color = RED;
                        x = x->p;//x.p左右是平衡的,但是x.p处少了一个黑结点,所以把x.p作为新的x继续循环
                        continue;
                    }
                }

                //情况3 w为黑色的,左孩子为红色。(走到这一步,说明w左右不同时为黑色。)
                if(w->right->color == BLACK){
                    w->left->color = BLACK;
                    w->color = RED;
                    rbt_right_rotate(T,w);
                    w = x->p->right;
                }//处理完之后,变成情况4

                //情况4 走到这一步说明w为黑色, w的左孩子为黑色, 右孩子为红色。

                w->color=x->p->color;
                x->p->color=BLACK;
                w->right->color=BLACK;
                rbt_left_rotate(T,x->p);
                x = T->root;
            }else{
                rbt_t* w = x->p->left;
                //1
                if(w->color == RED){
                    w->color = BLACK;
                    x->p->color = RED;
                    rbt_right_rotate(T,x->p);
                    w = x->p->left;
                }
                //2
                if(w->left->color==BLACK && w->right->color == BLACK){
                    if(x->p->color == RED){
                        x->p->color = BLACK;
                        w->color = RED;
                        break;
                    }else{
                        x->p->color = BLACK;
                        w->color = RED;
                        x = x->p;
                        continue;
                    }
                }

                //3
                if(w->left->color == BLACK){
                    w->color = RED;
                    w->right->color = BLACK;
                    w = x->p->left;
                }

                //4
                w->color=w->p->color;
                x->p->color = BLACK;
                w->left->color = BLACK;
                rbt_right_rotate(T,x->p);
                x = T->root;
            }

        }
        x->color = BLACK;
    }

    //放心删除todelete 吧
    free(toDelete);

    return T;
}

/*
*@brief rbt_left_rotate
*@param[in] T 树根
*@param[in] x 要进行旋转的结点
*/
void rbt_left_rotate( rbt_root_t* T, rbt_t* x){
    rbt_t* y = x->right;

    x->right = y->left;
    if(x->right != T->nil)
        x->right->p = x;

    y->p = x->p;
    if(y->p == T->nil){
        T->root = y;
    }else if(y->key < y->p->key)
        y->p->left = y;
    else
        y->p->right = y;

    y->left = x;
    x->p = y;
}
/*
*@brief rbt_right_rotate
*@param[in] 树根
*@param[in] 要进行旋转的结点
*/
void rbt_right_rotate( rbt_root_t* T, rbt_t* x){
    rbt_t * y = x->left;
    x->left = y->right;

    if(T->nil != x->left)
        x->left->p = x;

    y->p = x->p;
    if(y->p == T->nil)
        T->root = y;
    else if(y->key < y->p->key)
        y->p->left= y;
    else
        y->p->right = y;

    y->right = x;
    x->p = y;
}
void rbt_prePrint(const rbt_root_t* T, rbt_t* t){
    if(T->nil == t)return ;
    if(t->color == RED)
        printf("%3dR",t->key);
    else
        printf("%3dB",t->key);
    rbt_prePrint(T,t->left);
    rbt_prePrint(T,t->right);
}
void rbt_inPrint(const rbt_root_t* T, rbt_t* t){
    if(T->nil == t)return ;
    rbt_inPrint(T,t->left);
    if(t->color == RED)
        printf("%3dR",t->key);
    else
        printf("%3dB",t->key);
    rbt_inPrint(T,t->right);
}

//打印程序包括前序遍历和中序遍历两个,因为它俩可以唯一确定一棵二叉树
void rbt_print(const rbt_root_t* T){
    assert(T!=NULL);
    printf("\n前序遍历 :");
    rbt_prePrint(T,T->root);
    printf("\n中序遍历 :");
    rbt_inPrint(T,T->root);
    printf("\n");
}

void rbt_test(){
    rbt_root_t* T = rbt_init();

    /************************************************************************/
    /* 1    测试插入
    /*
    /*
    /*输出  前序遍历 :  7B  2R  1B  5B  4R 11R  8B 14B 15R
    /*      中序遍历 :  1B  2R  4R  5B  7B  8B 11R 14B 15R
    /************************************************************************/

    T = rbt_insert(T,11);
    T = rbt_insert(T,7);
    T = rbt_insert(T,1);
    T = rbt_insert(T,2);
    T = rbt_insert(T,8);
    T = rbt_insert(T,14);
    T = rbt_insert(T,15);
    T = rbt_insert(T,5);
    T = rbt_insert(T,4); 

    T = rbt_insert(T,4); //重复插入测试
    rbt_print(T);

    /************************************************************************/
    /* 2    测试删除
    /*
    /*操作  连续删除4个元素 rbt_delete(T,8);rbt_delete(T,14);rbt_delete(T,7);rbt_delete(T,11);
    /*输出  前序遍历 :  2B  1B  5R  4B 15B
    /*      中序遍历 :  1B  2B  4B  5R 15B
    /************************************************************************/

    rbt_delete(T,8);
    rbt_delete(T,14);rbt_delete(T,7);rbt_delete(T,11);

    rbt_delete(T,8);//删除不存在的元素
    rbt_print(T);

}

代码只做了少量的测试,如果有BUG请不吝指出。

版权声明:原创不易,转载请注明转自weewqrer 红黑树

时间: 2024-11-05 22:38:34

红黑树(附完整C代码)的相关文章

mser 最大稳定极值区域(文字区域定位)算法 附完整C代码

mser 的全称:Maximally Stable Extremal Regions 第一次听说这个算法时,是来自当时部门的一个同事, 提及到他的项目用它来做文字区域的定位,对这个算法做了一些优化. 也就是中文车牌识别开源项目EasyPR的作者liuruoze,刘兄. 自那时起就有一块石头没放下,想要找个时间好好理理这个算法. 学习一些它的一些思路. 因为一般我学习算法的思路:3个做法, 第一步,编写demo示例. 第二步,进行算法移植或效果改进. 第三步,进行算法性能优化. 然后在这三个过程中

音频降噪算法 附完整C代码

降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据为主,矩形数据分布. 音频更偏向 时间 处理,例如语音中的某短时长. 音频一般是一维数据为主,单声道波长. 处理方式也是差不多,要不单通道处理,然后合并,或者直接多通道处理. 只是处理时候数据参考系维度不一而已. 一般而言, 图像偏向于多通道处理,音频偏向于单通道处理. 而从数字信号的角度来看,也可

音频自动增益 与 静音检测 算法 附完整C代码

前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. 不过在项目实测的时候,其实真的很难定标准, 到底在什么样的环境下,要增大音量,还是降低. 在通讯行业一般的做法就是采用静音检测, 一旦检测为静音或者噪音,则不做处理,反之通过一定的策略进行处理. 这里就涉及到两个算法,一个是静音检测,一个是音频增益. 增益其实没什么好说的,类似于数据归一化拉伸的做法. 静音检

音频自动增益 与 静音检测 算法 附完整C代码【转】

转自:https://www.cnblogs.com/cpuimage/p/8908551.html 前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. 不过在项目实测的时候,其实真的很难定标准, 到底在什么样的环境下,要增大音量,还是降低. 在通讯行业一般的做法就是采用静音检测, 一旦检测为静音或者噪音,则不做处理,反之通过一定的策略进行处理. 这里就涉及到

自动曝光修复算法 附完整C代码

众所周知, 图像方面的3A算法有: AF自动对焦(Automatic Focus)自动对焦即调节摄像头焦距自动得到清晰的图像的过程 AE自动曝光(Automatic Exposure)自动曝光的是为了使感光器件获得合适的曝光量 AW自动白平衡(Automatic White Balance)白平衡的本质是使白色物体在任何光源下都显示白色 前面的文章也有提及过,在刚开始做图像算法的时候,我是先攻克的自动白平衡算法. 后来攻克自动曝光的时候,傻啦吧唧的,踩了不少坑. 我相信一定不止我一个,一开始的时

基于傅里叶变换的音频重采样算法 (附完整c代码)

前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲,我精力有限,但一般都会抽空回复一下. 大多数情况,阅读一下代码就能解决的问题, 也是要尝试一下的. 没准,你就解决了呢? WebRtc的采样算法本身就考虑到它的自身应用场景, 所以它会有一些局限性,例如不支持任意采样率等等. 而简洁插值的这个算法, 我个人也一直在使用,因为简洁明了,简单粗暴. 我自

图片文档倾斜矫正算法 附完整c代码

2年前在学习图像算法的时候看到一个文档倾斜矫正的算法. 也就是说能将一些文档图像进行旋转矫正, 当然这个算法一般用于一些文档扫描软件做后处理 或者用于ocr 文字识别做前处理. 相关的关键词: 抗倾斜 反倾斜  Deskew 等等. 最简单算法实现思路,采用 霍夫变换(Hough Transform)进行直线检测, 当然也可以用霍夫变换检测圆. 在倾斜矫正算法中,自然就是检测直线. 通过对检测出来的直线进行角度判断, 一般取 认可度最高的几条直线进行计算, 最后求取均衡后的角度值. 进行图像角度

c语言智能指针 附完整示例代码

是的,你没有看错, 不是c++不是c#, 就是你认识的那个c语言. 在很长一段时间里,c的内存管理问题, 层出不穷,不是编写的时候特别费劲繁琐, 就是碰到内存泄漏排查的各种困难, 特别在多线程环境下,就难上加难了, 诸如此类的老大难问题. c++用它的RAII机制妥妥影响了一代程序员. RAII大概介绍下,就不做科普, 有需要的同学,百度一下了解细节. 什么是RAII 资源获取即初始化 (Resource Acquisition Is Initialization, RAII),RAII是一种资

音频算法之小黄人变声 附完整C代码

前面提及到<大话音频变声原理 附简单示例代码>与<声音变调算法PitchShift(模拟汤姆猫) 附完整C++算法实现代码> 都稍微讲过变声的原理和具体实现. 大家都知道,算法从实现到最后工程应用,中间的环节和问题特别多. 尤其是编码的架构设计,好的数据结构和代码逻辑封装肯定是可复用,组件化的. 前几天写完<音频识别算法思考与阶段性小结>的时候, 我也提及到了. 会做一些算法编码优化相关的分享. 而有时候我总觉得文字表达很苍白, 所以我尽可能地把代码写得简洁易懂, 一方