红黑树 (c实现)

红黑树的概念

红黑树是一种二叉搜索树,但在每个结点上增加了一个存储位用于表示结点的颜色,这个颜色可以是红色的,也可以是黑色的,因此我们称之为红黑树。

红黑树通过对任何一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出两倍,因此红黑树是近似平衡的。
在这里插入图片描述

红黑树的性质

红黑树有以下五点性质

1. 每个结点不是红色就是黑色。
2. 根结点是黑色的。
3. 如果一个结点是红色的,则它的两个孩子结点是黑色的。
4. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点。
5. 每个叶子结点都是黑色的(此处的叶子结点指定是空结点)。

红黑树结点的定义

我们这里直接实现C语言的红黑树,为了方便后序的旋转操作,将红黑树的结点定义为三叉链结构,除此之外还新加入了一个成员变量,用于表示结点的颜色。

typedef enum _Color_
{
	RED,
	BLACK
}Color;

typedef struct _RBTreeNode_
{
    struct _RBTreeNode_ *_left;  
    struct _RBTreeNode_ *_right;
    struct _RBTreeNode_ *_parent;

    int _data;
    Color _color;
} RBTreeNode;

为什么构造结点时,默认将结点的颜色设置为红色?

当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质4,此时我们就需要对红黑树进行调整。

若我们插入红黑树的结点是红色的,此时如果其父结点也是红色的,那么表明出现了连续的红色结点,即破坏了红黑树的性质3,此时我们需要对红黑树进行调整;但如果其父结点是黑色的,那我们就无需对红黑树进行调整,插入后仍满足红黑树的要求。

总结一下:

  1. 插入黑色结点,一定破坏红黑树的性质4,必须对红黑树进行调整。
  2. 插入红色结点,可能破坏红黑树的性质3,可能对红黑树进行调整。

权衡利弊后,我们在构造结点进行插入时,默认将结点的颜色设置为红色。

红黑树的插入

红黑树插入结点的逻辑分为三步:

  1. 按二叉搜索树的插入方法,找到待插入位置。
  2. 将待插入结点插入到树中。
  3. 若插入结点的父结点是红色的,则需要对红黑树进行调整。
    其中前两步与二叉搜索树插入结点时的逻辑相同,红黑树的关键在于第三步对红黑树的调整。

红黑树在插入结点后是如何调整的?

实际上,在插入结点后并不是一定会对红黑树进行调整,若插入结点的父结点是黑色的,那么我们就不用对红黑树进行调整,因为本次结点的插入并没有破坏红黑树的五点性质。

只有当插入结点的父结点是红色时才需要对红黑树进行调整,因为我们默认插入的结点就是红色的,如果插入结点的父结点也是红色的,那么此时就出现了连续的红色结点,因此需要对红黑树进行调整。

因为插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点(父结点的父结点)就一定存在。

红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。

情况一:插入结点的叔叔存在,且叔叔的颜色是红色。

此时为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。
在这里插入图片描述
但调整还没有结束,因为此时祖父结点变成了红色,如果祖父结点是根结点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。

但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进而进行不同的调整操作。

因此,情况一的抽象图表示如下:
在这里插入图片描述

注意: 叔叔存在且为红时,cur结点是parent的左孩子还是右孩子,调整方法都是一样的。

情况二:插入结点的叔叔存在,且叔叔的颜色是黑色。

需要注意:

  1. 从根结点一直走到空位置就算一条路径,而不是从根结点走到左右结点均为空的叶子结点时才算一条路径。
  2. 情况二和情况三均需要进行旋转处理,旋转处理后无需继续往上进行调整,所以说情况二一定是由情况一往上调整的过程中出现的。
    出现叔叔存在且为黑时,单纯使用变色已经无法处理了,这时我们需要进行旋转处理。若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。

抽象图表示如下:
在这里插入图片描述

说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。

若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。

抽象图表示如下:
在这里插入图片描述

说明一下: 当折现关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。

情况三:插入结点的叔叔不存在。

在这种情况下的cur结点一定是新插入的结点,而不可能是由情况一变化而来的,因为叔叔不存在说明在parent的下面不可能再挂黑色结点了,如下图:
在这里插入图片描述

