1. 程式人生 > >學習之旅-紅黑樹之刪除

學習之旅-紅黑樹之刪除

前人栽樹,後人乘涼。關於這樣紅黑樹的帖子已經很多。我就不再重複造輪子了。我自己發現一個刪除說的很好的部落格,連結:http://gengning938.blog.163.com/blog/static/1282253812011420103852696/

我只是說說自己的理解。方便以後自己回來忘了的時候,能很快看懂。

分為幾種情況:

1、刪除的是葉子節點。那麼,如果是紅色,直接刪除。如果是黑色,則這條分支少了一個黑色節點。需要調整。


2、刪除的不是葉子節點,那麼從他的右子樹中找一個最小的節點和他交換,然後要刪除的點就移到了葉子節點,回到情況1。

那麼這個時候回到情況1了。這個運氣好是紅色的。但是如果是刪除黑色的點呢?比如刪除根節點13 。

如果刪除的是13,那麼右子樹最小的是15,交換13和15。然後刪除位置變為15。這個時候要刪除的是黑色的節點,那就複雜了。又可以分為下面幾種情況

1、刪除的點的兄弟是紅色,那麼他的父親,孩子都是黑色。這一種,交換兄弟與父親的顏色,然後以父節點為準,旋轉樹,旋轉之後新的兄弟變成了黑色。。轉到下一種情況

2、刪除的點的兄弟是黑色。又可以分為幾種情況:

1)、黑色的兄弟的孩子都是黑色,那麼把兄弟染成紅色,這樣,兄弟子樹和自己都少了一個黑色節點,兄弟子樹和自己平衡了。但是整個子樹又比其他子樹少了一個黑色,所以需要看父親節點是紅色還是黑色,如果父親是紅色,直接染黑就完全平衡了,但是父親節點如果是黑色,那就繼續看父親的兄弟顏色。。

2)、黑色的兄弟孩子有紅色的。那麼如果遠親侄子是紅色,那麼遠親侄子染成黑色,交換父親與兄弟的顏色,然後以父親為準,根據刪除點是父親的左孩子還是右孩子進行相應的旋轉,如果是左孩子就左旋,如果是右孩子就右旋。最後平衡。

3)、如果黑色的兄弟的遠親侄子是紅色,那就轉換為2)的情況。

所以複雜的情況就是四種,第1種,和第二種的三種情況。

以下是程式碼,寫的時候參照了一下STL :

void tst_rbt_delete(tst_rbtree* tree, tst_rbtnode* node)
{
	tst_rbtnode **root, *subt, *temp, *sentinel, *w;
	root = &tree->root;
	sentinel = tree->sentinel;
	
	/*二叉樹的刪除*/
	
	//找到要刪除的節點。
	if (sentinel == node->lchild)		//fd1
	{
		subt = node->rchild;
		temp = node;
	}
	else if (sentinel == node->rchild)	//fd2
	{
		subt = node->lchild;
		temp = node;
	}
	else								//fd3
	{
		//node的左右孩子都不為空,那麼從右子樹中找到key最小的節點,交換node和這個點,再刪除node
		temp = tst_rbt_min_node(node->rchild, sentinel);
		if (temp->lchild == sentinel)
		{	
			//既然是最小的節點,其實並不會進入這個分支,如果還有左孩子,肯定不是最小的
			subt = temp->rchild;
		}
		else
		{
			subt = temp->lchild;
		}
	}
	//如果要刪除的點是根節點,這種情況只會是fd1,fd2中的一種
	if (temp == *root)
	{
		*root = subt;
		tst_rbt_set_black(subt);
		tst_rbt_node_reset(node);
		return;
	}

	unsigned char isRed = tst_rbt_is_red(temp);

	//把找到的節點移出樹外。孩子上提
	if (tst_rbt_is_lchild(temp))
	{
		tst_rbt_parent(temp)->lchild = subt;
	}
	else
	{
		tst_rbt_parent(temp)->rchild = subt;
	}
	if (temp == node)
	{	
		//如果要刪除的點就是node,那麼node已經被移出樹外
		subt->parent = temp->parent;
	}
	else
	{
		//如果要刪除的點不是node,而是右子樹中最小的節點

		if (node == tst_rbt_parent(temp))
		{
			//如果node是temp的父節點,那麼,subt的父節點還是temp,但是如果subt是sentinel,那麼sentinel一開始parent是NULL
			subt->parent = temp;
		}
		else
		{
			//如果不是,那麼subt的父節點該是temp的父節點
			subt->parent = temp->parent;
		}

		temp->parent = node->parent;
		temp->lchild = node->lchild;
		temp->rchild = node->rchild;
		tst_rbt_copy_color(temp, node);

		if (node == *root)
		{
			//如果node是根,設定根為找到的節點
			*root = temp;
		}
		else
		{
			//把找到的點替換node
			if (tst_rbt_is_lchild(node))
			{
				tst_rbt_parent(node)->lchild = temp;
			}
			else
			{
				tst_rbt_parent(node)->rchild = temp;
			}
		}

		if (!tst_rbt_is_leaf(tree, temp->lchild))
		{
			temp->lchild->parent = temp;
		}
		if (!tst_rbt_is_leaf(tree, temp->rchild))
		{
			temp->rchild->parent = temp;
		}
	}

	tst_rbt_node_reset(node);
	
	if (isRed)
	{
		return;
	}

	//dsptree(*root, sentinel);//列印樹

	/* 重新平衡紅黑樹 */
	while (subt != *root && tst_rbt_is_black(subt))
	{
		if (tst_rbt_is_lchild(subt))
		{
			w = tst_rbt_parent(subt)->rchild;
			/*複雜情況1:
			*兄弟是紅色,那麼父節點,兄弟的孩子都是黑色。交換兄弟與父親的顏色
			*然後左旋。轉換情況為要刪除的點的兄弟是黑色。
			*/
			if (tst_rbt_is_red(w))//w是紅色。則w的parent,child必然是黑色
			{
				tst_rbt_set_red(tst_rbt_parent(subt));
				tst_rbt_set_black(w);
				tst_rbt_rotate_left(root, tst_rbt_parent(subt), sentinel);
				w = tst_rbt_parent(subt)->rchild;//轉化為subt的兄弟是黑色的情況
			}
			/*複雜情況2:
			*如果要刪除的點的兄弟不是紅色,並且兄弟的左右孩子都是黑色
			*那麼把兄弟染成紅色,再看父節點。如果是紅色,跳出迴圈,最後染成黑色,ok
			*如果父節點是黑色,那麼又回到最原始的問題(情況1234都可能)
			*/
			if (tst_rbt_is_black(w->lchild) && tst_rbt_is_black(w->rchild))
			{
				tst_rbt_set_red(w);
				subt = tst_rbt_parent(subt);
			}
			else
			{
				/*複雜情況3:兄弟節點有紅色孩子
				*如果兄弟的遠侄子是黑色,那麼肯定近親侄子是紅色。
				*最終目的是把遠侄子變成紅色,轉換成下一種情況,可以最終解決問題。
				*/
				if (tst_rbt_is_black(w->rchild))
				{
					tst_rbt_set_black(w->lchild);
					tst_rbt_set_red(w);
					tst_rbt_rotate_right(root, w, sentinel);
					w = tst_rbt_parent(subt)->rchild;
				}

				/*複雜情況4:
				*如果遠侄子是紅色,直接把遠侄子設為黑色,交換父親與兄弟顏色
				*最終選擇合適的旋轉方向旋轉子樹。達到平衡。
				*/
				tst_rbt_copy_color(w, tst_rbt_parent(subt));
				tst_rbt_set_black(tst_rbt_parent(subt));
				tst_rbt_set_black(w->rchild);
				tst_rbt_rotate_left(root, tst_rbt_parent(subt), sentinel);
				subt = *root;//結束迴圈,同時方便後續設定根節點為黑色
			}

		}
		else
		{
			//與上面,刪除點是父親的左孩子相似,只不過,旋轉樹的時候方向與上面情況相反
			w = tst_rbt_parent(subt)->lchild;
			if (tst_rbt_is_red(w))//w是紅色。w的parent,child必然是黑色
			{
				tst_rbt_set_black(w);
				tst_rbt_set_red(tst_rbt_parent(subt));
				tst_rbt_rotate_right(root, tst_rbt_parent(subt), sentinel);
				w = tst_rbt_parent(subt)->lchild;
			}
			if (tst_rbt_is_black(w->lchild) && tst_rbt_is_black(w->rchild))
			{
				tst_rbt_set_red(w);
				subt = tst_rbt_parent(subt);
			}
			else
			{
				if (tst_rbt_is_black(w->lchild))
				{
					tst_rbt_set_red(w);
					tst_rbt_set_black(w->rchild);
					tst_rbt_rotate_left(root, w, sentinel);
					w = tst_rbt_parent(subt)->lchild;
				}
				tst_rbt_copy_color(w, tst_rbt_parent(subt));
				tst_rbt_set_black(tst_rbt_parent(subt));
				tst_rbt_set_black(w->lchild);
				tst_rbt_rotate_right(root, tst_rbt_parent(subt), sentinel);
				subt = *root;
			}

		}
		//dsptree(*root, sentinel);//列印樹
	}
	tst_rbt_set_black(subt);
}