之前重温本书写书评时,也尝试找寻更好的编程解法。今天把另一个问题的实现和大家分享。

问题定义

给定一棵二叉树,要求按分层遍历该二叉树,即从上到下按层次访问该二叉树(每一层将单独输出一行),每一层要求访问的顺序为从左到右,并将节点依次编号。下面是一个例子:

输出:

1
2 3
4 5 6
7 8

节点的定义:

struct Node {Node *pLeft;Node *pRight;int data;
};

书上的解法

书上举出两个解法。第一个解法是用递归方式,搜寻并打印某一层的节点,再打印下一层的节点。这方法简单但时间效率不高(但不需要额外空间),因此书中亦提供了第二个解法。

书中第二个解法,使用vector容器来储存n个节点信息,并用一个游标变量last记录前一层的访问结束条件,实现如下:

void PrintNodeByLevel(Node* root) {vector<Node*> vec; // 这里我们使用STL 中的vector来代替数组,可利用到其动态扩展的属性vec.push_back(root);int cur = 0;int last = 1;while(cur < vec.size()) {Last = vec.size(); // 新的一行访问开始,重新定位last于当前行最后一个节点的下一个位置while(cur < last) {cout << vec[cur] -> data << " "; // 访问节点if(vec[cur] -> lChild) // 当前访问节点的左节点不为空则压入vec.push_back(vec[cur] -> lChild);if(vec[cur] -> rChild) // 当前访问节点的右节点不为空则压入,注意左右节点的访问顺序不能颠倒vec.push_back(vec[cur] -> rChild);cur++;}cout << endl; // 当cur == last时,说明该层访问结束,输出换行符}
}

广度优先搜索

书中没有提及,本问题其实是以广度优先搜索(breath-first search, BFS)去遍历一个树结构。广度优先搜索的典型实现是使用队列(queue)。其伪代码如下:

enqueue(Q, root)
donode = dequeue(Q)process(node) //如把内容列印for each child of nodeenqueue(Q, child)
while Q is not empty

书上的解法,事实上也使用了一个队列。但本人认为,使用vector容器,较不直觉,而且其空间复杂度是O(n)。

如果用队列去实现BFS,不处理换行,能简单翻译伪代码为C++代码:

void PrintBFS(Node* root) {queue<Node*> Q;Q.push(root);do {Node *node = Q.front();Q.pop();cout << node->data << " ";if (node->pLeft)Q.push(node->pLeft);if (node->pRight)Q.push(node->pRight);}while (!Q.empty());
}

本人觉得这样的算法实现可能比较清楚,而且空间复杂度只需O(m),m为树中最多节点的层的节点数量。最坏的情况是当二叉树为完整,m = n/2。

之后的难点在于如何换行。

本人的尝试之一

第一个尝试,利用了两个队列,一个储存本层的节点,另一个储存下层的节点。遍历本层的节点,把其子代节点排入下层队列。本层遍历完毕后,就可换行,并交换两个队列。

void PrintNodeByLevel(Node* root) {deque<Node*> Q1, Q2;Q1.push_back(root);do {do {Node* node = Q1.front();Q1.pop_front();cout << node->data << " ";if (node->pLeft)Q2.push_back(node->pLeft);if (node->pRight)Q2.push_back(node->pRight);} while (!Q1.empty());cout << endl;Q1.swap(Q2); } while(!Q1.empty());
}

本实现使用deque而不是queue,因为deque才支持swap()操作。注意,swap()是O(1)的操作,实际上只是交换指针。

这实现要用两个循环(书上的实现也是),并且用了两个队列。能够只用一个循环、一个队列么?

本人的尝试之二

换行问题其实在于如何表达一层的结束。书上采用了游标,而第一个尝试则用了两个队列。本人想到第三个可行方案,是把一个结束信号放进队列里。由于使用queue<Node*>,可以插入一个空指针去表示一层的遍历结束。

void PrintNodeByLevel(Node* root) {queue<Node*> Q;Q.push(root);Q.push(0);do {Node* node = Q.front();Q.pop();if (node) {cout << node->data << " ";if (node->pLeft)Q.push(node->pLeft);if (node->pRight)Q.push(node->pRight);}else if (!Q.empty()) {Q.push(0);cout << endl;}} while (!Q.empty());
}

这个实现的代码很贴近之前的PrintBFS(),也只有一个循环。注意一点,当发现空指针(结束信号)时,要检查队列内是否还有节点,如果没有的话还插入新的结束信号,则会做成死循环。

测试代码