如果插入前parent下面再挂黑色结点,就会导致图中两条路径黑色结点的数目不相同,而parent是红色的,因此parent下面自然也不能挂红色结点,所以说这种情况下的cur结点一定是新插入的结点。

和情况二一样,若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。

抽象图表示如下:
图1. 右旋

说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。

若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。

抽象图表示如下:
在这里插入图片描述

说明一下: 当折现关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。

左旋 (无图)

场景:祖父节点.右孩子 = 父亲节点 父亲节点.右孩子 = 当前节点
此时以父亲节点为根进行变换
该图未画出来,建议读者自己画出该图,进行分析


// 左单旋   g.right = p    p.right = cur
void leftrotate(RBTreeNode *grandparent)  //  g
{
    RBTreeNode *parent = grandparent->_right;        // p
    RBTreeNode *pgrandparent = grandparent->_parent;    // g的parent

    grandparent->_right = parent->_left;    
    if(parent->_left)                      
    {
        parent->_left->_parent = grandparent;
    }

    parent->_left = grandparent;       // p的左孩子设置为g
    grandparent->_parent = parent;      // g的parent设置为p
    
    if (pgrandparent == NULL)           // 如果g的parent == null
	{
		_root = parent;                 // 根节点设置为p
		_root->_parent = NULL;          // 根节点parent设NULL
	}
	else
	{
		if (grandparent == pgrandparent->_left)     // 如果g为左孩子
		{
			pgrandparent->_left = parent;       // 设置g的parent的左孩子为p
		}
		else
		{
			pgrandparent->_right = parent;      // 设置g的parent的右孩子为p
		}
		parent->_parent = pgrandparent;   // 设置p的parent为g的parent
	}
}

右旋 (图1. 右旋)

场景:祖父节点.左孩子 = 父亲节点 父亲节点.左孩子 = 当前节点
此时以父亲节点为根进行变换
右旋代码中的注释与图1是对应的


// 右单旋       g.left = p    p.left = cur
void rightrotate(RBTreeNode *grandparent)  //  g
{
    RBTreeNode *parent = grandparent->_left;        // p
    RBTreeNode *uncle = grandparent->_right;        // u
    RBTreeNode *pgrandparent = grandparent->_parent;    // g的parent

    grandparent->_left = parent->_right;    // c变成g的左孩子
    if(parent->_right)                      // 如果c不为空, c的parent置为 g
    {
        parent->_right->_parent = grandparent;
    }

    parent->_right = grandparent;       // p的右孩子设置为g
    grandparent->_parent = parent;      // g的parent设置为p
    
    if (pgrandparent == NULL)           // 如果g的parent == null
	{
		_root = parent;                 // 根节点设置为p
		_root->_parent = NULL;          // 根节点parent设NULL
	}
	else
	{
		if (grandparent == pgrandparent->_left)     // 如果g为左孩子
		{
			pgrandparent->_left = parent;       // 设置g的parent的左孩子为p
		}
		else
		{
			pgrandparent->_right = parent;      // 设置g的parent的右孩子为p
		}
		parent->_parent = pgrandparent;   // 设置p的parent为g的parent
	}

}

全部代码

全部代码如下,可在linux下进行编译运行,gcc rbtree.c即可

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>

typedef enum _Color_
{
	RED,
	BLACK
}Color;

typedef struct _RBTreeNode_
{
    struct _RBTreeNode_ *_left;  
    struct _RBTreeNode_ *_right;
    struct _RBTreeNode_ *_parent;

    int _data;
    Color _color;
} RBTreeNode;

RBTreeNode *_root = NULL;

//中序遍历子函数
void _Inorder(RBTreeNode* root)
{
	if (root == NULL)
		return;
	_Inorder(root->_left);
	printf("%d ", root->_data);
	_Inorder(root->_right);
}

void Inorder()
{
	_Inorder(_root);
    printf("\n");
}


//判断是否为红黑树的子函数
bool _ISRBTree(RBTreeNode* root, int count, int BlackCount)
{
	if (root == NULL) //该路径已经走完了
	{
		if (count != BlackCount)
		{
			printf("error: 黑色结点的数目不相等");
			return false;
		}
		return true;
	}

	if (root->_color == RED&&root->_parent->_color == RED)
	{
		printf("error: 存在连续的红色结点");
		return false;
	}
	if (root->_color == BLACK)
	{
		count++;
	}
	return _ISRBTree(root->_left, count, BlackCount) && _ISRBTree(root->_right, count, BlackCount);
}

