数据结构实验7_C语言_二叉树的应用、递归实现先序、后序遍历、求树的深度,非递归使用自定义的栈的两种中序遍历、非递归使用自定义的队列的层序遍历

实验7-二叉树的应用

  • 1)实验目的

通过该实验,使学生理解二叉树的链式存储,掌握二叉树的几种遍历算法,并通过该实验使学生理解递归的含义,掌握C语言编写递归函数的方法和注意事项.

  • 2)实验内容

实现教材中算法6.4描述的二叉树创建算法,在此基础上实现二叉树的先序、后序递归遍历算法、两种非递归中序遍历、层序遍历、求二叉树的深度。注意:在非递归算法中用到栈和队列时,不要调用系统的栈和队列,需要自己实现栈和队列的操作。

  • 3)验收/测试用例
  • 创建 输入 :ABC$$DE$G$$F$$$ ($表示空格)
    在这里插入图片描述
  • 先序 屏幕输出 A B C D E G F
  • 后序 屏幕输出 C G E F D B A
  • 中序 屏幕输出 C B E G DF A (两种中序非递归还需看源代码)
  • 层序 屏幕输出 A B C D E F G
  • 深度 屏幕显示 深度为5
    另外自己画出一棵树,再测试一遍。

一、设计思想

  1. 程序按照数据结构大致分为3部分:
  • 1.1. 链二叉树结构 + 功能函数声明、定义: (先序输入空格补位的二叉树)

  • 1.2. 自定义定长顺序栈 + 功能函数声明、定义: 二叉树的 两种 中序非递归遍历算法,需要使用自定义的栈来完成

  • 1.3. 自定义链队列 + 功能函数声明、定义: 二叉树的 层序 非递归遍历算法,需要使用自定义的队列完成

  1. 需要注意的问题:
  • 2.1.对于创建二叉树,输入的是 普通树 补全版的 二叉树,对于 左右孩子为空的情况采用 空格 补齐,所以,获取输入的树 节点 不能使用cin\scanf();因为这两个读取函数会忽略空格,使用getchar()可以将空格当做字符读入。
  • 2.2. 对于 未初始化树树为空的情况,要求执行功能函数需要提示 树为空,这里采用返回值为0或1来判断,对于遍历操作是递归的,这里是在 函数开始的时候判断 if(t),如果树为空,直接return 0,不在遍历;在函数尾部设置return 1,(内层递归并不此返回值的影响),另外对于栈、队列开辟空间失败的情况 return -1,终止操作。
  • 2.3. 自己之前自定义的 栈数据结构 出栈元素采用return的形式返回,不兼容课本上的 中序非递归 算法,做了一些调整,使用&e来 及时 获取出栈、入栈下的 栈顶的元素
  1. 先序、后序、求树的深度 算法思想思想类似,只是对输出语句的位置做出调整,树的深度,就是返回左子树、右子树中最大的深度,然后+1
  2. 两种中序非递归算法 由 循环调整指针 + 入栈、出栈 来实现
  • 4.1. 算法的关键在于:在遍历完后 某个节点 的 左子树 后,怎么 找到 右子树,需要将访问过得节点压栈,当访问指针为空(即当前访问节点的左子树已经没有左子树),就要弹栈,调整p指针指向弹栈的元素,输出,然后调整指针指向右孩子,再以右孩子开启逻辑中序遍历
  • 4.2 对于非递归中序遍历1,可以把每个节点都当成逻辑根节点,开始先移动指针到逻辑第一个输出节点(此时一定是最左边的节点),只不过 待输出的 节点值 的 左孩子为NULL,因此弹出左孩子即可,接着弹出 待输出 节点,然后打印,并修改指针指向 右孩子,然后下层循环
  • 4.3.对于非递归中序遍历2,和1思想类似,只不过更明确一些,指针不断顺着左孩子链移动,并且经过的节点全部入栈,然后知道移动到指针为NULL,即栈顶元素为待输出节点,出栈,打印,调整指针指向右孩子,进入下层循环,
    打印过得元素都不在栈内,数的根节点在栈底,当访问完 左子树 后,也会打印根节点,然后根节点出栈,调整指针 指向 右子树,然后…
  1. 对于非递归层序遍历的实现,基于队列的,算法思想是,先根节点入队,然后从队列中取出一个元素,打印,如果该打印节点的左、右孩子不为空,就入队,即每次出父亲,入孩子。

