紅黑樹的實現和學習

紅黑樹是高效查找和插入刪除的數據結構,用途很普遍,如epoll的消息註冊機制,stl中的map都採用了紅黑樹。
## 紅黑樹的主要特性:
`(1)每一個節點或者是黑色,或者是紅色。`
`(2)根節點是黑色。`
`(3)每一個葉子節點(NIL)是黑色。 [注意:這裏葉子節點,是指爲空的葉子節點!]`
`(4)若是一個節點是紅色的,則它的子節點必須是黑色的。`
`(5)從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑節點。`python

定理:`一棵含有n個節點的紅黑樹的高度至多爲2log(n+1)。`
定義節點爲TreeNode,TreeNode類內容以下:git

class TreeNode
{
public:
    TreeNode():m_pParent(NULL), m_pLeftChild(NULL), m_pRightChild(NULL), m_nData(0), m_nColor(RED){}
    TreeNode(const TreeNode & tree){
        this->m_nData = tree.m_nData;
        this->m_pParent = NULL;
        this->m_pLeftChild = NULL;
        this->m_pRightChild = NULL;
        m_nColor = tree.m_nColor;
    }

    TreeNode(int data):m_nData(data), m_pRightChild(NULL), m_pLeftChild(NULL), m_pParent(NULL),m_nColor(RED){}
    ~TreeNode(){ 
        this->m_nData = 0;
        this->m_pLeftChild = NULL;
        this->m_pParent = NULL;
        this->m_pRightChild = NULL;
    }

    TreeNode& operator =(const TreeNode& treeNode)//賦值運算符
    {
        
        if (this != &treeNode)
        {
            this->m_pParent = treeNode.m_pParent;
            this->m_pLeftChild = treeNode.m_pLeftChild;
            this->m_pRightChild = treeNode.m_pRightChild;
            this->m_nData = treeNode.m_nData;
            this->m_nColor = treeNode.m_nColor;
        }
        return *this;
    } 

    
        
    friend ostream & operator<<(ostream &out, TreeNode &obj)
    {
        out <<  " " << obj.m_nData <<" ";
        return out;
    }


    TreeNode * m_pParent;
    TreeNode * m_pLeftChild;
    TreeNode * m_pRightChild;
    int m_nData;    
    int m_nColor;
};

## 樹的左旋和右旋
左旋:github

用C++實現左旋算法

 

void TreeClass::leftRotate(TreeNode * x)
{
	if(x->m_pRightChild == NULL)
	{
		return;
	}

	//取X的右孩子爲y
	TreeNode * y = x->m_pRightChild; 
	//判斷y是否有左孩子
	if(y->m_pLeftChild)
	{
		x->m_pRightChild = y->m_pLeftChild;
		y->m_pLeftChild->m_pParent = x;
	}

	//x的父節點賦值爲y的父節點
	y->m_pParent = x->m_pParent;

	if(x->m_pParent == NULL)
	{
		//X爲root節點,將y設置爲root節點
		m_pRoot = y; 
	}
	else if(x == x->m_pParent->m_pLeftChild)
	{
		//X爲其父節點的左孩子,將y設置爲其父節點的左孩子
		x->m_pParent->m_pLeftChild = y;
	}
	else
	{
		//X爲其父節點的右孩子,將y設置爲其父節點的右孩子
		x->m_pParent->m_pRightChild = y;
	}
	//將X設置爲y的左孩子
	y->m_pLeftChild = x;
	x->m_pParent = y;


}

  

以下圖爲左旋40節點

右旋:

用C++實現y節點右旋數據結構

 

/**************

		y                        x
	   / \                      / \
      x    m  ===>      z   y
	 / \                           / \
	z   h                        h   m
****************/