bool ISRBTree()
{
    bool ret = false;
	if (_root == NULL) //空树是红黑树
	{
		return true;
	}
	if (_root->_color == RED)
	{
		printf("error: 根结点为红色");
		return false;
	}
	
	//找最左路径作为黑色结点数目的参考值
	RBTreeNode* cur = _root;
	int BlackCount = 0;
	while (cur)
	{
		if (cur->_color == BLACK)
			BlackCount++;
		cur = cur->_left;
	}

	int count = 0;
	ret = _ISRBTree(_root, count, BlackCount);
    printf("this is%sa rb tree\n", ret ? " ":" not ");
    return ret;
}


// 左单旋   g.right = p    p.right = cur
void leftrotate(RBTreeNode *grandparent)  //  g
{
    RBTreeNode *parent = grandparent->_right;        // p
    RBTreeNode *pgrandparent = grandparent->_parent;    // g的parent

    grandparent->_right = parent->_left;    
    if(parent->_left)                      
    {
        parent->_left->_parent = grandparent;
    }

    parent->_left = grandparent;       // p的左孩子设置为g
    grandparent->_parent = parent;      // g的parent设置为p
    
    if (pgrandparent == NULL)           // 如果g的parent == null
	{
		_root = parent;                 // 根节点设置为p
		_root->_parent = NULL;          // 根节点parent设NULL
	}
	else
	{
		if (grandparent == pgrandparent->_left)     // 如果g为左孩子
		{
			pgrandparent->_left = parent;       // 设置g的parent的左孩子为p
		}
		else
		{
			pgrandparent->_right = parent;      // 设置g的parent的右孩子为p
		}
		parent->_parent = pgrandparent;   // 设置p的parent为g的parent
	}
}

// 右单旋       g.left = p    p.left = cur
void rightrotate(RBTreeNode *grandparent)  //  g
{
    RBTreeNode *parent = grandparent->_left;        // p
    RBTreeNode *uncle = grandparent->_right;        // u
    RBTreeNode *pgrandparent = grandparent->_parent;    // g的parent

    grandparent->_left = parent->_right;    // c变成g的左孩子
    if(parent->_right)                      // 如果c不为空, c的parent置为 g
    {
        parent->_right->_parent = grandparent;
    }

    parent->_right = grandparent;       // p的右孩子设置为g
    grandparent->_parent = parent;      // g的parent设置为p
    
    if (pgrandparent == NULL)           // 如果g的parent == null
	{
		_root = parent;                 // 根节点设置为p
		_root->_parent = NULL;          // 根节点parent设NULL
	}
	else
	{
		if (grandparent == pgrandparent->_left)     // 如果g为左孩子
		{
			pgrandparent->_left = parent;       // 设置g的parent的左孩子为p
		}
		else
		{
			pgrandparent->_right = parent;      // 设置g的parent的右孩子为p
		}
		parent->_parent = pgrandparent;   // 设置p的parent为g的parent
	}

}

//左右双旋
void lr_rotate(RBTreeNode* grandparent)
{
	leftrotate(grandparent->_left);  // 以 parent为grandparent进行左旋
	rightrotate(grandparent);
}

//右左双旋
void rl_rotate(RBTreeNode* grandparent)
{
	rightrotate(grandparent->_right);
	leftrotate(grandparent);
}

