当前位置: 首页 > news >正文

洛阳bbs/优化软件seo排名

洛阳bbs,优化软件seo排名,学做电商那个网站好,企业网站如何维护二叉树遍历是二叉树的最基本的操作,其实现方式主要有三种: 递归遍历非递归遍历Morris遍历 递归遍历的实现非常容易,非递归实现需要用到栈。而Morris算法可能很多人都不太熟悉,其强大之处在于只需要使用O(1)的空间就能实现对二叉…

二叉树遍历是二叉树的最基本的操作,其实现方式主要有三种:

  1. 递归遍历
  2. 非递归遍历
  3. Morris遍历

递归遍历的实现非常容易,非递归实现需要用到栈。而Morris算法可能很多人都不太熟悉,其强大之处在于只需要使用O(1)的空间就能实现对二叉树O(n)时间的遍历。

二叉树结点的定义

每个二叉树结点包括一个值以及左孩子和右孩子结点,其定义如下:

class TreeNode {public:int val;TreeNode *left, *right;TreeNode(int val) {this->val = val;this->left = this->right = NULL;}   
};

二叉树的遍历

二叉树的遍历,就是按照某条搜索路径访问树中的每一个结点,使得每个结点均被访问一次,而且仅被访问一次。常见的遍历次序有:

  1. 先序遍历:先访问根结点,再访问左子树,最后访问右子树
  2. 中序遍历:先访问左子树,再访问根结点,最后访问右子树
  3. 后序遍历:先访问左子树,再访问右子树,最后访问根结点

下面介绍,二叉树3种遍历方式的实现。

递归遍历

递归实现非常简单,按照遍历的次序,对当前结点分别调用左子树和右子树即可。

前序遍历

void preOrder(TreeNode *root) {if(root == NULL)return;cout << root->val << endl;preOrder(root->left);preOrder(root->right);
}

中序遍历

void inOrder(TreeNode *root) {if(root == NULL)return;inOrder(root->left);cout << root->val << endl;inOrder(root->right);
}

后序遍历

void postOrder(TreeNode *root) {if(root == NULL)return;postOrder(root->left);postOrder(root->right);cout << root->val << endl;
}

复杂度分析

二叉树遍历的递归实现,每个结点只需遍历一次,故时间复杂度为O(n)。而使用了递归,最差情况下递归调用的深度为O(n),所以空间复杂度为O(n)。

非递归遍历

二叉树遍历的非递归实现,可以借助栈。

前序遍历

  1. 将根结点入栈;
  2. 每次从栈顶弹出一个结点,访问该结点;
  3. 把当前结点的右孩子入栈;
  4. 把当前结点的左孩子入栈。

按照以上顺序入栈,这样出栈顺序就与先序遍历一样:先根结点,再左子树,最后右子树。

void preOrder2(TreeNode *root) {if(root == NULL)return;stack<TreeNode *> stk;stk.push(root);                                                                                                                                                                            while(!stk.empty()) {TreeNode *pNode = stk.top();stk.pop();cout << pNode->val << endl;if(pNode->right != NULL)stk.push(pNode->right);if(pNode->left != NULL)stk.push(pNode->left);}
}

中序遍历

  1. 初始化一个二叉树结点pNode指向根结点;
  2. 若pNode非空,那么就把pNode入栈,并把pNode变为其左孩子;(直到最左边的结点)
  3. 若pNode为空,弹出栈顶的结点,并访问该结点,将pNode指向其右孩子(访问最左边的结点,并遍历其右子树)
void inOrder2(TreeNode *root) {if(root == NULL)return;stack<TreeNode *> stk;TreeNode *pNode = root;while(pNode != NULL || !stk.empty()) {if(pNode != NULL) {stk.push(pNode);pNode = pNode->left;}else {pNode = stk.top();stk.pop();cout << pNode->val << endl;pNode = pNode->right;}}
}

后序遍历

  1. 设置两个栈stk, stk2;
  2. 将根结点压入第一个栈stk;
  3. 弹出stk栈顶的结点,并把该结点压入第二个栈stk2;
  4. 将当前结点的左孩子和右孩子先后分别入栈stk;
  5. 当所有元素都压入stk2后,依次弹出stk2的栈顶结点,并访问之。