void TreeClass::rightRotate(TreeNode * y)
{
	TreeNode * x = y->m_pLeftChild;
	if(x == NULL)
	{
		return;
	}

	if(x->m_pRightChild)
	{
		 // 將 「y」 設爲 「x的右孩子的父親」
		x->m_pRightChild->m_pParent = y;
		// 將 「x的右孩子」 設爲 「y的左孩子」
		y->m_pLeftChild = x->m_pRightChild;
	}
	else
	{
		y->m_pLeftChild = NULL;
	}

	x->m_pParent = y->m_pParent ;
	if(y->m_pParent == NULL)
	{
		 // 狀況1:若是 「y的父親」 是空節點,則將x設爲根節點
		m_pRoot = x;
	}
	else if(y == y->m_pParent->m_pRightChild)
	{
		//若是 y是它父節點的右孩子,則將x設爲「y的父節點的左孩子」
		y->m_pParent->m_pRightChild = x;
	}
	else
	{
		//(y是它父節點的左孩子) 將x設爲「y的父節點的左孩子」
		y->m_pParent->m_pLeftChild = x;
	}

	//y設置爲x的右孩子
	x->m_pRightChild = y;
	y->m_pParent = x;
	
}

  

右旋示意圖

## 紅黑樹的插入
紅黑樹的插入分三步:
`1.將紅黑樹看成一顆二叉查找樹,將節點插入`
`2.將插入的節點着色爲"紅色"`
`3.經過一系列的旋轉或着色等操做,使之從新成爲一顆紅黑樹`
將節點插入後着色爲紅色可能會致使紅黑樹特性失效,有可能使特性(4)失效:若是一個節點是紅色的,則它的子節點必須是黑色的。
前面將結果二叉搜索樹的插入,這裏和以前相似,再也不贅述,C++實現以下:函數

void TreeClass::rbInsertNode(TreeNode * z)
{
	//定義一個節點的指針
	TreeNode * y = NULL;
	TreeNode * x = m_pRoot;
	//循環查找節點z插入的位置,指導找到葉子結點
	while(x)
	{
		y = x;
		if(z->m_nData < x->m_nData)
		{
			x = x->m_pLeftChild;
		}
		else
		{
			x = x->m_pRightChild;
		}
	}

	//循環結束時,x指向某個葉子結點,y指向x的父親
	//設置z的父節點爲y
	z->m_pParent = y;
	//若是y爲NULL,設置z爲root節點
	if(y == NULL)
	{
		m_pRoot = z;
	}
	else if(z->m_nData < y->m_nData)
	{
		y->m_pLeftChild = z;
	}
	else
	{
		y->m_pRightChild = z;
	}

	// z的左孩子設爲空
	z->m_pLeftChild = NULL;
	 // z的右孩子設爲空
	z->m_pRightChild = NULL;
	// 將z着色爲「紅色」
	z->m_nColor = RED;
	//經過rbInsertFixUp(z)對紅黑樹的節點進行顏色修改以及旋轉,讓樹T仍然是一顆紅黑樹
	rbInsertFixUp(z);				
}

  

根據被插入節點的父節點的狀況,能夠將"當節點z被着色爲紅色節點,並插入二叉樹"劃分爲三種狀況來處理。
① 狀況說明:被插入的節點是根節點。
處理方法:直接把此節點塗爲黑色。
② 狀況說明:被插入的節點的父節點是黑色。
處理方法:什麼也不須要作。節點被插入後,仍然是紅黑樹。
③ 狀況說明:被插入的節點的父節點是紅色。
處理方法:那麼,該狀況與紅黑樹的「特性(4)」相沖突。這種狀況下,被插入節點是必定存在非空祖父節點的;進一步的講,被插入節點也必定存在叔叔節點(即便叔叔節點爲空,咱們也視之爲存在,空節點自己就是黑色節點)。理解這點以後,咱們依據"叔叔節點的狀況",將這種狀況進一步劃分爲3種狀況(Case)。this

### case1 當前節點的父節點是紅色,且當前節點的祖父節點的另外一個子節點(叔叔節點)也是紅色。
處理策略
(01) 將「父節點」設爲黑色。
(02) 將「叔叔節點」設爲黑色。
(03) 將「祖父節點」設爲「紅色」。
(04) 將「祖父節點」設爲「當前節點」(紅色節點);即,以後繼續對「當前節點」進行操做。spa