void insertdata(int data)
{
   if (_root == NULL) //若红黑树为空树,则插入结点直接作为根结点
	{
		_root = malloc(sizeof(RBTreeNode));
        _root->_data = data;
		_root->_color = BLACK; //根结点必须是黑色
        _root->_left = NULL;
        _root->_right = NULL;
        _root->_parent = NULL;
		return; //插入成功
	}

    //1、按二叉搜索树的插入方法,找到待插入位置
	RBTreeNode* cur = _root;
	RBTreeNode* parent = NULL;
	while (cur)
	{
		if (data < cur->_data) //待插入结点的key值小于当前结点的key值
		{
			//往该结点的左子树走
			parent = cur;
			cur = cur->_left;
		}
		else if (data > cur->_data) //待插入结点的key值大于当前结点的key值
		{
			//往该结点的右子树走
			parent = cur;
			cur = cur->_right;
		}else
        {
            return;
        }	
	}

    //2、将待插入结点插入到树中
	cur = malloc(sizeof(RBTreeNode));
    cur->_data = data;
    cur->_color = RED;
    cur->_left = NULL;
    cur->_right = NULL;
    cur->_parent = NULL;
	RBTreeNode* newnode = cur; //记录新插入的结点(便于后序返回)
	if (data < parent->_data) //新结点的key值小于parent的key值
	{
		//插入到parent的左边
		parent->_left = cur;
		cur->_parent = parent;
	}
	else //新结点的key值大于parent的key值
	{
		//插入到parent的右边
		parent->_right = cur;
		cur->_parent = parent;
	}

    //3、若插入结点的父结点是红色的,则需要对红黑树进行调整
	while (parent&&parent->_color == RED)
	{
		RBTreeNode* grandfather = parent->_parent; //parent是红色,则其父结点一定存在
		if (parent == grandfather->_left) //parent是grandfather的左孩子
		{
			RBTreeNode* uncle = grandfather->_right; //uncle是grandfather的右孩子
			if (uncle&&uncle->_color == RED) //情况1:uncle存在且为红
			{
				//颜色调整
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else //情况2+情况3:uncle不存在 + uncle存在且为黑
			{
				if (cur == parent->_left)
				{
					rightrotate(grandfather); //右单旋

					//颜色调整
					grandfather->_color = RED;
					parent->_color = BLACK;
				}
				else //cur == parent->_right
				{
					lr_rotate(grandfather); //左右双旋

					//颜色调整
					grandfather->_color = RED;
					cur->_color = BLACK;
				}
				break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
			}
		}
		else //parent是grandfather的右孩子
		{
			RBTreeNode* uncle = grandfather->_left; //uncle是grandfather的左孩子
			if (uncle&&uncle->_color == RED) //情况1:uncle存在且为红
			{
				//颜色调整
				uncle->_color = parent->_color = BLACK;
				grandfather->_color = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else //情况2+情况3:uncle不存在 + uncle存在且为黑
			{
				if (cur == parent->_left)
				{
					rl_rotate(grandfather); //右左双旋

					//颜色调整
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				else //cur == parent->_right
				{
					leftrotate(grandfather); //左单旋

					//颜色调整
					grandfather->_color = RED;
					parent->_color = BLACK;
				}
				break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
			}
		}
	}
	_root->_color = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
	return; //插入成功
}

int main()
{
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        insertdata(i);
    }
    
    Inorder();

    ISRBTree();
    return 0;
}
  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
红黑树的c实现源码与剖析 原作者:那谁 源码剖析作者:July ===================== July说明: 由于原来的程序没有任何一行注释,我把它深入剖析,并一行一行的添加了注释, 详情请参见此文: 教你彻底实现红黑树红黑树的c源码实现与剖析 http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx 关于红黑树系列的教程,还可看下以下倆篇文章: 教你透彻了解红黑树: http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx 红黑树算法的层层剖析与逐步实现 http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx Ok,君自看。 -------------------------------------------------------------------------------------------------- //以下是最初的源程序。 //如果你看不太懂,那么就证明了我所做的源码剖析工作有意义了。:D。 //详情,参见My Blog[谷歌或百度搜"结构之法"] //http://blog.csdn.net/v_JULY_v ------------------------------------------------------------------------------------------------- #include #include #include ............. .............. //:D。这下,你应该发现我所添加的注释的价值了。 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx 一切的详情请参见此文: 教你彻底实现红黑树红黑树的c源码实现与剖析 http://blog.csdn.net/v_JULY_v/archive/2011/01/03/6114226.aspx 关于红黑树系列的教程,还可看下以下倆篇文章: 教你透彻了解红黑树: http://blog.csdn.net/v_JULY_v/archive/2010/12/29/6105630.aspx 红黑树算法的层层剖析与逐步实现 http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx 我博客里还有有关微软等公司数据结构+算法面试100题的资料, 详情,参见My Blog: http://blog.csdn.net/v_JULY_v ----- July、二零一一年一月三日。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值