二、源代码

# include<iostream>
# include<stdio.h>
# include<stdlib.h>
using namespace std;

	//一、二叉树的链式存储

typedef  char TElemType;
typedef  int Status;


typedef struct BiTNode{
	TElemType data;
	struct BiTNode *lchild;
	struct BiTNode *rchild;
	 
}*BiTree; 


	//二、自定义的栈 
# define STACK_INIT_SIZE 88
# define STACKINCRMENT 6
typedef BiTree SElemType;


typedef struct {
	SElemType *base;
	SElemType *top;
	int stacksize;
}MyStack; 


	// 三、自定义队列

typedef BiTree QElemType;

typedef struct QNode {
	
	QElemType data;
	struct QNode *next;
}*QueuePtr;

//把两个指针封装在一个结构体内 
//front、rear都是指向队列节点的结构体指针 
typedef struct{
	QueuePtr front;
	QueuePtr rear;
	
}LinkQueue; 


		//函数声明

		//一、树的功能函数声明 

//1. 先序创建二叉树
Status CreateBiTree(BiTree &t);

//2. 先序遍历二叉树
Status Pre(BiTree t) ;

//3. 打印节点值函数 
Status PrintElem(TElemType e);

//4. 中序遍历二叉树(递归) 
Status Middle(BiTree t);

//5.1 中序遍历二叉树(非递归1)
Status Middle_otherOne(BiTree t,MyStack &ms);

//5.2 中序遍历二叉树(非递归2)
Status Middle_otherTwo(BiTree t,MyStack &ms);

//6. 后续遍历二叉树 
Status Last(BiTree t);

//7. 层序遍历二叉树
 Status cengXu(BiTree t,LinkQueue &Q);
 
//8. 求二叉树的深度
int getBiTreeLength(BiTree t); 


		//二、栈的功能函数声明 

// 1. 初始化栈
Status InitStack(MyStack &s);

// 2. 栈判空
bool JudgeEmpty(MyStack s) ;

// 3. 插入一个元素(入栈) 
Status Push(MyStack &s,SElemType e) ;

// 4. 删除一个元素(出栈) 
SElemType Pop(MyStack &s,SElemType &e);

// 5. 获取栈顶元素 
SElemType GetTop(MyStack s,SElemType &e); 

			//三、队列函数声明 

// 1. 初始化队列函数
void InitQueue(LinkQueue &Q) ;

// 2. 销毁队列
void DestoryQueue(LinkQueue &Q); 

// 3. 清空队列
void ClearQueue(LinkQueue &Q) ;

// 4. 队列判空
bool JudgeEmpty(LinkQueue Q); 

// 5. 求队列长度
int GetLength(LinkQueue Q); 

// 6. 获取队头元素
QElemType GetFront(LinkQueue Q);

// 7. 插入一个元素(入队) 
void InsertElem(LinkQueue &Q,QElemType elem) ;

