1. 程式人生 > >詳解Linux核心紅黑樹演算法的實現

詳解Linux核心紅黑樹演算法的實現

    開發平臺:Ubuntu11.04

    核心原始碼:linux-2.6.38.8.tar.bz2

    平衡二叉樹(BalancedBinary Tree或Height-Balanced Tree)又稱AVL樹。它或者是一棵空樹,或者是具有下列性質的二叉樹:它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。若將二叉樹上結點的平衡因子BF(BalanceFactor)定義為該結點的左子樹的深度減去它的右子樹的深度,則平衡二叉樹上所有結點的平衡因子只可能是-1、0和1。(此段定義來自嚴蔚敏的《資料結構(C語言版)》)

    紅黑樹是一種在插入或刪除結點時都需要維持平衡的二叉查詢樹,並且每個結點都具有顏色屬性:

    (1)、一個結點要麼是紅色的,要麼是黑色的。

    (2)、根結點是黑色的。

    (3)、如果一個結點是紅色的,那麼它的子結點必須是黑色的,也就是說在沿著從根結點出發的任何路徑上都不會出現兩個連續的紅色結點。

    (4)、從一個結點到一個NULL指標的每條路徑上必須包含相同數目的黑色結點。

 

(此圖片來自維基百科)

    Linux核心紅黑樹的演算法都定義在linux-2.6.38.8/include/linux/rbtree.h和linux-2.6.38.8/lib/rbtree.c兩個檔案中。

    1、結構體 

struct rb_node
{
	unsigned long  rb_parent_color;
#define	RB_RED		0
#define	RB_BLACK	1
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

     這裡的巧妙之處是使用成員rb_parent_color同時儲存兩種資料,一是其雙親結點的地址,另一是此結點的著色。__attribute__((aligned(sizeof(long))))屬性保證了紅黑樹中的每個結點的首地址都是32位對齊的(在32位機上),也就是說每個結點首地址的bit[1]和bit[0]都是0,因此就可以使用bit[0]來儲存結點的顏色屬性而不干擾到其雙親結點首地址的儲存。

    操作rb_parent_color的函式: 

#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))  //獲得其雙親結點的首地址
#define rb_color(r)   ((r)->rb_parent_color & 1) //獲得顏色屬性
#define rb_is_red(r)   (!rb_color(r))   //判斷顏色屬性是否為紅
#define rb_is_black(r) rb_color(r) //判斷顏色屬性是否為黑
#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)  //設定紅色屬性
#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0) //設定黑色屬性

static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)  //設定其雙親結點首地址的函式
{
	rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p;
}
static inline void rb_set_color(struct rb_node *rb, int color) //設定結點顏色屬性的函式
{
	rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
}

    初始化新結點: 

static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
				struct rb_node ** rb_link)
{
	node->rb_parent_color = (unsigned long )parent;   //設定其雙親結點的首地址(根結點的雙親結點為NULL),且顏色屬性設為黑色
	node->rb_left = node->rb_right = NULL;   //初始化新結點的左右子樹

	*rb_link = node;  //指向新結點
}

    指向紅黑樹根結點的指標: 

struct rb_root
{
	struct rb_node *rb_node;
};


#define RB_ROOT	(struct rb_root) { NULL, }  //初始化指向紅黑樹根結點的指標
#define	rb_entry(ptr, type, member) container_of(ptr, type, member) //用來獲得包含struct rb_node的結構體的首地址

#define RB_EMPTY_ROOT(root)	((root)->rb_node == NULL) //判斷樹是否為空
#define RB_EMPTY_NODE(node)	(rb_parent(node) == node)  //判斷node的雙親結點是否為自身
#define RB_CLEAR_NODE(node)	(rb_set_parent(node, node)) //設定雙親結點為自身

    2、插入

    首先像二叉查詢樹一樣插入一個新結點,然後根據情況作出相應的調整,以使其滿足紅黑樹的顏色屬性(其實質是維持紅黑樹的平衡)。

    函式rb_insert_color使用while迴圈不斷地判斷雙親結點是否存在,且顏色屬性為紅色。

    若判斷條件為真,則分成兩部分執行後續的操作:

    (1)、當雙親結點是祖父結點左子樹的根時,則:

    a、存在叔父結點,且顏色屬性為紅色。

 

    b、當node是其雙親結點右子樹的根時,則左旋,然後執行第c步。

 

    c、當node是其雙親結點左子樹的根時。

 

    (2)、當雙親結點是祖父結點右子樹的根時的操作與第(1)步大致相同,這裡略過不談。

    若為假,則始終設定根結點的顏色屬性為黑色。

