【算法导论】二叉搜索树的插入和删除

上一篇说了有关二叉树遍历的三种方式,文本将继续探讨如何实现二叉搜索树的插入和删除节点。

在继续之前,我们先来了解两个概念:前驱和后继。

一、后继和前驱

后继:如果所有的关键字互不相同,则一个节点x的后继是大于x.key的最小关键字的节点。

前驱:如果所有的关键字互不相同,则一个节点x的前驱是小于x.key的最大关键字的节点。

如果联系二叉搜索树的性质:

节点的key值总是大于它的左节点(如果存在)的key值并且小于它的右节点(如果存在)的key值。那么我们容易推知:如果一个节点有右子树,它后继即是它的右子树的最“左”端的节点,也即右子树的最左节点,否则它的后继位于它的某个父或祖父点,这个父或祖父节点有最后一次的左分支指向当前节点所在的子树;而一个节点如果有左子树,它的前驱即是它的左子树的最“右”端的节点,也即左子树的最大节点,否则它的前驱位于它的某个父或者祖父节点,这个父或者祖父节点有最后一次的右分支指向当前节点所在的子树。

了解了上面的内容,就不难给出前驱和后继的代码:

后继:

	/*
	 * 求x节点的后继,后继的key值为大于x.key的最小值
	 */
	node* successor(node* x) {
		if (x->right != NULL) {
			//右子树中的最小值,没有左节点
			return minimum(x->right);
		}

		node* y = x->parent;

		while (y != NULL && x == y->right) {
			x = y;
			y = y->parent;
		}
		return y;
	}

前驱:

	/*
	 * 求x节点的前驱,前驱的key值为小于x.key的最大值
	 */
	node* predessor(node* x) {
		if (x->left != NULL) {
			//左子树中的最大值,没有右节点
			return maximum(x->left);
		}

		node* y = x->parent;
		while (y != NULL && x == y->left) {
			x = y;
			y = y->parent;
		}
		return y;
	}

二、节点的插入

插入的操作相对比较简单,我们只需要注意满足二叉树节点之间的关系,以及注意树是否为空的判断即可,代码如下:

递归的方式插入:

	/*
	 * 递归方式插入,r:当前递归遍历到的节点,pre:r的父节点,x要插入的节点
	 */
	void tree_insert(node* pre, node* r, node*x) {
		if (r == NULL) {
			if (pre == NULL) {
				this->root = x;
			} else if (pre->key > x->key) {
				pre->left = x;
			} else {
				pre->right = x;
			}
			x->parent = pre;
		} else {
			if (r->key > x->key) {
				tree_insert(r, r->left, x);
			} else {
				tree_insert(r, r->right, x);
			}
		}
	}

	void tree_insert(node* x) {
		tree_insert(NULL, root, x);
	}

非递归方式插入:

	/*
	 * 插入一个关键值为key的节点
	 */
	void tree_insert(int key) {
		node* n = new node(key);
		node* back = NULL;
		if (root == NULL) {
			root = n;
		} else {
			node* current = root;

			while (current != NULL) {
				back = current;
				if (current->key > key) {
					current = current->left; //左边放小的key值
				} else {
					current = current->right; //右边放大的key值
				}
			}

			if (back == NULL) {
				//表明树是空树
				this->root = n;
			} else if (back->key > key) {
				back->left = n;
			} else {
				back->right = n;
			}
			n->parent = back;
		}
	}

三、节点的删除

二叉搜索树删除节点的操作比较复杂,主要考虑以下几种情况:

①要删除的节点没有孩子,那么直接删除即可

②要删除的节点只有一个孩子,那么将孩子放到节点位置,再删除节点即可

③要删除的节点有两个孩子,那么可以寻找节点的后继。

由于有两颗子树,所以它的后继一定在右子树中,且是右子树中最小的那个节点,所以没有左子节点。这里又要分量小细分情况:如果这个后继节点就是要删除节点的右子节点,那么使用右子节点替换原节点,再删除原节点即可;如果这个后继节点并不是要删除节点右子节点,而是在右子树的下层之中,那么要先用后继的右子树替换后继,然后再用后继节点替换要删除的节点。

上面提到的“替换”操作,实际上是一种节点的“移植”,我们将这个操作抽取出来,以方便使用:

	/*
	 * 移植操作:
	 * 用以b为根的子树去替换以a为根的子树,这里只管替换,不管a的处理,也不管b的值是否合适,
	 * 也不管b节点左右子树的处理
	 */
	void transplant(node* a, node* b) {
		if (b == NULL) {
			return;
		}
		if (a->parent == NULL) {
			root = b;
		}

		if (a->parent->left == a) {
			//a是其父节点的左节点
			a->parent->left = b;
		} else {
			a->parent->right = b;
		}

		if (b != NULL) {
			b->parent = a->parent;
		}
	}

节点的删除操作:

	void tree_delete(node* n) {
		if (n == NULL) {
			return;
		}
		if (n->left == NULL) { //如果左子节点为空,那么最多只会有一个右节点,直接执行移植操作即可
			transplant(n, n->right);
		} else if (n->right == NULL) { //如果左子节点不为空,而右子节点为空,则只有一个左子节点,也可以直接执行移植操作
			transplant(n, n->left);
		} else {
			//左右两个子节点都不为空的情况
			node* succ = minimum(n->right); //求右子树中的最小值

			if (succ == n->right) { //如果suc是n的直系右节点
				transplant(succ, succ->right);
				transplant(n, succ);
				succ->left = n->left;//因为是直系右节点,所以只需要处理左子树即可
				succ->left->parent = succ;

			} else { //如果suc不是n的直系右节点

				transplant(succ, succ->right); //将后继的右子树替换后继
				transplant(n, succ); //将后继替换要删除的节点
				succ->parent = n->parent;
				succ->right = n->right;//处理右子树
				succ->left = n->left;
				succ->left->parent = succ;//处理左子树
				succ->right->parent = succ;
			}
		}
		delete n;
	}