// 8. 删除一个元素(出队) 
QElemType  DeleteElem(LinkQueue &Q) ;


 
int main()
{
	
	//栈节点
	MyStack ms;
	ms.base = NULL;
	ms.top = NULL; 
	
	//树节点 
	BiTree t;
	t = NULL;
	
	//队列节点
	LinkQueue Q;
	Q.front = NULL;
	Q.rear = NULL;
	 
	bool flag = true;
	int n;
	while(flag){
		cout<<"☆欢迎使用二叉树的操作小程序☆"<<endl;
		cout<<"Design by 1925050351-软6-司超龙"<<endl<<endl;
		cout<<" 1. 创建二叉树"<<endl;
		cout<<" 2. 先序遍历二叉树"<<endl;
		cout<<" 3. 中序遍历二叉树1"<<endl;
		cout<<" 4. 中序遍历二叉树2"<<endl;
		cout<<" 5. 后序遍历二叉树"<<endl;
		cout<<" 6. 层序遍历二叉树"<<endl;
		cout<<" 7. 求二叉书的深度"<<endl;
		cout<<" 8. 退出"<<endl<<endl;
		cout<<"请输入相应指令完成相关操作:"<<endl; 
		
		cin>>n;
		//功能函数返回值 
		Status s;
		switch(n){
			case 1:
				system("cls");
				cout<<"请按先序输入节点的值:"<<endl<<endl; 
				getchar(); 
				s = CreateBiTree(t) ;
				if(s == -1){
					cout<<"二叉树创建失败!节点空间开辟失败,请重新创建二叉树~"<<endl<<endl;
					free(t);
					
				}
				else if(s == 1){
					cout<<"二叉树创建成功!"<<endl<<endl;
				}
				break;
			case 2:
				system("cls");
				s = Pre(t);
				if( s == 0){
					cout<<"树为空!" ;
				}
				cout<<endl<<endl;
				break;
			case 3:
				system("cls");
				//Middle(t);中序递归遍历 
			
			
				s = Middle_otherOne(t,ms);
				
				if( s == 0){
					cout<<"树为空!"<<endl;
				}
				else if(s == -1){
					cout<<"栈开辟失败!请重新操作~" <<endl;
				}
				
				cout<<endl<<endl;
				break;
			case 4:
				system("cls");
				s = Middle_otherTwo(t,ms);
				if(s == 0){
					cout<<"树为空!"<<endl;
				}
				else if(s == -1){
					cout<<"栈开辟失败!请重新操作~" <<endl;
				}
				cout<<endl<<endl;
				break;
			case 5:
				system("cls");
				s = Last(t);
				if( s == 0){
					cout<<"树为空!"; 
				}
				cout<<endl<<endl;
				break;
			case 6:
				system("cls");
				s = cengXu(t,Q);
				if( s == 0){
					cout<<"树为空!"; 
				}
				
				cout<<endl<<endl;
				break;
			case 7:
				system("cls");
				cout<<"树的深度为:"<<getBiTreeLength(t)<<endl<<endl;
				break;
			case 8:
				system("cls");
				flag = false;
				cout<<"退出程序成功!欢迎下次使用~~"<<endl<<endl; 
				break;
			default:
				system("cls");
				cout<<"输入指令有误!请重新输入~~" <<endl<<endl;
				break;
			
		}
		
	}
}

//三、自定义栈功能函数实现

//1. 初始化一个栈

Status InitStack(MyStack &s){
	s.base = (SElemType *)malloc(STACK_INIT_SIZE *sizeof(SElemType));
	if(s.base == NULL){
		return -1;
	}
	s.top = s.base;
	s.stacksize = STACK_INIT_SIZE;
	return 1;
} 

//2. 栈判空
bool JudgeEmpty(MyStack s){
	if(s.base == s.top){
		return true;
	}
	else
		return false;
} 

//3. 插入一个元素
Status Push(MyStack &s,SElemType e){
	if(s.base == NULL || s.top == NULL){
		return -1;
	}
	
	*(s.top++) = e;
	return 1;
} 



//4. 删除一个元素(出栈) 
SElemType Pop(MyStack &s,SElemType &e){
	if(s.base == NULL){
		return NULL;
	}
	e =  *(-- s.top);
}

//5. 获取栈顶元素
SElemType GetTop(MyStack s,SElemType &e) {
	if(s.base == NULL){
		return NULL;
	}
	e = *(--s.top);
	//注意优先级,先使用还是先减减 
	//return *(--s.top);
	//return ;
}


//四、二叉树功能实现
 
//1. 先序创建二叉树

Status CreateBiTree(BiTree &t){
	//按照先序 输入二叉树 节点的值(全部输入后一次回车)
	TElemType ch;
	
	//cin>>ch;
	
	
	ch = getchar();
	
	//回车键 
	if(ch == 13) 
		return 1;
	//if(ch == '\n')
		//return 1;
	//ch = getchar();
	
	//空格键 
	if(ch == 32){
		t = NULL;
		
	} 
	else{
		if(!(t = (BiTNode *)malloc (sizeof(BiTNode)))){
			//节点空间开辟失败,返回值-1 
			return -1;
		}
		t->data = ch;
		
		//递归节点的左子树
		 
		CreateBiTree(t->lchild);
		
		//递归节点的右子树 
		CreateBiTree(t->rchild);
		 
	}
	return 1;


} 

//2. 先序遍历二叉树
Status Pre(BiTree t){
	
	if(!t){
		return 0;
	} 
	if(t){
		PrintElem(t->data);
		Pre(t->lchild);
		//PrintElem(t->data);
			
		Pre(t->rchild);
		//PrintElem(t->data);		
	}
	//作为程序执行完成的标志,便于区分是不是空树 
	return 1;
	
} 

//3. 打印节点值函数 
Status PrintElem(TElemType e){
	cout<<e<<" ";
	return 1; 
} 

//4. 中序遍历二叉树(递归) 
Status Middle(BiTree t){
	
	if(!t){
		return 0;
	} 
	
	if(t){
		
		Middle(t->lchild);
		//PrintElem(t->data);
		PrintElem(t->data);	
		Middle(t->rchild);
		//PrintElem(t->data);		
	}
	return 1;
	
}

//5.1 中序遍历二叉树(非递归1)
Status Middle_otherOne(BiTree t,MyStack &ms){
	
	if(!t){
		return 0;
	} 
	BiTree p;
	int stack_flag = InitStack(ms);
	//先开栈,根据返回值判断是否开辟成功 
	if( stack_flag != -1) {
		//根指针进栈 
		
		//根节点 在 栈底 
		
		//父节点永远在子节点下面 
		Push(ms,t);
		
		while(!JudgeEmpty(ms)){
			
			 
			//有栈顶元素 ,并且节点不为NULL 
			while(GetTop(ms,p) && p){
				Push(ms,p->lchild);
			} 
			
			//直到左孩子为NULL,此时栈顶为整棵树最左边的节点,将NULL进栈,循环结束 
			
			//弹出栈顶空节点NULL ,因为子孩子为NULL,不需要打印值 
			Pop(ms,p) ;
			
			
			if(!JudgeEmpty(ms)){
				//弹出最上面的节点 ,也就是逻辑中序第一个节点 
				Pop(ms,p);
				//打印值 
				PrintElem(p->data);
				
				
				//就是在这 调整 指针 
				Push(ms,p->rchild);
				
				//如果有右孩子,则右孩子进栈,,接着以右孩子为逻辑根节点来中序遍历,判断右孩子是否有左孩子和右孩子....
				//如果没有右孩子,就将NULL压进栈,下次循环会被弹出并不打印 
			}
		}
	
		
		 
		return 1; 
	}
	
	//栈空间开辟失败 
	else{
		return -1;
	}
	
}

//5.2 中序遍历二叉树(非递归2)
Status Middle_otherTwo(BiTree t,MyStack &ms){
	//树为空 
	if(!t){
		return 0;
	}
	BiTree p = t;
	int stack_flag = InitStack(ms);
	//先开栈,根据返回值判断是否开辟成功 
	if( stack_flag != -1) {
		while(p || !(JudgeEmpty(ms))) {
			if(p){
				Push(ms,p);
				p = p->lchild;
			}
			else{
				Pop(ms,p);
				PrintElem(p->data);
				p = p->rchild;
			}
		}
		
	} 
	else{
		return -1;
	}
	return 1;
	
	
	
	
}
//6. 后续遍历二叉树 
Status Last(BiTree t){
	
	if(!t){
		return 0;
	} 
	if(t){
		
		Last(t->lchild);
		//PrintElem(t->data);
			
		Last(t->rchild);
		PrintElem(t->data);
		//PrintElem(t->data);		
	}
	return 1;
	
	
}

//7. 层序遍历二叉树
 Status cengXu(BiTree t,LinkQueue &Q){
 	//空树直接返回 
	if(!t){
		return 0;
	}
	BiTree p = t;
	InitQueue(Q);
	InsertElem(Q,t) ;
	while(!JudgeEmpty(Q)){
		p = DeleteElem(Q);
		PrintElem(p->data);
		if(p->lchild){
			InsertElem(Q,p->lchild);
		}
		if(p->rchild){
			InsertElem(Q,p->rchild);
		}
	}
	return 1;
	
 }
 
//8. 求二叉树的深度
int getBiTreeLength(BiTree t){
	if(t == NULL){
		return 0;
				
	}
	else{
		int	n = getBiTreeLength(t->lchild);
		//PrintElem(t->data);
			
		int m = getBiTreeLength(t->rchild);
		
		//PrintElem(t->data);
		int max = m > n? (m + 1):(n + 1);
		return max;
	}
		
	
}

//五、自定义队列

// 1. 初始化队列函数
void InitQueue(LinkQueue &Q){
	
	Q.front = (QueuePtr )malloc(sizeof(QNode));
	Q.rear = Q.front;
	
	
	
} 

