您的位置:首页 > 理论基础 > 数据结构算法

数据结构总结-- 树和二叉树

2016-09-03 20:25 225 查看

树的性质

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 <T>
*/
static class BTNode<T> {
T value;
BTNode<T> leftChild;
BTNode<T> 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 <T> */ static class BTNode<T> { T value; BTNode<T> leftChild; BTNode<T> rightChild; /** * 结点构造方法 * * @param value */ public BTNode(T value) { this.value = value; leftChild = null; rightChild = null; } public BTNode() { value = null; leftChild = rightChild = null; } }

/**
* 根节点
*/
BTNode<T> root;

/**
* 构造方法
*/
public BinaryTree() {
root = new BTNode<T>(null);
}


二叉树的性质

1.叶子节点数= 双分支结点+1,分支数 = 总结点数-1;

2.编号为i (i>=0,i<=n)的结点,左孩子为2i + 1,右孩子为2i+2,一般使用数组存储时使用。如果起始i ==1,则左孩子 2i,右孩子2i+1

二叉树的存储结构

1.顺序存储

优点:结构紧凑,没有额外的指针空间,随机存取能力强,

缺点:插入删除耗时,所需空间较大时,较难找到连续的内存空间,不利于碎片空间利用。

2.链式存储结构

优点:碎片利用率高,不产生空结点,

缺点:不能随机存取,必须从头遍历

二叉树的基本操作

由括号表示法创建二叉树

括号表示法:兄弟结点用“,”分割,父节点后()的内表示其子节点

Java实现

/**
* 使用括号表示法创建二叉树
* @param kuohao
* @return
*/
public BTNode<Character> createBTree(String kuohao){

BTNode<Character> [] stack= (BTNode<Character>[]) new Object[maxSize];
int top =-1;
//是否是左子结点
boolean isLeft = true;

BTNode<Character> 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<Character> 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<T> root) {
// 对根节点进操作
System.out.println(root.value.toString());
preOrder(root.leftChild);
preOrder(root.rightChild);
}


中序遍历

遍历顺序:左子结点–》根节点–》右子节点

/**
* 中序遍历
*
* @param root
*/
public void inOrder(BTNode<T> root) {
inOrder(root.leftChild);
System.out.println(root.value.toString());
inOrder(root.rightChild);
}


后序遍历

遍历顺序:左子节点–》右子节点–》根节点

/**
* 后序遍历
*
* @param root
*/
public void postOrder(BTNode<T> 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<T> root){
if(root == null){
return;
}
int top =-1;
BTNode<T> [] stack= (BTNode<T>[]) new Object[maxSize];
top ++;
stack[top] = root;//根节点入栈

while(top>-1){
BTNode<T> 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<T> root) {
if (root == null) {
return;
}
int top = -1;
BTNode<T>[] stack = (BTNode<T>[]) new Object[maxSize];
//
top++;
stack[top] = root;
BTNode<T> 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<T> root) {
if (root == null) {
return;
}
int top = -1;
BTNode<T>[] stack = (BTNode<T>[]) new Object[maxSize];
stack[++top] = root;
while (top > -1) {
BTNode<T> current = stack[top];
while (current != null) {
top++;
stack[top] = current;
current = current.leftChild;
}
// 左子结点都入栈了,开始判断栈顶是否能访问,
boolean leftCompeleted = true;
// 上一次访问的节点
BTNode<T> previous = null;
while (leftCompeleted && top > -1) {
BTNode<T> 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<T> root) {
if (root == null) {
return;
}
int max = 50;
int front = 0, rear = 0;
int count = 0;

//创建object数组转为泛型数组
BTNode<T>[] queue = (BTNode<T>[]) new Object[max];

// 入队
queue[rear] = root;
rear = (rear + 1) % max;
count++;

while (count > 0) {
//出队
BTNode<T> 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<T> findParentNode(BTNode<T> child){
if (child == null || child == root) {
return null;
}
int max = 50;
int front = 0, rear = 0;
int count = 0;

//创建object数组转为泛型数组
BTNode<T>[] queue = (BTNode<T>[]) new Object[max];

// 入队
queue[rear] = root;
rear = (rear + 1) % max;
count++;

BTNode<T> 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<T> getBrotherNode(BTNode<T> child) {

BTNode<T> 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<T> 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<T> 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<T> subTree, T node) {
if (node == null) {
return;
}
if (subTree.root == null) {
return;
}
BTNode<T> found = findNode(root, node);
if (found.leftChild == null) {
found.leftChild = subTree.root;
}
}


查找节点

递归查找,先序遍历

/**
* 查找节点
*
* @param root
* @param node
* @return
*/
private BTNode<T> findNode(BTNode<T> root, T node) {
if (root == null) {
return null;
}
if (root.value == node) {
return root;
}
BTNode<T> found = null;
found = findNode(root.leftChild, node);
if (found != null)
return found;

return findNode(root.rightChild, node);
}


删除子树

删除子树即删除某一节点的左子树或者右子树的所有节点。

递归删除节点,后序遍历树,先删除该节点的左子树,再删右子树,最后删根节点

javaj实现

/**
* 删除所有节点
*
* @param node
*/
public void deleteNodes(BTNode<T> node) {
if (node == null) {
return;
}
if (root == null) {
return;
}
deleteNodes(root.leftChild);
deleteNodes(root.rightChild);
node = null;// 置为null
}

/**
* 删除左子树
* @param node
*/
public void deleteSubTree(BTNode<T> node) {
if (node == null)
return;
deleteNodes(node.leftChild);
}


重建树

有先序遍历和中序遍历可以重建树,同理由后序遍历和中序遍历实现方式一样,都是先在中序序列上找到根节点,在重建左右子树。

/**
* 有前序和中序序列,重建二叉树
* @param preStr
* @param inStr
* @param nodesNum
* @param preStart
* @param inStart
* @return
*/
public BTNode<T> rebuildBinaryTree(T[] preStr, T[] inStr, int nodesNum, int preStart, int inStart) {
if (preStr == null || inStr == null || nodesNum <= 0) {
return null;
}
BTNode<T> 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;


线索化二叉树

若节点的左右子节点为空,可以利用节点的空指针指向,该节点的直接前去和直接后继。

得到的线索化二叉树,根据不同的遍历方式不一样,
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息