红黑数之原理分析及C语言实现
目录:
1.红黑树简介(概念,特征,用途)
2.红黑树的C语言实现(树形结构,添加,旋转)
3.部分面试题()
1.红黑树简介
1.1 红黑树概念
红黑树(Red-Black Tree,简称R-B Tree)是一棵特殊的二叉搜索树(任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值)。
它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK。通过对一条从根节点到NIL叶节点(指空结点或者下面说的哨兵)的简单路径上各个结点在颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。
1.2 红黑树特征
1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
1.3 红黑树用途
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。对于查找、插入、删除、最大、最小等动态操作的时间复杂度为O(lgn).常见的用途有以下几种:
- STL(标准模板库)中在set map是基于红黑树实现的。
- epoll在内核中的实现,用红黑树管理事件块。
- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
2. 红黑树C语言实现
2.1 树形结构基本实现
红黑树属于特殊的查找树,因此先对树形结构进行基本讲解。
首先,树形结构有各个节点组成,节点的描述如下:
struct BTNode{
int data;
};
节点需要有自己的左右子孩子,因此需要两个指针
typedef struct BTNode{
int data;
struct BTNode *lChild;
struct BTNode *rChild;
}BiNode;
对二叉树进行创建
//先序创建 int CreateBiTree(BiTNode **T)
{
int ch;
scanf("%d",&ch);
if (ch == -)
{
*T = NULL;
return ;
}
else
{
*T = (BiTNode *)malloc(sizeof(BiTNode)); //为此节点申请内存空间,返回指针强制转换成BiNode*类型
if (T == NULL)
{
printf("failed\n");
return ;
}
else
{
(*T)->data = ch;
printf("输入%d的左子节点:",ch);
CreateBiTree(&((*T)->lChild)); //递归方式存储左子节点数据
printf("输入%d的右子节点:",ch);
CreateBiTree((&(*T)->rChild)); //递归方式存储右子节点数据
}
}
return ;
}
最开始时对二叉树的遍历难以理解,不过从算法中的递归角度进行理解将会简单很多。
二叉树先序遍历
void PreOrderBiTree(BiTNode *T)
{
if (T == NULL)
{
return;
}
else
{
printf("%d ",T->data);
PreOrderBiTree(T->lChild);
PreOrderBiTree(T->rChild);
}
}
二叉树中序遍历
void MiddleOrderBiTree(BiTNode *T)
{
if (T == NULL)
{
return;
}
else
{
MiddleOrderBiTree(T->lChild);
printf("%d ",T->data);
MiddleOrderBiTree(T->rChild);
}
}
二叉树后序遍历
void PostOrderBiTree(BiTNode *T)
{
if (T == NULL)
{
return;
}
else
{
PostOrderBiTree(T->lChild);
PostOrderBiTree(T->rChild);
printf("%d ",T->data);
}
}
二叉树深度计算
int TreeDeep(BiTNode *T)
{
int deep = ;
if (T != NULL)
{
int leftdeep = TreeDeep(T->lChild);
int rightdeep = TreeDeep(T->rChild);
deep = leftdeep >= rightdeep?leftdeep+:rightdeep+; //从最后节开始,递归逐步后退,每退回一个节点 深度+1
} return deep;
}
二叉树叶子结点个数
int LeafCount(BiTNode *T)
{
static int count;
if (T != NULL)
{
if (T->lChild == NULL && T->rChild == NULL)
{
count++;
} LeafCount(T->lChild);
LeafCount(T->rChild);
} return count;
}
2.2 红黑树实现
红黑树首先作为树的结构出现,包含了树形结构的基本特征,操作具有查找、添加、删除。在添加节点时必然导致红黑树的结构不符合红黑树规则,此时将对添加节点后的树进行旋转(左旋和右旋)以保证回复红黑树的特性。
下面将以红黑树的旋转、添加、删除进行逐步讲解。
红黑树基本节点定义
#define RED 0 // 红色节点
#define BLACK 1 // 黑色节点 typedef int Type; // 红黑树的节点
typedef struct RBTreeNode{
unsigned char color; // 颜色(RED 或 BLACK)
Type key; // 关键字(键值)
struct RBTreeNode *left; // 左孩子
struct RBTreeNode *right; // 右孩子
struct RBTreeNode *parent; // 父结点
}Node, *RBTree; // 红黑树的根
typedef struct rb_root{
Node *node;
}RBRoot;
红黑树旋转
左旋(即上图从右到左)代码实现
static void left_rotate(RBRoot *root, Node *x)
{
// 设置x的右孩子为y
Node *y = x->right; // 将 “y的左孩子” 设为 “x的右孩子”;
// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
x->right = y->left;
if (y->left != NULL)
y->left->parent = x; // 将 “x的父亲” 设为 “y的父亲”
y->parent = x->parent; if (x->parent == NULL)
{
//tree = y; // 如果 “x的父亲” 是空节点,则将y设为根节点
root->node = y; // 如果 “x的父亲” 是空节点,则将y设为根节点
}
else
{
if (x->parent->left == x)
x->parent->left = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else
x->parent->right = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
} // 将 “x” 设为 “y的左孩子”
y->left = x;
// 将 “x的父节点” 设为 “y”
x->parent = y;
}
右旋(即上图从左向右)代码实现
static void rbtree_right_rotate(RBRoot *root, Node *y)
{
// 设置x是当前节点的左孩子。
Node *x = y->left; // 将 “x的右孩子” 设为 “y的左孩子”;
// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
y->left = x->right;
if (x->right != NULL)
x->right->parent = y; // 将 “y的父亲” 设为 “x的父亲”
x->parent = y->parent; if (y->parent == NULL)
{
//tree = x; // 如果 “y的父亲” 是空节点,则将x设为根节点
root->node = x; // 如果 “y的父亲” 是空节点,则将x设为根节点
}
else
{
if (y == y->parent->right)
y->parent->right = x; // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
else
y->parent->left = x; // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
} // 将 “y” 设为 “x的右孩子”
x->right = y; // 将 “y的父节点” 设为 “x”
y->parent = x;
}
红黑树添加
插入操作需要保证插入数据后红黑树的基本特征不被破坏,红黑树的5个基本特征复习一下:
1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
因此插入操作的关键在于以下几点:
- 新插入的节点一定是红色的。(如果是黑色的,会破坏条件5)
- 如果新插入的节点的父亲是黑色的,则没有破坏任何性质,那么插入完成。
- 如果插入节点的父节点是红色, 破坏了性质4. 故插入算法就是通过重新着色或旋转, 来维持性质
插入新的数据(红色节点),一定要考虑他的叔叔的情况。并且,插入操作主要是维护颜色来保证树的平衡。将插入操作分为以下几种情况分别考虑:
case 1. 如果插入的节点是根节点,也就是说初始的红黑树为空,这是最简单的情况,直接将该节点标识成黑色即可。
case 2. 如果新插入的节点不是红黑树的根节点,如果新插入的节点的父节点是黑色的话,那么红黑树是不需要调整的。
case 3. 如果新插入的节点的父节点是红色的话,显然这里违反了红黑树中不能存在父节点和子节点同时为红色的性质。
叔节点为红色:修改祖父节点为红色,父节点及叔节点改为黑色。祖父节点再视为新加入节点与上层比对,保证红黑树不被破坏。
修改后:
叔节点为黑色:旋转,使父节点占据祖父节点位置,之后交换P与G的颜色:
旋转后:
3.红黑树知识点
3.1 红黑树和哈希表怎么选择?
- 应该从多个方面分析:数据总量,空间消耗,查找效率
- 查找效率:哈希表,常数时间不是吹的,这个和数据总量无关的,红黑树是logn级别的。(但是不一定选哪个,如果数量到达了一定的级别,哈希表还是比较好的,但是哈希表还有别的函数的消耗,这个也会影响)
- 内存消耗:对内存消耗十分严格,最好是用红黑树,哈希表有可能会太大太难管理
- 在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。
3.2 红黑树相比于BST和AVL树有什么优点?
- 红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。
- 相比于BST(log(n+1)/log2),它的时间复杂度是更低的,而且最坏的时间复杂度是oN,这是不如红黑树的
- 相比于AVL树,时间复杂度是相同的,但是红黑树的统计效率更高。其实选择哪个树主要是看插入的数据,插入的数据分布比较好则适合AVL,插入的数据比较混乱的话就适合红黑树了
- 插入和删除操作(画图解释)
- 左旋(将X变为左子节点),右旋操作(将Y变成右子节点);插入(三种情况)删除(四种情况)操作。(画图解释原理)
待续。。。
红黑数之原理分析及C语言实现的更多相关文章
- linux kernel的cmdline參数解析原理分析
利用工作之便,今天研究了kernel下cmdline參数解析过程.记录在此.与大家共享.转载请注明出处.谢谢. Kernel 版本:3.4.55 Kernel启动时会解析cmdline,然后依据这些參 ...
- 数据结构--树(遍历,红黑,B树)
平时接触树还比较少,写一篇博文来积累一下树的相关知识. 很早之前在数据结构里面学的树的遍历. 前序遍历:根节点->左子树->右子树 中序遍历:左子树->根节点->右子树 后序遍 ...
- jdk TreeMap工作原理分析
TreeMap是jdk中基于红黑树的一种map实现.HashMap底层是使用链表法解决冲突的哈希表,LinkedHashMap继承自HashMap,内部同样也是使用链表法解决冲突的哈希表,但是额外添加 ...
- Java集合原理分析和知识点大杂烩(多图初学者必备!!)
一.数据结构 数据结构就是计算机存储.组织数据的方式. 在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间,常用O符号来表述. 时间复杂度是同一问题可用不同算法解决, ...
- [转]SGI STL 红黑树(Red-Black Tree)源代码分析
STL提供了许多好用的数据结构与算法,使我们不必为做许许多多的重复劳动.STL里实现了一个树结构-Red-Black Tree,它也是STL里唯一实现的一个树状数据结构,并且它是map, multim ...
- HashMap 与 ConcrrentHashMap 使用以及源码原理分析
前奏一:HashMap面试中常见问题汇总 HashMap的工作原理是近年来常见的Java面试题,几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和Has ...
- ConcurrentHashMap原理分析(1.7与1.8)-put和 get 需要执行两次Hash
ConcurrentHashMap 与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Seg ...
- 总结HashMap实现原理分析
一.底层数据结构在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低. 而JD ...
- 2021超详细的HashMap原理分析,面试官就喜欢问这个!
一.散列表结构 散列表结构就是数组+链表的结构 二.什么是哈希? Hash也称散列.哈希,对应的英文单词Hash,基本原理就是把任意长度的输入,通过Hash算法变成固定长度的输出 这个映射的规则就是对 ...
随机推荐
- BZOJ 4555(第二类斯特林数+NTT)
传送门 解题思路 数学题,推式子.求\(f(n)=\sum\limits_{i=0}^n\sum\limits_{j=0}^iS(i,j)2^jj!\) 首先可以把\(j\)往前提: \[f(n)=\ ...
- form表单submit事件
form表单submit事件同时会触发form表单中button按钮的click事件 <!DOCTYPE html> <html> <head> <meta ...
- Java封装JDBC数据库增、删、改、查操作成JAR文件,以供Web工程调用,适用于多种数据库
废话不多说,直接上源代码,最后有使用方法,当然,也可以作为普通公用类使用,只是封装成JAR更方便使用. package db.util; import java.io.BufferedReader; ...
- Intellij IDEA 安装Scala插件 + 创建Scala项目
一.IDEA 2018 Ultimate edition (旗舰破解版下载地址) 百度网盘地址:https://pan.baidu.com/s/1d9ArRH6adhDUGiJvRqnZMw 二.I ...
- Java 实例 - 状态监测
以下实例演示了如何通过继承 Thread 类并使用 currentThread.getName() 方法来监测线程的状态: Main.java 文件 1 2 3 4 5 6 7 8 9 10 11 1 ...
- MySQL中的关系
关系 将实体与实体的关系,反应到最终数据库表的设计中来.将关系分为三种:一对一,一对多(多对一)和多对多.所有的关系都是指的是表与表之间的关系. 一对一 一张表中的一条记录一定只能与另外一张表的一条记 ...
- Mysq sql语句教程
mysql管理命令 show databases; 显示服务器上当前所有的数据库 use 数据库名称; 进入指定的数据库 show tables; 显示当前数据库中所有的数据表 d ...
- sql server 事务隔离性 snapshot 、read committed说明
一. --该 read committed 默认事务隔离级别 在 systemuser修改事务未完成时 select * from [SystemUser] where id=62; 该语句是不可读取 ...
- Node.js中的fs文件系统
fs.stat 检测是文件还是目录 fs.mkdir 创建目录 fs.writeFile 创建写入文件 fs.appendFile 追加文件 fs.readFile 读取文件 fs.readdir 读 ...
- shell整数运算