二叉查找树

前几天一直在看《算法导论》中二叉查找树一章,觉得收获颇多。后为了加深印象就写了代码来进行操作。

文章前面的是摘自其他博客的信息

1、问题引入

查找树是一种数据结构,它支持多种动态集合操作,包括构造、查找、插入、删除、寻找最小值、寻找最大值等 ,其中构造过程主要采用不断插入值来完成。

二叉查找树中的关键字存储方式满足以下性质:设X为二叉查找树的一个节点,如果Y是X的左子树中的一个节点,则Y->data <= X->data;如果Y是X右子树上的一个节点,则Y->data >= X->data。

二叉查找树上执行的基本操作的时间与树的高度成正比,对于一棵含有n个节点的完全二叉树,这些操作的最坏情况运行时间为o(lgn)。同样的,如果树退化成还有n个节点的线性链表时,这些操作的最坏时间为o(n)。

2、数据结构

二叉查找树是按照二叉树结构来组织的,这样的树可以采用数组来表示,也可以采用链表结构来表示,不过数组表示没有链表结构简单明了,这里采用链表结构来表示,每一个节点都表示一个节点对象,节点中包含data数据部分、left指针(指向左儿子)、right指针(指向右儿子),如果某个节点的儿子节点不存在,则相应的儿子节点为NULL。

定义节点数据结构

/*
 * 二叉排序树(或者说是二叉查找树)
 */
struct TreeNode
{
	int keyWord;   //结点中的关键字(我这里为了简单起见)
        //DataType elem;   //结点中的关键字
	TreeNode *pLeft;  //结点中的左指针
	TreeNode *pRight;  //节点中的右指针
	TreeNode(int word):keyWord(word),pLeft(NULL),pRight(NULL){}//树结点的构造函数
};
二叉树的类结构


*
 * 二叉排序树(或者说是二叉查找树)
 */
struct TreeNode
{
	int keyWord;   //结点中的关键字
	TreeNode *pLeft;  //结点中的左指针
	TreeNode *pRight;  //节点中的右指针
	TreeNode(int word):keyWord(word),pLeft(NULL),pRight(NULL){}//树结点的构造函数
};

class BSTree
{
public:
	BSTree(void);
	~BSTree(void);

	/*
	 * 向树中插入元素
	 */
	void Insert(int key);

	/*
	 * 在二叉树中查找给定的元素的结点
	 * @param key :给定的元素
	 * @return TreeNode*:指向该结点的指针(如果不存在这样的结点返回NULL) 
	 */
	TreeNode* Search(int key);

	/*
	 * 验证二叉树是不是一个空二叉树
	 * @param root:二叉树的根节点
	 * @return bool:返回值
	 */
	bool IsEmptyBSTree(TreeNode *root); 

	/*
	 * 中序遍历二叉树(递归)
	 * @param root :树的根节点
	 */
	void MiddleOrderRecur(TreeNode *root);  

	/*
	 * 中序遍历二叉树(非递归)
	 */
	void MiddleOrder(); 

	/*
	 * 查找二叉树中的最大结点
	 * @parem root:二叉树的根节点
	 * @return TreeNode * :指向最大结点的指针
	 */
	TreeNode* MaxNode(TreeNode *root);

	/*
	 * 查找二叉树中的最小结点
	 * @param root:二叉树的根节点
	 * @retrn TreeNode * :指向最小结点的指针
	 */
       TreeNode* minNode(TreeNode *root);

	/*
	 * 查找指定结点的前驱
	 * @param keyWord:指定节点
	 * @return TreeNode:指向前驱节点的指针
	 */
	TreeNode * Precursor(int keyWord);
	
	/*
	 * 查找指定节点的后继
	 * @param keyWord: 指定节点
	 * @return TreeNode: 指向后继结点的指针
	 */
	TreeNode * Successor(int keyWord);

	/*
	 * 删除某个结点信息
	 * @param keyWord:结点中的关键字
	 */
	void Delete(int keyWord);

	/*
	 * 查找某个结点的父节点
	 * @param node:某个结点
	 * @param TreeNode * : 其父节点(注意根节点没有父节点,这时返回值为NULL)
	 */
	TreeNode * FatherNode(TreeNode *node);

	TreeNode *m_pRoot;   //树的根结点

};
3、基本算法

3.1 构造二叉查找树、插入

构造一棵二叉查找树实际上就是往二叉查找中插入数据,边插入边构造。

/*
 * 向二叉树中插入元素
 * @param keyWord:要插入的元素
 */
void BSTree::Insert(int key)
{
	TreeNode *node = new TreeNode(key); //生成该节点
	//如果此二叉树不为空
	if (IsEmptyBSTree(m_pRoot))
	{
		TreeNode * parent = NULL;   //记录要插入结点的父节点
		TreeNode * location = m_pRoot; //
		while(location!= NULL)
		{
			if (location->keyWord > key)
			{
				parent = location;
				location = location->pLeft;
			}
			else if(location->keyWord <key)
			{
				parent = location;
				location = location->pRight;
			}
		}
		if (parent->keyWord>key)
		{
			parent->pLeft = node;
		}
		else
		{
			parent->pRight= node;
		}
	}	
	else
	{  //如果此二叉树为空
		m_pRoot = node;			
	}
}
在二叉树中插入结点,可以使用递归算法(这个我之前在《算法导论》的课后习题中看到这个题目)


/*
 * 递归向数中插入元素
 */
