【数据结构】实验九:二叉树

实验九 二叉树

一、实验目的与要求

1)理解二叉树的类型定义;

2掌握二叉树的存储方式及基于存储结构的基本操作实现;

二、 实验内容

1. 二叉树的结点定义如下:

struct TreeNode

{

int m_nvalue;

TreeNode* m_pLeft;

TreeNode* m_pRight;

};

输入二叉树中的两个结点,输出这两个结点在树中的最近公共祖先结点。

说明:最近公共祖先定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个结点也可以是它自己的祖先)。

2. 某同学非常喜欢玩二叉树。最喜欢的游戏是用结点中的大写字母构造查找二叉树。这是他构造的二叉树:

他为每棵树写下先序遍历和中序遍历两个字符串,例如:对于上面构造的树,先序遍历为DBACEGF中序遍历为ABCDEFG几年后,他想重新构造这棵树,请你来编写一个程序帮他实现基于上述遍历序列构造树。

题意解析:根据所给的两串序列,分别是前序和中序,求出二叉树的后序。

三、实验结果

1)简述算法步骤:

2)分析算法时间复杂度:

 


题目1:

0 实验代码及结果

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

#define Max 100
vector<int> E[Max]; 
int parent[Max];     
int depth[Max];     

//构造递归的树  v->当前结点 p->当前的双亲结点 d->当前结点深度 
void BuildTree(int v,int p,int d){
	parent[v]=p;
	depth[v]=d;
	for(int i=0;i<E[v].size();i++){
		if(E[v][i]!=p){
			BuildTree(E[v][i],v,d+1);
		}
	}
}

//lowest common ancestor的寻找函数 
int LCA(int u,int v){
	//深度不同时,用双亲结点替换 
	while(depth[u]!=depth[v]){
		if(depth[v]<depth[u]){
			u=parent[u];
		}
		else{
			v=parent[v];
		}
	}
	//同深度时,同时迭代寻找ancestor 
	while(u!=v){
		u=parent[u];
		v=parent[v];
	}
	return v;//返回公共祖先 
}

int main(){
	int n,root=1;//n->总结点数  root->根结点 
	//root需要定义!!!!!!!!!!!!! 
	cout<<"请输入总结点数:"<<endl;
	cin>>n;
	if(n==1){
		cout<<"输入错误!不能只有一个结点!"<<endl;
		return 0;
	}
	//i<n-1 根结点无双亲,所以少输入一次 
	for(int i=0;i<n-1;i++){
		int u,v;
		cout<<"请输入双亲结点编号及其孩子结点编号:"<<endl;
		cin>>u>>v;
		E[u].push_back(v);
		E[v].push_back(u);  
	}
	BuildTree(root,-1,0);//根结点双亲为-1,深度为0 
	int u,v;
	cout<<"请输入两个待测结点的编号:" <<endl;
	cin>>u>>v;
	cout<<"其LCA为:"<<LCA(u,v)<<endl;
	return 0;
}

实验报告测试用例的二叉树如下图所示:

 代码测试结果如下图所示:

 

 

 

1 简述算法步骤

    定义存储当前结点数据、双亲结点、当前结点深度的数组,并规定最大范围为100。

    构造递归结构存储的数,存储当前结点的双亲结点和深度,然后通过for循环遍历,利用递归构造子树。在for循环中,如果遍历发现当前结点与双亲结点不同,即当前结点不是叶子结点,那么继续调用BuildTree函数构造子树,并将当前深度加一。

 

    寻找最近公共祖先时,先判断两个结点的深度是否相同。如果深度不同,则先回溯深度较深的结点,寻找其与另外一个结点深度相同时的祖先。回溯完以后,此时u、v深度相同,直接比较u、v结点是否相同。如果结点不同,则分别向上回溯一个祖先,再判断其祖先是否相同。最后两个结点回溯到相同值,返回其中一个结点即可。

    最后通过vector自带函数和for循环,将预设的树进行入栈并构造。

 

