您的位置:首页 > 其它

二叉查找树之——非递归实现

2013-10-30 19:59 267 查看
上一一篇文章介绍了二叉查找树的递归实现,递归算法以简洁明了,便于阅读与理解,但是只要是与递归有关,就少不了递归调用栈,也就是函数栈,这会导致栈溢出。假如递归层次达到10000次,程序就崩溃了,二叉查找树当插入的节点是升序或者逆序,就形成了链表,这样就形成了10000层递归。并且,递归没有非递归效率高,语音在于函数调用会相对耗时。所以非递归算法在安全性和效率上更实用。下面对二叉查找操作的非递归算法实现。

查找节点,很简单,通过循环来查找,代码如下:

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;
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: