树与树的表示
什么是树?
这里举一个查找的例子:
查找:
- 静态查找(集合固定不变,只有查找操作)
- 动态查找(集合动态变化,有插入删除查找操作)
首先我们来看看静态查找
一个有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条边
树的基本术语:
- 结点的度(Degree):结点的子树个数
- 树的度:树的所有结点中最大的度数
- 叶结点(Leaf):度为0的结点
- 父结点(Parent):有子树的结点是其子树的根结点的父结点
- 子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点
- 兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点。
- 路径和路径长度:从结点n1到nk的路径为一个结点序列n1 , n2,… , nk, ni是 ni+1的父结点。路径所包含边的个数为路径的长度。
- 祖先结点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点。
- 子孙结点(Descendant):某一结点的子树中的所有结点是这个结点的子孙。
- 结点的层次(Level):规定根结点在1层,其它任一结点的层数是其父结点的层数加1。
- 树的深度(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.建立二叉树
用结构数组表示二叉树
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 -
*/