您的位置:首页 > 其它

二叉树的各种遍历,二叉树改链表,二叉树复习

2015-08-31 19:29 295 查看


二叉树的一个节点的结构如下:

struct BinaryTree
{
int m_value;
BinaryTree* left;
BinaryTree* right;
};


二叉树的递归遍历

用递归来表达二叉树的遍历,算法思想最明白:

void pre_order_access(BinaryTree* pRoot)//先序遍历
{
if (pRoot != nullptr)
{
cout << pRoot->m_value <<"    ";   //先访问根节点
pre_order_access(pRoot->left);     //左子树
pre_order_access(pRoot->right);    //右子树
}
}

void mid_order_access(BinaryTree* pRoot)  //中续遍历
{
if (pRoot != nullptr)
{                                                                 //1、如果在这里定义int i;
mid_order_access(pRoot->left);   //先访问左子树
cout << pRoot->m_value <<"    ";  //访问根节点            //2、在这里给i赋值 ipRoot->m_value
mid_order_access(pRoot->right);   //访问右子树            //3、在末尾为i赋值,那么最终返回的i的值将是根节点的值 i
}                                                             //4、虽然最内层的i 最先被赋值
}

<pre name="code" class="cpp">void post_order_access(BinaryTree* pRoot)  //后续遍历
{
if (pRoot != nullptr)
{
post_order_access(pRoot->left);   //先访问左子树
post_order_access(pRoot->right);   //然后访问右子树
cout << pRoot->m_value <<"    ";   //最后根节点
}
}




用数组元素来构建一棵二叉树

对于遍历的测试,需要先构建一棵二叉树,一般的,可以用一个数组来存储一个完全二叉树(按层序来存储),对于索引为 i 的节点,其左右孩子节点的索引分别为 2*i + 1 和 2*i + 2;

BinaryTree* creatBinaryTree(int arr[], int i, int n)   //递归的来构造一棵完全二叉树
{
BinaryTree* pRoot;
if (i >= n)
return nullptr;
pRoot = (BinaryTree*)malloc(sizeof(BinaryTree));
pRoot->m_value = arr[i];
pRoot->left = creatBinaryTree(arr, 2 * i + 1, n);
pRoot->right = creatBinaryTree(arr, 2 * i + 2, n);
return pRoot;
}
调用时,这样就行

int arr[] = { 8, 4, 10, 2, 6, 9, 11, 1, 3, 5, 7};
BinaryTree* proot =  creatBinaryTree(arr, 0, sizeof(arr) / sizeof(int));
对于非完全二叉树,可以将左右节点为空的节点扩展出子节点来,相当于虚节点,节扩展点值为特殊的值,用来标记其父节点的对应左右为空。
BinaryTree* creatPreBinaryTree(int arr[], int& i) //注意这里的第二个参数用来表示索引   必须是个引用类型
{                                                    //表示每层递归使用的是同一个索引变量  不至于混乱
    BinaryTree* proot = nullptr;
    if (arr[i] == 0)
        return nullptr;
    proot = (BinaryTree*)malloc(sizeof(BinaryTree));
    proot->m_value = arr[i];    //先创建根节点
    ++i;
    proot->left = creatPreBinaryTree(arr, i);    //然后创建左子树
    ++i;
    proot->right = creatPreBinaryTree(arr, i);     //然后创建右子树
    return proot;                                //实际相当于一个前序遍历的创建
}
调用时

int a[] = { 8, 4, 2, 1,0, 0, 3, 0, 0, 6, 5, 0, 0, 7, 0, 0,10, 9, 0, 0, 11, 0, 0 };
int i = 0;
BinaryTree* root = creatPreBinaryTree(a, i);




层序遍历二叉树

层序遍历借助一个队列即可,从根节点开始入列之后,

当队列不为空,循环的的操作(注意循环的必须保持一致性,每个节点的操作相同):出列,将左右子节点入列即可

void level_access(BinaryTree* pRoot)   //分层遍历 相当于广度优先搜索
{
if (pRoot != nullptr)
{
queue<BinaryTree*> queueBinaryNode;
queueBinaryNode.push(pRoot);
BinaryTree* curNode = nullptr;
while (!queueBinaryNode.empty())   //每次访问队列的首元素  同时把其连个孩子放入队列中
{
curNode = queueBinaryNode.front();
cout << curNode->m_value << "    ";
queueBinaryNode.pop();
if (curNode->left != nullptr)
queueBinaryNode.push(curNode->left);
if (curNode->right != nullptr)
queueBinaryNode.push(curNode->right);
}
}
}


二叉树的非递归遍历

先序,中序,后续的遍历需要借助栈来实现,既然是栈,每次就只能对栈顶的元素进行操作,也就是每次访问输出都只能是栈顶的元素(当然要满足输出条件),而且必须保持操作的一致性,也就是循环体内对每个节点的处理相同。

前序遍历的非递归遍历:

void pre_deep_access(BinaryTree* pRoot)   //深度优先搜索  在这里相当于前序遍历
{
if (nullptr != pRoot)
{
stack<BinaryTree*> stackBinaryTree;
stackBinaryTree.push(pRoot);   //根节点入栈
BinaryTree* curNode = nullptr;
while (!stackBinaryTree.empty())
{
curNode = stackBinaryTree.top();   //每次对栈顶元素进行处理
stackBinaryTree.pop();
cout << curNode->m_value <<"    ";
if (curNode->right != nullptr)
stackBinaryTree.push(curNode->right);   //先右子节点入栈
if (curNode->left != nullptr)
stackBinaryTree.push(curNode->left);    //再左子节点入栈  这样保证左子节点先于右子节点访问
}
}
}
中序遍历的非递归遍历:

