二叉查找树之——非递归实现
2013-10-30 19:59
267 查看
上一一篇文章介绍了二叉查找树的递归实现,递归算法以简洁明了,便于阅读与理解,但是只要是与递归有关,就少不了递归调用栈,也就是函数栈,这会导致栈溢出。假如递归层次达到10000次,程序就崩溃了,二叉查找树当插入的节点是升序或者逆序,就形成了链表,这样就形成了10000层递归。并且,递归没有非递归效率高,语音在于函数调用会相对耗时。所以非递归算法在安全性和效率上更实用。下面对二叉查找操作的非递归算法实现。
查找节点,很简单,通过循环来查找,代码如下:
查找最小元素,一直往左走,找到后访问,代码如下:
查找最大元素,一直往右走,找到后访问,代码如下:
插入一个节点,就要找到位置后插入,因为找到后,遍历指针为NULL,就需要一个指针尾随着遍历指针,这样就可以插入后返回正确位置的指针,代码如下:
二叉树的非递归删除也是很复杂,首先,让我们用一个公用的删除策略,用删除的节点的右子树的最小节点代替被删除节点,这样就可以满足二叉树的定义:左小右大。删除时,又要看看特殊情况,如果删除的节点没有右孩子怎么处理?只有一个节点或者没有节点怎么处理?下面让我们来分情况讨论:
删除的节点有左右子树:选取右子树的最小值代替删除节点,然后删除最小节点。
删除的节点只有一个或着没有孩子:需要用一个指针跟踪要删除的节点的上一个节点位置,这样就可以删除节点后设置它的父亲节点左右子树正确的指针。
更多细节请看如下代码:
下面来讲解遍历操作,遍历需要用到栈来保存将要被访问的节点指针,遍历有一个特点,就是中后遍历,只要遍历的左孩子,就是相当于遍历了左节点和跟节点,所以主要关注的是右子树。
前序遍历:根左右。先跟后左右子树,这个代码很好写,只需要将根节点入栈,然后出栈访问后再将其左右子树入栈,然后重复这个过程直到栈空,代码如下:
中序的遍历解放方案是:将一直往左找,找到最左边的节点后,访问该节点,然后出栈,将它的右子树入栈访问,重复这个过程。代码如下:
后序遍历比较复杂,因为不能单单通过入栈出栈来全部访问,必须设置一个标志,标志这个节点的右子树是否已经访问了,如果没有访问,则访问,否则将遍历右子树。
删除策略:首先将左边全部入栈,然后出栈访问,看看它的右子树是否已经访问,如果没有,则访问右子树否则访问它。必须用到一个辅助数据结构,这里用一个标志栈来与节点栈匹配,看对应的节点栈顶元素的右子树是否已经访问,false为未访问,true为已访问。
查找节点,很简单,通过循环来查找,代码如下:
Position CBinarySearchTreeNoRecursion::find( int iElement,const Root pRoot ) { if ( NULL == pRoot ) { return NULL; } TreeNode* pNode = pRoot; while ( pNode != NULL ) { if ( iElement < pNode->idata ) { pNode = pNode->lchild; }else if ( iElement > pNode->idata ) { pNode = pNode->rchild; }else { visit(pNode); return pNode; //找到则返回 } } return NULL; }
查找最小元素,一直往左走,找到后访问,代码如下:
Position CBinarySearchTreeNoRecursion::findMin( const Root pRoot ) { if ( NULL == pRoot ) { return NULL; } TreeNode *pNode = pRoot; while ( pNode->lchild != NULL) //一直走到最左边 { pNode = pNode->lchild; } visit(pNode); return pNode; }
查找最大元素,一直往右走,找到后访问,代码如下:
Position CBinarySearchTreeNoRecursion::findMax( const Root pRoot ) { if ( NULL == pRoot ) { return NULL; } TreeNode *pNode = pRoot; while ( pNode->rchild != NULL) //一直走到最右 { pNode = pNode->rchild; } visit(pNode); return pNode; }
插入一个节点,就要找到位置后插入,因为找到后,遍历指针为NULL,就需要一个指针尾随着遍历指针,这样就可以插入后返回正确位置的指针,代码如下:
Root CBinarySearchTreeNoRecursion::insertNode( int iElement,Root pRoot ) { if ( NULL == pRoot ) { return NULL; } TreeNode* pNode = NULL; TreeNode* pTemp = pRoot; while ( pRoot != NULL ) //找到插入的位置 { pNode = pRoot;//保存上一个节点的指针 if ( iElement < pRoot->idata ) { pRoot = pRoot->lchild; }else if (iElement > pRoot->idata ) { pRoot = pRoot->rchild; }else { pRoot->uiTimes++; return pTemp; } } if ( iElement < pNode->idata) //插入该位置的左边还是右边 { pNode->lchild = new TreeNode(iElement); }else if (iElement > pNode->idata ) { pNode->rchild = new TreeNode(iElement); } return pNode; }
二叉树的非递归删除也是很复杂,首先,让我们用一个公用的删除策略,用删除的节点的右子树的最小节点代替被删除节点,这样就可以满足二叉树的定义:左小右大。删除时,又要看看特殊情况,如果删除的节点没有右孩子怎么处理?只有一个节点或者没有节点怎么处理?下面让我们来分情况讨论:
删除的节点有左右子树:选取右子树的最小值代替删除节点,然后删除最小节点。
删除的节点只有一个或着没有孩子:需要用一个指针跟踪要删除的节点的上一个节点位置,这样就可以删除节点后设置它的父亲节点左右子树正确的指针。
更多细节请看如下代码:
Root CBinarySearchTreeNoRecursion::deleteNode( int iElement,Root pRoot ) { if ( NULL == pRoot ) { return pRoot; } Root pNode = pRoot; //遍历用的指针 Root pLastNode = pNode; //遍历指针的上一个节点的指针 Root pMinOnRight = NULL; //右子树最小节点的指针 Root pSecondMinOnRight = NULL; //右子树次最小节点的指针 while ( pNode != NULL ) { if ( iElement < pNode->idata ) { pLastNode = pNode; pNode = pNode->lchild; }else if ( iElement > pNode->idata ) { pLastNode = pNode; pNode = pNode->rchild; }else //找到要删除的节点 { if ( pNode->lchild != NULL && pNode->rchild != NULL )//有两个孩子情况 { pSecondMinOnRight = pNode; pMinOnRight = pNode->rchild; while ( pMinOnRight->lchild != NULL) //删除策略:用右子树最小节点代替要删除的节点。找到最小的和次最小的节点 { pSecondMinOnRight = pMinOnRight; pMinOnRight = pMinOnRight->lchild; } pNode->idata = pMinOnRight->idata; //将找到的最小节点放到要删除的节点位置,其实是值交换 if ( NULL == pMinOnRight->rchild ) //找到的最小节点是叶子节点 { pSecondMinOnRight->lchild = NULL; }else //找到的最小节点是非叶子节点,则要处理它的右孩子,将右孩子连接到最小节点的父亲节点处 { pSecondMinOnRight->lchild = pMinOnRight->rchild; } printf("delete the element: %d\n",pMinOnRight->idata); delete pMinOnRight; pMinOnRight = NULL; return pRoot; }else//一个或者零个孩子的情况 { if ( pLastNode->lchild == pNode ) //要删除的节点是左子树还是右子树 { if ( NULL == pNode->lchild ) //删除的节点是左节点 { pLastNode->lchild = pNode->rchild; //如果删除节点的左子树是空,则返回右子树 }else if ( NULL == pNode->rchild ) { pLastNode->lchild = pNode->lchild;//如果删除节点的右子树是空,则返回左子树 } } else if( pLastNode->rchild == pNode )//删除的节点是右节点 { if ( NULL == pNode->lchild ) { pLastNode->rchild = pNode->rchild; }else if ( NULL == pNode->rchild ) { pLastNode->rchild = pNode->lchild; } } printf("delete the element: %d\n",pNode->idata); delete pNode; pNode = NULL; return pRoot; } } } return pRoot; }
下面来讲解遍历操作,遍历需要用到栈来保存将要被访问的节点指针,遍历有一个特点,就是中后遍历,只要遍历的左孩子,就是相当于遍历了左节点和跟节点,所以主要关注的是右子树。
前序遍历:根左右。先跟后左右子树,这个代码很好写,只需要将根节点入栈,然后出栈访问后再将其左右子树入栈,然后重复这个过程直到栈空,代码如下:
void CBinarySearchTreeNoRecursion::preOrder( const Root pRoot ) { if ( NULL == pRoot ) { return; } stack<TreeNode*> nodeStack; nodeStack.push(pRoot); TreeNode* pNode = pRoot; while ( !nodeStack.empty() ) { pNode = nodeStack.top(); nodeStack.pop(); visit(pNode);//访问根节点的逻辑 if ( pNode->rchild != NULL ) { nodeStack.push(pNode->rchild); } if ( pNode->lchild != NULL ) { nodeStack.push(pNode->lchild); } } }
中序的遍历解放方案是:将一直往左找,找到最左边的节点后,访问该节点,然后出栈,将它的右子树入栈访问,重复这个过程。代码如下:
void CBinarySearchTreeNoRecursion::inOrder( const Root pRoot ) { if ( NULL == pRoot ) { return; } stack<TreeNode*> nodeStack; TreeNode* pNode = pRoot; while ( pNode || !nodeStack.empty() ) { if ( pNode != NULL ) { nodeStack.push(pNode); //遍历左子树 pNode = pNode->lchild; }else { pNode = nodeStack.top(); nodeStack.pop(); visit(pNode);//访问左节点的逻辑 pNode = pNode->rchild; //遍历右子树 } } }
后序遍历比较复杂,因为不能单单通过入栈出栈来全部访问,必须设置一个标志,标志这个节点的右子树是否已经访问了,如果没有访问,则访问,否则将遍历右子树。
删除策略:首先将左边全部入栈,然后出栈访问,看看它的右子树是否已经访问,如果没有,则访问右子树否则访问它。必须用到一个辅助数据结构,这里用一个标志栈来与节点栈匹配,看对应的节点栈顶元素的右子树是否已经访问,false为未访问,true为已访问。
void CBinarySearchTreeNoRecursion::posOrder( const Root pRoot ) { if ( NULL == pRoot ) { return; } TreeNode* pNode = pRoot; stack<TreeNode*> nodeStack; stack<bool> flageStack; //标志栈,false:未访问,true:已访问 while ( pNode != NULL ) //将树的最左边节点全部入栈 { nodeStack.push(pNode); flageStack.push(false); //这两个栈有同时出入,节点栈出栈,则标志栈也要出入。 pNode = pNode->lchild; } while ( !nodeStack.empty() ) { pNode = nodeStack.top(); if (NULL == pNode->rchild || flageStack.top() ) //如果没有右子树或者右子树已经访问,则可以访问这个节点。 { nodeStack.pop(); flageStack.pop(); visit(pNode); }else //右孩子存在并且这个节点没有被访问,则遍历右子树 { flageStack.pop(); flageStack.push(true); //因为下面将右子树入栈,右子树后肯定会先于它被访问 //所以这里可以设置这个节点的右孩子已经被访问。 pNode = pNode->rchild; while ( pNode != NULL ) //遍历右子树 { nodeStack.push(pNode); flageStack.push(false); pNode = pNode->lchild; } } } }
相关文章推荐
- C++数据结构--二分查找的递归和非递归实现
- 查找二叉树的实现-镜像、层序,先序非递归实现
- 递归和非递归实现二分查找
- Python实现对文件夹内文本文件递归查找
- 二分查找的递归和非递归实现,二分查找的扩展
- 递归与非递归下二叉查找数的插入·查找和删除
- 递归实现二分查找
- 二分查找的递归与非递归实现【Java版】
- 数据结构10:二分查找的递归与非递归表示与实现
- 二叉树遍历及查找、统计个数、比较、求深度的递归实现
- 二叉查找树的实现
- 二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
- 二分法查找实现(递归与非递归)
- C语言实现顺序表和有序表的查找以及有序表的递归查找
- 数据结构(六)——二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
- Java实现折半查找(二分查找)的递归和非递归算法
- 数据结构(六)——二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
- 用PHP实现二分法查找之递归和迭代
- javascript实现二叉树的创建,遍历,添加,查找最大值最小值和指定值的寻找及删除功能——递归的多次运用
- 使用c语言指针和递归方法实现二分查找