至此就完成了删除的操作。

时间: 2024-08-10 17:21:30

【算法导论】二叉搜索树的插入和删除的相关文章

算法导论—二叉搜索树(BST)

华电北风吹 天津大学认知计算与应用重点实验室 日期:2015/9/9 与散列表一样,搜索树数据结构也支持动态集合操作,包含插入,查询,删除,最小值,最大值,前驱,后继等. 一.二叉搜索树: 二叉搜索树节点:关键字key,卫星数据,左孩子指针,右孩子指针,父节点指针,其他特殊类型(红黑树的节点颜色,AVL树的树高等). 二叉搜索树性质:x是二叉搜索树中的任意一个节点.若y是x左子树中任意一个节点有x.key>=y.key.若y是x右子树中任意一个节点有x.key<=y.key. 二.二叉搜索树的

二叉搜索树的插入与删除图解

=================================================================== 一.二叉搜索树(BSTree)的概念 二叉搜索树又被称为二叉排序树,那么它本身也是一棵二叉树,那么满足以下性质的二叉树就是二叉搜索树: 1.若左子树不为空,则左子树上左右节点的值都小于根节点的值 2.若它的右子树不为空,则它的右子树上所有的节点的值都大于根节点的值 3.它的左右子树也要分别是二叉搜索树 ==============================

C++实现二叉搜索树的插入,删除

二叉搜索树即左孩子的值小于根节点,右孩子的值大于根节点. 二叉搜索树的插入: 即检查要插入的数(key,下文都用它表示)是否存在,若存在返回false,不存在即插入,在插入时,若key大于根节点,则插入到右子树中,更新根节点,依次类推:若key小于根节点,则插入到左子树中,更新根节点,以此类推.下面用图表示 现在要插入13,由二叉树的性质可知,13要插到10的右子树上,即 代码实现: bool Insert(const k& key, const v& value) {  if (_roo

二叉搜索树的插入,删除,和中序遍历

构建一个值的类型为int的二叉搜索树,输入N和M,然后进行N次插入操作,每次插入之后进行一次遍历验证代码正确性.然后进行M次删除操作,每次删除之后进行一次遍历验证代码正确性. #include "bits/stdc++.h" using namespace std; typedef long long LL; const int INF = 0x3f3f3f3f; struct BST { int value; BST* lson; BST* rson; }* root; void r

算法导论-----------二叉搜索树

先上二叉树查找树的删除的代码,因为删除是二叉查找树最复杂的操作: int BinarySearchTree<T>::tree_remove(const T& elem) { BinarySearchTreeNode<T> *z = tree_search(elem);//根据元素查找到要删除的节点 BinarySearchTreeNode<T> *x, *y; if (z != NULL) { //用y来表示实际要删除的节点 if (z->left ==

数据结构与算法之二叉搜索树

与链表不同,树是一种非线性的数据结构.树中最常用的是二叉树,二叉树限制了子树的数量,也就是每个结点的子树至多2个,并且这两个子树是有顺序的.而二叉搜索树(二叉查找树,二叉排序树)是指根节点的关键字大于左子树的,而小于右子树,并且,左右子树也是一颗二叉搜索树.也就是说中序遍历一颗二叉搜索树,它的输出是从小到大排序好的. 除了普通的二叉搜索树之外,还有很多关于它的变形. 二叉平衡搜索树,即即是一颗二叉平衡树,也是一颗搜索树,平衡树即任意一个结点的左子树的高度与右子树的高度之差的绝对值不大于1. 红黑

70 数组的Kmin算法和二叉搜索树的Kmin算法对比

[本文链接] http://www.cnblogs.com/hellogiser/p/kmin-of-array-vs-kmin-of-bst.html [分析] 数组的Kmin算法和二叉搜索树的Kmin算法非常类似,其本质是找序列中的第K大或者第K小的元素,可以借鉴QuickSort的思想加以实现. [Kmin_of_Array] C++ Code 1234567891011121314151617181920212223242526272829303132333435363738394041

二叉平衡树的插入和删除操作

1.      二叉平衡树 二叉排序树的时间复杂度和树的深度n有关.当先后插入的结点按关键字有序时,二叉排序树退化为单枝树,平均查找长度为(n+1)/2,查找效率比较低.提高查找效率,关键在于最大限度地降低树的深度n.因此需要在构成二叉排序树的过程中进行“平衡化”处理,使之成为二叉平衡树. 二叉平衡树,又称AVL树.它或者是一棵空树,或者是具有下列性质的树: 1)      具备二叉排序树的所有性质: 2)      左子树和右子树深度差的绝对值不超过1: 3)      左子树和右子树都是二叉

算法导论-二叉查找数

目录 引言 二叉查找树 节点定义 查找操作 插入操作 删除操作 二叉查找树存在问题 完整源码 讨论区 参考资料 内容                             1.引言                                   前面的文章介绍过二分查找.散列表查找:二分查找效率为Θ(lgn).二分查找要求数组是静态的,即元素不能动态的插入和删除,否则会耗费较多的时间:散列表查找效率可以到达Θ(1),但是一般用于“等于性查找“,不能进行“范围查找”;本文介绍的二叉查找树,(