1.树的结点数 = 结点度数和 + 1;(n0 + n1 + nm ….+1)
2.度为m的数第i层最多m^(i-1)次方个结点
3.高度为h的m次树,最多(m^h)/(m-1)
4.n个结点的m次树,最小高度logm(n(m-1)+1)
结点定义,根节点,左孩子,右孩子
java代码:
/**
* 结点类
*
* @author Administrator
*
* @param
*/
static class BTNode {
T value;
BTNode leftChild;
BTNode rightChild;
/**
* 结点构造方法
*
* @param value
*/
public BTNode(T value) {
this.value = value;
leftChild = null;
rightChild = null;
}
public BTNode() {
value = null;
leftChild = rightChild = null;
}
}
二叉树类:
public class BinaryTree<T> {
/**
* 结点类
*
* @author Administrator
*
* @param
*/
static class BTNode<T> {
T value;
BTNode leftChild;
BTNode rightChild;
/**
* 结点构造方法
*
* @param value
*/
public BTNode(T value) {
this.value = value;
leftChild = null;
rightChild = null;
}
public BTNode() {
value = null;
leftChild = rightChild = null;
}
}
/**
* 根节点
*/
BTNode root;
/**
* 构造方法
*/
public BinaryTree() {
root = new BTNode(null);
}
1.叶子节点数= 双分支结点+1,分支数 = 总结点数-1;
2.编号为i (i>=0,i<=n)的结点,左孩子为2i + 1,右孩子为2i+2,一般使用数组存储时使用。如果起始i ==1,则左孩子 2i,右孩子2i+1
优点:结构紧凑,没有额外的指针空间,随机存取能力强,
缺点:插入删除耗时,所需空间较大时,较难找到连续的内存空间,不利于碎片空间利用。
优点:碎片利用率高,不产生空结点,
缺点:不能随机存取,必须从头遍历
括号表示法:兄弟结点用“,”分割,父节点后()的内表示其子节点
Java实现
/**
* 使用括号表示法创建二叉树
* @param kuohao
* @return
*/
public BTNode createBTree(String kuohao){
BTNode [] stack= (BTNode[]) new Object[maxSize];
int top =-1;
//是否是左子结点
boolean isLeft = true;
BTNode tempRoot = null ;
for (int i = 1; i < kuohao.length(); i++) {
switch (kuohao.charAt(i)) {
case '(':
isLeft = true;
stack[++top] = tempRoot;
break;
case ')':
top--;
break;
case ',':
isLeft = false;
break;
default:
BTNode tempNode = new BTNode<>(kuohao.charAt(i));
if(tempRoot == null){
tempRoot = tempNode;
}else{
if(isLeft){
stack[top].leftChild = tempNode;
}else{
stack[top].rightChild = tempNode;
}
}
break;
}
}
return tempRoot;
}
所谓遍历就是找到结点并对其进行一定的操作
二叉树是递归结构,遍历很显然也是递归的,遍历顺序:根节点–》左子结点–》右子节点
/**
* 先序遍历
*/
public void preOrder(BTNode root) {
// 对根节点进操作
System.out.println(root.value.toString());
preOrder(root.leftChild);
preOrder(root.rightChild);
}
遍历顺序:左子结点–》根节点–》右子节点
/**
* 中序遍历
*
* @param root
*/
public void inOrder(BTNode root) {
inOrder(root.leftChild);
System.out.println(root.value.toString());
inOrder(root.rightChild);
}
遍历顺序:左子节点–》右子节点–》根节点
/**
* 后序遍历
*
* @param root
*/
public void postOrder(BTNode root) {
postOrder(root.leftChild);
postOrder(root.rightChild);
System.out.println(root.value.toString());
}
非递归遍历使用栈保存需要遍历的结点,出栈表示遍历,因为栈的后入先出,适合回溯。
栈需要维护栈顶指针top,初始为top=-1,入栈top++,出栈top–;
先序遍历,根节点进栈,栈不为空时循环出栈,根节点出战,将根节点孩子进栈,注意先将右孩子进栈,再将左孩子进栈,
java 实现:
/**
* 非递归先序遍历
* @param root
*/
public void preOrderNoRecur(BTNode root){
if(root == null){
return;
}
int top =-1;
BTNode [] stack= (BTNode[]) new Object[maxSize];
top ++;
stack[top] = root;//根节点入栈
while(top>-1){
BTNode current = stack[top];//出栈
top --;
System.out.println(current.value.toString());//访问根节点,下次循环访问左子结点(如果存在)
if(current.rightChild !=null){
stack[top] = current.rightChild;//右子结点入栈
top ++;
}
if(current.leftChild != null){//右子节点入栈
stack[top] = current.leftChild;
top++;
}
}
}
中序遍历,开始访问的结点是根节点的最左子节点,在该节点之前的节点都进栈,出栈最左子节点访问之,将其右子节点入栈,重复上面的步骤。直到节点为null 或者栈空。两重循环,外循环判是否栈空结束,内循环入栈左子树。
java实现:
/**
* 非递归中序遍历
*
* @param root
*/
public void inOrderNoRecur(BTNode root) {
if (root == null) {
return;
}
int top = -1;
BTNode[] stack = (BTNode[]) new Object[maxSize];
//
top++;
stack[top] = root;
BTNode current = root;// 根节点入栈
while (top > -1 || current != null) {// ||
while (current.leftChild != null) {
stack[top] = current.leftChild;
top++;
}
// current 为根的左子结点都入栈了
if (top > -1) {
current = stack[top];
top--;
//
System.out.println(current.value.toString());
// stack[top] = current.rightChild;//入栈不是在这,在前面的循环
// top++;
current = current.rightChild;// 这里处理右子节点,下一步将右子树的左子树入栈
}
}
}
难点在于如何判断一个节点的右子树已经访问过了,需要一个标志刚刚访问过的节点 previous,如果current->rightChild == previous 那么说明current的右子树已经访问过,可以访问current。还要一个标志左子树是否全访问过。
后序遍历是先访问左子树,在访问右子树,最后访问根节点。先将左子树全部入栈,再判断栈顶的右子树是否访问过(即判断刚才访问的是否是栈顶的右子节点),是则访问栈顶出栈,否则重复上面步骤访问右子树的左子树,直到栈空。
三次循环。外部循环判断是否栈为空结束,内部1将左子树并入栈,内部2判断栈顶是否可以访问,可以则出栈,不可以则将右子树的左子树入栈。
栈中保存的是当前节点current的所有祖先节点
java实现:
/**
* 非递归后序遍历
*/
public void postOrderNoRecur(BTNode root) {
if (root == null) {
return;
}
int top = -1;
BTNode[] stack = (BTNode[]) new Object[maxSize];
stack[++top] = root;
while (top > -1) {
BTNode current = stack[top];
while (current != null) {
top++;
stack[top] = current;
current = current.leftChild;
}
// 左子结点都入栈了,开始判断栈顶是否能访问,
boolean leftCompeleted = true;
// 上一次访问的节点
BTNode previous = null;
while (leftCompeleted && top > -1) {
BTNode stackTop = stack[top];
if (stackTop.rightChild == previous) {// previous 说明没有右子节点
top--;
previous = stackTop;// !! 将stacktop设为之前访问过的节点。
System.out.println(stackTop.value.toString());
} else {
current = current.rightChild;
leftCompeleted = false;
}
}
}
}
层次遍历也叫广度优先遍历,从根节点开始,第一层遍历完遍历第二层,借助队列入队保存每层遍历过的结点,每次出队遍历其子节点,同时将子节点入队。队列先进先出,适合按层遍历。
java实现
/**
* 使用数组实现队列管理结点访问顺序
*
* @param root
*/
@SuppressWarnings("unchecked")
public void layOrder(BTNode root) {
if (root == null) {
return;
}
int max = 50;
int front = 0, rear = 0;
int count = 0;
//创建object数组转为泛型数组
BTNode[] queue = (BTNode[]) new Object[max];
// 入队
queue[rear] = root;
rear = (rear + 1) % max;
count++;
while (count > 0) {
//出队
BTNode out = queue[front];
front = (front + 1) % max;
count --;
//遍历出队结点的左右结点
//入队
if(out.leftChild != null){
queue[rear] = out.leftChild;
rear = (rear+1)%max;
count ++;
}
if(out.rightChild != null){
queue[rear] = out.rightChild;
rear = (rear+1)%max;
count ++;
}
}
}
利用层次遍历法,在队列出队时,会遍历其左右子节点,此时比较待查找的结点和左右子节点是否相等,相等则返回刚刚出队的父节点。
特殊情况,待查结点是根节点,结点为null,结点不在树上
Java实现
public BTNode findParentNode(BTNode child){
if (child == null || child == root) {
return null;
}
int max = 50;
int front = 0, rear = 0;
int count = 0;
//创建object数组转为泛型数组
BTNode[] queue = (BTNode[]) new Object[max];
// 入队
queue[rear] = root;
rear = (rear + 1) % max;
count++;
BTNode out;
while (count > 0) {
//出队
out = queue[front];
front = (front + 1) % max;
count --;
//遍历出队结点的左右结点
//入队
if(out.leftChild != null){
//判断是否是待查的子节点,是则返回刚刚出列的节点,为待求的父节点
if(out.leftChild.value == child.value){
return out;
}
queue[rear] = out.leftChild;
rear = (rear+1)%max;
count ++;
}
if(out.rightChild != null){
if(out.rightChild.value == child.value){
return out;
}
queue[rear] = out.rightChild;
rear = (rear+1)%max;
count ++;
}
}
//所有遍历完了没找到,则返回null,不在树上
return null;
}
由上面的求父节点的方法,求得父节点后,再求该节点的兄弟结点即可
public BTNode getBrotherNode(BTNode child) {
BTNode parent = findParentNode(child);
// 没有父节点
if (parent == null || child == null) {
return null;
}
if (parent.rightChild == child) {// 返回做兄弟
return parent.leftChild;
} else if (parent.leftChild == child) {
return parent.rightChild;
} else {
return null;
}
}
求树高度就是后序遍历树,取左子树和右子树较大者+1。
递归实现:
/**
* 求树的高度,递归后序遍历
* @param root
* @return
*/
public int getTreeHeight(BTNode root) {
if (root == null) {
return 0;
}
int leftHeight = getTreeHeight(root.leftChild);
int rightHeight = getTreeHeight(root.rightChild);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
非递归实现主要是非递归后序遍历的修改。
判断是否是叶子节点,是则更新最大高度。
/**
* 求任意节点的高度
*
* @param root
* @param node
* @param h,初始值为1,递归了多少次
* @return
*/
public int nodeHeight(BTNode root, T node, int h) {
if (node == null) {
return 0;
}
if (root == null) {
return 0;
}
if (root.value == node) {
return h;
}
int leftHeight = nodeHeight(root.leftChild, node, h + 1);
if (leftHeight == 0) {
return nodeHeight(root.rightChild, node, h + 1);
}
return leftHeight;
}
找到待插入的节点,将其左子树改为要插入的二叉树。
/**
* 插入子树
*
* @param subTree
* @param node
*/
public void insertSubTree(BinaryTree subTree, T node) {
if (node == null) {
return;
}
if (subTree.root == null) {
return;
}
BTNode found = findNode(root, node);
if (found.leftChild == null) {
found.leftChild = subTree.root;
}
}
递归查找,先序遍历
/**
* 查找节点
*
* @param root
* @param node
* @return
*/
private BTNode findNode(BTNode root, T node) {
if (root == null) {
return null;
}
if (root.value == node) {
return root;
}
BTNode found = null;
found = findNode(root.leftChild, node);
if (found != null)
return found;
return findNode(root.rightChild, node);
}
删除子树即删除某一节点的左子树或者右子树的所有节点。
递归删除节点,后序遍历树,先删除该节点的左子树,再删右子树,最后删根节点
javaj实现
/**
* 删除所有节点
*
* @param node
*/
public void deleteNodes(BTNode node) {
if (node == null) {
return;
}
if (root == null) {
return;
}
deleteNodes(root.leftChild);
deleteNodes(root.rightChild);
node = null;// 置为null
}
/**
* 删除左子树
* @param node
*/
public void deleteSubTree(BTNode node) {
if (node == null)
return;
deleteNodes(node.leftChild);
}
有先序遍历和中序遍历可以重建树,同理由后序遍历和中序遍历实现方式一样,都是先在中序序列上找到根节点,在重建左右子树。
/**
* 有前序和中序序列,重建二叉树
* @param preStr
* @param inStr
* @param nodesNum
* @param preStart
* @param inStart
* @return
*/
public BTNode rebuildBinaryTree(T[] preStr, T[] inStr, int nodesNum, int preStart, int inStart) {
if (preStr == null || inStr == null || nodesNum <= 0) {
return null;
}
BTNode tempRoot = new BTNode<>();
tempRoot.value = preStr[preStart];// 先序根节点为第一个
int i;
// 在中序中找到根节点
for (i = inStart; i < nodesNum; i++) {
if (inStr[i] == preStr[preStart]) {
break;
}
}
// 递归创建左右子树
//调整先序和中序开始构建的下标
tempRoot.leftChild = rebuildBinaryTree(preStr, inStr, i, preStart + 1, inStart);
tempRoot.rightChild = rebuildBinaryTree(preStr, inStr, nodesNum - i - 1, preStart + i + 1, i + 1);
return tempRoot;
若节点的左右子节点为空,可以利用节点的空指针指向,该节点的直接前去和直接后继。
得到的线索化二叉树,根据不同的遍历方式不一样,