到难点了,个人认为初阶数据结构最难的点就在于二叉树,所以我尽量写的详细,清楚一些
文章目录
概要
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。这种结构类似于一颗大树的成长。首先是一个结点,树成长了,形成了几个叶子,而这几个叶子也将成为这个枝干的结点。只不过这是自然界的树,在计算机数据结构中,这颗树是倒着这的。
树
这个图树一个比较特殊的树,名字叫二叉树

这个机构我先提出几个问题:
- 如何获取到我想要的元素
- 我在添加元素时该如去做
- 如何通过根结点找到子结点的位置。
答:
- 通过遍历获取,怎么遍历?一般的循环遍历肯定不适合这个结构 ,那么就需要通过递归来遍历整个数据结构
- 通过类似于LinkedList(链表)结构去添加元素
- 也通过记录字节点的位置,寻找子叶结点。
补充几个概念:(重要)

- 结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为6
- 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为6
- 叶子结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I…等节点为叶结点
- 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
- 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
- 根结点:一棵树中,没有双亲结点的结点;如上图:A
- 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推
- 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4
二叉树(重要)
规定:
- 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树


二叉树又分为。满二叉树和完全二叉树。
满二叉树:及每个结点的子结点个数都为2,也就是满的
百度定义:
满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,如果一棵二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树。

———————————————————————————————————————————
完全二叉树:具有二叉树性质的,最差满足左树的二叉树。也就是说,无需去理会是否同时具有左树和右树的情况(满足这个情况的其实是满二叉树),但必须满足左树存在的情况。
百度定义:
完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。

注意:
是满二叉树是一种特殊的完全二叉树。
二叉树的性质:
-
若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) (i>0)个结点

-
若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^k-1 (k>=0)

-
对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

-
具有n个结点的完全二叉树的深度(层数)k为log2(n+1) 上取整

-
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
(1):若2i+1<n,左孩子序号:2i+1,否则无左孩子
(2):若2i+2<n,右孩子序号:2i+2,否则无右孩子

二叉树的存储结构分为:顺序存储(下面回仔细讲解(堆排序))和类似于链表(这里将实现)的链式存储
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}