這麼作的目的是保持黑色節點數量一致,而且將紅色節點上移,直到其爲根,將根節點設置爲紅色。
以下圖爲case1:

### case2 當前節點的父節點是紅色,當前節點的叔叔節點爲黑色。分爲四種狀況。在case2中只討論前兩種,後面兩種歸類爲case3
`case 2.1 當前節點父節點是其祖父節點的左孩子,當前節點是其父節點的右孩子,那麼以當前節點的父節點爲
支點進行左旋`,以下圖:

`case 2.1 當前節點父節點是其祖父節點的右孩子,當前節點是其父節點的左孩子,那麼以當前節點的父節點爲
支點進行右旋`,以下圖:

綜上所述:case2的兩種狀況就是將父節點和子節點經過旋轉放置在一側。
### case3 當前節點的父節點是紅色,當前節點的叔叔節點是黑色,和case2兩種狀況不一樣的是
`case3.1 當前節點父節點是其祖父節點的左孩子,當前節點是其父節點的左孩子,那麼將父節點設置爲黑色,祖父節點設置爲紅色,
以祖父節點爲支點右旋`,以下圖:

`case3.2 當前節點父節點是其祖父節點的右孩子,且當前節點是其父節點的右孩子,那麼將父節點設置爲黑色,祖父節點設置爲紅色,
以祖父節點爲支點進行左旋`,同上例子,再也不贅述,只是左右不一樣。
將上述着色和旋轉過程完善,封裝爲rbInsertFixUp()函數以下3d

void TreeClass::rbInsertFixUp(TreeNode * z)
{
	//z爲root節點,那麼只須要設置z顏色爲黑色便可。
	if(z->m_pParent == NULL)
	{
		m_pRoot->m_nColor = BLACK;
		return ;
	}

	//若當前節點z的父節點爲紅色,須要一直調色和旋轉,直到z爲黑色爲止
	while(z->m_pParent->m_nColor == RED)
	{
		// 若「z的父節點」是「z的祖父節點的左孩子」,則進行如下處理。
		if(z->m_pParent->m_pParent->m_pLeftChild == z->m_pParent)
		{
			// 將y設置爲「z的叔叔節點(z的祖父節點的右孩子)」
			TreeNode * y = z->m_pParent->m_pParent->m_pRightChild;
			 // Case 1條件:叔叔是紅色
			if(y->m_nColor == RED)
			{
				 //  (01) 將「父節點」設爲黑色
				z->m_pParent->m_nColor = BLACK;
				 //  (02) 將「叔叔節點」設爲黑色
				y->m_nColor = BLACK;
				// (03)將「祖父節點」設爲「紅色」
				z->m_pParent->m_pParent->m_nColor = RED;
				//  (04) 將「祖父節點」設爲「當前節點」(紅色節點)
				z = z->m_pParent->m_pParent;
			}//if(y->m_nColor == RED)
			else 
			{
				 // Case 2條件:叔叔是黑色,且當前節點是右孩子
				if(z == z->m_pParent->m_pRightChild)
				{
					//  (01) 將「父節點」做爲「新的當前節點」
					z = z->m_pParent;
					//  (02) 以「新的當前節點」爲支點進行左旋
					leftRotate(z);
				}
				// Case 3條件:叔叔是黑色,且當前節點是左孩子。(01) 將「父節點」設爲「黑色」。
				z->m_pParent->m_nColor = BLACK;
				//  (02) 將「祖父節點」設爲「紅色」。
				z->m_pParent->m_pParent->m_nColor = RED;
				//  (03) 以「祖父節點」爲支點進行右旋。
				rightRotate(z->m_pParent->m_pParent);
			}
		}//if(z->m_pParent->m_pParent->m_pLeftChild == z->m_pParent)
		else
		{
			// 若「z的父節點」是「z的祖父節點的右孩子」,將上面的操做中「right」和「left」交換位置,而後依次執行。
			TreeNode * y = z->m_pParent->m_pParent->m_pLeftChild;
			// Case 1條件:叔叔是紅色
			if(y->m_nColor == RED)
			{
				//  (01) 將「父節點」設爲黑色
				z->m_pParent->m_nColor = BLACK;
				//  (02) 將「叔叔節點」設爲黑色
				y->m_nColor = BLACK;
				// (03)將「祖父節點」設爲「紅色」
				z->m_pParent->m_pParent->m_nColor = RED;
				//  (04) 將「祖父節點」設爲「當前節點」(紅色節點)
				z = z->m_pParent->m_pParent;
			}//if(y->m_nColor == RED)
			else 
			{
				// Case 2條件:叔叔是黑色,且當前節點是左孩子
				if(z == z->m_pParent->m_pLeftChild)
				{
					//  (01) 將「父節點」做爲「新的當前節點」
					z = z->m_pParent;
					//  (02) 以「新的當前節點」爲支點進行右旋
					rightRotate(z);
				}
				// Case 3條件:叔叔是黑色,且當前節點是右孩子。(01) 將「父節點」設爲「黑色」。
				z->m_pParent->m_nColor = BLACK;
				//  (02) 將「祖父節點」設爲「紅色」。
				z->m_pParent->m_pParent->m_nColor = RED;
				//  (03) 以「祖父節點」爲支點進行右旋。
				leftRotate(z->m_pParent->m_pParent);
			}

		}
	
	}
	//最後將root節點設置爲黑色
	m_pRoot->m_nColor = BLACK;
}

  

