概述:
我个人是这么看树这种数据结构的:
- 它跟链表有一点差不多,都是由结点组成,类似于这样,链表是:list ->node,树也是tree ->node,都是由一个个小结点拼成的
- 当然肯定也有不一样,链表是一个接一个,树是一个后面可以跟多个,但只有一个根。
- 有关树的编程题有很多都是二叉树的,可能重点在二叉树。
基本二叉树
结点
属性
data 结点里面存储的东西
left 指向这个结点的左子结点
right 指向这个结点的右子结点
class Node{
? data;
Node left;
Node right;
}
后续线索化的时候还会再加一些属性
方法
各个属性的set/get方法,toString展示data就行,构造方法
前序遍历
中序遍历
后序遍历
根据数据查询某个结点(有前中后序三种)
删除结点
//前序遍历
public void preOrder(){
System.out.println(this);//先输出父节点
//递归向左子树
if (this.left!=null){
this.left.preOrder();
}
//递归向右子树
if (this.right!=null){
this.right.preOrder();
}
}
//中序遍历
public void infixOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.infixOrder();
}
//输出当前节点
System.out.println(this);
//递归向右子树中序遍历
if (this.right != null){
this.right.infixOrder();
}
}
//后序遍历
public void postOrder(){
if (this.left!=null){
this.left.postOrder();
}
if (this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
/**
* 前序遍历查询
* @param no 要查找的no
* @return 如果找到就返回该node,没有找到就返回null
*/
public Node preOrderSearch(int no){
System.out.println("前序遍历查找");
//比较当前节点是不是
if (this.no == no){
return this;
}
Node resNode = null;
if (this.left!=null){
resNode = this.left.preOrderSearch(no);//向左递归
}
if (resNode != null){//说明左子树上找到了
return resNode;
}
//如果在左边找到了,就返回了
//目前还没有找到,就看右边是不是空,不是空就往右递归
if (this.right!=null){
resNode = this.right.preOrderSearch(no);//向右递归
}
return resNode;
}
**
* 递归删除节点
* 如果是叶子结点,就删除该结点,如果是非叶子结点就删除该子树
*/
public void delNode(int no){
if (this.left!=null && this.left.no == no){
this.left = null;
return;
}
if (this.right!=null && this.right.no == no){
this.right = null;
return;
}
if (this.left != null){
this.left.delNode(no);
}
//如果这里还没删除掉,进入右子树
if (this.right!=null){
this.right.delNode(no);
}
}
树
属性
像链表规定头节点那样规定根结点就好了,因为结点有指向下一个结点的属性,所以规定根节点,就是规定了树。
class Tree{
private Node root;
}
方法
遍历方法
查找方法
删除方法
以上都是在结点遍历方法的基础上实现,看一眼代码就知道啥意思了
public void preOrder(){
if (this.root !=null){
this.root.preOrder();
}else {
System.out.println("二叉树为空,无法遍历");
}
}
public Node preOrderSearch(int no){
if (this.root != null){
return root.preOrderSearch(no);
}else {
return null;
}
}
public void delNode(int no){
if (root != null){
//判断root是不是要删除的节点
if (root.getNo() == no){
root = null;
}else{
//开始递归删除
root.delNode(no);
}
}else {
System.out.println("这是一个空树");
}
}
一道小算法题
根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字
int[] preorder = {3,10,9,20,15,7};
int[] inorder = {9,10,3,15,20,7};
根据他们重建出二叉树。
我感觉这道题在纸上画很容易就能画出重构出来的二叉树是什么样子,但要用代码实现我们在纸上推演的步骤,找到一个规律应用递归,感觉还是挺难的。我看着答案(伪代码)愣是写了四十分钟
以下是我更改后的解题步骤,参考下就行,毕竟咱水平也就这样。
首先要先把结点和树构建出来,他数组里面放的是int,结点的data数据类型就写int。
写好树了之后在纸上推演一下,步骤大概是这样
- 先序遍历的第一个肯定是根节点,记住这个根节点
- 在中序遍历数组中找到它,它左面的是这个树的左子树,右面的是这个树的右子树。
- 我们看他左子树有n个结点{9,10},2个
- 我们在前序结点里3往后跳过这两个元素,找到子树的根节点20
- 进行步骤2
怎么说呢,我感觉我还是没有把它描述清楚,看代码实现吧
总的来说就是在每次遍历里规定每个树的范围以及根节点,当他还有元素时就继续递归。直到preL>preR
public class RebuildTree {
public static void main(String[] args) {
int[] preorder = {3,10,9,20,15,7};
int[] inorder = {9,10,3,15,20,7};
Tree tree = reConstructBinaryTree(preorder, inorder);
tree.preOrder();
}
private static Map<Integer, Integer> indexForInOrders = new HashMap<>();
public static Tree reConstructBinaryTree(int[] pre, int[] in) {
for (int i = 0; i < in.length; i++) {
indexForInOrders.put(in[i], i);
}
Node root = reConstructBinaryTree(pre, 0, pre.length - 1, 0);
Tree tree = new Tree(root);
return tree;
}
/**
*
* @param pre 前序遍历的数组
* @param preL 前序遍历的左
* @param preR 前序遍历的右
* @param inL 每个树 根节点 在前序遍历中的位置,第一次0->3,第二次3->20,要通过他来计算左子树的结点数
* @return 返回这一段前序遍历范围内生成的子树,由preL,preR规定范围
*/
private static Node reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
if (preL > preR) {
return null;
}
Node root = new Node(pre[preL]);//3
int inIndex = indexForInOrders.get(root.getNo());//9->0 10 ->1 3->2
int leftTreeSize = inIndex - inL;//2
root.setLeft(reConstructBinaryTree(pre,preL + 1,preL + leftTreeSize,inL));
root.setRight(reConstructBinaryTree(pre,preL+leftTreeSize+1,preR,inL+leftTreeSize+1));
Tree tree = new Tree(root);
return root;
}
}
线索化二叉树
概述
我们正常写二叉树地下总会有叶子点嘛,他们会有一些空指针域,比如他没有左孩子,Node left就是空的,很浪费空间,又没啥实际用途,所以我们可以用这些空指针域来指向自己的前后结点
呃,其实我是怕面试问道才看这个,我真不知道哪里会用到,能干吗,它并不会把这些浪费的空间节约下来啊,可能就是会稍稍提高一点遍历的速度。
不感兴趣的直接跳过就好,我就是在这记一下笔记。。
代码
class Node {
private int no;
//private String name;
private Node left;
private Node right;
//如果leftType == 0表示指向的是左子树,为1这表示指向前驱结点
private int leftType;
private int rightType;
}
Tree中属性不变,写线索化的方法:
public void threadedNodes(Node node){
if (node == null){
return;
}
threadedNodes(node.getLeft());
//线索化当前结点
if (node.getLeft() == null){
node.setLeft(pre);
node.setLeftType(1);
}
if (pre!=null && pre.getRight() == null){
pre.setRight(node);
pre.setRightType(1);
}
pre = node;
threadedNodes(node.getRight());
}
//线索化遍历二叉树
public void threadedList(){
Node node = root;
while (node!=null){
while (node.getLeftType()==0){
node = node.getLeft();
}
System.out.println(node.toString());
while (node.getRightType() == 1){
node = node.getRight();
System.out.println(node.toString());
}
node = node.getRight();
}
}
后面还要写一些:赫夫曼树,二叉排序树,平衡二叉树,都挺重要,但这篇博客已经挺长的了,太长的话我自己复习就懒得看了。有兴趣可以进我博客翻翻。