第一个栈的入栈顺序是:根结点,左孩子和右孩子;于是,压入第二个栈的顺序是:根结点,右孩子和左孩子。因此,弹出的顺序就是:左孩子,右孩子和根结点。

void postOrder2(TreeNode *root) {if(root == NULL)return;stack<TreeNode *> stk, stk2;stk.push(root);while(!stk.empty()) {TreeNode *pNode = stk.top();stk.pop();stk2.push(pNode);if(pNode->left != NULL)stk.push(pNode->left);if(pNode->right != NULL)stk.push(pNode->right);}while(!stk2.empty()) {cout << stk2.top()->val << endl;stk2.pop();}
}

另外,二叉树的后序遍历的非递归实现,也可以只使用一个栈来实现。

void postOrder2(TreeNode *root) {if(root == NULL)return;stack<TreeNode *> stk;stk.push(root);TreeNode *prev = NULL;while(!stk.empty()) {TreeNode *pNode = stk.top();if(!prev || prev->left == pNode || prev->right == pNode) {  // traverse downif(pNode->left)stk.push(pNode->left);else if(pNode->right)stk.push(pNode->right);/* else {
                cout << pNode->val << endl;
                stk.pop();
            }
        */}else if(pNode->left == prev) {  // traverse up from leftif(pNode->right)stk.push(pNode->right);}/* else if(pNode->right == prev) { // traverse up from right
                cout << pNode->val << endl;
                stk.pop();
        }
    */else {cout << pNode->val << endl;stk.pop();}prev = pNode;}
}

复杂度分析

二叉树遍历的非递归实现,每个结点只需遍历一次,故时间复杂度为O(n)。而使用了栈,空间复杂度为二叉树的高度,故空间复杂度为O(n)。

Morris遍历

Morris遍历算法最神奇的地方就是,只需要常数的空间即可在O(n)时间内完成二叉树的遍历。O(1)空间进行遍历困难之处在于在遍历的子结点的时候如何重新返回其父节点?在Morris遍历算法中,通过修改叶子结点的左右空指针来指向其前驱或者后继结点来实现的。

中序遍历

  1. 如果当前结点pNode的左孩子为空,那么输出该结点,并把该结点的右孩子作为当前结点;
  2. 如果当前结点pNode的左孩子非空,那么就找出该结点在中序遍历中的前驱结点pPre
    • 当第一次访问该前驱结点pPre时,其右孩子必定为空,那么就将其右孩子设置为当前结点,以便根据这个指针返回到当前结点pNode中,并将当前结点pNode设置为其左孩子;
    • 当该前驱结点pPre的右孩子为当前结点,那么就输出当前结点,并把前驱结点的右孩子设置为空(恢复树的结构),将当前结点更新为当前结点的右孩子
  3. 重复以上两步,直到当前结点为空。
void inOrder3(TreeNode *root) {if(root == NULL)return;TreeNode *pNode = root;while(pNode != NULL) {if(pNode->left == NULL) {cout << pNode->val << endl;pNode = pNode->right;}else {TreeNode *pPre = pNode->left;while(pPre->right != NULL && pPre->right != pNode) {pPre = pPre->right;}if(pPre->right == NULL) {pPre->right = pNode;pNode = pNode->left;}else {pPre->right = NULL;cout << pNode->val << endl;pNode = pNode->right;}}}
}

因为只使用了两个辅助指针,所以空间复杂度为O(1)。对于时间复杂度,每次遍历都需要找到其前驱的结点,而寻找前驱结点与树的高度相关,那么直觉上总的时间复杂度为O(nlogn)。其实,并不是每个结点都需要寻找其前驱结点,只有左子树非空的结点才需要寻找其前驱,所有结点寻找前驱走过的路的总和至多为一棵树的结点个数。因此,整个过程每条边最多走两次,一次使定位到该结点,另一次是寻找某个结点的前驱,所以时间复杂度为O(n)。

如以下一棵二叉树。首先,访问的是根结点F,其左孩子非空,所以需要先找到它的前驱结点(寻找路径为B->D->E),将E的右指针指向F,然后当前结点为B。依然需要找到B的前驱结点A,将A的右指针指向B,并将当前结点设置为A。下一步,输出A,并把当前结点设置为A的右孩子B。之后,会访问到B的前驱结点A指向B,那么令A的右指针为空,继续遍历B的右孩子。依次类推。 一棵二叉树