遍历
双叉树的遍历分为3种: 前序遍历,中序遍历,后序遍历
前序遍历:访问根结点—>根的左子树—>根的右子树
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
/**
* 创建一棵二叉树 返回这棵树的根节点
*
* @return
*/
public TreeNode root=null;
public TreeNode createTree() {
TreeNode root=null;
return root;
}
// 前序遍历
public void preOrder(TreeNode root) {
if (root==null) {
return;
}
//先访问根结点的数据
System.out.println(root.val);
//在访问左子字节点的数据
preOrder(root.left);
//最后访问右子结点的数据
preOrder(root.right);;
}
前序遍历
中序遍历:根的左子树—>根节点—>根的右子树。
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
/**
* 创建一棵二叉树 返回这棵树的根节点
*
* @return
*/
public TreeNode root=null;
public TreeNode createTree() {
TreeNode root=null;
return root;
}
//中序遍历
void inOrder(TreeNode root) {
if (root==null) {
return;
}
//先遍历左字节点
inOrder(root.left);
//再遍历根节点
System.out.println(root.val);
//最后遍历右结点
inOrder(root.right);;
}
中序遍历
后序遍历:根的左子树—>根的右子树—>根节点。
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
/**
* 创建一棵二叉树 返回这棵树的根节点
*
* @return
*/
public TreeNode root=null;
public TreeNode createTree() {
TreeNode root=null;
return root;
}
// 后序遍历
void postOrder(TreeNode root) {
if (root==null) {
return;
}
//先访问左树的数据
postOrder(root.left);
//再访问右树的数据
postOrder(root.right);
//最后访问根的数据
System.out.println(root.val);
}
后序遍历
层序遍历:从左往右依次遍历
//层序遍历
public void levelOrder(TreeNode root){
if (root==null){
return;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.print(cur.val+"");
if (cur.left!=null){
queue.offer(cur.left);
}
if (cur.right!=null){
queue.offer(cur.right);
}
}
}
技术细节
到了这里,其实大家对于二叉树的结构已经较为了解了,接下来,大家可以尝试书写一下我所实现的功能。真没心力去做视频了。等我回复一点血,在讲解下面的代码。求求了。。。。,我好像挖了不少的坑。。。。
public class TestBinaryTree {
static class TreeNode{
public char val;//数据域
public TreeNode left;//左孩子
public TreeNode right;//右孩子
public TreeNode(char val){
this.val=val;
}
}
public TreeNode root;//二叉树的根节点
public void createTree(){
}
//前序遍历
public void preOrder(TreeNode root){
if (root==null){
return;
}
System.out.println(root.val+"");
preOrder(root.left);
preOrder(root.right);
}
public List<Character> preorderTraversal(TreeNode root) {
List<Character> ret=new ArrayList<>();
if (root==null){
return null;
}
ret.add(root.val);
List<Character> leftret= preorderTraversal(root.left);
leftret.addAll(leftret);
List<Character> rightret= preorderTraversal(root.left);
leftret.addAll(rightret);
return ret;
}
// 中序遍历
public void inOrder(TreeNode root){
if (root==null){
return;
}
inOrder(root.left);
System.out.println(root.val+"");
inOrder(root.right);
}
// 后序遍历
public void postOrder(TreeNode root){
if (root==null){
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val+"");
}
// 获取树中节点的个数
public int size(TreeNode root){
if (root==null){
return 0;
}
int leftSize=size(root.left);
int rightSize=size(root.right);
return leftSize+rightSize+1;
}
// 获取叶子节点的个数
public int getLeafNodeCount(TreeNode root){
if (root==null){
return 0;
}
if (root.left==null&&root.right==null){
return 1;
}
int leftroot=getLeafNodeCount(root.left);
int rightroot=getLeafNodeCount(root.right);
return leftroot+rightroot;
}
// 子问题思路-求叶子结点个数
// 获取第K层节点的个数
public int getKLevelNodeCount(TreeNode root,int k){
if (root==null){
return 0;
}
if (k==1){
return 1;
}
int leftSzie= getKLevelNodeCount(root.left,k-1);
int rightSize= getKLevelNodeCount(root.right,k-1);
return leftSzie+rightSize;
}
// 获取二叉树的高度
public int getHeight(TreeNode root){
if (root==null){
return 0;
}
int rightSzie =getHeight(root.right);
int leftSzie=getHeight(root.left);
if (rightSzie>=leftSzie){
return rightSzie+1;
}else {
return leftSzie+1;
}
}
// 检测值为value的元素是否存在
public TreeNode find(TreeNode root, int val){
if (root==null){
return null;
}
if (root.val==val){
return root;
}
TreeNode leftTree=find(root.left, val);
if (leftTree!=null){
return leftTree;
}
TreeNode rightTree=find(root.right, val);
if (rightTree!=null){
return rightTree;
}
return null;
}
//层序遍历
public void levelOrder(TreeNode root){
if (root==null){
return;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
TreeNode cur=queue.poll();
System.out.print(cur.val+"");
if (cur.left!=null){
queue.offer(cur.left);
}
if (cur.right!=null){
queue.offer(cur.right);
}
}
}
// 判断一棵树是不是完全二叉树
public boolean isCompleteTree(TreeNode root){
if (root==null){
return false;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
TreeNode cur= queue.poll();
if (cur!=null){
queue.offer(cur.left);
queue.offer(cur.right);
}else {
break;
}
}
while (!queue.isEmpty()){
TreeNode tmp=queue.poll();
if (tmp!=null){
return false;
}
}
return true;
}
}
小结
- 树这种结构对于计算机来说极为重要,而这篇文章所讲的其实连毛都算不上,主要就是让大家了解了一下树这种结构
- 理解上述代码,对于绝大部分的算法都会用到,别问,问就是在牛客上刷到了
- 重点理解遍历就可。大家肯定都比我聪明。下面的代码我都理解了好久。
- 重点要关注一下树的叶子,和高度的求法。
- 求求了,给个赞,我图画的很多很多。视频制作真的很累。。。
十一、其他文章接口
1.String方法(重要,对于操作字符串有巨大的帮助)
2.java常用的接口及其方法(包含拷贝,比较,排序,构造器)
3.初阶数据结构
3.1 顺序表:ArrayList
3.2 链表:LinkedList
3.3 栈:Stack
3.4 队列:Queue
3.5 二叉树:Tree
3.6 优先级队列:PriorityQueue(堆排序)
3.7 Map和Set
HashMap和HashSet,TreeMap和TreeSet
文章链接
4. 排序(7种方式)
4.1 插入排序(两种)
4.2 选择排序(两种)
4.3 快速排序
4.4 堆排序
里面有堆排序的实现和逻辑
文章链接





