树与二叉树数据结构详解
Posted zjruiiiiii
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树与二叉树数据结构详解相关的知识,希望对你有一定的参考价值。
一、树的基本概念
1.树的知识框架
1.树的定义
树是n(n>=0)个结点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的结点。
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,并且称为根的子树。
显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
- 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
- 树中所有结点可以有零个或多个后继。
因此n个结点的树中有n-1条边。
3.树的基本术语
结合图示来说明一下树的一些基本术语和概念。
- 考虑结点K。根A到结点K的唯一路径上的任意结点,称为结点K的祖先。如结点B是结点K的祖先,而结点K是结点B的子孙。路径上最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
- 树中其中某个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3(此结点度最大),树的度为3。
- 度大于0的结点称为分支结点(又称非终端结点);度为0(没有孩子结点)的结点称为叶子结点(又称终端结点)。在分支结点中,每个结点的分支数就是该结点的度。
- 结点的深度、高度和层次。
结点的层次从树根开始定义,根结点为第1层,它的子结点为第2层,以此类推。双亲在同一层的结点互为堂兄弟,图中结点G与E,F,H,I,J互为堂兄弟。
结点的深度是从根结点开始自顶向下逐层累加的。它可以是某一层,例如结点F所在的深度为3,而L结点所在的深度为4。
结点的高度是从叶结点开始自底向上逐层累加的。高度只讲的是树的高度,树的高度为4。
树的高度(或深度)是树中结点的最大层数。图中树的高度为4。 - 有序树和无序树。**树中结点的各子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。**假设图为有序树,若将子结点位置互换,则变成一棵不同的树。
- 路径和路径长度。树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
注意:由于树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。 - 森林。森林是m (m≥0)棵互不相交的树的集合。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给m棵独立的树加上一个结点,并把这m棵树作为该结点的子树,则森林就变成了树。一棵树也可以称作一个森林。
4.树的性质
注意其中两个性质:
1、
其中这两个性质是可以互相转化的,只要记住其中一个性质就行。
2、
这个性质可以推为:
1.已经父亲结点的下标为i,则其左孩子的结点为2i+1,右孩子的结点为2i+2。
2.已知孩子结点n,推父亲节点:(n-1)/2 。
面试题:
比如:假设一棵完全二叉树中总共有1000个节点,则该二叉树中多少个叶子节点,多少个非叶子节点,多少个节点只有左孩子,多少个节点只有右孩子。
答:因为该二叉树是一棵完全二叉树,所以不可能有结点是只有右孩子的,因此最后一个空为0。
再求出该二叉树一共有多少层,因为有1000个结点,又由log2的(n+1)向上取整得深度为10,但是第十层还没放满(放满的结点数为2的10次方-1)。我们还可以求出10层前的结点一共有多少个,由2的9次方-1得511,所以第十层放的是1000-511=489个结点。
因此第十层的489个结点都为叶子结点,但是不要忘了第十层是未放满的,因此推出第九层中也是有叶子结点的。将第十层的叶子结点除2,得第9层的非叶子结点为:489/2=244.5,说明有第九层非叶子结点中有一个结点是只有左节点的。第九层结点个数:2的(9-1)次方=256个。因此非叶子结点有245个。求得第九层的叶子结点为256-245=11个。所以一共加起来的叶子结点有11+489=500个。
又因为二叉树的结点数是由叶子结点和非叶子结点组成的,所以非叶子结点有1000-500==500个,叶子结点有500个,只有左孩子的只有1个,没有只有右孩子的结点。
5.树的存储结构
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:
// 孩子表示法
class Node
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
// 孩子双亲表示法
class Node
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
其实二叉树如何表示,主要是看创建的TreeNode结点类是如何设置来存储二叉树中的结点的。不过大多数情况下都是采取孩子表示法来构建二叉树。
二、二叉树的操作
1.二叉树的遍历
所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题(比如:打印节点内容、节点内容加1)。 遍历是二叉树上最重要的操作之一,是二叉树上进行其它运算之基础。
在遍历二叉树时,如果没有进行某种约定,每个人都按照自己的方式遍历,得出的结果就比较混乱,如果按照某种规则进行约定,则每个人对于同一棵树的遍历结果肯定是相同的。如果N代表根节点,L代表根节点的左子树,R代表根节点的右子树,则根据遍历根节点的先后次序有以下遍历方式:
二叉树的遍历分为:
1.NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。
2.LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树。
3.LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
2.二叉树的基本操作
import java.util.*;
class TreeNode
public char val;
public TreeNode left;
public TreeNode right;
public TreeNode(char val)
this.val = val;
public class BinaryTree
public TreeNode creatTree()
TreeNode A = new TreeNode('A');
TreeNode B = new TreeNode('B');
TreeNode C = new TreeNode('C');
TreeNode D = new TreeNode('D');
TreeNode E = new TreeNode('E');
TreeNode F = new TreeNode('F');
TreeNode G = new TreeNode('G');
TreeNode H = new TreeNode('H');
A.left = B;
B.left = D;
B.right = E;
E.right = H;
A.right = C;
C.left = F;
C.right = G;
return A;
// 前序遍历
void preOrderTraversal(TreeNode root)
if (root == null)
return;
System.out.print(root.val + " ");
preOrderTraversal(root.left);
preOrderTraversal(root.right);
// 中序遍历
void inOrderTraversal(TreeNode root)
if (root == null)
return;
inOrderTraversal(root.left);
System.out.print(root.val + " ");
inOrderTraversal(root.right);
// 后序遍历
void postOrderTraversal(TreeNode root)
if (root == null)
return;
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.print(root.val + " ");
// 遍历思路-求结点个数
static int size = 0;
void getSize1(TreeNode root)
if (root == null)
return;
getSize1(root.left);
getSize1(root.right);
size++;
// 子问题思路-求结点个数
int getSize2(TreeNode root)
if (root == null)
return 0;
return getSize2(root.left) + getSize2(root.right) + 1;
// 遍历思路-求叶子结点个数
static int leafSize = 0;
void getLeafSize1(TreeNode root)
if (root == null)
return;
if (root.left == null && root.right == null)
leafSize++;
getLeafSize1(root.left);
getLeafSize1(root.right);
// 子问题思路-求叶子结点个数
int getLeafSize2(TreeNode root)
if (root == null)
return 0;
if (root.left == null && root.right == null)
return 1;
return getLeafSize2(root.left) + getLeafSize2(root.right);
// 子问题思路-求第 k 层结点个数
int getKLevelSize(TreeNode root, int k)
if (root == null)
return 0;
if (k == 1)
return 1;
return getKLevelSize(root.left, k - 1) + getKLevelSize(root.right, k - 1);
// 获取二叉树的高度
int getHeight(TreeNode root)
if (root == null)
return 0;
int leftNode = getHeight(root.left);
int rightNode = getHeight(root.right);
return Math.max(rightNode, leftNode) + 1;
// 查找 val 所在结点,没有找到返回 null
// 按照 根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
TreeNode find(TreeNode root, char val)
if (root == null)
return null;
if (root.val == val)
return root;
TreeNode ret = find(root.left, val);
if (ret != null)
return ret;
ret = find(root.right, val);
if (ret != null)
return ret;
return null;
// 层序遍历
void levelOrderTraversal(TreeNode root)
if (root == null)
return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty())
TreeNode top = queue.poll();
System.out.print(top.val + " ");
if (top.left != null)
queue.offer(top.left);
if (top.right != null)
queue.offer(top.right);
System.out.println();
//层序遍历
public List<List<Character>> levelOrder(TreeNode root)
List<List<Character>> list = new ArrayList<>();
if (root == null)
return list;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty())
List<Character> list1 = new ArrayList<>();
int size = queue.size();
while (size != 0)
TreeNode top = queue.poll();
list1.add(top.val);
if (top.left != null)
queue.add(top.left);
if (top.right != null)
queue.add(top.right);
size--;
list.add(list1);
return list;
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root)
if (root == null)
return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty())
TreeNode top = queue.poll();
if (top != null)
queue.offer(top.left);
queue.offer(top.right);
else
break;
while(!queue.isEmpty())
TreeNode cur = queue.peek();
if(cur==null)
queue.poll();
else
return false;
return true;
//求二叉树的左视图
public void leftScenery(TreeNode root)
if(root==null)
return ;
List<List<TreeNode>> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty())
List<TreeNode> list1 = new LinkedList<>();
int size = queue.size();
while(size!=0)
TreeNode top = queue.poll();
list1.add(top);
if(top.left!=null)
queue.offer(top.left);
if(top.right!=null)
queue.offer(top.right);
size--;
list.add(list1);
for (List<TreeNode> e:list)
for (TreeNode p: e)
System.out.print(p.val+" ");
break;
//二叉树的非递归先序遍历
public void preOrderTraversalNot(TreeNode root)
if(root==null)
return;
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while(cur!=null||!stack.empty())
while(cur!=null)
stack.push(cur);
System.out.print(cur.val+" ");
cur=cur.left;
TreeNode top = stack.pop();
cur=top.right;
// 中序遍历
public void inOrderTraversalNot(TreeNode root)
if(root==null)
return;
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while(cur!=null||!stack.empty())
while(cur!=null)
stack.push(cur);
cur=cur.left;
TreeNode top = stack.pop();
System.out.print(top.val+" ");
cur=top.right;
// 后序遍历非递归
public void postOrderTraversalNot(TreeNode root)
if(root==null)
return;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while(cur!=null||!stack.empty())
while(cur!=null)
stack.push(cur);
cur=cur.left;
cur=stack.peek();
if(cur.right==null||cur.right==pre)
TreeNode top = stack.pop();
System.out.print(top.val+" ");
pre=以上是关于树与二叉树数据结构详解的主要内容,如果未能解决你的问题,请参考以下文章