二、树(二叉树)
文章目录
1、备注:
- 树的逻辑结构大致可以分为三种:双亲表示法,孩子表示法,孩子兄弟表示法
- 树以遍历操作为主,一定要弄清楚非递归的先序,中序,后序和层次遍历算法,明白自定义栈或队列如何操作节点元素,方便应对许多题型.main函数如下,栈与队列的节点和函数定义详见【线性表】篇
int main()
{
TNode *root = (TNode *)malloc(sizeof(TNode));
root->data = 1;
TNode *node2 = (TNode *)malloc(sizeof(TNode));
node2->data = 2;
TNode *node3 = (TNode *)malloc(sizeof(TNode));
node3->data = 3;
TNode *node4 = (TNode *)malloc(sizeof(TNode));
node4->data = 4;
TNode *node5 = (TNode *)malloc(sizeof(TNode));
node5->data = 5;
TNode *node6 = (TNode *)malloc(sizeof(TNode));
node6->data = 6;
TNode *node7 = (TNode *)malloc(sizeof(TNode));
node7->data = 7;
TNode *node8 = (TNode *)malloc(sizeof(TNode));
node8->data = 8;
TNode *node9 = (TNode *)malloc(sizeof(TNode));
node9->data = 9;
TNode *node10 = (TNode *)malloc(sizeof(TNode));
node10->data = 10;
root->lchild = node2;
root->rchild = node3;
node2->lchild = node4;
node2->rchild = node5;
node3->lchild = node6;
node3->rchild = node7;
node4->lchild = NULL;
node4->rchild = node10;
node5->lchild = node8;
node5->rchild = NULL;
node6->lchild = NULL;
node6->rchild = NULL;
node7->lchild = NULL;
node7->rchild = NULL;
node8->lchild = node9;
node8->rchild = NULL;
node9->lchild = NULL;
node9->rchild = NULL;
node10->lchild = NULL;
node10->rchild = NULL;
//levelOrder
levelOrder(root);
//preOrder
preOrder_No_Recur(root);
//inOrder
inOrder_No_Recur1(root);
//postOrder
// postOrder_No_Recur(root);
//postOrder1
postOrder_No_Recur1(root);
}
2、先序遍历
Ⅰ、Demo 1:非递归先序
算法思想:
- 先将根结点压入栈,取出根结点。
undefined把右孩子压入栈,再把左孩子压入栈
void preOrder_No_Recur(TNode *root)
{
Stack stack;
InitStack(&stack);
push(&stack, root);
printf("preOrder: ");
while (!isStackEmpty(&stack))
{
TNode *node = pop(&stack);
printf("%d ", node->data);
//先压入右孩子,再压左孩子
if (node->rchild != NULL)
{
push(&stack, node->rchild);
}
if (node->lchild != NULL)
{
push(&stack, node->lchild);
}
}
printf("\n");
}
3、中序遍历
Ⅰ、Demo 1:非递归中序
算法思想:
- 将左孩子一次性压入栈,
- 取出栈顶结点,先打印,再压入右孩子。
- 如果右孩子有左孩子,取出栈顶结点,再次把其左孩子一次性压入
- 如果无,则将指针置为NULL,不用左压
void inOrder_No_Recur1(TNode *root)
{
Stack stack;
InitStack(&stack);
printf("inOrder1: ");
TNode *p = root;
while (p != NULL || !isStackEmpty(&stack))
{
if (p != NULL)
{
push(&stack, p);
p = p->lchild;
}
else
{
p = pop(&stack);
printf("%d ", p->data);
if(p->rchild != NULL){
p = p->rchild;
push(&stack,p);
p = p->lchild;
}
else{
p = NULL;
}
}
}
printf("\n");
}
4、后序遍历
Ⅰ、Demo 1:非递归后序
算法思想(较难,好好理解):
1)双栈法
-
一个节点栈,一个标记栈,标记子树中的根节点是否被访问两次
-
先一次性压入左孩子,peek一下栈顶,
- 如果sign为false,将sign相应位置为True,再访问右孩子
- 如果sign为true,表示已经访问过右孩子,无需继续访问,直接输出
- 取出栈顶结点,先打印,再压入右孩子。
- 如果右孩子有左孩子,取出栈顶结点,再次把其左孩子一次性压入
- 【注】:如何保证根节点标记为true后,不会连续压入右孩子???
void postOrder_No_Recur(TNode *root)
{
PostStack postStack;
InitPostStack(&postStack);
TNode *node = root;
bool flag = false;
printf("postOrder: ");
while (node != NULL || !isPostStackEmpty(&postStack))
{
//如果flag为false,为第一次访问该节点,一次性压入左孩子
while (flag == false && node != NULL)
{
pushPostStack(&postStack, node);
pushPostStackSign(&postStack, false);
node = node->lchild;
}
node = peekPostStack(&postStack);
flag = peekPostStackSign(&postStack);
//先将根标记true,表示第二次查询,再访问右孩子
if (!flag)
{
flag = true;
popPostStackSign(&postStack);
pushPostStackSign(&postStack, flag);
if (node->rchild != NULL)
{
node = node->rchild;
//将右孩子标记为未被访问
flag = false;
}
}
else
{
node = popPostStack(&postStack);
printf("%d ", node->data);
node = node->rchild;
popPostStackSign(&postStack);
}
}
printf("\n");
}
postStack代码
//后序遍历用的栈
typedef struct PostStack
{
ElemType *data[maxsize]; //指针数组
bool sign[maxsize];
int top1;
int top2;
} PostStack;
void pushPostStackSign(PostStack *stack, bool flag)
{
stack->sign[++stack->top2] = flag;
}
bool popPostStackSign(PostStack *stack)
{
return stack->sign[stack->top2--];
}
bool peekPostStackSign(PostStack *stack)
{
return stack->sign[stack->top2];
}
2)单栈法
- 用r来表示最近访问的节点
- 主要判断是否有右孩子,并根据r判断右孩子是否访问过
void postOrder_No_Recur1(TNode *root){
Stack stack;
InitStack(&stack);
// push(&stack,root); //(概念上的)根节点在p非空,依次压入左孩子时压入栈,不在初始时压入根结点
TNode *r = NULL; //r的初值为NULL
//表示最近一次访问的结点(若r为左结点,则右结点未访问,需访问,然后将r置为右结点)
TNode *p = root;
printf("postOrder1: ");
while(p != NULL || !isStackEmpty(&stack)){//若栈空,遍历指针p也为空,则遍历结束
if(p != NULL){ //若p非空
push(&stack,p);
p = p->lchild;
}
else //p为空,则左孩子压入完毕,先取出当前p的根节点,即栈顶元素
{
p = peek(&stack);
if(p -> rchild != NULL && p -> rchild != r)//若右孩子非空且右孩子未被访问
{
p = p -> rchild;
push(&stack,p);//先将右孩子压入栈
p = p -> lchild; //将指针指向左结点,进入下一次循环,一次性压入左结点
}
else{ //若右孩子被访问过,则弹出栈顶元素,即为右孩子,并将右孩子标识访问过
//若无右孩子,则弹出栈顶元素,即为根结点
p = pop(&stack);
printf("%d ",p->data);
r = p;
p = NULL; //由于这里是打印“右孩子”,下一步需要打印其根节点,
//即为出栈操作,须将p置null,避免继续压入左孩子。
}
}
}
printf("\n");
}
Ⅱ、Demo 2:x的祖先
1)描述:
- 在二叉树中查找值为x的结点,编写算法打印值为x的结点的所有祖先,假设值为x的结点不多于一个
2)算法思想:
- 采用非递归的后序遍历,当压入的结点为需要打印的x结点时,即栈内元素,即为结点x的所有祖先
void getAncestors(TNode *root,int x){
Stack stack;
InitStack(&stack);
TNode *r = NULL;
TNode *p = root;
printf("postOrder1: ");
while(p != NULL || !isStackEmpty(&stack)){//若栈空,遍历指针p也为空,则遍历结束
if(p != NULL){ //若p非空
push(&stack,p);
p = p->lchild;
}
else //p为空,则左孩子压入完毕,先取出当前p的根节点,即栈顶元素
{
p = peek(&stack);
if(p -> rchild != NULL && p -> rchild != r)//若右孩子非空且右孩子未被访问
{
p = p -> rchild;
push(&stack,p);//先将右孩子压入栈
p = p -> lchild; //将指针指向左结点,进入下一次循环,一次性压入左结点
}
else{
p = pop(&stack);
if(p -> data == x){
break;
}
else
{
r = p;
p = NULL;
}
}
}
}
while (!isStackEmpty(&stack))
{
printf("%d ",pop(&stack) -> data);
}
printf("\n");
}
5、层次遍历
Ⅰ、Demo 1:非递归层次
算法思想:
- 先将根结点压入队列,取出根结点。
- 把左孩子压入队列,再把右孩子压入队列
void levelOrder(TNode *root)
{
Queue queue;
InitQueue(&queue);
Enqueue(&queue, root);
printf("levelOrder: ");
while (!isQueueEmpty(&queue))
{
TNode *node = Dequeue(&queue);
printf("%d ", node->data);
//先压入左孩子,再压右孩子
if (node->lchild != NULL)
{
Enqueue(&queue, node->lchild);
}
if (node->rchild != NULL)
{
Enqueue(&queue, node->rchild);
}
}
printf("\n");
}
Ⅱ、Demo 2:逆层次遍历
1)描述:
- 试给出二叉树的自下而上,从右到左的层次遍历算法
2)算法思想:
- 采用层次遍历的非递归算法,不同的是右孩子先进入队列,左孩子再进入队列
void levelOrder(TNode *root)
{
Queue queue;
InitQueue(&queue);
Enqueue(&queue, root);
printf("levelOrder: ");
while (!isQueueEmpty(&queue))
{
TNode *node = Dequeue(&queue);
printf("%d ", node->data);
//先压入左孩子,再压右孩子
if (node->lchild != NULL)
{
Enqueue(&queue, node->lchild);
}
if (node->rchild != NULL)
{
Enqueue(&queue, node->rchild);
}
}
printf("\n");
}
Ⅲ、Demo 3:树的高度
1)描述:
- 假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度
2)算法思想:
- 采用层次遍历的算法,设置变量level记录当前结点所在的层数,
- 设置last指针指向当前层的最右的结点(last指针就好比操作链表的cur,pre指针,只不过赋予了不同的职责)
- 每次层次遍历出队时与last指针比较,若两者相等,则层数+1(说明当前层及以上已经遍历完,更新一下level),
- 并让last指向下一层的最右的结点,直到遍历完成。level即为二叉树的高度
- 【注】:这里可以使用循环队列,但需增加一个getRear(), 也可以不使用循环队列
循环队列
void getLevel1(TNode *root){
Queue queue;
InitQueue(&queue);
Enqueue(&queue,root);
TNode *last = root; //用来记录出队时最右的结点,再root出队后,初始时指向root
int level = 0;
while(!isQueueEmpty(&queue)){
TNode *p = Dequeue(&queue);
if(p -> lchild != NULL){
Enqueue(&queue,p -> lchild);
}
if(p -> rchild != NULL){//不能用else if
Enqueue(&queue,p -> rchild);
}
if(last == p){
level++;
/**
* last指向下一层的最右结点,因为last和p相同,last为上一层最右的结点
* 这时两种情况:p有孩子,p无孩子
* p有孩子:压入队列的孩子即为下一层最右的结点
* p无孩子:当前的队列的rear结点即为下一层最右的结点
*/
last = getRear(&queue);
}
}
printf("level : %d ",level);
}
非循环队列(可能会溢出)
void getLevel2(TNode *root){
int front = -1, rear = -1;
TNode *queue[maxsize]; //二叉链表的指针数组
queue[++rear] = root;
TNode *last = root;
int level = 0;
while(front != rear){
TNode *p = queue[++front];
if(p -> lchild != NULL){
queue[++rear] = p -> lchild;
}
if(p -> rchild != NULL){//不能用else if
queue[++rear] = p -> rchild;
}
if(last == p){
level++;
last = queue[rear];
}
}
printf("level1 : %d ",level);
}
Ⅳ、Demo 4:判断完全二叉树
1)描述:
- 二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法
2)算法思想:
- 采用层次遍历的算法,将所有的结点加入队列(包括空结点),出队遇到空结点时
查看其后是否有非空结点,若有,则二叉树不是完全二叉树。
int isComplete(TNode *root){
Queue queue;
InitQueue(&queue);
Enqueue(&queue,root);
while(!isQueueEmpty(&queue)){
TNode *p = Dequeue(&queue);
//空结点也入队列
if(p != NULL){
Enqueue(&queue,p -> lchild);
Enqueue(&queue,p -> rchild);
}
else
{
while(!isQueueEmpty(&queue)){
p = Dequeue(&queue);
//若空结点后还有非空结点,则非完全二叉树
if(p != NULL){
printf("isn't a complete tree\n");
return 0;
}
}
}
}
printf("is a complete tree\n");
return 1;
}
Ⅴ、Demo 5:左右子树交换
1)描述:
- 设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左右子树进行交换的函数
2)算法思想:
-
采用层次遍历的算法,设置一个temp指针,用它的右指针指向当前非空结点cur的右结点,
待cur的右指针指向左孩子后,cur的左指针指向temp的右孩子。
void swap(TNode *root){
Queue queue;
InitQueue(&queue);
Enqueue(&queue,root);
TNode *temp = (TNode *) malloc(sizeof(TNode));
while(!isQueueEmpty(&queue))
{
TNode *p = Dequeue(&queue);
temp -> rchild = p -> rchild;
p->rchild = p -> lchild;
p->lchild = temp -> rchild;
//不要忘了层次遍历的主体部分
if(p -> lchild != NULL){
Enqueue(&queue,p -> lchild);
}
if(p -> rchild != NULL){//不能用else if
Enqueue(&queue,p -> rchild);
}
temp -> rchild = NULL;
}
}