void mid_deep_access(BinaryTree* pRoot)
{
if (nullptr != pRoot)
{
stack<BinaryTree*> stackBinaryTree;
BinaryTree* curNode = pRoot;
while (!stackBinaryTree.empty() || (curNode != nullptr))
{                                          //根节点弹出之后 变空了  但是还有右边的子树  curNode用来标记的
while (nullptr != curNode) //沿着左下方向,将节点入栈
{
stackBinaryTree.push(curNode);
curNode = curNode->left;
}
if (!stackBinaryTree.empty())
{
curNode = stackBinaryTree.top();
cout << curNode->m_value << "    ";
stackBinaryTree.pop();    //如果将根节点弹出 此时栈为空但是右子树还没遍历所以wile 的循环条件中 需要或上curNode != nullptr
curNode = curNode->right;
}
}
//对于跟节点  最后弹出来  但是右子树还是不为空  所以这里需要用个或运算
}
}
后续遍历逻辑相对麻烦一些,先要沿着左下方向把节点入栈,不过最左下的子节点(左子节点为空),也就是栈顶的节点A,若右子节点非空,需要对右子节点进行沿着左下方向压入,最后返回到栈顶的节点又是A时,由于A的右子树已经遍历过,所以不能再重复,于是引入一个变量,指示当前访问(这里的访问在程序里就是输出节点值)节点的上一个访问的节点,如果栈顶的元素是A,但是上一个访问的节点是 A的右子节点,说明A的右子节点已经访问过,代码如下

void post_deep_access(BinaryTree* pRoot)
{
if (nullptr != pRoot)
{
stack<BinaryTree*> stackBinaryTree;
BinaryTree* curNode = pRoot;
BinaryTree* preNode = nullptr;   //指向前一个被访问的节点 也就是输出过的
while (curNode != nullptr || !stackBinaryTree.empty())
{
while (curNode != nullptr)
{
stackBinaryTree.push(curNode);
curNode = curNode->left;
}
curNode = stackBinaryTree.top();   //栈顶节点
if (curNode->right == nullptr || curNode->right == preNode)   //栈顶元素可以马上访问的条件 没有右子树  或者右子树刚访问过
{
cout << curNode->m_value << "    ";
stackBinaryTree.pop();
preNode = curNode;
curNode = nullptr;
}
else
curNode = curNode->right;     //栈顶元素不能马上访问
}
}
}
还有一种使用双栈来进行后序遍历的,将根节点入栈1,然后每次弹出时,压入到栈2当中,且把弹出的节点的左,右子节点压入栈1当中;这样保证最终所有的节点都会入栈2,且栈2中,所有的右子树在左子树的下边。

void post_deep_access2stack(BinaryTree* pRoot)
{
if (nullptr != pRoot)
{
stack<BinaryTree*> stackBinaryTree1, stackBinaryTree2;
BinaryTree* curNode = nullptr;
stackBinaryTree1.push(pRoot);   //将根节点入栈1
while (!stackBinaryTree1.empty())
{
curNode = stackBinaryTree1.top();
stackBinaryTree1.pop();
stackBinaryTree2.push(curNode);
if (curNode->left != nullptr)
stackBinaryTree1.push(curNode->left);
if (curNode->right != curNode)
stackBinaryTree1.push(curNode->right);
}
while (!stackBinaryTree2.empty())
{
curNode = stackBinaryTree2.top();
stackBinaryTree2.pop();
cout << curNode->m_value << "   ";
}
}
}
总结:沿着左下方向把节点入栈的,在循环内压入根节点,且循环判断的条件会多一个。循环外压入根节点的,会在循环里弹栈顶元素,然后压入左,右子节点。

对于队列,每次访问输出的只能队列的首元素;对于栈,每次访问输出的只能是栈顶元素。

将二叉树按中续遍历顺序改成链表

每访问到一个节点cur,将该节点的指向左子节点的指针指向前一个访问的节点 pre,所以这里需要保存下来前一个访问的节点,同时将pre 的指向右节点的指针指向当前节点即可,即cur->left = pre. pre->right = cur. 代码如下:

void binary2list(BinaryTree* pRoot, BinaryTree*& pre, BinaryTree* &result)
{
    if (pRoot != nullptr)
    {
        //BinaryTree* pHead = nullptr;
        binary2list(pRoot->left, pre, result);

        cout << pRoot->m_value << "    ";
        pRoot->left = pre;    //返回时赋值
        if (nullptr == result)
            result = pRoot;
        //对于非静态成员 pHead = pRoot;   //最外层的递归执行到这里  当前的pRoot是根节点了  此时左节点已经访问完毕
        //static BinaryTree* result = pRoot;
        if (nullptr != pre)
            pre->right = pRoot;
        pre = pRoot;   //pre最后指向尾节点
//空行这之间的代码第一次执行是左下边的那个节点  也就是中序遍历访问的第一个节点才执行
//而最外层的 binary2list() 执行到这里时,左子树已经遍历玩,所以想要使用一个局部变量pHead保存pRoot
//结果保存下来的这个pRoot实际指向的是根节点  而不是第一个访问的节点
        binary2list(pRoot->right, pre, result);
    }     
}


这里需要说明的是在不能在函数内部使用 static BinaryTree* pre = nullptr; 静态变量在静态存储区,只会赋值一次,也就是第一次调用该函数进入该函数的第一层会赋值,这样虽然每层函数使用的也是同一个pre,不过在外部调用完成之后,由于pre 不再为空,所以第二次在外部调用该函数,会发生错误(链表的头节点的左指针不为空)

该函数的调用如下:

BinaryTree* pTail = nullptr;
BinaryTree* pHead = nullptr;
binary2list(root, pTail, pHead);    //在这里 pHead一定要传一个空指针进去 否则会出错
//pHead为指向链表表头  pTail指向链表的最后一个元素
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: