数据结构--树(上)

树与树的表示

什么是树?
这里举一个查找的例子:
查找:

  • 静态查找(集合固定不变,只有查找操作)
  • 动态查找(集合动态变化,有插入删除查找操作)

首先我们来看看静态查找
一个有n个元素有序列,查找元素k的位置

方法1:顺序查找

for (int i=0;i<length;i++)
	if (a[i]==k)
		return i ;
	return -1 ;

顺序查找的算法时间复杂度为O(n)

方法2:二分查找(前提是有序)

int BinarySearch(int a[] , int k)
{
	int left , right;
	left = 0 ;
	right = length;  //数组长度
	while (left<=right)
	{
		mid = (right-left)/2;
		if (a[mid]>k)
		{
			right = mid-1 ;
		}
		else if (a[mid]<k)
			left = mid +1 ;
		else 
			return mid ;
	}
	return -1 ;//没找到
}

二分查找算法的时间复杂度为O(logn)

为什么二分查找会比顺序查找效率高呢?其实二分查找的过程就是对树中元素查找。

在这里插入图片描述
ASL是指平均查找的次数,1的深度为3,最多三次就可以查找到1的位置。

树的定义
在这里插入图片描述

  • 子树不相交
  • 除了根结点外,每个结点有且仅有一个父结点
  • 一棵N个结点的树有N-1条边

树的基本术语

  1. 结点的度(Degree):结点的子树个数
  2. 树的度:树的所有结点中最大的度数
  3. 叶结点(Leaf):度为0的结点
  4. 父结点(Parent):有子树的结点是其子树的根结点的父结点
  5. 子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点
  6. 兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点。
  7. 路径和路径长度:从结点n1到nk的路径为一个结点序列n1 , n2,… , nk, ni是 ni+1的父结点。路径所包含边的个数为路径的长度。
  8. 祖先结点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点。
  9. 子孙结点(Descendant):某一结点的子树中的所有结点是这个结点的子孙。
  10. 结点的层次(Level):规定根结点在1层,其它任一结点的层数是其父结点的层数加1。
  11. 树的深度(Depth):树中所有结点中的最大层次是这棵树的深度。

树的知识在离散数学中有学过,想要学习树,一定要先对树的基本术语有一定的掌握

树的表示
在这里插入图片描述
这是一棵树,可以看到树的度为3,但是有些节点的度只有1或者2,这样如果用链表来表示一颗树的话就会变成这样:
在这里插入图片描述
有的节点要三个指针,有的节点要两个指针,如果要统一的话,就需要把节点统一都开三个指针出来,这样就会导致空间很大的浪费。为了节省空间,树还有另一种表示方法:儿子-兄弟表示法。
儿子兄弟表示法
在这里插入图片描述
用儿子兄弟表示法一个节点只需开两个指针,一个指向儿子,一个指向兄弟。A有三个儿子 B C D,B是A 的儿子,B的兄弟是 C,C的兄弟是D,儿子的兄弟,儿子兄弟的兄弟,一样是A的儿子,这样一样可以把树的关系表示出来。

如果我们把儿子-兄弟树旋转45°更有趣的事情发生了
在这里插入图片描述
这个树就变成了一颗二叉树,因此任何一颗树都可以用二叉树来表示,所以对二叉树的学习至关重要。

二叉树及储存结构

二叉树的定义
一个有穷的结点集合。 这个集合可以为空若不为空,则它是由根结点和称为其左子树TL和右子树TR的两个不相交的二叉树组成。

二叉树的物种基本形态
在这里插入图片描述
二叉树有左右顺序之分

在这里插入图片描述
特殊的二叉树
1.斜二叉树
在这里插入图片描述
2.完美二叉树/满二叉树
在这里插入图片描述
3.完全二叉树
有n个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i(1 ≤ i ≤ n)结点与满二叉树中编号为 i 结点在二叉树中位置相同
在这里插入图片描述
完全二叉树与满二叉树有一点类似,满二叉树一定是完全二叉树,完全二叉树允许最后一层右边是空的,但是不允许左边有位置是空的。例如:
在这里插入图片描述
二叉树的重要性质

  • 一个二叉树第 i 层的最大结点数为:2^i -1,i >= 1。
  • 深度为k的二叉树有最大结点总数为: 2^k -1,k >= 1。
  • 对任何非空二叉树 T,若n0表示叶结点的个数、n2是度为2的非叶结点个数,那么两者满足关系n0= n2+1。
    在这里插入图片描述

二叉树的实现

二叉树的抽象数据类型定义
在这里插入图片描述
其中先序 中序 后序遍历,是依托前面学习的线性结构中栈实现的,层序遍历,是依托线性结构中队列来实现的。