讀者能夠反覆推敲上面代碼,下面介紹紅黑樹的刪除。紅黑樹的刪除和普通的二叉搜索樹同樣,只是刪除後多增長一些旋轉和調節顏色的手段。
先刪除節點z,而後在旋轉和着色。若是節點z僅有一個孩子或者沒有孩子,直接刪除,並用子節點替代它便可。若是z有兩個孩子,那麼找到後繼
節點,將z替換爲後繼節點的數值,而且刪除後繼節點,再對後繼節點的孩子進行旋轉和着色便可。
下面是紅黑樹刪除的代碼指針

 

TreeNode *  TreeClass::rbDeleteNode(TreeNode * z)
{
	TreeNode * y = NULL;
	TreeNode * x = NULL;
	//z只有一個孩子或者沒有孩子,那麼刪除z,將孩子鏈接到父節點便可
	if(z->m_pLeftChild == NULL || z->m_pRightChild == NULL)
	{
		 y = z;
	}
	else
	{
		//z有兩個孩子,那麼將z的後繼節點替換z的數值,刪除後繼節點便可。
		y = findNextNode(z);
	}
	
	//找到節點y的子樹,節點y的子樹爲空或者只有一支,不存在雙子樹狀況。
	//由於若是z只有一個孩子或者沒有孩子,那麼y==z,若是z有兩個孩子,那麼y==z的後繼節點,
	//z的後繼節點必定只有一個孩子或者沒有孩子。
	if(y->m_pLeftChild)
	{
		x = y->m_pLeftChild;
	}
	else
	{
		x = y->m_pRightChild;
	}

	if(x)
	{
		x->m_pParent = y->m_pParent;
	}

	if(y->m_pParent == NULL)
	{
		m_pRoot = x;
	}
	else if(y == y->m_pParent->m_pLeftChild)
	{
		y->m_pParent->m_pLeftChild = x;
	}
	else
	{
		y->m_pParent->m_pRightChild = x;
	}

	 if(y != z)
	 {
		 // 若「y的值」 賦值給 「z」。注意:這裏只拷貝z的值給y,而沒有拷貝z的顏色
		 z->m_nData = y->m_nData;
	 }
	
	 if(y->m_nColor == BLACK)
	 {
		rbDeleteFixUp(x);
	 }
	 return y;
	 
}