// 2. 销毁队列
void DestoryQueue(LinkQueue &Q){
	//队列元素为空 
	if(Q.front == Q.rear){
		free(Q.front);
		Q.front = NULL;
		Q.rear = NULL;
	}
	//队列不为空,边移动,边释放空间,rear紧邻在front前开路 
	else{
		while(Q.front){
			Q.rear = Q.front->next;
			free(Q.front);
			Q.front = Q.rear;
		}
		Q.front = NULL; 
		
	}
	
	

} 

// 3. 清空队列
void ClearQueue(LinkQueue &Q){
	InitQueue(Q);
	
}

// 4. 队列判空
bool JudgeEmpty(LinkQueue Q){

	if(Q.front == Q.rear){
		return true;
	}
	else{
		return false;
	}
} 

// 5. 求队列长度
int GetLength(LinkQueue Q){
	int length = 0;
	while(Q.front != Q.rear){
		length ++;
		Q.front = Q.front->next;
	}
	return length;
} 

// 6. 获取队头元素
QElemType GetFront(LinkQueue Q){
	
	return Q.front->next->data;
	
}

// 7. 插入一个元素(入队) 
void InsertElem(LinkQueue &Q,QElemType elem){
	
	QueuePtr p = (QueuePtr )malloc(sizeof(QNode));
	p->data = elem;
	
	Q.rear->next = p;
	Q.rear = p;
}

// 8. 删除一个元素(出队) 
QElemType  DeleteElem(LinkQueue &Q){
	
	//注意顺序,Q.front指向的当前节点不做数据节点 
	Q.front = Q.front->next;
	QElemType elem = Q.front->data;
	
	return elem; 
} 

// 9. 输出所有的元素
QElemType PrintElem(QueuePtr p){
	
	return  p->data;

} 

 

三、部分运行截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、小结

  1. 熟悉了二叉树的建立,递归遍历,非递归遍历,层序遍历的相关操作,遍历融合栈、队列的综合操作、体会到程序健壮性的好处,自定义类型名实现快速修改定义数据类型,练习尽量在功能函数中使用返回值 提高功能函数独立性、健壮性
  2. 遇到的问题就是,对于非递归中序1的逻辑开始不太理解,后来跟着代码顺了几遍,清晰了许多,相对于非递归中序2,还是后者更容易理解
  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
``` #include<stdio.h> #include<stdlib.h> //定义叉树结构体 struct TreeNode { int data; struct TreeNode *left; struct TreeNode *right; }; //创建节点函数 struct TreeNode *createNode(int data) { struct TreeNode *newNode = (struct TreeNode *)malloc(sizeof(struct TreeNode)); newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } //插入节点函数 struct TreeNode *insert(struct TreeNode *root, int data) { if (root == NULL) { return createNode(data); } else if (data <= root->data) { root->left = insert(root->left, data); } else { root->right = insert(root->right, data); } return root; } //函数 void preOrder(struct TreeNode *root) { if (root == NULL) { return; } printf("%d ", root->data); preOrder(root->left); preOrder(root->right); } //函数 void inOrder(struct TreeNode *root) { if (root == NULL) { return; } inOrder(root->left); printf("%d ", root->data); inOrder(root->right); } //归后函数 void postOrder(struct TreeNode *root) { if (root == NULL) { return; } postOrder(root->left); postOrder(root->right); printf("%d ", root->data); } //归层次遍函数 void levelOrder(struct TreeNode *root) { if (root == NULL) { return; } struct TreeNode *queue[100]; int front = 0; int rear = 0; queue[rear] = root; rear++; while (front <= rear) { struct TreeNode *current = queue[front]; front++; printf("%d ", current->data); if (current->left != NULL) { queue[rear] = current->left; rear++; } if (current->right != NULL) { queue[rear] = current->right; rear++; } } } int main() { struct TreeNode *root = NULL; root = insert(root, 5); root = insert(root, 3); root = insert(root, 7); root = insert(root, 2); root = insert(root, 4); root = insert(root, 6); root = insert(root, 8); printf(":"); preOrder(root); printf("\n"); printf(":"); inOrder(root); printf("\n"); printf("归后:"); postOrder(root); printf("\n"); printf("归层次遍:"); levelOrder(root); printf("\n"); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scl、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值