首先我们来看看什么是树:
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具有以下的特点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父节点;除了根结点外,每个子结
点可以分为多个不相交的子树
二叉树:
一棵二叉树是节点的有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
特点:
每个节点最多有两棵子树,即二叉树不存在度大于2的节点
二叉树的子树有左右之分,其子树的次序不能颠倒。
满二叉树:一个 二叉树,如果每一个层的节点数都达到最大值,则这个二叉树就是满二叉树,即:如果一个二叉树的层数为k,且节点总数是2^k-1,则它为满二叉树。
如图所示:
完全二叉树:对于深度为k的,有n个节点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
如图所示:
二叉树的顺序结构:
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储
注:此堆非彼堆,它 并非为操作系统虚拟进程地址空间中的堆,它是一种数据结构:
大根堆和小根堆以前探讨过,在此就不做过多的赘述。
堆的 性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
堆的一些操作
(1)堆的向下调整:
从每棵子树的根节点开始调整
代码实现:
public void AdjustDown(int root, int len) {
int parent=root;
int child=2*parent+1; //左孩子
while(child<len){
//判断是否有右孩子 找到最大值下标
if(child+1<len&&this.elem[child]<this.elem[child+1]){
child++;
}
if(elem[child]>elem[parent]){
int temp=elem[child];
elem[child]=elem[parent];
elem[parent]=temp;
parent=child;
child=2*parent+1;
}
else{
break;
}
}
}
(2)堆的初始化建立
将其初始化建立为大根堆
代码实现:
public void initHeap(int[] array) { //初始化成大根堆
for (int i = 0; i < array.length; i++) {
this.elem[i] = array[i];
this.usedSize++; //先放入数据
}
for (int i = (elem.length - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(i,this.usedSize);
}
}
(3)堆的向上调整
从孩子节点开始调整
调整到child等于根节点时才能结束:
代码实现:
public void AdjustUp(int child, int len) { //child等于根节点才能结束
int parent=(child-1)/2;
while(child>0){
if(elem[child]>elem[parent]){
int temp=elem[child];
elem[child]=elem[parent];
elem[parent]=temp;
child=parent;
parent=(child-1)/2;
}else{
break;
}
}
}
(3)堆的插入
代码实现:
private boolean isFull(){
return this.usedSize==this.elem.length;
}
@Override
public void pushHeap(int item) {
if(isFull()){
this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
}
this.elem[this.usedSize]=item;
this.usedSize++;
AdjustUp(this.usedSize-1,this.usedSize); //调整的下标,调整的长度
}
(4)堆的删除
删除堆是删除堆顶的数据,将堆顶的数据和最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
代码实现:
public int popHeap() {
if(this.usedSize==0){
throw new NullPointerException("堆为空");
}
int temp=elem[0];
elem[0]=elem[this.usedSize-1];
elem[this.usedSize-1]=temp;
usedSize--;
AdjustDown(0,usedSize);
return 0;
}
(5)堆排序
代码实现:
public void HeapSort() {
int end=this.usedSize-1;
while(end>0){
int temp=this.elem[0];
this.elem[0]=this.elem[end];
this.elem[end]=temp;
AdjustDown(0,end);
end--;
}
}
二叉树的链式存储:
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
二叉树链式结构的遍历:
所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。
当节点的左孩子和右孩子节点都为空时返回。
(1)先序遍历(NLR):
先遍历根节点,再遍历左子树,然后遍历右子树。
先序遍历的递归实现:
先定义一个类:
class TreeNode{
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value){
this.value=value;
}
}
先序遍历的递归实现:
public static void preOrderRe(TreeNode Btree){ //递归
if(Btree==null){
return;
}
System.out.print(Btree.value+" "); //先根
TreeNode leftTree=Btree.left;
TreeNode rightTree=Btree.right;
//再左
preOrderRe(leftTree);
preOrderRe(rightTree);
}
先序遍历的非递归实现:
一想到非递归,我们就会引入栈。把遍历到的节点压入栈,当没有子节点时再出栈
代码实现:
public static void preOrderFi(TreeNode root){
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
System.out.print(cur.value+" ");
stack.push(cur);
cur=cur.left;
}
if(!stack.isEmpty()){
cur=stack.pop();
cur=cur.right;
}
}
}
(2)中序遍历(LNR):
先遍历左子树,再遍历根节点,然后遍历右子树。
中序遍历的递归实现:
public static void midOrder(TreeNode BTree){
if(BTree==null){
return;
}
midOrder(BTree.left); //左--根--右
System.out.print(BTree.value+" ");
midOrder(BTree.right);
}
中序遍历的非递归实现:
public static void midOrder1(TreeNode root){
TreeNode cur=root;
TreeNode top=null;
Stack<TreeNode> stack=new Stack<>();
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur); //将左节点入栈
cur=cur.left;
}
if(!stack.isEmpty()){
top=stack.pop();
System.out.print(top.value+" "); //在出栈的时候打印
cur=top.right;
}
}
}
(3)后序遍历(LRN):
先遍历左子树,再遍历右子树,然后遍历根节点。
后序遍历的递归实现:
public static void LastOrder(TreeNode BTree){
if(BTree==null){
return;
}else{
LastOrder(BTree.left); //左--右--根
LastOrder(BTree.right);
System.out.print(BTree.value+" ");
}
}
后序遍历的非递归实现:
public static void LastOrder2(TreeNode root) {
TreeNode cur = root;
TreeNode top = null;
TreeNode pre=null;
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur); //将左节点入栈
cur = cur.left;
}
cur = stack.peek();
if (cur.right == null||cur.right==pre) {
System.out.print(cur.value + " ");
stack.pop();
pre=cur; //代表cur已打印
cur=null;
}
else{
cur=cur.right;
}
}
}
注:在用栈实现后序遍历时,一定要引入一个pre标记一下此时的cur是已经打印了的,否则导致一直是同一个节点出不去。
综上所述,建议写递归方式,比较简便。
未完待续…