二叉树的存储结构
1.顺序存储结构
在这里插入图片描述
但是对于一些节点数很少,但是树深很大的树,就会造成空间的大量浪费。
在这里插入图片描述
二叉树的数组实现这个就不写了,用的很少,后面实现判断树同构中,会用结构数组去表示二叉树,也就是用数组去模拟链表来表示二叉树,也叫静态链表。

2.链式存储
在这里插入图片描述
我们建立一棵如上图所示的二叉树,输入数据为: FCA##DB###EH##GM###(先序)

1:结构:

typedef struct TreeNode *BinTree;
struct TreeNode{
	ElementType Date ;
	BinTree left ;
	BinTree right;
};

在这里插入图片描述
在这里插入图片描述
2.建树(先序)

void CreatBiTree(BiTree &T)
{
	ElementType c = getchar();
	if (c == '#')
	{
		T = NULL;
		return;
	}
	else
	{
		T = (BiTree)malloc(sizeof(BiTNode)); //生成根节点 (先序) 
		(T)->Date = c;
		CreatBiTree(T->left); // 建立左子树
		CreatBiTree(T->right);//建立右子树 
	}
}

3.遍历(先序 中序 后续 )

  • 先序 F C A D B E H G M
    在这里插入图片描述
void PreOrderTraversal(BiTree T) // 先序遍历 
{
	if (T)
	{
		printf("%c ", T->Date);
		PreOrderTraversal(T->left);
		PreOrderTraversal(T->right);
	}
}
  • 中序 A C B D F H E M G

在这里插入图片描述

void InOrderTraversal(BiTree T) // 中序遍历 
{
	if (T)
	{
		InOrderTraversal(T->left);
		printf("%c ", T->Date);
		InOrderTraversal(T->right);
	}
}
  • 后序 A B D C H M G E F
    在这里插入图片描述
void PostOrderTraversal(BiTree T) // 后序遍历 
{
	if (T)
	{
		PostOrderTraversal(T->left);
		PostOrderTraversal(T->right);
		printf("%c ", T->Date);
	}
}

遍历结果:
在这里插入图片描述
3.输出叶子结点

void PreOrderPrintLeaves(BiTree T)
{
	if (T)
	{
		if (!T->left && ! T->right )//左右都为空说明为叶子结点
			cout << T->Date <<" ";
		PreOrderPrintLeaves(T->left);
		PreOrderPrintLeaves(T->right);
	}
}

在这里插入图片描述

4.求树高

int PostOrderGetHeight(BiTree T)
{
	if (T)
	{
	return max(PostOrderGetHeight(T->left)+1,PostOrderGetHeight(T->right)+1);//左右树的高度的最大值即树深度
	}
	else 
		return 0;
} 

在这里插入图片描述
完整代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#define ElementType char  
#define Maxsize 10000
using namespace std;


typedef struct TreeNode {
	ElementType Date;
	struct TreeNode* left;
	struct TreeNode* right;
}BiTNode, *BiTree;

void CreatBiTree(BiTree &T)
{
	ElementType c = getchar();
	if (c == '#')
	{
		T = NULL;
		return;
	}
	else
	{
		T = (BiTree)malloc(sizeof(BiTNode)); //生成根节点 (先序) 
		(T)->Date = c;
		CreatBiTree(T->left); // 建立左子树
		CreatBiTree(T->right);//建立右子树 
	}
}

void PreOrderTraversal(BiTree T) // 先序遍历 
{
	if (T)
	{
		printf("%c ", T->Date);
		PreOrderTraversal(T->left);
		PreOrderTraversal(T->right);
	}
}

void InOrderTraversal(BiTree T) // 中序遍历 
{
	if (T)
	{
		InOrderTraversal(T->left);
		printf("%c ", T->Date);
		InOrderTraversal(T->right);
	}
}

void PostOrderTraversal(BiTree T) // 后序遍历 
{
	if (T)
	{
		PostOrderTraversal(T->left);
		PostOrderTraversal(T->right);
		printf("%c ", T->Date);
	}
}

void PreOrderPrintLeaves(BiTree T)
{
	if (T)
	{
		if (!T->left && ! T->right )
			cout << T->Date <<" ";
		PreOrderPrintLeaves(T->left);
		PreOrderPrintLeaves(T->right);
	}
}

int PostOrderGetHeight(BiTree T)
{
	if (T)
	{
		return max(PostOrderGetHeight(T->left)+1,PostOrderGetHeight(T->right)+1);
	}
	else 
		return 0;
} 
int main()
{
	// input:FCA##DB###EH##GM### (#代表空)
	BiTree root;
	CreatBiTree(root);
	cout << "先序: " ; 
	PreOrderTraversal(root);
	cout <<endl <<"中序:"; 
	InOrderTraversal(root);
	cout <<endl <<"后续:";
	PostOrderTraversal(root);
	cout <<endl<<"叶子结点:";
	PreOrderPrintLeaves(root);
	int TreeHigh = PostOrderGetHeight(root);
	cout <<"树高:"<<TreeHigh<<endl;
	cout <<endl;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
可以看到,我上面的输入是按满二叉树的方式输入的,那如何用两种遍历序列确定一棵二叉树呢?

两种遍历序列确定二叉树

首先需要确定一点,一定要有中序遍历次才可确立二叉树
如果只有前序和后序,则会出现下面这种情况:
在这里插入图片描述
所以必须要有中序遍历+前序/后序即可确立二叉树

这里以前序+中序为例
在这里插入图片描述
二叉树如下:
在这里插入图片描述
思路:
1.先序的第一个为树根,然后在中序中确定树根的位置
在这里插入图片描述
2.中序中树根左边为左子树的字符串,右边为右子树的字符串,前序树根后对应长度的字串就为左子树的前序,后面的为右子树的前序。
在这里插入图片描述
3.将左子树的先序+左子树的中序+左子树的指针 以及右子树的先序+右子树的中序+右子树的指针 递归下去分别建立左子树右子树即可。

代码:

void CreatBiTree(string pre ,string in ,BiTree &T)
{
	if (pre.length()!=in.length())
		{
			T = NULL ;
			return ;
		}
	if (pre.length()!=0)
	{
		T = (BiTree)malloc(sizeof (BiTNode));
		T->Date = pre[0];
		int pos = int(in.find(pre[0]));
		int left = pos ;// 中序中 左子树字符长度
		int right= pre.length() - left-1 ;//中序中 右子树字符的长度
		CreatBiTree(pre.substr(1,left),in.substr(0,left),T->left);
		CreatBiTree(pre.substr(left+1,right),in.substr(left+1,right),T->right);
	}
	else 
	{
		T=NULL;
	}
}

总代码:

#include <iostream>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <algorithm>
#define ElementType char  
#define Maxsize 10000
using namespace std;

typedef struct TreeNode {
	ElementType Date;
	struct TreeNode* left;
	struct TreeNode* right;
}BiTNode, *BiTree;

void CreatBiTree(string pre ,string in ,BiTree &T)
{
	if (pre.length()!=in.length())
		{
			T = NULL ;
			return ;
		}
	if (pre.length()!=0)
	{
		T = (BiTree)malloc(sizeof (BiTNode));
		T->Date = pre[0];
		int pos = int(in.find(pre[0]));
		int left = pos ;// 中序中 左子树字符长度
		int right= pre.length() - left-1 ;//中序中 右子树字符的长度
		CreatBiTree(pre.substr(1,left),in.substr(0,left),T->left);
		CreatBiTree(pre.substr(left+1,right),in.substr(left+1,right),T->right);
	}
	else 
	{
		T=NULL;
	}
}

void PreOrderTraversal(BiTree T) // 先序遍历 
{
	if (T)
	{
		printf("%c ", T->Date);
		PreOrderTraversal(T->left);
		PreOrderTraversal(T->right);
	}
}

void InOrderTraversal(BiTree T) // 中序遍历 
{
	if (T)
	{
		InOrderTraversal(T->left);
		printf("%c ", T->Date);
		InOrderTraversal(T->right);
	}
}

void PostOrderTraversal(BiTree T) // 后序遍历 
{
	if (T)
	{
		PostOrderTraversal(T->left);
		PostOrderTraversal(T->right);
		printf("%c ", T->Date);
	}
}

void PreOrderPrintLeaves(BiTree T)
{
	if (T)
	{
		if (!T->left && ! T->right )
			cout << T->Date <<" ";
		PreOrderPrintLeaves(T->left);
		PreOrderPrintLeaves(T->right);
	}
}

int PostOrderGetHeight(BiTree T)
{
	if (T)
	{
		return max(PostOrderGetHeight(T->left)+1,PostOrderGetHeight(T->right)+1);
	}
	else 
		return 0;
} 

int main()
{
	//FCA##DB###EH##GM###
	BiTree root;
	string pre, in;
	cout <<"输入前序序列:";  //abcdefghij
	cin >> pre ;
	cout <<"输入后序序列:" ; //cbedahgijf
	cin >> in ;
	CreatBiTree(pre,in,root);
	cout << "先序: " ; 
	PreOrderTraversal(root);
	cout <<endl <<"中序:"; 
	InOrderTraversal(root);
	cout <<endl <<"后续:";
	PostOrderTraversal(root);
	cout <<endl<<"叶子结点:";
	PreOrderPrintLeaves(root);
	int TreeHigh = PostOrderGetHeight(root);
	cout <<"树高:"<<TreeHigh<<endl;
	cout <<endl;
}

运行结果:
在这里插入图片描述

习题: 树的同构(用静态链表实现)

在这里插入图片描述

在这里插入图片描述
解题思路

  1. 建立两棵二叉树
  2. 同构判别

1.建立二叉树

用结构数组表示二叉树

struct TreeNode {
	char date ;
	int left = -1 ;
	int right = -1 ;
}T1[100],T2[100];

在这里插入图片描述
left 和 right 就是模拟链表中的指针,只是现在指向的是指向结点的数组下标,同时还可以发现left和right中出现了1 2 3 ,没有0,这样可以说明0是树的树根

建树:

void buildT1()
{
	memset(hash,0,sizeof (hash));//清0 
	for (int i=0;i<n;i++)
	{
		cin >> T1[i].date>>left>>right ;
		if (left!='-')
		{
			T1[i].left = left-'0';
			hash[left-'0']  = 1 ; 
		}
		if (right!='-')
		{
			T1[i].right = right-'0';
			hash[right-'0'] = 1 ;
		}
	}
	for (int i=0;i<n;i++)
	{
		if (hash[i]==0)
		{
			T1head = i ;
			break ;
		}
	}
}

2.判同构

  • 左右树为空 --同构
  • 一个树为空一个树不为空–非同构
  • 树根不同–非同构
  • 判断 (T1的左子树 = =T2的左子树 && T1右子树= =T2右子树 || T1的左子树= =T2的右子树 && T1右子树= =T2左子树)
bool Isomorphic(int root1 ,int  root2)
{
	if (root1 == -1 && root2 == -1)
		return true;
	if (root1==-1&&root2!=-1 || root1!=-1&&root2==-1)
		return false ;
	if (T1[root1].date != T2[root2].date)
		return false ;
	else if (Isomorphic(T1[root1].left,T2[root2].left)&&Isomorphic(T1[root1].right,T2[root2].right)  || Isomorphic(T1[root1].left,T2[root2].right)&&Isomorphic(T1[root1].right,T2[root2].left)  )
		return true ;
	else 
		return false ;
 } 

总代码:

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

struct TreeNode {
	char date;
	int left = -1; //-1 为空 
	int right = -1;
}T1[100], T2[100];
int T1head, T2head, n;
int Hash[100];
char date;
char Left, Right;

void buildT1()
{
	cin >> n;
	memset(Hash, 0, sizeof(Hash));//清0 
	for (int i = 0; i < n; i++)
	{
		cin >> T1[i].date >> Left >> Right;
		if (Left != '-')
		{
			T1[i].left = Left - '0';
			Hash[Left - '0'] = 1;
		}
		if (Right != '-')
		{
			T1[i].right = Right - '0';
			Hash[Right - '0'] = 1;
		}
	}
	for (int i = 0; i < n; i++)
	{
		if (Hash[i] == 0)
		{
			T1head = i;
			break;
		}
	}
}
void buildT2()
{
	cin >> n;
	memset(Hash, 0, sizeof(Hash));//清0 
	for (int i = 0; i < n; i++)
	{
		cin >> T2[i].date >> Left >> Right;
		if (Left != '-')
		{
			T2[i].left = Left - '0';
			Hash[Left - '0'] = 1;
		}
		if (Right != '-')
		{
			T2[i].right = Right - '0';
			Hash[Right - '0'] = 1;
		}
	}
	for (int i = 0; i < n; i++)
	{
		if (Hash[i] == 0)
		{
			T2head = i;
			break;
		}
	}
}

bool Isomorphic(int root1, int  root2)
{
	if (root1 == -1 && root2 == -1)
		return true;
	if (root1 == -1 && root2 != -1 || root1 != -1 && root2 == -1)
		return false;
	if (T1[root1].date != T2[root2].date)
		return false;
	else if (Isomorphic(T1[root1].left, T2[root2].left) && Isomorphic(T1[root1].right, T2[root2].right) || Isomorphic(T1[root1].left, T2[root2].right) && Isomorphic(T1[root1].right, T2[root2].left))
		return true;
	else
		return false;
}
int main()
{
	buildT1();
	buildT2();
	if (Isomorphic(T1head, T2head))
		cout << "Yes" << endl;
	else
		cout << "No" << endl;
}
/*
8
A 1 2
B 3 4
C 5 -
D - -
E 6 -
G 7 -
F - -
H - -
8
G - 4
B 7 6
F - -
A 5 1
H - -
C 0 -
D - -
E 2 -
*/
/*
8
A 1 2
B 3 4
C 5 -
D - -
E 6 -
G 7 -
F - -
H - -
8
G - 4
B 7 6
F - -
A 5 1
H - -
C 0 -
D - -
E 2 -
*/

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值