缘由
看到这本书就有一种高大上的感觉,随手翻翻,今天又搞一道!
题干
我的思路
使用循环队列
首先,看到问题1我首先想到了图的广度优先搜索遍历算法,后来一查书(数据结构高分笔记),才想起来树本来就有一种遍历方式叫“层次遍历(P110)"。也就问题1中想要打印树的方式。利用了数据结构“循环队列”作为辅助的工具。其实自己想的时候也想到了用队列的这个办法。不过想来这本数据结构之高分笔记是我很久之前看过的书,所以想到用队列这个办法肯定是受到了这本书的影响。但是书中描述的算法还是不能完全满足本题的要求,主要在于书中描述的算法是将分层遍历的元素依次放在一个数组。但是这样就造成一个问题,无法满足书中要求的分行打印。
那么我对书中分层遍历的思路进行改进,那么就可以满足书中的要求了:
就拿编程之美的题干中的图来说:
- 遍历到1时,将1放入队列,然后在1的后面插入一个标记,标记这是一行,再将1的孩子2和3插入到队列。
- 然后取1出队列,然后打印1
- 再从队列中取,取的是标记,那么把此标记再插入队尾。
- 然后从队列中取,此时是新的一行了吧。然后再将2的孩子插入到队尾,接着打印2
- 那么再从队列中取,取到3,那么将3的孩子插入到队尾,再打印3.
- 再从队列中取,会取到标记.....
- 就这样一直循环下去。
使用数组
这个做法是我自己新发现的,主要是从昨天看到的nginx里面的结构体ngx_cycle_t的元素:- void ****conf_ctx
在讲这个算法之前,提醒大家注意,在本题后面不是还有几个扩展问题么?我这个算法也考虑到了后面的扩展问题,所以扩展问题也一起被解决了。
这个算法实际上用一个图就能很请清楚的看懂,如下所示:
简单来说就是利用双向链表,由于使用单项链表是无法满足扩展问题中要求每一层的访问数据变为由右到左的,但是双向链表可以很好的解决这个问题。
注意这个数组中每一个元素都是双向链表的表头,为了标识表头,我们使得其的data部分为NULL。那么当,从head开始遍历或者是从Tail开始遍历都能够很好的解决判断是否遍历完整个链表数据。
代码
/**
* 编程之美3.10:分层遍历二叉树
*
* 使用双向完成该功能
* 作者:zy
* 更详细的内容参见:
*/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
typedef struct NODE{
int data;
struct NODE *lChild;
struct NODE *rChild;
}Node;
typedef struct doubleQueue{
int data;
struct doubleQueue *pre;
struct doubleQueue *next;
}dq;
/**
* 这个是要递归调用的
* 第一次调用的时候的根节点
* depth是层次的高度,同时也是dqArray的下标
* node节点应该加到与depth对应的下标
ndoe节点的孩子加到depth+1 的下标
*/
void buildDoubleQueue(Node *node , int depth,dq **dqArray){
if(node == NULL){
return;
}
//currentdphead指向表头
dq *currentdphead = dqArray[depth];
if(currentdphead->next == currentdphead){
//说明一个元素都没有
dq *dpEle =(dq *) malloc(sizeof(dq));
dpEle->data = node->data;
dpEle->next = currentdphead;
dpEle->pre = currentdphead;
currentdphead->next = dpEle;
currentdphead->pre = dpEle;
}else{
//不相等,说明之前已经插入过元素了,那么把这个元素插入到双向链表的末尾
dq *dpEle =(dq *) malloc(sizeof(dq));
dpEle->data = node->data;
currentdphead->pre->next = dpEle;
dpEle->next = currentdphead;
dpEle->pre = currentdphead->pre;
currentdphead->pre = dpEle;
}
buildDoubleQueue(node->lChild,depth+1,dqArray);
buildDoubleQueue(node->rChild,depth+1,dqArray);
}
int main() {
//先把书中的二叉树给构建出来
Node *root =(Node *) malloc(sizeof(Node));
root->data = 1;
Node *node2 =(Node *) malloc(sizeof(Node));
node2->data = 2;
Node *node3 =(Node *) malloc(sizeof(Node));
node3->data = 3;
Node *node4 =(Node *) malloc(sizeof(Node));
node4->data = 4;
Node *node5 =(Node *) malloc(sizeof(Node));
node5->data = 5;
Node *node6 =(Node *) malloc(sizeof(Node));
node6->data = 6;
Node *node7 =(Node *) malloc(sizeof(Node));
node7->data = 7;
Node *node8 =(Node *) malloc(sizeof(Node));
node8->data = 8;
root->lChild = node2;
root->rChild = node3;
node2->lChild = node4;
node2->rChild = node5;
node4->lChild = NULL;
node4->rChild = NULL;
node5->lChild = node7;
node5->rChild = node8;
node7->lChild = NULL;
node7->rChild = NULL;
node8->lChild = NULL;
node8->rChild = NULL;
node3->rChild = node6;
node3->lChild = NULL;
node6->lChild = NULL;
node6->rChild = NULL;
/**
* 验证一下二叉树构建对没有
* printf("%d %d %d",root->data,root->lChild->data,root->rChild->rChild->data);
*/
int treeDepth = 4;//简单起见这里先赋值,实际上恐怕要针对每一棵树都算一下深度
dq *dqArray[treeDepth];
//需要4个表头
int i;
for(i = 0; i < 4 ; i++){
dq *dphead =(dq *) malloc(sizeof(dq));
dphead->data = (dq *)NULL;
dphead->next = dphead;
dphead->pre = dphead;
dqArray[i] = dphead;
}
//root是第0层,所以我们把root
buildDoubleQueue(root,0,dqArray);
printf("从左到右打印---------------\n");
//建好之后,我们按照书上的要求打印
for(i = 0; i < 4 ; i++){
dq *currentDp = dqArray[i];
while(currentDp->next->data !=NULL){
currentDp = currentDp->next;
printf("%d ",currentDp->data);
}
printf("\n");
}
printf("从右到左打印---------------\n");
for(i = 0; i < 4 ; i++){
dq *currentDp = dqArray[i];
while(currentDp->pre->data != NULL){
currentDp = currentDp->pre;
printf("%d ",currentDp->data);
}
printf("\n");
}
printf("从下到上打印---------------\n");
//建好之后,我们按照书上的要求打印
for(i = 3; i >= 0 ; i--){
dq *currentDp = dqArray[i];
while(currentDp->next->data != NULL){
currentDp = currentDp->next;
printf("%d ",currentDp->data);
}
printf("\n");
}
}
运行结果
从左到右打印---------------
1
2 3
4 5 6
7 8
从右到左打印---------------
1
3 2
6 5 4
8 7
从下到上打印---------------
7 8
4 5 6
2 3
1
总结
使用了递归来构建双向链表,实际上也可以用循环来完成。用循环完成时构建的时间复杂度就是o(N),因为遍历了二叉树中的每一个元素。然而我在这里采用了递归,时间复杂度为o(NlogN)。我写的时候无意识的就写成了递归,可见这到题用递归更容易完成。其次,我们有额外的空间复杂度,但是好处就是我们在一次计算好之后,在得到需要的第几层的元素、第几层的第几个元素的时间复杂度都为o(1);