现在的位置: 首页 > 综合 > 正文

排序二叉树BST的基本操作(2)前驱,后继,删除

2013年09月15日 ⁄ 综合 ⁄ 共 2363字 ⁄ 字号 评论关闭

前文中写了基本操作中的插入,查找,最大节点,最小节点。本文主要写如何进行删除。

删除的最重要的原则是,删除后依然要维护BST的性质,也就是说,中序遍历后为升序。 删除的思路有三个。 


思路一

先理解前驱和后继的含义。

节点key的前驱,就是中序遍历时,比key小的所有节点中最大的那个节点。

节点key的后继,就是中序遍历时,比key小的所有节点中最大的那个节点。

容易看出,节点key的前驱一定没有右儿子,可能有左儿子。反证很容易。如下图,节点10的前驱8可能有左儿子,可能没有左儿子。

 

容易看出,节点key的后继一定没有左儿子,可能有右儿子。反证很容易。如下图,节点10的后继12可能有右儿子,可能没有右儿子。

假定要删除的节点为key,那么要删除key可以用key的前驱替换key也可以用key的后继替换key。

第一种思路,优先用key的前驱替换key,如果key没有前驱,则用key的后继替换key。

    首先,如果key的前驱不存在,后继也不存在。

    则key是叶结点,直接删就行。

    其次,如果key的前驱存在。

要做到删除p并且不丢掉p的左儿子并且依然维护BST的特性,可以用p的左儿子替换p,用p替换key。这个过程也嵌套了一个删除的过程,是一个以p的左儿子为root的子树上删除p的过程,所以接下来可以看到代码中递归的实现。(不管P的左儿子是不是NULL都可以这样嵌套地删除)

最后,如果key的前驱不存在,后继存在,

则设key的后继为p,那么类似的,根据上面的结论,p一定没有左儿子,可能有右儿子。接下来的过程和上面是对称的。

用p的值替换key,然后在以p的右儿子为root的子树中,删除p,不断嵌套删除下去。(同样,不管P的右儿子是不是NULL,这样嵌套地删除都合理。)

代码如下

link erase(link t, int key)
{
	link p;
	if(!t)
		return NULL;
	if(t->item>key)
		t->l=erase(t->l,key);
	else if(t->item<key)
		t->r=erase(t->r,key);
	else{
		if(t->l == NULL&&t->r == NULL) {		
			free_node(t);
			t = NULL;
		} else if(t->l) {
			for(p=t->l;p->r;p=p->r);
			t->item = p->item;				
			t->l = erase(t->l,t->item);
		} else {
			for(p=t->r;p->l;p=p->l);
			t->item = p->item;
			t->r = erase(t->r,t->item);
		}
	}
	return t;
}

思路二

把上面的优先顺序颠倒一下,优先删除后继,再删除前驱。

link erase_3(link t, int key)
{
	link p;
	if(!t)
		return NULL;
	if(t->item>key)
		t->l=erase_3(t->l,key);
	else if(t->item<key)
		t->r=erase_3(t->r,key);
	else{
		if(t->l == NULL&&t->r == NULL) {		
			free_node(t);
			t = NULL;
		} else if(t->r) { //先判断是否有右儿子,再判断是否有左儿子,顺序和link erase(link t, int key)相反
			for(p=t->r;p->l;p=p->l);
			t->item = p->item;
			t->r = erase_3(t->r,t->item);			
		} else {
			for(p=t->l;p->r;p=p->r);
			t->item = p->item;				
			t->l = erase_3(t->l,t->item);
		}
	}
	return t;
}

思路三

来自于《CLRS算法导论》的做法。如果说上面的思路一和思路二是从节点升序排列的角度进行的删除,那么思路三就是从形状角度进行的删除。

分3种情况处理.

(待删除的节点key和图a图b图c中的节点y是相同的节点。)

情况1  key为叶节点,则直接删。

情况2  key只有一个子节点,就把key给排挤掉。也就是,让key的父节点指向key的左儿子节点(或右儿子节点),并且让key的左子节点(或右儿子节点)的父节点指向key的父节点。如果要通过这样的图形形象的方式而非前面数字逻辑的方式进行删除,那么需要在节点结构体中增加parent指针,从而维护树的结构。

情况3 key有两个子节点,则找到key的后继,然后先用key的后继的值替换key的值,在进入到以key的后继为root的子树中,删除key节点。容易根据后继的特点知道,key的后继没有左儿子,所以删除key节点,转化为了在以key后继为root的子树中,进行情况2的删除。

代码如下。

首先修改node结构体

typedef struct node* link;
struct node{
	int item;
	link l,r,parent;
};

接下来删除

link erase_4(link t, int key)
{
	link del_node = search_recur(t,key);
	link y = NULL;
	link x = NULL;
	if ((del_node->l == NULL) || (del_node->r == NULL))
		y =  del_node;
	else 
		y = successor(del_node);
	if(y->l!=NULL)
		x = y->l;
	else
		x = y->r;
	if(x!=NULL)
		x->parent = y->parent;

	if(y->parent != NULL) {	    
        if(y==y->parent->l)
	    	y->parent->l= x;
	    else 
	        y->parent->r= x;
	}else {
		t=x;
	}
	if(y!=del_node)
		del_node->item = y->item;
	free_node(y);
	return t;
}

抱歉!评论已关闭.