對於刪除的旋轉和顏色變換是有規律的,因爲紅黑樹是由2-3樹變化而來,因此想了解紅黑樹刪除原理須要進一步瞭解2-3樹便可。
我只是根據算法導論和紅黑樹原理進行總結。對於節點X旋轉和着色能夠歸納爲3種狀況。
① 狀況說明:x是紅色節點。
處理方法:直接把x設爲黑色,結束。此時紅黑樹性質所有恢復。
② 狀況說明:x是黑節點,且x是根。
處理方法:什麼都不作,結束。此時紅黑樹性質所有恢復。
③ 狀況說明:x是黑節點,且x不是根。紅黑樹的刪除分以下四種狀況:
`Case 1 x是黑節點,x的兄弟節點是紅色`
`Case 1.1 若是x是其父節點的左孩子, 將x的兄弟節點設爲黑色,將x的父節點設爲紅色,對x的父節點進行左旋,
左旋後從新設置x的兄弟節點 `
`Case 1.2 若是x是其父節點的右孩子, 將x的兄弟節點設爲黑色,將x的父節點設爲紅色,對x的父節點進行右旋,
左旋後從新設置x的兄弟節點 `
以下圖所示

`Case 2 x是黑節點,x的兄弟節點是黑色,x的兄弟節點的兩個孩子都是黑色,將x的兄弟節點設爲紅色, 設置x的父節點爲新的x節點`。
處理結果以下圖:

`Case 3 x是黑節點,x的兄弟節點是黑色;`
`Case 3.1 x是其父節點的左孩子,X的兄弟節點的左孩子是紅色,右孩子是黑色的,`解決策略以下:
(01) 將x兄弟節點的左孩子設爲「黑色」。
(02) 將x兄弟節點設爲「紅色」。
(03) 對x的兄弟節點進行右旋。
(04) 右旋後,從新設置x的兄弟節點。
`Case 3.2 x是其父節點的右孩子,x的兄弟節點的右孩子是紅色,左孩子是黑色的,`解決策略以下:
(01) 將x兄弟節點的右孩子設爲「黑色」。
(02) 將x兄弟節點設爲「紅色」。
(03) 對x的兄弟節點進行左旋。
(04) 左旋後,從新設置x的兄弟節點。

