当前位置:   article > 正文

二叉树的层序遍历以及队列的实现_树的层次遍历怎么通过根传入队列

树的层次遍历怎么通过根传入队列

思维导图:

一,什么是层序遍历

层序遍历,顾名思义就是一层一层的遍历。比如我的这棵二叉树

如果使用层序遍历的话它的结果就会是这样的:

1->5->9->7->10->13->8,这就是一层一层的遍历,一层一层的打印节点的值。

这个遍历方法与前面我介绍过的二叉树里面的其它方法都不一样,因为这是一个非递归的遍历方法。层序遍历的实现还要与另一种数据结构——栈结合在一起实现。

 二,队列的实现

1.队列的结构

栈的结构可能是现在我们学过的比较复杂的一个结构,因为它是一个双层嵌套的结构。

代码:

  1. typedef struct BTreeNode* QnodeData;
  2. typedef struct Qnode {//队列中的某一个节点的结构
  3. QnodeData data;//节点值
  4. struct Qnode* next;//节点指针
  5. }Qnode;
  6. typedef struct Queue//整个队列的结构
  7. {
  8. Qnode* phead;//头
  9. Qnode* ptail;//尾
  10. int size;//长度
  11. }Queue;

 一个节点:

多个节点:

 队列的结构大概就是这样的,就像一个链表的结构。

2.初始化

队列的初始化就是要将队列的模型给刻画出来,我们一般会将指针的指向初始化为NULL,int型变量初始化为0。所以我们便可以写出如下代码。

 代码:

  1. void QueueInit(Queue* pq )
  2. {
  3. assert(pq);
  4. pq->phead = NULL;
  5. pq->ptail = NULL;
  6. pq->size = 0;
  7. }

3.插入

      队列的插入操作与链表的插入操作不会有太多的区别,基本上都是一样的。主要需要注意一个地方就是当这个节点是空节点的时候需要将phead与ptail指向同一个节点。

  1. void QueuePush(Queue* pq)
  2. {
  3. assert(pq);
  4. Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. return;
  9. }
  10. if (pq->phead == NULL)//当节点为空时的处理方法
  11. {
  12. assert(pq->ptail == NULL);
  13. pq->phead = pq->ptail = newnode;
  14. }
  15. else
  16. {
  17. pq->ptail->next = newnode;
  18. pq->ptail = newnode;
  19. }
  20. pq->size++;//插入一个节点时就要将长度增加
  21. }

4.弹出元素

因为队列的特点是先进先出所以队列的弹出操作是固定的,不存在头删与尾删的选择。它只能够实现头删,而不存在尾删。所以根据这个逻辑我们实现的代码如下。

代码:

  1. void QueuePop(Queue* pq)
  2. {
  3. assert(pq);//pq不能为空
  4. assert(pq->ptail!=NULL);//队列不能为空
  5. Queue* next = pq->phead->next;
  6. free(pq->phead);
  7. pq->phead = next;
  8. if(pq->phead == NULL)
  9. {
  10. pq->phead == NULL;//当头指针指向NULL时就要将pq->ptail也置为NULL
  11. }
  12. pq->size--;
  13. }

其实这里还有可以改进的地方,比如说:

  1. assert(pq);//pq不能为空
  2. assert(pq->ptail!=NULL);//队列不能为空

这两个assert就会比较让人混淆,所以我们可以将第二个断言这样实现。

先写一个判空的函数:

  1. bool isEmpty(Queue* pq)
  2. {
  3. return pq->ptail == NULL
  4. && pq->phead == NULL;//当两者为空时返回true来达到判空的作用
  5. }

说以第二个断言就改成:

assert(!isEmpty(pq));//队列不能为空

这样的断言就清晰一点了。

5.获取头元素与尾元素以及长度。

这两个操作算是最简单的操作了,所以在这里就不再多说了。

代码:

  1. Qnode* QueueFront(Queue* pq)//前
  2. {
  3. assert(pq);
  4. return pq->phead;
  5. }
  6. Qnode* QueueBack(Queue* pq)//后
  7. {
  8. assert(pq);
  9. return pq->ptail;
  10. }
  11. int QueueSize(Queue* pq)//长度
  12. {
  13. assert(pq);
  14. return pq->size;
  15. }

6.销毁队列

这个对列看起来其实就是一个单链表的结构,所以销毁队列的操作也是像销毁单链表一样,也是一个一个节点这样子销毁释放。所以,按照销毁单链表的逻辑来写的代码就是如下代码:

代码:

  1. void QueueDestory(Queue* pq)
  2. {
  3. assert(pq);
  4. while (pq->phead)
  5. {
  6. Qnode* next = pq->phead->next;//记录下一个节点的值
  7. free(pq->phead);//销毁当前节点
  8. pq->phead = next;
  9. }
  10. pq->phead = pq->ptail = NULL;//销毁完以后将头尾节点置为NULL
  11. }

三,层序遍历的实现

层序遍历就像前面所说的那样,它就是一层一层的来遍历显示的。这个遍历方法不是递归的而是非递归的,所以想要实现这个遍历就得用栈的先进先出的特点来实现。为了更好的讲解我先将代码写上:

  1. void LevelOrder(BTreeNode* root)
  2. {
  3. Queue pq;//创造一个队列
  4. QueueInit(&pq);//初始化队列
  5. QueuePush(&pq, root);//先将一个根节点插入到队列中
  6. while (!isEmpty(&pq))
  7. {
  8. BTreeNode* front = QueueFront(&pq);//接收队列中第一个元素
  9. printf("%d->", front->val);
  10. QueuePop(&pq);//将第一个元素清出队列
  11. if (front->left)
  12. QueuePush(&pq, front->left);//插入左节点
  13. if (front->right)
  14. QueuePush(&pq, front->right);//插入右节点
  15. }
  16. QueueDestory(&pq);//销毁队列
  17. }

在这个层序遍历的代码中,值得研究的点有很多比如QueueFront这个函数。

QueueFront函数代码:

  1. Qnode* QueueFront(Queue* pq)
  2. {
  3. assert(pq);
  4. return pq->phead->data;
  5. }

data的类型:树节点的指针

typedef struct BtreeNode* DataType;

为什么这里的data要使用节点的指针的类型呢?这是因为我需要找到接下来的左节点与右节点。只有在知道了树节点的地址以后才能找到当前节点的左右节点

再比如这一段代码:

  1. BTreeNode* front = QueueFront(&pq);//接收队列中第一个元素
  2. printf("%d->", front->val);
  3. QueuePop(&pq);//将第一个元素清出队

可能会有人疑惑为什么在我们pop以后front还没有变成一个野指针。现在来画图看看Queue的结构:

 在执行pop操作的时候这个图就会变成这样:

pop这个操作只是将队列的头节点删除了而对这个front指针其实没有什么影响,因为front指向的是一个地址这个地址是树节点的地址树节点没有被销毁,所以这个地址仍然有效。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/846525?site
推荐阅读
相关标签
  

闽ICP备14008679号