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

来源:互联网 发布:linux手机版 编辑:程序博客网 时间:2024/04/29 04:36

        上一一篇文章介绍了二叉查找树的递归实现,递归算法以简洁明了,便于阅读与理解,但是只要是与递归有关,就少不了递归调用栈,也就是函数栈,这会导致栈溢出。假如递归层次达到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;            }}}}
原创粉丝点击