前序遍历

与中序遍历类似,区别仅仅是输出的顺序不同。

void preOrder3(TreeNode *root) {if(root == NULL)return;TreeNode *pNode = root;while(pNode) {if(pNode->left == NULL) {cout << pNode->val << endl;pNode = pNode->right;}   else {TreeNode *pPre = pNode->left;while(pPre->right != NULL && pPre->right != pNode)pPre = pPre->right;if(pPre->right == NULL) {pPre->right = pNode;cout << pNode->val << endl;pNode = pNode->left;}   else {pPre->right = NULL;pNode = pNode->right;}   }   }   
}

后序遍历

  1. 先建立一个临时结点dummy,并令其左孩子为根结点root,将当前结点设置为dummy;
  2. 如果当前结点的左孩子为空,则将其右孩子作为当前结点;
  3. 如果当前结点的左孩子不为空,则找到其在中序遍历中的前驱结点
    • 如果前驱结点的右孩子为空,将它的右孩子设置为当前结点,将当前结点更新为当前结点的左孩子;
    • 如果前驱结点的右孩子为当前结点,倒序输出从当前结点的左孩子到该前驱结点这条路径上所有的结点。将前驱结点的右孩子设置为空,将当前结点更新为当前结点的右孩子。
  4. 重复以上过程,直到当前结点为空。
void reverse(TreeNode *p1, TreeNode *p2) {if(p1 == p2) return;TreeNode *x = p1; TreeNode *y = p1->right;while(true) {TreeNode *temp = y->right;y->right = x;x = y;y = temp;if(x == p2) break;}   
}void printReverse(TreeNode *p1, TreeNode *p2) {reverse(p1, p2);TreeNode *pNode = p2; while(true) {cout << pNode->val << endl;if(pNode == p1) break;pNode = pNode->right;}   reverse(p2, p1);
}void postOrder3(TreeNode *root) {if(root == NULL)return;TreeNode *dummy = new TreeNode(-1);dummy->left = root;TreeNode *pNode = dummy;while(pNode != NULL) {if(pNode->left == NULL)pNode = pNode->right;else {TreeNode *pPrev = pNode->left;while(pPrev->right != NULL && pPrev->right != pNode)pPrev = pPrev->right;if(pPrev->right == NULL) {pPrev->right = pNode;pNode = pNode->left;}else {printReverse(pNode->left, pPrev);pPrev->right = NULL;pNode = pNode->right;}}}
}

http://www.jmfq.cn/news/4841605.html

相关文章:

  • 成都市建设网扬尘监控网站/竞价推广方案
  • 网站建设必须注意的事项/营销网站案例
  • 网站制作营销型/株洲百度seo
  • 网站支付界面怎么做/中文搜索引擎大全
  • 怎样购买起名软件自己做网站/山东济南最新消息
  • 小程序云开发教程/百度seo排名推广
  • 宜宾建设网站/网络营销的含义特点
  • 做母婴网站/seo优化公司
  • 手机网站建设流程 知乎/拉新推广怎么快速拉人
  • 108社区找工作/seo咨询岳阳
  • 福建建设人才网/黄冈seo顾问
  • 做网站公司排名/营销网站建设方案
  • 比较简洁大方的网站/做网站推广的公司
  • 建设网站论坛/上海百度seo网站优化
  • 别人帮自己做网站有后门吗/网站结构优化
  • 官方网站下载官方版本/排名前50名免费的网站
  • 做网站要要多少钱/关键字挖掘爱站网
  • 做初中物理题目的网站/推广一次多少钱
  • vip电影网站建设/网站百度权重
  • 泉州建设部网站/周口网络推广哪家好
  • b2b网站怎么做关键词优化/广东今日最新疫情通报
  • 营销平台建设/湛江seo网站管理
  • 网站建设专业可行性分析/开发制作app软件
  • 网站建设摊销时间是多久/推广普通话手抄报简单又好看
  • 泰州做网站 泰公网络科技公司/2345网址导航删除办法
  • 学做家常菜去那个网站/网站建设的好公司
  • 上海专业制作电子商务网站/百度关键词多少钱一个月
  • 个人备案能做企业网站吗/网站seo外链
  • 杭州定制网站建设/应用商店aso
  • wordpress仿淘宝/seo的概念