void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
	struct rb_node *parent, *gparent;

	while ((parent = rb_parent(node)) && rb_is_red(parent)) //雙親結點不為NULL,且顏色屬性為紅色
	{
		gparent = rb_parent(parent); //獲得祖父結點

		if (parent == gparent->rb_left) //雙親結點是祖父結點左子樹的根
		{
			{
				register struct rb_node *uncle = gparent->rb_right; //獲得叔父結點
				if (uncle && rb_is_red(uncle)) //叔父結點存在,且顏色屬性為紅色
				{
					rb_set_black(uncle); //設定叔父結點為黑色
					rb_set_black(parent); //設定雙親結點為黑色
					rb_set_red(gparent); //設定祖父結點為紅色
					node = gparent;  //node指向祖父結點 
					continue; //繼續下一個while迴圈
				}
			}

			if (parent->rb_right == node)  //當node是其雙親結點右子樹的根時
			{
				register struct rb_node *tmp;
				__rb_rotate_left(parent, root); //左旋
				tmp = parent;  //調整parent和node指標的指向
				parent = node;
				node = tmp;
			}

			rb_set_black(parent); //設定雙親結點為黑色
			rb_set_red(gparent); //設定祖父結點為紅色
			__rb_rotate_right(gparent, root); //右旋
		} else { // !(parent == gparent->rb_left)
			{
				register struct rb_node *uncle = gparent->rb_left;
				if (uncle && rb_is_red(uncle))
				{
					rb_set_black(uncle);
					rb_set_black(parent);
					rb_set_red(gparent);
					node = gparent;
					continue;
				}
			}

			if (parent->rb_left == node)
			{
				register struct rb_node *tmp;
				__rb_rotate_right(parent, root);
				tmp = parent;
				parent = node;
				node = tmp;
			}

			rb_set_black(parent);
			rb_set_red(gparent);
			__rb_rotate_left(gparent, root);
		} //end if (parent == gparent->rb_left)
	} //end while ((parent = rb_parent(node)) && rb_is_red(parent))

	rb_set_black(root->rb_node);
}

    3、刪除

    像二叉查詢樹的刪除操作一樣,首先需要找到所需刪除的結點,然後根據該結點左右子樹的有無分為三種情形:

 

    若node結點的顏色屬性為黑色,則需要呼叫__rb_erase_color函式來進行調整。

void rb_erase(struct rb_node *node, struct rb_root *root)
{
	struct rb_node *child, *parent;
	int color;

	if (!node->rb_left) //刪除結點無左子樹
		child = node->rb_right;
	else if (!node->rb_right) //刪除結點無右子樹
		child = node->rb_left;
	else //左右子樹都有
	{
		struct rb_node *old = node, *left;

		node = node->rb_right;
		while ((left = node->rb_left) != NULL)
			node = left;

		if (rb_parent(old)) {
			if (rb_parent(old)->rb_left == old)
				rb_parent(old)->rb_left = node;
			else
				rb_parent(old)->rb_right = node;
		} else
			root->rb_node = node;

		child = node->rb_right;
		parent = rb_parent(node);
		color = rb_color(node);

		if (parent == old) {
			parent = node;
		} else {
			if (child)
				rb_set_parent(child, parent);
			parent->rb_left = child;

			node->rb_right = old->rb_right;
			rb_set_parent(old->rb_right, node);
		}

		node->rb_parent_color = old->rb_parent_color;
		node->rb_left = old->rb_left;
		rb_set_parent(old->rb_left, node);

		goto color;
	}  //end else

	parent = rb_parent(node); //獲得刪除結點的雙親結點
	color = rb_color(node); //獲取刪除結點的顏色屬性

	if (child)
		rb_set_parent(child, parent);
	if (parent)
	{
		if (parent->rb_left == node)
			parent->rb_left = child;
		else
			parent->rb_right = child;
	}
	else
		root->rb_node = child;

 color:
	if (color == RB_BLACK) //如果刪除結點的顏色屬性為黑色,則需呼叫__rb_erase_color函式來進行調整
		__rb_erase_color(child, parent, root);
}

    4、遍歷

    rb_first和rb_next函式可組成中序遍歷,即以升序遍歷紅黑樹中的所有結點。 

struct rb_node *rb_first(const struct rb_root *root)
{
	struct rb_node	*n;

	n = root->rb_node;
	if (!n)
		return NULL;
	while (n->rb_left)
		n = n->rb_left;
	return n;
}

struct rb_node *rb_next(const struct rb_node *node)
{
	struct rb_node *parent;

	if (rb_parent(node) == node)
		return NULL;

	/* If we have a right-hand child, go down and then left as far
	   as we can. */
	if (node->rb_right) {
		node = node->rb_right; 
		while (node->rb_left)
			node=node->rb_left;
		return (struct rb_node *)node;
	}

	/* No right-hand children.  Everything down and left is
	   smaller than us, so any 'next' node must be in the general
	   direction of our parent. Go up the tree; any time the
	   ancestor is a right-hand child of its parent, keep going
	   up. First time it's a left-hand child of its parent, said
	   parent is our 'next' node. */
	while ((parent = rb_parent(node)) && node == parent->rb_right)
		node = parent;

	return parent;
}

    5、在應用程式中使用

    Linux核心中紅黑樹演算法的實現非常通用、巧妙,而且免費又開源,因此完全可以把它運用到自己的應用程式中。

    (1)、從核心中拷貝原始檔: 

$ mkdir redblack
$ cd redblack/
$ cp ../linux-2.6.38.8/lib/rbtree.c .
$ cp ../linux-2.6.38.8/include/linux/rbtree.h .

    (2)、修改原始檔:

    a、C檔案rbtree.c

    修改包含標頭檔案的程式碼 

//刪除以下兩行程式碼
#include <linux/rbtree.h>
#include <linux/module.h>
//新增以下程式碼,即包含當前目錄中的標頭檔案rbtree.h
#include "rbtree.h"

    刪除所有的EXPORT_SYMBOL巨集 

EXPORT_SYMBOL(rb_insert_color);
EXPORT_SYMBOL(rb_erase);
EXPORT_SYMBOL(rb_augment_insert);
EXPORT_SYMBOL(rb_augment_erase_begin);
EXPORT_SYMBOL(rb_augment_erase_end);
EXPORT_SYMBOL(rb_first);
EXPORT_SYMBOL(rb_last);
EXPORT_SYMBOL(rb_next);
EXPORT_SYMBOL(rb_prev);
EXPORT_SYMBOL(rb_replace_node);

    b、標頭檔案rbtree.h

    刪除包含標頭檔案的程式碼,並新增三個巨集定義 

//刪除以下兩行程式碼
#include <linux/kernel.h>
#include <linux/stddef.h>

/* linux-2.6.38.8/include/linux/stddef.h */
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif

/* linux-2.6.38.8/include/linux/stddef.h */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/* linux-2.6.38.8/include/linux/kernel.h */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

    (3)、示例程式碼

    Linux核心紅黑樹的使用方法請參考linux-2.6.38.8/Documentation/rbtree.txt檔案。 

/* test.c */
#include <stdio.h>
#include <stdlib.h>
#include "rbtree.h"

struct mytype {
    struct rb_node my_node;
    int num;
};

struct mytype *my_search(struct rb_root *root, int num)
{
    struct rb_node *node = root->rb_node;

    while (node) {
	struct mytype *data = container_of(node, struct mytype, my_node);

	if (num < data->num)
	    node = node->rb_left;
	else if (num > data->num)
	    node = node->rb_right;
	else
	    return data;
    }
    
    return NULL;
}

int my_insert(struct rb_root *root, struct mytype *data)
{
    struct rb_node **tmp = &(root->rb_node), *parent = NULL;

    /* Figure out where to put new node */
    while (*tmp) {
	struct mytype *this = container_of(*tmp, struct mytype, my_node);

	parent = *tmp;
	if (data->num < this->num)
	    tmp = &((*tmp)->rb_left);
	else if (data->num > this->num)
	    tmp = &((*tmp)->rb_right);
	else 
	    return -1;
    }
    
    /* Add new node and rebalance tree. */
    rb_link_node(&data->my_node, parent, tmp);
    rb_insert_color(&data->my_node, root);
    
    return 0;
}

void my_delete(struct rb_root *root, int num)
{
    struct mytype *data = my_search(root, num);
    if (!data) { 
	fprintf(stderr, "Not found %d.\n", num);
	return;
    }
    
    rb_erase(&data->my_node, root);
    free(data);
}