TreeNode* BSTree::InsertRecur(TreeNode * root, int key)
{
	if (root == NULL)
	{
		root = new TreeNode(key);
		root->pLeft = NULL;
		root->pRight = NULL;
	} 
	else
	{
		if (root->keyWord > key)
		{
			root->pLeft = InsertRecur(root->pLeft, key); //往左子树中插入数据
		}
		else
		{
			root->pRight = InsertRecur(root->pRight, key); //往右子树中插入数据
		}
	}
	return root;
}


3.2、查找

/*
 * 查找
 */
TreeNode* BSTree::Search(int key)
{
	//该二叉树不为空
	if (IsEmptyBSTree(m_pRoot))
	{
		TreeNode * p = m_pRoot;
		while(p!= NULL)
		{
			if (p->keyWord == key)
			{
				return p; //找到该结点
			}
			else if (p->keyWord>key)  //往左子树方向查找
			{
				p = p->pLeft;
			}
			else                    //往右子树方向查找
			{
				p = p->pRight;
			}
		}
		return NULL;	
	}
	return NULL;
}
引申问题:若查找不到,找到该元素要插入的位置

解答:在查找中我们要随时要记录节点的父节点,下面用递归解决该问题

3.3、查找最大值最小值

//查找最大结点(右子树的最右边的节点)
TreeNode* BSTree::MaxNode(TreeNode *root)
{
	if(IsEmptyBSTree(root))
	{
		//如果该树不为空
		TreeNode *p = root; 
		while(p->pRight != NULL)
		{
			p = p->pRight;
		}
		return p;
	}

	//该树为空
	return NULL;
}

//查找最小结点(左子树的最左边的节点)
TreeNode * BSTree::minNode(TreeNode *root)
{
	if(IsEmptyBSTree(root))
	{
		//如果该树不为空
		TreeNode *p = root;
		while(p->pLeft != NULL)
		{
			p = p->pLeft;
		}
		return p;
	}
	return NULL;
}
3.4、中序遍历二叉查找树


/*
 * 中序遍历二叉树(递归)
 */
void BSTree::MiddleOrderRecur(TreeNode *root)
{
	//不是空的二叉树
	if (IsEmptyBSTree(root))
	{
		MiddleOrderRecur(root->pLeft);  //遍历左子树
		cout<<root->keyWord<<" ";  //遍历根节点
		MiddleOrderRecur(root->pRight);  //遍历右子树;	
	}
}
3.5、删除某个结点


//删除某个结点
void BSTree::Delete(int keyWord)
{
	TreeNode *pCurrent;
	TreeNode *pParent;
	if ((pCurrent = Search(keyWord)) != NULL)
	{
		//pCurrent指示当前结点,pParent指示其父节点
		pParent = FatherNode(pCurrent);
		if ((pCurrent->pLeft == NULL) && (pCurrent->pRight == NULL))
		{
			//该节点无左右子树
			if(pParent == NULL)  //该结点是根节点
			{
				m_pRoot = NULL;
			}
			else
			{					//该结点不是根节点
				if (pParent->pLeft == pCurrent)
				{
					pParent->pLeft = NULL;
					delete pCurrent;					
				}
				else
				{
					pParent->pRight = NULL;
					delete pCurrent;   //释放结点
				}
			}
		}
		else if ((pCurrent->pLeft == NULL && pCurrent->pRight != NULL) || (pCurrent->pLeft != NULL && pCurrent->pRight == NULL))
		{
			//该结点只有一个孩子
			if(pParent == NULL)
			{
				//该结点为根节点
				m_pRoot = pCurrent->pLeft == NULL?pCurrent->pRight:pCurrent->pLeft;  //
				delete pCurrent;
			}
			else
			{
				//该结点不是根结点
				//就将该父结点指向它的指针指向他的孩子
				(pParent->pLeft == pCurrent?pParent->pLeft:pParent->pRight) = (pCurrent->pLeft == NULL?pCurrent->pRight:pCurrent->pLeft);
				delete pCurrent;  //释放结点
			}
		}
		else
		{
			//如果该节点有两个孩子,那个该结点一定有一个后继和一个前驱
			//现在我们假设寻找他的后继来代替他,这个后继至多只有一个孩子并且是右孩子
			TreeNode *pSuccessor = Successor(keyWord);  //寻找该节点的后继
			TreeNode *pSuccessorParent = FatherNode(pSuccessor);  //找到该后继的父节点
			pCurrent->keyWord = pSuccessor->keyWord;
			//将其后继父节点指向他的指针指向他的右孩子
			(pSuccessorParent->pLeft == pSuccessor ?pSuccessorParent->pLeft:pSuccessorParent->pRight) = pSuccessor->pRight;
			delete pSuccessor;
		}
		
	} 
	else
	{
		cout<<"未找到该结点"<<endl;
	}
	MiddleOrderRecur(m_pRoot);
}
二叉查找树的删除有些复杂,要考虑三种情况

1、删除的节点没有孩子,就直接释放这个节点即可,同时将其父节点的相应指针付空,释放该节点;

2、删除的节点只有一个孩子,就将其父节点指向他的指针指向他的孩子节点,释放该节点;

3、删除的节点有两个孩子,这时候有两种方法

1)找到这个节点的后继(一定有一个后继,此后继至多有一个孩子,并且还是右孩子),然后将这个后继的key值复制到此节点的key值中,同时将这个后继的父节点指向他的指针指向他的孩子节点;

2)找到这个节点的前驱(一定有一个前驱,此前驱至多有一个孩子,并且还是左孩子),然后将这个前驱的key值复制到此节点的key值中,同时将这个前驱的父节点指向他的指针指向他的孩子节点;

然后释放后继\前驱节点。



转载于:https://my.oschina.net/eillenme/blog/113660

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值