文章目录
树
- 树:元素与元素之间存在一对多的关系、层次关系
节点分类
节点拥有的子树数称为节点的度。度为0的节点称为叶子结点或终端节点;度部位0的节点称为非终端节点或分支节点。根节点除外,分支节点也称为内部节点。树的度是树内各节点的度的最大值。
树中节点的最大层次称为树的深度或高度
-
如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树
线性表与树的结构
孩子表示法
每个结点有多个指针域,其中每个指针指向一颗子树的根节点,我们把这种方法叫做多重链表表示法
- 方案一
指针域的个数就等于树的度 - 方案二
专门用一个位置来存储结点指针域的个数
孩子表示法:把每个节点的孩子结点排列起来,以单链表作存储结构,则n个节点有n个孩子链表,如果是叶子结点则此单链表为null,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组
二叉树
- 二叉树:就是一种特殊的树结构
二叉树特点
- 每个结点最多有两颗子树
- 左右子树是有顺序的,次序不能颠倒
- 即使只有一颗子树,也要区分左右子树
二叉树性质
性质一:在二叉树的第i层上至多有2i-1次方个节点(i>=1)
性质二:深度为k的二叉树至多有2k-1个结点(k>=1)
性质三:对任何一颗二叉树T,如果其终端节点数为n0度为2的结点数为n2,则n0=n2+1
性质四:具有n个结点的完全二叉树的深度为不大于log2n+1的整数
性质五:
-
增删O(logn)
-
二叉树具有唯一根节点
-
二叉树中每个结点最多有两个孩子,没有孩子的结点称之为叶子结点
-
二叉树中每个结点最多有一个父亲结点,根节点没有父亲结点
-
二叉树具有天然递归结构,每个结点的左子树或右子树,也是一个二叉树
-
若二叉树的层次从0开始,则在二叉树的第i层至多有2^i个结点(i>=0)。
-
高度为k的二叉树最多有2^(k+1) - 1个结点(k>=-1)。 (空树的高度为-1)
-
对任何一棵二叉树,如果其叶子结点(度为0)数为m, 度为2的结点数为n, 则m = n + 1。
-
满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。
-
完全二叉树:完全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。
-
极端的左或右都是二叉树,只不过退化成了线性表,这种情况称之为不平衡二叉树
-
一个结点也叫二叉树,无非左右孩子都为null,null 空也是二叉树
二叉树的遍历
深度优先遍历
- 前序遍历 DLR
- 中序遍历 LDR
- 后序遍历 LRD
- 层序遍历
前序和中序,可以唯一确定一颗二叉树
后序和中序,可以唯一确定一颗二叉树
前序和后序,是不能确定一颗二叉树的
前序遍历算法
//前序遍历
public List<E> preIte(Node root){
List<E> list=new LinkedList<>();
preIte(root,list);
return list;
}
private void preIte(Node node,List<E> list){
if(node==null){
return;
}
list.add(node.e);//父结点
preIte(node.left,list);//左结点
preIte(node.right,list);//右结点
}
中序遍历算法
//中序输出打印
public void print(){
List<E> list=new ArrayList<E>();
print(list,root);
System.out.println(list);
}
private void print(List<E> list,Node node) {
if(node==null){
return;
}
print(list,node.left);//左结点
list.add(node.e);//父结点
print(list,node.right);//右结点
}
后序遍历算法
//交换一下顺序
print(list,node.left);//左结点
print(list,node.right);//右结点
list.add(node.e);//父结点
层序遍历算法
//广度优先遍历(层序遍历)
public void levelOrder(){
List<E> list=new ArrayList<>();
StringBuilder sb=new StringBuilder();
//用辅助队列
Queue<Node> queue=new LinkedList<>();
sb.append("");
queue.add(root);
while(!queue.isEmpty()){
Node n=queue.poll();
list.add(n.e);
if(n.left!=null){
queue.add(n.left);
}
if(n.right!=null){
queue.add(n.right);
}
}
System.out.println(list);
}
线索二叉树
指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)
n个结点的二叉树共有n-1条分支线数,存在2n-(n-1)=n+1个空指针域
中序遍历线索二叉树
- 空指针域存后驱
- 空指针域存前驱
我们对二叉树以某种次序遍历使其变成线索二叉树的过程称作是线索化
线索二叉树的结构实现
线索化的过程就是在遍历的过程中修改空指针的过程
package tree;
import java.util.LinkedList;
import java.util.List;
/**
* 二叉线索树
* @author zhang
*
* @param <E>
*/
public class BinaryThreadTree<E extends Comparable<E>> {
private class Node {
public E val;
public Node lChild;
public Node rChild;
public boolean lTag;//为true时,lChild是下一个结点
public boolean rTag;//为false时,rChild是右孩子
public Node() {
this(null);
}
public Node(E e) {
val = e;
}
}
private Node root;
private int size;
// 添加元素
public void add(E e) {
if (root == null) {
root = new Node(e);
}
root = add(root, e);
pre=root;
}
private Node add(Node node, E e) {
if (node == null) {
node = new Node(e);
}
if (e.compareTo(node.val) < 0) {
node.lChild=add(node.lChild, e);
} else if (e.compareTo(node.val) > 0) {
node.rChild=add(node.rChild, e);
}
return node;
}
public void test() {
List<E> list = new LinkedList<>();
test(root, list);
System.out.println(list);
}
private void test(Node node, List<E> list) {
if (node == null) {
return;
}
test(node.lChild, list);
list.add(node.val);
test(node.rChild, list);
}
private Node pre=root;
// 将结点按中序链接起来
private void midIte(Node node) {
if (node == null) {
return;
}
midIte(node.lChild);
//如果左孩子为null时
if (node.lChild == null) {
node.lTag = true;
//前驱为pre
node.lChild = pre;
}
//当前一结点右孩子为null时
if (pre.rChild == null) {
pre.rTag = true;
//前一结点的后驱为当前结点
pre.rChild = node;
}
//向后移动结点
pre = node;
midIte(node.rChild);
}
private Node headPrint() {
Node head = new Node();
head.lChild = root;//头结点的右孩子为root
head.lTag = false;
Node p = root;
while (p.rChild!=null) {
p = p.rChild;
}
head.rTag = true;//右指针域链接右边第一个
head.rChild = p;
p.rChild = head;//最后一个指向头
Node f = root;
while (!f.lTag) {
f = f.lChild;
}
f.lChild = head;//左边第一个指向头
return head;
}
public void midPrint() {
midIte(root);//给空指针域赋值,将元素按中序链接
Node head = headPrint();//头结点
Node p = head.lChild;//p=root
List<E> list =new LinkedList<>();
while (p != head) {
while (!p.lTag) {//找到左边第一个结点
p = p.lChild;
}
list.add(p.val);
//当结点有后驱时
while (p.rTag && p.rChild != head) {
p = p.rChild;
list.add(p.val);
}
//当结点没有后驱时,下一个就是它的右孩子
p = p.rChild;
}
System.out.println(list);
}
}
二叉树怎么存:
二叉顺序存储结构
用顺序结构也可以表现出二叉树的结构
- 存一个深度为h的二叉树,最多需要2^h-1
- 假设一个节点角标i(i>=0)
父节点:-------(i-1)/2
左子节点-----2i+1
右子节点-----2i+2
二叉链表
- 二叉树每个结点最多有两个孩子
- 一个节点有一个数据域和两个指针域
二分搜索树
时间复杂度
O(h)
2^h-1=n
h=log2(n-1)
h=logn
- 对于每个节点
大于其左子树的所有结点的值
小于其右子树的所有结点的值 - 二分搜索树中所存储的元素必须具有可比较性!实现Comparable接口
- 不包含重复元素
package tree;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* 二叉搜索树
* @author zhang
*
* @param <E>
*/
public class BinarySearchTree<E extends Comparable<E>> {
private class Node{
public E e;
public Node left,right;
public Node (E e){
this.e=e;
left=null;
right=null;
}
}
private Node root;
private int size;
public BinarySearchTree() {
root=null;
size=0;
}
//获取元素个数
public int size(){
return size;
}
//判断是否为空
public boolean isEmpty(){
return size==0;
}
//添加
public void add(E e){
root=add(e,root);//递归实现
/*if(root==null){
root=new Node(e);
size++;
}
Node p=root;
while(true){
if(e.compareTo(p.e)>0){
if(p.right!=null){
p=p.right;
}else{
p.right=new Node(e);
size++;
return;
}
}else if(e.compareTo(p.e)<0){
if(p.left!=null){
p=p.left;
}else{
p.left=new Node(e);
size++;
return;
}
}else{
return;
}
}*/
}
//以node为当前树 的根节点添加元素e 并返回该树的根,递归
private Node add(E e,Node node){
if(node==null){
size++;
return new Node(e);
}
//root不是null时
if(node.e.compareTo(e)>0){
node.left=add(e,node.left);
}else if(node.e.compareTo(e)<0){
node.right=add(e,node.right);
}
return node;
}
//查询元素
public boolean contains(E e){
//return contains(e,root);
if(size()==0){
return false;
}
Node p=root;
while(p!=null){
if(e.compareTo(p.e)>0){
p=p.right;
}else if(e.compareTo(p.e)<0){
p=p.left;
}else{
return true;
}
}
return false;
}
//以node为当前根节点进行查找元素e,递归实现
private boolean contains(E e, Node node) {
if(node==null){
return false;
}
if(node.e.compareTo(e)>0){
return contains(e,node.left);
}else if(node.e.compareTo(e)<0){
return contains(e,node.right);
}else{
return true;
}
}
//中序输出打印
public void print(){
List<E> list=new ArrayList<E>();
print(list,root);
System.out.println(list);
}
private void print(List<E> list,Node node) {
if(node==null){
return;
}
print(list,node.left);//左结点
list.add(node.e);//父结点
print(list,node.right);//右结点
}
//打印某个节点及子节点
public void print(Node n){
List<E> list=new ArrayList<E>();
print(list,n);
System.out.println(list);
}
//计算深度
public int level(Node node){
if(node==null){
return 1;
}
int left=0;
int right=0;
if(node.left!=null){
left=level(node.left)+1;
}
if(node.right!=null){
right=level(node.right)+1;
}
return Math.max(left,right);
}
//前序遍历
public List<E> preIte(){
List<E> list=new LinkedList<>();
preIte(root,list);
return list;
}
private void preIte(Node node,List<E> list){
if(node==null){
return;
}
list.add(node.e);//父结点
preIte(node.left,list);//左结点
preIte(node.right,list);//右结点
}
//广度优先遍历(层序遍历)
public void levelOrder(){
List<E> list=new ArrayList<>();
StringBuilder sb=new StringBuilder();
//用辅助队列
Queue<Node> queue=new LinkedList<>();
sb.append("");
queue.add(root);
while(!queue.isEmpty()){
Node n=queue.poll();
list.add(n.e);
if(n.left!=null){
queue.add(n.left);
}
if(n.right!=null){
queue.add(n.right);
}
}
System.out.println(list);
}
//层序遍历(递归)
public void levelIte(){
List<E> list=new LinkedList<>();
levelIte(root,list);
System.out.println(list);
}
private void levelIte(Node node, List<E> list) {
if(node==null){
return;
}
list.add(node.e);
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
generateString(root,0,sb);
return sb.toString();
}
private void generateString(Node node, int level, StringBuilder sb) {
if(node==null){
sb.append(generateSpace(level)+"null\n");
return;
}
generateString(node.left, level+1, sb);
sb.append(generateSpace(level)+node.e+"\n");
generateString(node.right, level+1, sb);
}
private String generateSpace(int level) {
StringBuilder sb=new StringBuilder();
for(int i=0;i<level;i++){
sb.append(" ");
}
return sb.toString();
}
//获取最小值
public E minNum(){
if(size()==0){
throw new IllegalArgumentException("Tree is Empty!");
}
return minNum(root).e;
}
private Node minNum(Node node) {
if(node.left==null){
return node;
}
return minNum(node.left);
}
//获得最大值
public E maxNum(){
if(size()==0){
throw new IllegalArgumentException("Tree is Empty!");
}
return maxNum(root).e;
}
private Node maxNum(Node node) {
if(node.right==null){
return node;
}
return maxNum(node.right);
}
//删除最大值
public E removeMax(){
E e=maxNum();
removeMax(root);
return e;
}
private Node removeMax(Node node) {
if(node.right==null){
Node nodeLeft=node.left;
node.left=null;
size--;
return nodeLeft;
}
node.right=removeMax(node.right);
return node;
}
//删除最小值
public E removeMin(){
E e=minNum();
removeMin(root);
return e;
}
private Node removeMin(Node node) {
if(node.left==null
){
Node nodeRight=node.right;
node.right=null;
size--;
return nodeRight;
}
node.left=removeMin(node.left);
return node;
}
//删除任意值
public void remove(E e){
root=remove(root,e);
}
private Node remove(Node node, E e) {
if(node==null){
return null;
}
if(e.compareTo(node.e)>0){
node.right=remove(node.right,e);
return node;
}else if(e.compareTo(node.e)<0){
node.left=remove(node.left,e);
return node;
}else{
//如果左孩子为空
if(node.left==null){
Node nodeRight=node.right;
node.right=null;
size--;
//直接返回该节点的右孩子
return nodeRight;
}else if(node.right==null){
Node nodeLeft=node.left;
node.left=null;
size--;
//直接返回该节点的左孩子
return nodeLeft;
}
//新结点为右边最小值
Node success=minNum(node.right);
//将删除右边最小值的右孩子赋给新结点右孩子
success.right=removeMin(node.right);
//将原结点的左孩子赋给新节点左孩子
success.left=node.left;
return success;
}
}
}
树、森林与二叉树的转换
树与二叉树的转换
步骤如下:
- 加线,在所有兄弟结点之间加一条线
- 去线,对树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线
- 层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子
森林转化为二叉树
森林是由若干个树组成,森林中的每棵树都是兄弟,步骤如下:
- 把每个树转化为二叉树
- 第一颗二叉树不动,从第二棵树开始,依次把后一颗二叉树的根结点作为前一颗二叉树的根结点的右孩子,用线连起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树
二叉树转换为树
树转换为二叉树的逆过程,步骤如下:
- 加线。若某结点的左孩子存在,将这个左孩子的右孩子、右孩子的右孩子…,就是左孩子的n个右孩子都化作为此节点的孩子。将该节点与这些右孩子用线链接起来
- 去线。删除原二叉树中所有结点与其右孩子结点的连线
- 调整层次。使之层次分明
二叉树转换为森林
判断一颗二叉树能够转换成一棵树还是森林,只要看这颗二叉树有没有右孩子,有就是森林,没有就是一棵树,步骤如下:
- 从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树…,直到所有右孩子连线都删除为止,得到分离的二叉树。
- 再将每颗分离后的二叉树转换为树即可
树与森林的遍历
- 先根遍历,先访问树的根结点,然后依次先根遍历每颗子树
- 后根遍历 ,先依次后根遍历每颗子树,然后再访问根结点
先根遍历次序:ABEFCDG,后根遍历次序:EFBCGDA
森林遍历分为两种: - 前序遍历:ABCDEFGHJI
- 后序遍历:BCDAFEJHIG
赫夫曼树及其应用
赫夫曼树定义及原理
- 从树中一个结点到另外一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。
- 树的路径长度就是从树根到每一结点的路径长度之和
- 带权路径长度WPL最小的二叉树称作赫夫曼树(最优二叉树)
赫夫曼树的构造: - 1、根据给定的n个权值{w1,w2,w3,…,wn}构成n颗树的集合F={T1,T2,…Tn},每棵树Ti中只有一个带权为wi的根结点,其左右子树为空
- 2、在F中选取两颗根结点的权值最小的树作为左右子树构造一颗新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和
- 3、在F中删除这两颗树,同时将新的二叉树加入F中
- 4、重复2和3步骤,直到F只含一棵树为止,这棵树便是赫夫曼树
例如:A5,E10,B15,D30,C40
赫夫曼编码
左图为构造赫夫曼树的过程的权值显示。右图为将权值左分支改为0,右分支改为1的赫夫曼树
- 若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码称作前缀编码
- 一般地,设需要编码的字符集为{d1,d2,d3,…dn},各个字符在电文中出现的次数或频率集合为{w1,w2,w3…wn},以d1,…dn作为叶子结点,以w1,w2,…wn作为相应叶子结点的权值来构造一颗赫夫曼树。
- 规定赫夫曼树左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是赫夫曼树