void print_rbtree(struct rb_root *tree)
{
    struct rb_node *node;
    
    for (node = rb_first(tree); node; node = rb_next(node))
	printf("%d ", rb_entry(node, struct mytype, my_node)->num);
    
    printf("\n");
}

int main(int argc, char *argv[])
{
    struct rb_root mytree = RB_ROOT;
    int i, ret, num;
    struct mytype *tmp;

    if (argc < 2) {
	fprintf(stderr, "Usage: %s num\n", argv[0]);
	exit(-1);
    }

    num = atoi(argv[1]);

    printf("Please enter %d integers:\n", num);
    for (i = 0; i < num; i++) {
	tmp = malloc(sizeof(struct mytype));
	if (!tmp)
	    perror("Allocate dynamic memory");

	scanf("%d", &tmp->num);
	
	ret = my_insert(&mytree, tmp);
	if (ret < 0) {
	    fprintf(stderr, "The %d already exists.\n", tmp->num);
	    free(tmp);
	}
    }

    printf("\nthe first test\n");
    print_rbtree(&mytree);

    my_delete(&mytree, 21);

    printf("\nthe second test\n");
    print_rbtree(&mytree);

    return 0;
}

    編譯並執行: 

$ gcc rbtree.c test.c -o test
[email protected]:~/algorithm/redblack$ ./test 10
Please enter 10 integers:
23
4
56
32
89
122
12
21
45
23
The 23 already exists.

the first test
4 12 21 23 32 45 56 89 122 

the second test
4 12 23 32 45 56 89 122 

相關推薦

Linux核心演算法實現

    開發平臺:Ubuntu11.04     核心原始碼:linux-2.6.38.8.tar.bz2     平衡二叉樹(BalancedBinary Tree或Height-Balanced Tree)又稱AVL樹。它或者是一棵空樹,或者是具有下列性質的二叉樹:

平衡二叉各種演算法一:

平衡二叉樹(Balanced Binary Tree)具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的常用演算法有紅黑樹、AVL、Treap、伸展樹、SBT等。最小二叉平衡樹的節點的公式如下 F(n)=

linux核心運用小例項

