Java 数据结构之二叉树
二叉树的定义:
二叉树是
n(n≥0)
个结点的有限集,它或者是空集(n=0)
,或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。这是二叉树的递归定义。
二叉树的遍历:
所谓遍历,是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题
对于二叉树的遍历,可进一步分为三种:
①前序遍历(根左右):对于每一棵子树,先访问该节点,然后是左子树,最后是右子树
②中序遍历(左根右):对于每一棵子树,先访问左子树,然后是该节点,最后是右子树
③后序遍历(左右根):对于每一棵子树,先访问左子树,然后是右子树,最后是该节点
其中递归是实现上三种遍历逻辑最简单的方式
第1关:二叉树的实现之前序遍历
1.任务描述
本关任务:完成用二叉链表存储的二叉树的前序遍历算法。
- 补全
preOrder(TreeNode root)
方法,实现二叉树的前序遍历功能,并输出结点的值。
2.本关答案
本关要求我们实现二叉树的前序遍历,我们根据递归来实现根左右的遍历方式
package step1;
/**
* Created by zengpeng on 2018/2/9.
*/
public class BinaryTree {
private TreeNode root;//根节点
public BinaryTree() {
root = null;
}
public void preOrder(TreeNode root) {
/********** Begin *********/
//递归实现 根左右
if(root==null){
return;
}
System.out.println(root.item);
preOrder(root.leftChild);
preOrder(root.rightChild);
/********** End *********/
}
/**
*以数组arr的数据,依次从上至下,从左至右构建一颗二叉树
*
* @param arr
* @param n
* @return
*/
public TreeNode createTree(int arr[]) {
TreeNode tmp[] = new TreeNode[arr.length + 1];
for (int k = 1; k <= arr.length; k++) {
TreeNode node = new TreeNode(arr[k - 1]);
tmp[k] = node;
if (k == 1) {
root = node;
} else {
int j = k / 2;
if (k % 2 == 0) {
tmp[j].leftChild = node;
} else {
tmp[j].rightChild = node;
}
}
}
return root;
}
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
第2关:二叉树的实现之中序遍历
1.任务描述
本关任务:实现以二叉链表存储的二叉树的中序遍历算法。
补全inOrder(TreeNode root)
方法,实现二叉树的中序遍历功能,并输出结点的值
2.本关答案
本关要求我们实现二叉树的中序遍历,我们根据递归来实现左根右的遍历方式
package step2;
/**
* Created by zengpeng on 2018/2/12.
*/
public class BinaryTree {
private TreeNode root;//根节点
public BinaryTree() {
root = null;
}
public void inOrder(TreeNode root) {
/********** Begin *********/
//递归实现 左根右
if(root==null){
return;
}
inOrder(root.leftChild);
System.out.println(root.item);
inOrder(root.rightChild);
/********** End *********/
}
/**
* 以数组arr的数据,依次从上至下,从左至右构建一颗二叉树
*
* @param arr
* @param n
* @return
*/
public TreeNode createTree(int arr[]) {
TreeNode tmp[] = new TreeNode[arr.length + 1];
for (int k = 1; k <= arr.length; k++) {
TreeNode node = new TreeNode(arr[k - 1]);
tmp[k] = node;
if (k == 1) {
root = node;
} else {
int j = k / 2;
if (k % 2 == 0) {
tmp[j].leftChild = node;
} else {
tmp[j].rightChild = node;
}
}
}
return root;
}
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
第3关: 二叉树的实现之后序遍历
1.任务描述
本关任务:实现以二叉链表存储的二叉树的后序遍历算法。
- 补全
postOrder(TreeNode root)
方法,实现后序遍历功能,并输出结点值。
2.本关答案
本关要求我们实现二叉树的后序遍历,我们根据递归来实现左右根的遍历方式
package step3;
/**
* Created by zengpeng on 2018/2/12.
*/
public class BinaryTree {
private TreeNode root;//根节点
public BinaryTree() {
root = null;
}
public void postOrder(TreeNode root) {
/********** Begin *********/
//递归实现 左右根
if(root==null){
return;
}
postOrder(root.leftChild);
postOrder(root.rightChild);
System.out.println(root.item);
/********** End *********/
}
/**
* 以数组arr的数据,依次从上至下,从左至右构建一颗二叉树
*
* @param arr
* @param n
* @return
*/
public TreeNode createTree(int arr[]) {
TreeNode tmp[] = new TreeNode[arr.length + 1];
for (int k = 1; k <= arr.length; k++) {
TreeNode node = new TreeNode(arr[k - 1]);
tmp[k] = node;
if (k == 1) {
root = node;
} else {
int j = k / 2;
if (k % 2 == 0) {
tmp[j].leftChild = node;
} else {
tmp[j].rightChild = node;
}
}
}
return root;
}
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
Java 数据结构之二叉搜索树
第1关:二叉搜索树的介绍与构建
1.任务描述
本关任务:构建一棵二叉搜索树,向其中添加结点。
- 平台将创建用户补全后的
BSTree
类的对象; - 调用对象的
insert(int key)
方法,向树中添加结点; - 调用对象的
preOrder()
方法执行前序遍历; - 调用对象的
inOrder()
方法执行中序遍历; - 调用对象的
postOrder()
方法执行后序遍历; - 接着根据程序的输出判断程序是否正确。
2.相关知识
二叉搜索树(binary search tree)
,又称二叉排序树,它或者是空树,或者是满足如下性质的二叉树:
- 若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
- 若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
- 左、右子树本身又各是一棵二叉排序树。
3.思路分析
本关要求我们实现二叉树的插入操作insert
首先我们来分析BSTree类中已经给我们了的方法
首先就是节点类TreeNode:其中该类是BSTree的静态内部类,外部类可直接调用该内部类的成员变量和方法
//节点内部类
public static class TreeNode {
private TreeNode leftChild;//左孩子
private TreeNode rightChild;//右孩子
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
然后就是BSTree的成员变量以及方法:一个成员变量root表示该树的根节点,空参的构造方法表示构建一个空树;然后是三种基本的遍历方式
题目要求我们补全插入节点的代码
我们根据二叉搜索树的性质,我们从根节点root开始一直向下寻找合适的插入位置,若插入的节点小于比较的节点,那么我们就向比较节点的左孩子处向下寻找;若插入的节点大于比较的节点,那么我们就向比较节点的右孩子处向下寻找,直到找到合适的位置
4.本关答案
package step1;
/**
* Created by zengpeng on 2018/3/3.
*/
public class BSTree {
private TreeNode root;//根结点
public BSTree() {
root = null;
}
/**
* 向树root中插入key
*
* @param key 要插入的值
*/
public void insert(int key) {
/********** Begin *********/
TreeNode newnode=new TreeNode(key);
if(root==null){//树为空,要插入的节点即为根节点
root=newnode;
}else{
TreeNode cur=root;
while(cur!=null){
if(cur.item==key){//已经存在
return;
}else if(key<cur.item){//在该节点的左边
if(cur.leftChild==null){
cur.leftChild=newnode;
}else{//继续向左找
cur=cur.leftChild;
}
}else if(key>cur.item){//在该节点的右边
if(cur.rightChild==null){
cur.rightChild=newnode;
}else{//继续向右找
cur=cur.rightChild;
}
}
}
}
/********** End *********/
}
//前序遍历
public void preOrder() {
preOrder(root);
}
//中序遍历
public void inOrder() {
inOrder(root);
}
//后序遍历
public void postOrder(){
postOrder(root);
}
private void preOrder(TreeNode node) {
if (node != null) {
System.out.print(node.item + " ");
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
private void inOrder(TreeNode node) {
if (node != null) {
inOrder(node.leftChild);
System.out.print(node.item + " ");
inOrder(node.rightChild);
}
}
private void postOrder(TreeNode node) {
if (node != null) {
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.print(node.item + " ");
}
}
//节点内部类
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
第2关:二叉搜索树的删除
1.任务描述
本关任务是:实现二叉搜索树的删除功能
- 平台将创建用户补全后的
BSTree
类的对象; - 调用对象的
insert(int key)()
方法,构建一棵二叉搜索树; - 调用对象的遍历方法,输出删除元素前的各种遍历结果;
- 调用对象的
delete(int key)
方法,删除元素key
; - 调用对象的遍历方法,输出删除元素后的各种遍历结果;
- 接着根据程序的输出判断程序是否正确。
2.相关知识
从一棵二叉搜索树T
中删除一个结点z
的整个策略可分为三个基本情况:
- 如果
z
没有孩子结点,那么只是简单地将它删除,并修改它的父结点; - 如果
z
只有一个孩子结点,那么将这个孩子提升到树中z
的位置上,并修改z
的父结点,使其指向z
的孩子结点; - 如果
z
有两个孩子,那么找z右子树的最小节点代替z,最后删除替换后的最小节点
3.思路分析
我们可以观察到,在第三种情况下(删除节点有左右两个节点),当我们将删除节点右子树的最小节点代替删除节点后,我们还需删除替换后的最小节点位置,但此时删除的情况就是情况一,二的一种,那么我们就可以从第三种情况开始,若存在第三种情况,那么我们就可以将其转化为第一二种情况的一种
那么具体怎么操作呢?以下图为例:
首先要删除一个节点,就要知道它的父节点以及其孩子节点,因此首先我们要先找到删除节点的父节点
然后我们从第三种情况开始考虑:找到删除节点右子树的最小节点并与删除节点替换,将其转换为一二种情况
那么此时要删除的节点以及其父节点就变了,我们要重新记录
最后我们就要寻找删除节点的孩子节点,并将其父节点与其孩子节点相连,这样就完成了节点的删除,但我们需要考虑的是,若删除的是根节点,那么我们就直接返回其孩子节点即可,不用再更改其父节点的指针了(根节点的父节点为null)
当然特殊情况(parent==null)不会包括第三种情况,因为经转化后删除节点必定有父节点;因此该特殊情况只存在于第一二中
4.本关答案
package step2;
/**
* Created by zengpeng on 2018/3/14.
*/
public class BSTree {
private TreeNode root;//根结点
public BSTree() {
root = null;
}
/**
* 向树root中插入a
*
* @param key 要插入的值
*/
public void insert(int key) {
TreeNode x = root;
TreeNode p = null;//始终指向x的父结点
while (x != null) {
p = x;
if (key < x.item) {
x = x.leftChild;
} else {
x = x.rightChild;
}
}
if (null == p) {//空树
root = new TreeNode(key);
} else if (key < p.item) {
p.leftChild = new TreeNode(key);
} else {
p.rightChild = new TreeNode(key);
}
}
/**
* 在树root中删除结点key
*
* @param key
* @return
*/
public void delete(int key) {
root = delete(root, key);
}
private TreeNode delete(TreeNode x, int key) {
/********** Begin *********/
//三种情况
TreeNode cur=x;
TreeNode parent=null;
while(x!=null && x.item!=key){
parent=x;
if(key<x.item){
x=x.leftChild;
}else{
x=x.rightChild;
}
}
//删除节点不存在
if(x==null) return cur;
//第三种情况:将删除节点与其右子树最小节点交换,并删除交换后的最小节点
if(x.leftChild!=null && x.rightChild!=null){
TreeNode mininode=x.rightChild;
TreeNode miniparent=x;
while(mininode.leftChild!=null){
miniparent=mininode;
mininode=mininode.leftChild;
}
//删除交换后的最小节点位置,转化为其他两种情况
x.item=mininode.item;
parent=miniparent;
x=mininode;
}
//最后两种情况,找删除节点的孩子节点
TreeNode child;
if(x.rightChild!=null){
child=x.rightChild;
}else if(x.leftChild!=null){
child=x.leftChild;
}else{
child=null;
}
//删除的是根节点
if(parent==null) return child;
//删除节点
if(parent.leftChild==x){
parent.leftChild=child;
}else if(parent.rightChild==x){
parent.rightChild=child;
}
return cur;
/********** End *********/
}
/**
* 删除树x中的最小结点
*
* @param x
* @return
*/
private TreeNode deleteMin(TreeNode x) {
if (x.leftChild == null) return x.rightChild;
x.leftChild = deleteMin(x.leftChild);
return x;
}
/**
* 查找树x中的最小结点
*
* @param x
* @return
*/
private TreeNode min(TreeNode x) {
TreeNode p = x;
while (p.leftChild != null) {
p = p.leftChild;
}
return p;
}
public void preOrder() {
preOrder(root);
}
private void preOrder(TreeNode node) {
if (node != null) {
System.out.print(node.item + " ");
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
public void inOrder() {
inOrder(root);
}
private void inOrder(TreeNode node) {
if (node != null) {
inOrder(node.leftChild);
System.out.print(node.item + " ");
inOrder(node.rightChild);
}
}
public void postOrder() {
postOrder(root);
}
private void postOrder(TreeNode node) {
if (node != null) {
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.print(node.item + " ");
}
}
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
第3关:二叉搜索树的查找
1.任务描述
本关任务:实现二叉搜索树的查找功能,如果指定的元素x
存在树中则返回true
,否则返回false
。
- 平台将创建用户补全后的
BSTree
类的对象; - 调用对象的
insert(int key)
方法,构建一棵二叉搜索树; - 调用对象的
preOrder()
方法,输出删除操作前树中的结点; - 调用对象的
delete(int key)
方法,删除最先添加的结点和最后添加的结点; - 调用对象的
preOrder()
方法,输出删除操作后树中的结点; - 调用对象的
search(int key)
方法,测试原来的结点哪些还在树中; - 接着根据程序的输出判断程序是否正确。
2.思路分析
根据二叉搜索树的性质,小了向左,大了向右,直到找到
3.本关答案
package step3;
/**
* Created by zengpeng on 2018/3/14.
*/
public class BSTree {
private TreeNode root;//根结点
public BSTree() {
root = null;
}
/**
* 向树root中插入a
*
* @param key 要插入的值
*/
public void insert(int key) {
TreeNode x = root;
TreeNode p = null;//始终指向x的父结点
while (x != null) {
p = x;
if (key < x.item) {
x = x.leftChild;
} else {
x = x.rightChild;
}
}
if (null == p) {//空树
root = new TreeNode(key);
} else if (key < p.item) {
p.leftChild = new TreeNode(key);
} else {
p.rightChild = new TreeNode(key);
}
}
/**
* 判断树root中是否包含key,包含则返回true,不包含返回false
*
* @param key
* @return
*/
public boolean search(int key) {
/********** Begin *********/
TreeNode p = root;
//小了向左,大了向右
while (p != null && key != p.item) {
if (key < p.item) {
p = p.leftChild;
} else {
p = p.rightChild;
}
}
if (p == null) {
return false;
} else {
return true;
}
/********** End *********/
}
/**
* 在树root中删除结点key
*
* @param key
* @return
*/
public void delete(int key) {
root = delete(root, key);
}
private TreeNode delete(TreeNode x, int key) {
if (x == null) {
return null;
}
if (key < x.item) {
x.leftChild = delete(x.leftChild, key);
} else if (key > x.item) {
x.rightChild = delete(x.rightChild, key);
} else {
if (x.leftChild == null) return x.rightChild;
if (x.rightChild == null) return x.leftChild;
TreeNode t = x;
x = min(t.rightChild);
x.rightChild = deleteMin(t.rightChild);
x.leftChild = t.leftChild;
}
return x;
}
/**
* 删除树x中的最小结点
* @param x
* @return
*/
private TreeNode deleteMin(TreeNode x) {
if (x.leftChild == null) return x.rightChild;
x.leftChild = deleteMin(x.leftChild);
return x;
}
/**
* 查找树x中的最小结点
*
* @param x
* @return
*/
private TreeNode min(TreeNode x) {
TreeNode p = x;
while (p.leftChild != null) {
p = p.leftChild;
}
return p;
}
public void preOrder() {
preOrder(root);
}
public void inOrder() {
inOrder(root);
}
public void postOrder() {
postOrder(root);
}
private void preOrder(TreeNode node) {
if (node != null) {
System.out.print(node.item + " ");
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
private void inOrder(TreeNode node) {
if (node != null) {
inOrder(node.leftChild);
System.out.print(node.item + " ");
inOrder(node.rightChild);
}
}
private void postOrder(TreeNode node) {
if (node != null) {
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.print(node.item + " ");
}
}
public static class TreeNode {
private TreeNode leftChild;
private TreeNode rightChild;
private int item;
public TreeNode(int item) {
this(null, null, item);
}
public TreeNode(TreeNode leftChild, TreeNode rightChild, int item) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.item = item;
}
}
}
Java算法与数据结构测试——二叉树
第1关:向二叉树中插入叶子节点
1.任务描述
本关任务:向二叉树中插入左叶子节点,请补全insertLeft(T x, Node<T> parent)
函数实现插入左叶子节点的功能
2.思路分析
我们主要看本关已经给出的insertRight方法,本关要求我们补全插入左子节点的方法,这与insertRight思路逻辑一致,我们只需稍微更改一下孩子的指向即可
//在当前二叉树的parent节点中插入一个新的左子结点,若已存在左子树,则将该左子树变成新左子结点的右子树
public boolean insertLeft(T x, Node<T> parent)
{
/********* Begin *********/
if(parent==null) return false;
Node<T> p=new Node<>(x);
if(parent.lChild==null){
parent.lChild=p;
}else{
p.rChild=parent.lChild;
parent.lChild=p;
}
return true;
/********* End *********/
}
//在当前二叉树的parent节点中插入一个新的右子结点,若已存在右子树,则将该右子树变成新右子结点的左子树
public boolean insertRight(T x, Node<T> parent)
{
if(parent==null) return false;
Node<T> p= new Node<T>(x);
if(parent.rChild==null) parent.rChild = p;
else
{
p.rChild = parent.rChild;
parent.rChild = p;
}
return true;
}