2 分析算法时间复杂度

    在利用递归创建树时,利用了for循环遍历双亲合集,来判断当前结点的双亲结点是否已经被存入。由此可见,时间复杂度为O(n^2)。

    在LCA函数中,通过第一个while循环收缩较远结点的深度进行回溯,通过第二个while循环同时收缩两个结点的深度进行回溯。由此可见,时间复杂度均为O(n)。

    在主函数输入预设树的基本信息时,利用了for循环将每一组【双亲结点+孩子结点】存入vector中。由此可见,时间复杂度为O(n)。

 

题目2:

0 实验代码及结果

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;

typedef struct Node{
    char data;//数据域 
    struct Node *lchild,*rchild;//左孩子结点+右孩子结点 
}Node,*BiTree;
 
char PreString[30],InString[30];//先序和中序遍历字符串 maxsize=30 

//根据先序+中序,重新构造原来的树,即BiTree T = new Node(); 
BiTree Build(char *PreString,char *InString,int s1,int e1,int s2,int e2){
	//s:start e:end 
    BiTree T = new Node();
    T -> data = PreString[s1];
    int rootIdx;//根结点所在序号
    for(int i = s2;i <= e2;i++){
        if(PreString[s1] == InString[i]){
            rootIdx = i;//寻找先序字符串中当前元素在中序字符串中的位置 
            break;//寻觅结束 
        }
    }
    int llen = rootIdx - s2;//左 为 根-初始 
    int rlen = e2 - rootIdx;//右 为 len(in)-根 
    if(llen != 0){//左 非空 
        T -> lchild = new Node();
        T -> lchild = Build(PreString,InString,s1 + 1,s1 + llen,s2,s2 + llen - 1);
		//继续在左子树里面重复上述操作;
		//prestring: start=s1+1; end=s1+llen
		//instring: start=s2; end=s2+llen-1 
    }
    else{
    	T -> lchild = NULL;
	}
    if(rlen != 0){//右 非空 
        T -> rchild = new Node();
        T -> rchild = Build(PreString,InString,e1 - rlen + 1,e1,e2 - rlen + 1,e2);
        //继续在右子树里面重复上述操作;
        //pre: start=e1-rlen+1; end=e1
        //in: start=e2-rlen+1; end=e2
    }
    else{
        T -> rchild = NULL;
    }
    return T;
}

//后序遍历 -> 递归输出 
void PostOrder(BiTree T){
    if(T != NULL){ //树非空 
        PostOrder(T -> lchild); //走左子树 
        PostOrder(T -> rchild); //走右子树 
        cout<<T -> data; //走根 or 叶子结点 
    }
}
 
int main(){
    cout<<"请输入先序遍历字符串:";
    cin>>PreString;
    cout<<"请输入中序遍历字符串:";
    cin>>InString;
    BiTree T = NULL; //构建空树 
    int e1=strlen(PreString)-1,e2=strlen(InString)-1;//两个字符串下标位数 
    T = Build(PreString,InString,0,e1,0,e2); //通过先序遍历+中序遍历推断树结构 
    cout<<"此二叉树的后序遍历为:";
	PostOrder(T);//后序遍历输出该树 
    return 0;
}

题干给的先序遍历+中序遍历构成的二叉树如下图所示:

代码测试结果如下图所示:

 

 

1 简述算法步骤

构造一个二叉树,属性携带当前结点数据、当前结点左孩子指针和当前结点右孩子指针。

根据先序遍历【根->左子树->右子树】的特点可知,先序遍历字符串中的第一个结点必然为根,然后通过for循环在中序遍历字符串中寻找与当前结点数据相同的结点,并锁定其在中序遍历字符串中的位置为rootIdx。此时,在中序遍历字符串中,rootIdx左侧的字符串为左子树的内容,rootIdx右侧的字符串为右子树的内容。同时回溯到先序遍历字符串中,可锁定左子树的始末下标和右子树的始末下标。

