前几天一直在看《算法导论》中二叉查找树一章,觉得收获颇多。后为了加深印象就写了代码来进行操作。
文章前面的是摘自其他博客的信息
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值中,同时将这个前驱的父节点指向他的指针指向他的孩子节点;
然后释放后继\前驱节点。