`Case 4 x是黑節點,x的兄弟節點是黑色;
`Case 4.1 x是其父節點的左孩子,x的兄弟節點的右孩子是紅色的,x的兄弟節點的左孩子任意顏色`
(01) 將x父節點顏色 賦值給 x的兄弟節點。
(02) 將x父節點設爲「黑色」。
(03) 將x兄弟節點的右子節設爲「黑色」。
(04) 對x的父節點進行左旋。
(05) 設置「x」爲「根節點」。
`Case 4.2 x是其父節點的右孩子,x的兄弟節點的左孩子是紅色的,x的兄弟節點的右孩子任意顏色`
(01) 將x父節點顏色 賦值給 x的兄弟節點。
(02) 將x父節點設爲「黑色」。
(03) 將x兄弟節點的左孩子設爲「黑色」。
(04) 對x的父節點進行右旋。
(05) 設置「x」爲「根節點」。

用C++實現上述旋轉和着色過程。

  

void TreeClass::rbDeleteFixUp(TreeNode * x)
{
	while(x != m_pRoot && x->m_nColor == BLACK)
	{
		if(x->m_pParent->m_pLeftChild == x)
		{
			TreeNode * w = x->m_pParent->m_pRightChild;
			// Case 1: x是黑節點,x的兄弟節點是紅色。
			if(w->m_nColor == RED)
			{
				//   (01) 將x的兄弟節點設爲「黑色」。
				w->m_nColor = BLACK;
				//   (02) 將x的父節點設爲「紅色」。
				x->m_pParent->m_nColor = RED;
				//   (03) 對x的父節點進行左旋。
				leftRotate(x->m_pParent);
				//   (04) 左旋後,從新設置x的兄弟節點。
				w = x->m_pParent->m_pRightChild;
			}
			
			// Case 2: x是黑節點,x的兄弟節點是黑色,x的兄弟節點的兩個孩子都是黑色。
			if(w->m_pLeftChild->m_nColor == BLACK && w->m_pRightChild->m_nColor == BLACK)
			{
				 //   (01) 將x的兄弟節點設爲「紅色」
				w->m_nColor = RED;
				//   (02) 設置「x的父節點」爲「新的x節點」。
				x = x->m_pParent;
			}
			else 
			{
				 // Case 3: x是黑節點,x的兄弟節點是黑色;x的兄弟節點的左孩子是紅色,右孩子是黑色的
				if(w->m_pRightChild->m_nColor == BLACK)
				{
					//   (01) 將x兄弟節點的左孩子設爲「黑色」
					w->m_pLeftChild->m_nColor = BLACK;
					//   (02) 將x兄弟節點設爲「紅色」
					w->m_nColor = RED;
					//   (03) 對x的兄弟節點進行右旋
					rightRotate(w);
					 //   (04) 右旋後,從新設置x的兄弟節點。
					w = x->m_pParent->m_pRightChild;
				}

				// Case 4: x是黑節點,x的兄弟節點是黑色;x的兄弟節點的右孩子是紅色的。
				//(01) 將x父節點顏色 賦值給 x的兄弟節點。
				w->m_nColor = x->m_pParent->m_nColor;
				 //   (02) 將x父節點設爲「黑色」
				x->m_pParent->m_nColor = BLACK;
				 //   (03) 將x兄弟節點的右子節設爲「黑色」。
				x->m_pRightChild->m_nColor = BLACK;
				//  (04) 對x的父節點進行左旋。
				leftRotate(x->m_pParent);
				x = m_pRoot;
					
			}

		}//if(x->m_pParent->m_pLeftChild == x)
		else
		{
			// 若 「x」是「它父節點的右孩子」,將上面的操做中「right」和「left」交換位置,而後依次執行
			TreeNode * w = x->m_pParent->m_pLeftChild;
			// Case 1: x是黑節點,x的兄弟節點是紅色。
			if(w->m_nColor == RED)
			{
				//   (01) 將x的兄弟節點設爲「黑色」。
				w->m_nColor = BLACK;
				//   (02) 將x的父節點設爲「紅色」。
				x->m_pParent->m_nColor = RED;
				//   (03) 對x的父節點進行右旋。
				rightRotate(x->m_pParent);
				//   (04) 右旋後,從新設置x的兄弟節點。
				w = x->m_pParent->m_pLeftChild;
			}

			// Case 2: x是黑節點,x的兄弟節點是黑色,x的兄弟節點的兩個孩子都是黑色。
			if(w->m_pLeftChild->m_nColor == BLACK && w->m_pRightChild->m_nColor == BLACK)
			{
				//   (01) 將x的兄弟節點設爲「紅色」
				w->m_nColor = RED;
				//   (02) 設置「x的父節點」爲「新的x節點」。
				x = x->m_pParent;
			}
			else 
			{
				// Case 3: x是黑節點,x的兄弟節點是黑色;x的兄弟節點的左孩子是黑色,右孩子是紅色的
				if(w->m_pLeftChild->m_nColor == BLACK)
				{
					//   (01) 將x兄弟節點的右孩子設爲「黑色」
					w->m_pRightChild->m_nColor = BLACK;
					//   (02) 將x兄弟節點設爲「紅色」
					w->m_nColor = RED;
					//   (03) 對x的兄弟節點進行左旋
					leftRotate(w);
					//   (04) 左旋後,從新設置x的兄弟節點。
					w = x->m_pParent->m_pLeftChild;
				}

				// Case 4: x是黑節點,x的兄弟節點是黑色;x的兄弟節點的左孩子是紅色的。
				//(01) 將x父節點顏色 賦值給 x的兄弟節點。
				w->m_nColor = x->m_pParent->m_nColor;
				//   (02) 將x父節點設爲「黑色」
				x->m_pParent->m_nColor = BLACK;
				//   (03) 將x兄弟節點的左子節設爲「黑色」。
				x->m_pLeftChild->m_nColor = BLACK;
				//  (04) 對x的父節點進行右旋。
				rightRotate(x->m_pParent);
				x = m_pRoot;

			}
		} //else
	}

	x->m_nColor = BLACK;
}

  

到此爲止紅黑樹的實現和原理介紹完畢,讀者熟悉後能夠利用紅黑樹作一些排序和高效的數據處理。
源碼下載:
https://github.com/secondtonone1/RBTree

 

相關文章
相關標籤/搜索