根据树的定义,每一个子树可作为一棵新的树。于是我们将左子树和右子树分别看作新的两个树,确定好新的prestring和instring起始下标之后,重新进行上述操作来确定整个树的空间结构,直至左子树或右子树为空树。最后返回整个树。

在确定整个树的空间结构后,通过递归的后序遍历法【左子树->右子树->根】输出整个树的结点数据。

最后通过主函数依次调用上述算法函数。

2 分析算法时间复杂度

在寻找先序字符串中当前元素在中序字符串中的位置的时候,使用了for循环遍历instring里面的所有结点数据。由此可见,时间复杂度为O(n)。整个递归调用的时间复杂度为O(n logn)。

    前俩个递归调用的时候,均通过二分法处理两个遍历字符串,从而进行下一次函数调用。第三个递归调用的时候,也与上述两个递归类似,先通过锁定根结点,分为左子树和右子树继续遍历。由此可见,时间复杂度均为O(n/2)。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是数据结构二叉树实验报告。 一、实验目的 1. 了解二叉树的概念、性质和基本操作; 2. 掌握二叉树的遍历算法; 3. 实现二叉树的建立、遍历和查找等基本操作。 二、实验原理 二叉树是一种树形结构,它的每个节点最多有两个子节点。二叉树性质如下: 1. 每个节点最多有两个子节点,分别称为左子节点和右子节点; 2. 左子树和右子树都是二叉树; 3. 空树也是一棵二叉树二叉树的遍历有三种方式: 1. 先序遍历:先访问根节点,然后遍历左子树,最后遍历右子树; 2. 中序遍历:先遍历左子树,然后访问根节点,最后遍历右子树; 3. 后序遍历:先遍历左子树,然后遍历右子树,最后访问根节点。 三、实验步骤 本次实验我们将实现二叉树的建立、遍历和查找等基本操作。 1. 定义二叉树结构体。 ```c typedef struct Node { int data; struct Node *left; struct Node *right; } Node, *pNode; ``` 2. 实现二叉树的创建函数。 ```c pNode createTree() { int data; scanf("%d", &data); if(data == -1) { return NULL; } else { pNode node = (pNode)malloc(sizeof(Node)); node->data = data; node->left = createTree(); node->right = createTree(); return node; } } ``` 3. 实现二叉树的先序遍历函数。 ```c void preOrder(pNode node) { if(node != NULL) { printf("%d ", node->data); preOrder(node->left); preOrder(node->right); } } ``` 4. 实现二叉树的中序遍历函数。 ```c void inOrder(pNode node) { if(node != NULL) { inOrder(node->left); printf("%d ", node->data); inOrder(node->right); } } ``` 5. 实现二叉树的后序遍历函数。 ```c void postOrder(pNode node) { if(node != NULL) { postOrder(node->left); postOrder(node->right); printf("%d ", node->data); } } ``` 6. 实现二叉树的查找函数。 ```c pNode search(pNode node, int data) { if(node == NULL) { return NULL; } else if(node->data == data) { return node; } else if(node->data > data) { return search(node->left, data); } else { return search(node->right, data); } } ``` 7. 编写主函数进行测试。 ```c int main() { pNode root = createTree(); printf("先序遍历结果:"); preOrder(root); printf("\n中序遍历结果:"); inOrder(root); printf("\n后序遍历结果:"); postOrder(root); printf("\n请输入要查找的节点值:"); int data; scanf("%d", &data); pNode node = search(root, data); if(node == NULL) { printf("未找到该节点!"); } else { printf("已找到该节点,节点值为:%d", node->data); } return 0; } ``` 四、实验结果 经过测试,程序能够正确地实现二叉树的建立、遍历和查找等基本操作。 五、实验总结 通过本次实验,我们深入理解二叉树的概念、性质和基本操作,并实现了二叉树的建立、遍历和查找等基本操作。同时,也加深了对指针和动态内存分配的理解

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MorleyOlsen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值