linux核心版本linux-3.10.36 在linux核心原始碼中,紅黑樹是一個比較獨立的模組,很容易將其剝離出來,拿到應用層使用。 結構 linux核心的rb_node結構體 struct rb_node { unsigned long

hash函式 hashMap的深入理解,jdk8 hashMap加入演算法

一 hash表的介紹 非hash表的特點:關鍵字在表中的位置和它之間不存在一個確定的關係,查詢的過程為給定值一次和各個關鍵進行比較,查詢的效率取決於和給定值進行比較的次數。 雜湊表的特點:關鍵字在表中位置和它之間存在一種確定的關係 雜湊函式:翻譯為雜湊,就是把任意長度的輸入,通過雜湊

資料結構和演算法 | 演算法(一)

1 紅黑樹簡介 紅黑樹是一種自平衡的二叉查詢樹,是一種高效的查詢樹。它是由 Rudolf Bayer 於1978年發明,在當時被稱為對稱二叉 B 樹(symmetric binary B-trees)。後來,在1978年被 Leo J. Guibas 和 Rob

演算法和應用(更高階的二叉查詢)

紅黑樹(R-B TREE,全稱:Red-Black Tree),本身是一棵二叉查詢樹,在其基礎上附加了兩個要求: 樹中的每個結點增加了一個用於儲存顏色的標誌域; 樹中沒有一條路徑比其他任何路徑長出兩倍,整棵樹要接近於“平衡”的狀態。 這裡所指的路徑,指的是從任何一個結點開始,一直到其子孫的葉子結點

演算法】java版演算法的完整實現及swing介面演示程式

【前言】 當初因為覺得資料結構及演算法是碼農的基礎(正如鋤頭對農民一樣)才決定話費時間來補習的,但是真正自行實現演算法及演算法的視覺化演示的時候才發現難度是如此之大。 演算法寫起來慢,swing介面寫起來也慢。 紅黑樹的結構最重要就是幾個規則: 1、根節點為黑色,NIL

演算法實現與剖析

 一、紅黑樹的介紹 先來看下演算法導論對R-BTree的介紹:紅黑樹,一種二叉查詢樹,但在每個結點上增加一個儲存位表示結點的顏色,可以是Red或Black。通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長

演算法的思想與實現(一)

紅黑樹 是一顆二叉搜尋樹:樹中每一個節點不是黑色就是紅色。可以把一顆紅黑樹視為一顆擴充二叉樹,用外部節點表示空指標。。。 有如下特性: 1.根節點和所有外部節點的顏色是黑色。 2.從根節點到外部節點的途中沒有連續兩個節點的顏色是紅色。 3.所有從根節點到外部節點的路徑上都有

Linux核心程序排程函式schedule()的觸發和執行時機

核心的排程操作分為觸發和執行兩個部分,觸發時僅僅設定一下當前程序的TIF_NEED_RESCHED標誌,執行的時候則是通過schedule()函式來完成程序的選擇和切換。當前程序的thread_info->flags中TIF_NEED_RESCHED位表示需要呼叫schedule()函式進行排程。核心在

程序間通訊之-IPCS/IPCRM命令--linux核心剖析(十二)

SystemV的IPC通訊 System V IPC共有三種類型: * System V訊息佇列 System V 訊號量 System V 共享記憶體區。 ipcs命令 用於報告Linux中程序間通訊設施的狀態,顯示的資訊包括訊息列

演算法原理(十三)

前言 最近斷斷續續花了一個禮拜的時間去看紅黑樹演算法,關於此演算法還是比較難,因為涉及到諸多場景要考慮,同時接下來我們要講解的HashMap、TreeMap等原理都涉及到紅黑樹演算法,所以我們不得不瞭解其原理,關於一些基礎知識這裡不再講解,本文參考博文:《https://www.cnblogs.com/as

C++實現

con colors end ase 復制代碼 設置 typename ucc 技術 1 /* 2 * rbtree.h 3 * 1. 每個節點是紅色或者黑色 4 * 2. 根節點是黑色 5 * 3. 每個葉子節點是黑色(該葉子節點就空的節點)

Linux安裝配置samba服務器實現文件共享

通信 pan strong 網上 實驗環境 rpm 上傳文件 vpd 過濾 概述Samba是在Linux和UNIX系統上實現SMB協議的一個免費軟件,由服務器及客戶端程序構成。SMB(Server Messages Block,信息服務塊)是一種在局域網上共享文件和打印機的

實現——c++

實現 define nor 過程 紅黑樹刪除 實現類 節點類 技術 class 紅黑樹介紹參考上一篇。 1. 基本定義 enum RBTColor{RED, BLACK}; template <class T> class RBTNode{ pu

下——實現

1. 實現紅黑樹的基本思想 實際上,紅黑樹是有固定的平衡過程的:遇到什麼樣的節點分佈,我們就對應怎麼去調整。只要按照這些固定的調整規則來操作,就能將一個非平衡的紅黑樹調整成平衡的。 首先,我們需要再來看一下紅黑樹的定義: 根節點是黑色的; 每個葉子節點都是黑色的空節點(NIL),也就是說,葉子

(3)殘疾版新增實現

為什麼說是殘疾版呢,因為標準的紅黑樹是是三個值在一層,也就是父節點的左右分支節點都可以是紅,但在此,我規定了只有左分支為紅,也就是規定了最多隻有兩個值在一層。這樣能減少很多修復平衡判斷條件。在此我以實現簡化版的treeMap 為例。 新增節點的第一步就是找出節點將要加入的位

一個應用例項卡爾曼濾波及其演算法實現

為了可以更加容易的理解卡爾曼濾波器,這裡會應用形象的描述方法來講解,而不是像大多數參考書那樣羅列一大堆的數學公式和數學符號。但是,他的5條公式是其核心內容。結合現代的計算機,其實卡爾曼的程式相當的簡單,只要你理解了他的那5條公式。在介紹他的5條公式之前,先讓我們來根據下面的例子一步一步的探索。假設我們要研究的

HashMap中操作實現

// 紅黑樹操作方法實現, 從CLR引入 static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,

STL 簡單實現

1.紅黑樹簡介 二叉搜尋樹能夠提供對數的元素插入和訪問。二叉搜尋樹的規則是:任何節點的鍵值一定大於其左子樹的每一個節點值,並小於右子樹的每一個節點值。 常見的二叉搜尋樹有AVL-tree、RB-tree(紅黑樹)。紅黑樹具有極佳的增、刪、查效能,故我們選擇紅黑樹作為關聯式容