void Link(Node* nodes, int parent, int left, int right) {if (left != -1)nodes[parent].pLeft = &nodes[left]; if (right != -1)nodes[parent].pRight = &nodes[right];
}void main()
{Node test1[9] = { 0 };for (int i = 1; i < 9; i++)test1[i].data = i;Link(test1, 1, 2, 3);Link(test1, 2, 4, 5);Link(test1, 3, 6, -1);Link(test1, 5, 7, 8);PrintBFS(&test1[1]);cout << endl << endl;PrintNodeByLevel(&test1[1]);cout << endl;
}

结语

第一个尝试是几个月前做的,没想到今晚写博文又想到了第二个尝试。两个尝试难分优劣,但两种思维或许也可以解决其他问题。还有其他方法么?

《编程之美:分层遍历二叉树》的另外两个实现相关推荐

  1. 编程之美-分层遍历二叉树方法整理

    [试题描述] 递归程序: 上述程序的缺点是递归函数的调用效率较低,我们知道二叉树的深度n,则只需要调用n次PrintNodeAtLevel(): 更好的算法:

  2. 编程之美2.10:寻找数组中的最大值和最小值

    编程之美2.10: 对于一个有N个整数组成的数组,需要比较多少次才能把最大值和最小值找出来呢? 算法的思想是: 分而治之 测试数据:---------------------------------- ...

  3. 编程之美2.1 求二进制中1的个数

    最近一段的时间,一直在看编程之美之类的算法书籍,刚开始看编程之美,感觉到难度太大,有时候也不愿意去翻动这本书,不过,经过一段时间的修炼,我也彻底的喜欢上这本书了, 书中的算法涉及到很多方面,树,链表, ...

  4. C#获取二叉树深度及分层遍历二叉树

    尝试了一下用C#写了一下二叉树的相关算法: 代码         #region 获取二叉树深度         static int z, d = 0;    //z用于记录遍历到某节点时的深度,d ...

  5. 2017“编程之美”终章:AI之战勇者为王

    编者按:8月15日,第六届微软"编程之美"挑战赛在选手的火热比拼中圆满落下帷幕."编程之美"挑战赛是由微软主办,面向高校学生开展的大型编程比赛.自2012年起, ...

  6. Java 并发编程之美:并发编程高级篇之一-chat

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  7. Java 并发编程之美:并发编程高级篇之一

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

  8. 层次遍历二叉树(编程之美3.10)

    问题(假定根节点位于第0层) 1. 层次遍历二叉树(每层换行分开) 2. 层次遍历二叉树指定的某层 例如 上图中 1. 1 2 3 4 5 6 7 8 2. 第三层 7 8 可以看出得出第二问的解,第 ...

  9. 编程之美 3.10 分层遍历二叉树

    解题思路: 一开始想到递归,发现用递归很难保证节点的访问顺序.(递归对于先,中,后序遍历都有效,但是对于层次遍历似乎没有找到可行的方法) 按照层次遍历二叉树的做法,通常用一个队列来保证节点按照先入先出 ...

最新文章

  1. python的sklearn机器学习SVM中的NuSVC运行报错:ValueError: b'specified nu is infeasible'
  2. MySQL(一)MySQL基础介绍
  3. 关于range方法,如果你觉得python很简单就错了
  4. gamaredon_Gamaredon组织某样本分析
  5. 笨方法学python3怎么样_有个很笨的女朋友,是怎么样的体验?
  6. android 图标错误的是什么,如何修复:android.app.RemoteServiceException:从包中发布的错误通知*:无法创建图标:StatusBarIcon...
  7. 1.Windows下 PHP 开源框架 laravel 的搭建
  8. php点击表格单元格链接,详解PhpSpreadsheet单元格设置样式、图片、超链接等
  9. 移动端设计尺寸基础知识
  10. 发出警报声的c语言程序,1、编写一个函数能够发出警报声并打印HelloWorld!;
  11. Excel表格-数据统计
  12. 微信公众平台开发1-OAuth2.0网页授权(含源码)
  13. 交互设计好书推荐:【A029】[图灵交互设计丛书].简约至上:交互式设计四策略.第2版
  14. APICloud前端框架
  15. android qq右上加号,Android 模拟QQ空间小加号+用popupWindow制作spinner
  16. rdo远程桌面管理快捷键在哪里?
  17. 卷积层与BN层的融合方式
  18. .启动ARCGIS提示“Automation错误”
  19. 美国网站空间如何选择
  20. NFT背后的区块链之争:公链还是联盟链?

热门文章

  1. Linux基础命令---accept打印机控制
  2. mysql 授权 navicat的登录数据库
  3. gradle:Creating New Gradle Builds
  4. strchr,wcschr 及strrchr, wcsrchr,_tcschr,_tcsrchr函数
  5. 在dos下的文件及文件夹操作命令
  6. Linux 命令(22)—— touch 命令
  7. Linux下编程获取本地IP地址的常见方法
  8. Java多线程_JUC包下的阻塞队列
  9. eclipse图标含义
  10. GIMP用Path作画了解一下