Java数据结构——二叉树
1.二叉树的遍历
前序遍历——根 左 右
中序遍历——左 根 右
后序遍历——左 右 根
2.二叉树的实现
class Stack_BinaryTree{ private int maxSize; //栈的长度 private Node[] stackArray; //创建栈的数组的引用 private int top; //创建栈顶的引用 public Stack_BinaryTree(int s) { //构造函数 this.maxSize = s; stackArray = new Node[maxSize]; //创建对象 top = -1; //栈顶等于-1 } public void push(Node j){ //入栈操作 stackArray[++top] = j; //先把top=-1自加成0,再入栈 } public Node pop(){ return stackArray[top--]; //弹出当前栈顶的元素后,再自减 } public Node peek(){ return stackArray[top]; //返回当前栈顶的元素 } public boolean isEmpty(){ //栈顶为-1,即栈为空 return (top == -1); } public boolean isFull(){ //栈顶为maxSize-1,即栈为满 return (top == maxSize-1); } } class Node{ int iData; double fData; Node leftChild; Node rightChild; public void display(){ System.out.println('{'); System.out.println(iData); System.out.println(','); System.out.println(fData); System.out.println('}'); } } class Tree{ public Node root; public Tree(){ root = null; } public Node find(int key){ //查找关键字的节点 Node current = root; while(current.iData != key){ //不等于就一直循环 if(key<current.iData){ current = current.leftChild; }else{ current = current.rightChild; } if(current == null){ return null; } } return current; } public void insert(int id,double dd){ //插入新的节点 Node newNode = new Node(); newNode.iData = id; newNode.fData = dd; if(root == null){ root = newNode; }else{ Node current = root; Node parent; while(true){ parent = current; if(newNode.iData<current.iData){ //如果插入的节点的数据小于当前节点的数据 current = current.leftChild; if(current == null){ parent.leftChild = newNode; //把父亲节点的左孩子设为新节点 return; } }else{ //如果插入的节点的数据大于当前节点的数据 current = current.rightChild; if(current == null){ parent.rightChild = newNode; //把父亲节点的右孩子设为新节点 return; } } } } } public void displayTree(){ //显示整个二叉树 Stack_BinaryTree globalStack = new Stack_BinaryTree(128); //用来放置每一层的二叉树 globalStack.push(root); //入栈 int nBlanks = 32; boolean isRowEmpty = false; System.out.println("............................................."); while(isRowEmpty==false){ Stack_BinaryTree localStack = new Stack_BinaryTree(128); //用来放置下一层的二叉树 isRowEmpty = true; for(int j=0;j<nBlanks;j++){ System.out.print(' '); } while(globalStack.isEmpty()==false){ //当globalStack不为空,就一直出栈 Node temp = (Node)globalStack.pop(); //temp等于globalStack出栈的节点 if(temp!=null){ System.out.print(temp.iData); //当当前的节点不为空的时候,输出节点的值 localStack.push(temp.leftChild); //入栈globalStack的左孩子到下一层 localStack.push(temp.rightChild); //入栈globalStack的右孩子到下一层 if(temp.leftChild != null || temp.rightChild != null){ isRowEmpty = false; //只要有一个子节点不为空,就把isRowEmpty置为false } } else{ //否则输出--,并把下一层置为空 System.out.print("--"); localStack.push(null); localStack.push(null); } for(int j=0;j<nBlanks*2-2;j++){ System.out.print(' '); } } System.out.println(); nBlanks /= 2; //输出的空格数减半 while(localStack.isEmpty() == false){ globalStack.push(localStack.pop()); //还原本来的层 } } System.out.println("............................................."); } public void preOrder(Node localRoot){ //前序遍历 if(localRoot != null){ System.out.print(localRoot.iData+" "); preOrder(localRoot.leftChild); preOrder(localRoot.rightChild); } } public void inOrder(Node localRoot){ //中序遍历 if(localRoot != null){ inOrder(localRoot.leftChild); System.out.print(localRoot.iData+" "); inOrder(localRoot.rightChild); } } public void postOrder(Node localRoot){ //后序遍历 if(localRoot != null){ postOrder(localRoot.leftChild); postOrder(localRoot.rightChild); System.out.print(localRoot.iData+" "); } } public Node minimum(){ //找到最小的节点 Node current; Node last = null; current = root; while(current != null){ last = current; current = current.leftChild; } return last; } public Node maxmum(){ //找到最大的节点 Node current; Node last = null; current = root; while(current != null){ last = current; current = current.rightChild; } return last; } public boolean delete(int key){ Node current = root; Node parent = root; boolean isLeftChild = true; while(current.iData != key){//查找要删除的节点,并把其置为current,如果没有返回null parent = current; if(key<current.iData){ current = current.leftChild; isLeftChild = true; //当前current节点的parent节点有左孩子节点 }else{ current = current.rightChild; isLeftChild = false; //当前current节点的parent节点没有左孩子节点 } if(current == null){ return false; } } //进入以下的时候,说明current已经匹配到要删除的节点 //如果匹配的current节点没有孩子节点 if(current.leftChild == null && current.rightChild == null){ if(current == root){ //如果这个节点就是根节点 root = null; }else if(isLeftChild){ parent.leftChild = null; //父节点断开左孩子节点 }else{ parent.rightChild = null; //父节点断开右孩子节点 } } //如果current没有右孩子,则要把左子树上移 else if(current.rightChild == null){ if(current == root){ //如果是根节点 root = current.leftChild; }else if(isLeftChild){ //如果current节点是左孩子,则把current的左孩子放到parent的左孩子位置 parent.leftChild = current.leftChild; }else{ //如果current节点是右孩子,则把current的左孩子放到parent的右孩子位置 parent.rightChild = current.leftChild; } } //如果current没有左孩子,则要把右子树上移 else if(current.leftChild == null){ if(current == root){ //如果是根节点 root = current.rightChild; }else if(isLeftChild){ //如果current节点是左孩子,则把current的右孩子放到parent的左孩子位置 parent.leftChild = current.rightChild; }else{ //如果current节点是右孩子,则把current的右孩子放到parent的右孩子位置 parent.rightChild = current.rightChild; } } //如果current有左右孩子 else{ Node successor = getSuccessor(current); if(current == root){ //如果要删除的节点是根节点 root = successor; }else if(isLeftChild){ //如果要删除的节点是左孩子 parent.leftChild = successor; }else{ //如果要删除的节点是右孩子 parent.rightChild = successor; } successor.leftChild = current.leftChild;//把最小的值的节点连接到要删除节点的左子树上 } return true; } private Node getSuccessor(Node delNode){ Node successorParent = delNode; Node successor = delNode; Node current = delNode.rightChild; while(current != null){ //循环,直到返回右子树中最小的值 successorParent = successor; successor = current; current = current.leftChild; } if(successor != delNode.rightChild){ successorParent.leftChild = successor.rightChild; //把最小的值的右孩子放到该最小值的位置 successor.rightChild = delNode.rightChild; //把最小的值连接到要删除的节点的右子树上 } return successor; } } public class BinaryTree { public static void main(String[] args) throws Exception{ // TODO 自动生成的方法存根 int value; Tree theTree = new Tree(); theTree.insert(50, 1.5); theTree.insert(25, 1.4); theTree.insert(75, 1.3); theTree.insert(12, 1.6); theTree.insert(37, 1.7); //theTree.insert(43, 1.2); theTree.insert(60, 1.1); theTree.insert(85, 1.1); theTree.insert(77, 1.1); theTree.displayTree(); System.out.println("查找节点的值:"+theTree.find(77).iData); theTree.preOrder(theTree.root); System.out.println(); theTree.inOrder(theTree.root); System.out.println(); theTree.postOrder(theTree.root); System.out.println(); System.out.println("最小的节点:"+theTree.minimum().iData); System.out.println("最大的节点:"+theTree.maxmum().iData); theTree.delete(75); theTree.displayTree(); } }
3.树、森林和二叉树之间的转换
树转换为二叉树
1. 加线
在所有兄弟结点之间加一条连线。
2. 去线
树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线。
3. 层次调整
以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子)
森林转换为二叉树
1. 把每棵树转换为二叉树。
2. 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
二叉树转换为树
是树转换为二叉树的逆过程。
1. 加线
若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
2. 去线
删除原二叉树中所有结点与其右孩子结点的连线。
3. 层次调整。
二叉树转换为森林
假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
1. 从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
2. 将每棵分离后的二叉树转换为树。
4.平衡二叉树的平衡因子
若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。
失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于 1 的结点作为根的子树。假设用 A 表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。
( 1 ) LL 型平衡旋转法
由于在 A 的左孩子 B 的左子树上插入结点 F ,使 A 的平衡因子由 1 增至 2 而失去平衡。故需进行一次顺时针旋转操作。 即将 A 的左孩子 B 向右上旋转代替 A 作为根结点, A 向右下旋转成为 B 的右子树的根结点。而原来 B 的右子树则变成 A 的左子树。
( 2 ) RR 型平衡旋转法
由于在 A 的右孩子 C 的右子树上插入结点 F ,使 A 的平衡因子由 -1 减至 -2 而失去平衡。故需进行一次逆时针旋转操作。即将 A 的右孩子 C 向左上旋转代替 A 作为根结点, A 向左下旋转成为 C 的左子树的根结点。而原来 C 的左子树则变成 A 的右子树。
( 3 ) LR 型平衡旋转法
由于在 A 的左孩子 B 的右子数上插入结点 F ,使 A 的平衡因子由 1 增至 2 而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将 A 结点的左孩子 B 的右子树的根结点 D 向左上旋转提升到 B 结点的位置,然后再把该 D 结点向右上旋转提升到 A 结点的位置。即先使之成为 LL 型,再按 LL 型处理 。
如图中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到 A 的左子树上,此时成为 LL 型,再按 LL 型处理成平衡型。
( 4 ) RL 型平衡旋转法
由于在 A 的右孩子 C 的左子树上插入结点 F ,使 A 的平衡因子由 -1 减至 -2 而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将 A 结点的右孩子 C 的左子树的根结点 D 向右上旋转提升到 C 结点的位置,然后再把该 D 结点向左上旋转提升到 A 结点的位置。即先使之成为 RR 型,再按 RR 型处理。
如图中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到 A 的左子树上,此时成为 RR 型,再按 RR 型处理成平衡型。
平衡化靠的是旋转。 参与旋转的是 3 个节点(其中一个可能是外部节点 NULL ),旋转就是把这 3 个节点转个位置。注意的是,左旋的时候 p->right 一定不为空,右旋的时候 p->left 一定不为空,这是显而易见的。
如果从空树开始建立,并时刻保持平衡,那么不平衡只会发生在插入删除操作上,而不平衡的标志就是出现 bf == 2 或者 bf == -2 的节点。
5.树的三种存储结构
(转自http://blog.csdn.net/x1247600186/article/details/24670775)
说到存储结构,我们就会想到常用的两种存储方式:顺序存储和链式存储两种。
先来看看顺序存储,用一段地址连续的存储单元依次存储线性表中数据元素,这对于线性表来说是很自然的,但是对于树这种一对多的结构而言是否适合呢?
树中某个结点的孩子可以有多个,这就意味着,无论用哪种顺序将树中所有的结点存储到数组中,结点的存储位置都无法直接反映逻辑关系,试想一下,数据元素挨个存储,那么谁是谁的双亲,谁是谁的孩子呢?所以简单的顺序存储是不能满足树的实现要求的。
不过可以充分利用顺序存储和链式存储结构的特点,完全可以实现对树的存储结构的表示。
下面介绍三种不同的树的表示法:双亲表示法,、孩子表示法,、孩子兄弟表示法。
1、双亲表示法:
我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指向其双亲结点到链表中的位置。也就是说每个结点除了知道自己之外还需要知道它的双亲在哪里。
它的结构特点是如图所示:
以下是我们的双亲表示法的结构定义代码:
/*树的双亲表示法结点结构定义 */
#define MAXSIZE 100
typedef int ElemType; //树结点的数据类型,暂定为整形
typedef struct PTNode //结点结构
{
ElemType data; //结点数据
int parent; //双亲位置
}PTNode;
typedef struct
{
PTNode nodes[MAXSIZE]; //结点数组
int r,n; //根的位置和结点数
}PTree;
2、孩子表示法
换一种不同的考虑方法。由于每个结点可能有多棵子树,可以考虑使用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法。不过树的每个结点的度,也就是它的孩子个数是不同的。所以可以设计两种方案来解决。
方案一:
一种是指针域的个数就等于树的度(树的度是树的各个结点度的最大值)
其结构如图所示:
不过这种结构由于每个结点的孩子数目不同,当差异较大时,很多结点的指针域就都为空,显然是浪费空间的,不过若树的各结点度相差很小时,那就意味着开辟的空间都被利用了,这时这种缺点反而变成了优点。
方案二:
第二种方案是每个结点指针域的个数等于该结点的度,我们专门取一个位置来存储结点指针域的个数。
其结构如图所示:
这种方法克服了浪费空间的缺点,对空间的利用率是很高了,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。
能否有更好的方法呢,既可以减少空指针的浪费,又能是结点结构相同。
说到这大家肯定就知道是有的麦,那就是孩子表示法。
具体办法是,把每个结点的孩子排列起来,以单链表做存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针有组成一个线性表,采用顺序存储结构,存放进入一个一维数组中。
为此,设计两种结点结构,
一个是孩子链表的孩子结点,如下所示:
其中child是数据域,用来存储某个结点在表头数组中的下标。next是指针域,用来存储指向某结点的下一个孩子结点的指针。
另一个是表头结点,如下所示:
其中data是数据域,存储某结点的数据信息。firstchild是头指针域,存储该结点的孩子链表的头指针。
以下是孩子表示法的结构定义代码:
/*树的孩子表示法结点结构定义 */
#define MAXSIZE 100
typedef int ElemType; //树结点的数据类型,暂定为整形
typedef struct CTNode //孩子结点
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct //表头结构
{
ElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAXSIZE]; //结点数组
int r,n; //根结点的位置和结点数
}CTree;
3、孩子兄弟表示法
我们发现,任意一颗树,它的结点的第一个孩子如果存在就是的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
其结点结构如图所示:
以下是孩子兄弟表示法的结构定义代码:
/*树的孩子兄弟表示法结构定义 */
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;
本文只发表于博客园和tonglin0325的博客,作者:tonglin0325,转载请注明原文链接:https://www.cnblogs.com/tonglin0325/p/5379021.html