数据结构---树

本文介绍了树的基本概念,包括无序树、有序树和二叉树,特别是详细讲解了完全二叉树、满二叉树和平衡二叉树,如AVL树、红黑树。此外,还提到了哈夫曼树以及二分搜索树的特点和应用场景,展示了树在数据结构中的重要性。
摘要由CSDN通过智能技术生成

1.01 树结构

树结构本身是一种天然的组织结构,将数据使用树结构存储后,会大幅高效率。

在这里插入图片描述

树是包含n(n>=0)个结点的有穷集,其中:

(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合
Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。

 空集合也是树,称为空树。空树中没有结点。

相关术语:

(1)节点的度:一个节点含有的子树的个数称为该节点的度;
(2)叶节点或终端节点:度为0的节点称为叶节点;
(3)非终端节点或分支节点:度不为0的节点;
(4)双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
(5)孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
(6)兄弟节点:具有相同父节点的节点互称为兄弟节点;
(7)树的度:一棵树中,最大的节点的度称为树的度;
(8)节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
(9)树的高度或深度:树中节点的最大层次;
(10)堂兄弟节点:双亲在同一层的节点互为堂兄弟;
(11)节点的祖先:从根到该节点所经分支上的所有节点;
(12)子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
(13)森林:由m(m>=0)棵互不相交的树的集合称为森林;

1.02 无序树:

树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;

1.03 有序树:

这树中任意节点的子结点之间有顺序关系,这种树称为有序树;

1.04 二叉树:

每个节点最多含有两个子树的树称为二叉树;
和链表一样,动态数据结构 ;

在这里插入图片描述
树中结点的定义:

class Node{
	 E e; 
	 Node left;    //左孩子
	 Node right;  //右孩子
   } 

二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1)空二叉树——如图(a);
(2)只有一个根结点的二叉树——如图(b);
(3)只有左子树——如图©;
(4)只有右子树——如图(d);
(5)完全二叉树——如图(e)。

在这里插入图片描述
1.05 完全二叉树:

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
在这里插入图片描述

特点:

(1)叶子结点只可能在最大的两层上出现,对任意结点,若其右分支下的子孙最大层次为L,则其左分支下的子孙的最大层次必为L 或 L+1;
(2)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;
(3)对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点只有1个或0个

完全二叉树的性质:

(1)若i为奇数且i>1,那么tree的左兄弟为tree[i-1];
(2)若i为偶数且i<n,那么tree的右兄弟为tree[i+1];
(3)若i>1,tree的父亲节点为tree[i div 2];
(4)若2*i<=n,那么tree的左孩子为tree[2*i];若2*i+1<=n,那么tree的右孩子为tree[2*i+1];
(5)若i>n div 2,那么tree[i]为叶子结点(对应于(3));
(6)若i<(n-1) div 2.那么tree[i]必有两个孩子(对应于(4))。
(7)满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
(8)完全二叉树第i层至多有2^(i-1)个节点,共i层的完全二叉树最多有2^i-1个节点。

出于简便起见,完全二叉树通常采用数组而不是链表存储,其存储结构如下:

1.06 满二叉树:

除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树。

在这里插入图片描述
应用:
(1)基于满二叉树的原地快速排序
(2)基于满二叉树二分K-means聚类并行推荐算法
(3)基于满二叉树的RA码交织器设计
在这里插入图片描述
满二叉树满足如下性质:

1、一个层数为k 的满二叉树总结点数为:  。因此满二叉树的结点数一定是奇数个。
2、第i层上的结点数为。
3、一个层数为k的满二叉树的叶子结点个数(也就是最后一层)。

1.07 哈夫曼树:

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉
树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大
的结点离根较近。
哈夫曼树(霍夫曼树)又称为最优树.

1、路径和路径长度

  在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长
度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。

2、结点的权及带权路径长度

  若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该
结点之间的路径长度与该结点的权的乘积。

3、树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

1.08 平衡二叉树

平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,

性质:

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

最小二叉平衡树的节点总数的公式:

F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci(斐波那契)数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。

平衡二叉树的常用实现方法:
(1)红黑树

红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。

(2)AVL

AVL是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

(3)Treap

Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap并不一定是。

(4)伸展树

伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

(5)SBT

Size Balanced Tree(简称SBT)是一自平衡二叉查找树,是在计算机科学中用到的一种数据结构。相比红黑树、AVL树等自平衡二叉查找树,SBT更易于实现。SBT能在O(log n)的时间内完成所有二叉搜索树(BST)的相关操作,而与普通二叉搜索树相比,SBT仅仅加入了简洁的核心操作Maintain。由于SBT赖以保持平衡的是size域而不是其他“无用”的域,它可以很方便地实现动态顺序统计中的select和rank操作。

1.09 二分搜索树:

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

特点:

 二叉树具有唯一根节点 
 二叉树中每个结点最多有两个孩子,没有孩子的结点称之为叶子结点 
 二叉树中每个结点最多有一个父亲结点,根节点没有父亲结点 
 二叉树具有天然递归结构,每个结点的左子树或右子树,也是一个二叉树

在这里插入图片描述
在这里插入图片描述
不平衡二叉树:极端的左或右都是二叉树,只不过退化成了线性表。

在这里插入图片描述
在这里插入图片描述
一个结点也叫二叉树,无非左右孩子都为null,null 空也是二叉树。

对于二分搜索树的每个结点的值而言

大于其左子树的所有结点的值 
小于其右子树的所有结点的值

在这里插入图片描述
二分搜索树的实现:

  1. 定义类

    public class BinarySearchTree<E extends Comparable<E>> 
    
  2. 定义结点(内部类)

private class Node{
	public E e;
	public Node left;
	public Node right;
	public Node(E e){
		this.e=e;
		left=null;
		right=null;
}
  1. 定义成员变量
private Node root;
private int size;
  1. 定义构造函数
public BinarySearchTree(){
	root=null;
	size=0;
}
  1. 获取数中元素的个数
public int size(){
	return size;
}
  1. 判断树是否为空
public boolean isEmpty(){
	return size==0;
}
  1. 添加元素

在这里插入图片描述

递归实现
public void add(E e){
	root=add(root,e);	//外界访问添加元素时 内部调用add(Node node,E e)方法
}
//递归实现 以node为当前的根节点,添加元素e并返回该树的根
private Node add(Node node,E e){
	if(node==null){	//根节点为空时
		size++;
		return new Node(e);
	}
	if(e.compareTo(node.e)<0){	//要添加的节点的元素小于当前根节点的元素时
		node.left=add(node.left,e);	//在其左子树上添加元素  以左子树为根节点 递归实现
	}
	if(e.compareTo(node.e)>0){	//要添加的结点的元素大于当前根节点的元素时
		node.right=add(node.rigth,e);	//在其右子树上添加元素 以右子树为根节点 递归实现
	}
	return node;	//返回该树的根
}
  1. 查询元素
递归实现
public boolean contains(E e){
	return contains(root,e);	//外界调用查询元素时 内部调用contains(Node node,E e)方法
}
//以node为根的树中是否包含元素e
private boolean contains(Node node,E e){
	if(node==null){	//如果根节点为空 则没有树
		return false;
	}else if(e.compareTo(node.e)==0){	//查询元素等于当前节点的值
		return true;
	}else if(e.compareTo(node.e)<0){	//查询元素小于当前节点的值
		return contains(node.left,e);	//在当前节点的左子树中寻找查询的元素
	}else{	//查询元素大于当前节点的值
		return contains(node.rigth,e);	//在当前右子树中寻找查询的元素
	}
}

  1. 前序遍历 (DLR)(深度优先遍历)

在这里插入图片描述

递归实现
public void preOrder(){
	preOrder(root);	//内部调用preOrder(Node node)
}
//前序遍历以node为根的树
private void preOrder(Node node){
	if(node==null){	//树为空
		return;
	}
	System.out.println(node.e);	//根节点
	preOrder(node.left);	//递归调用左子树
	preOrder(node.right);	//递归调用右子树
}
  1. 中序遍历 (LDR) (深度优先遍历)

在这里插入图片描述

递归实现
public void inOrder(){
	inOrder(root);	//内部调用inOrder(Node node)
}
private void inOrder(Node node){
	if(node==null){	//树为空
     	return;
    }
    inOrder(node.left);	//递归调用左子树
    System.out.println(node.e);	//根节点
    inOrder(node.rigth);	//递归调用右子树
}

  1. 后序遍历(LDR)(深度优先遍历)
    在这里插入图片描述
递归调用
public void postOrder(){
	postOrder(root);	//内部调用postOrder(Node node)
}
private void postOrder(Node node){
	if(node==null){	//树为空
		return;
	}
	postOrder(node.left);	//递归调用左子树
	postOrder(node.right);	//递归死奥用右子树
	System.out.println(node.e);//根节点
}
  1. 层次遍历(广度优先遍历)
    在这里插入图片描述
public void levelOrder(){
	LinkedQueue<Node> queue=new LinkedQueue<Node>(); //队列实现
	queue.enqueue(root);	//将根节点进队
	while(!queue.isEmpty()){	//循环实现 条件队列不为空
		Node cur=queue.dequeue();	//定义指针cur,cui所指向的元素出队
		Sysetem,out,println(cur.e);	//显示cur的值
		if(cur.left!=null){	//如果cur的左子树不为空 将左子树加入队列中
			queue.enqueue(cur.left);
		}
		if(cur.right!=null){	//如果cur的右子树不为空 将右子树加入队列中
			queue.enqueue(cur.right);
		}
	}
}

  1. 获取最小值
public E minmum(){
	if(size==0){	//树为空时 抛异常
		throw new IllegalArguementException("Tree is Empty");
	}
	return minmun(root).e;
}
private Node minmum(Node node){	//找到最小的结点 二叉搜索树的左子树<根节点<右子树
	if(node.left==null){	//左子树为空 返回根节点
		return node;
	}else{	//左子树不为空 递归调用 寻找左子树的的最小值
		minmum(node.left);
	}
}
  1. 获取最大值
public E maxmum(){
	if(size==0){	//树为空时 抛异常
		throw new IllegalArguementException("Tree is Empty");
	}
	return maxmum(root).e;
}
private Node maxmun(Node node){  //找到最大的结点 二叉搜索树的左子树<根节点<右子树
	if(node.rigth==null){	//如果右子树为空时 返回根节点
		return node;
	}else{	//如果右子树不为空时 递归调用maxmum(Node node)寻找最大值
		return maxmun(node.rigth);
	}
}
  1. 删除最小值
public E removeMin(){
	E ret=minmun();	//获取最小元素
	root=removeMin(root);	//调用removeMin()方法
	return ret;
}
private Node removeMin(Node node){
	if(node.left==null){	//左子树为空
		Node rigthNode=node.right;	//rightNode指针指向node的右子树根节点
		node.right=null;	//node的右子树置空
		size--;//有效值-1
		return rigthNode;	//返回node的右子树根节点 给上一级递归调用的node.left=removeMin(node.left);	位置
	}
	node.left=removeMin(node.left);	//将rightNode的根节点接上要删除结点的的左子树
	return node;	 //返回删除后的根节点
}
  1. 删除最大值
public E removeMax(){
	E ret=maxmun();	//获取最大元素
	root=removeMax(root); //调用removeMax()方法
	return ret; 
}
private Node removeMax(Node node){
	if(node.right==null){
		Node leftNode=node.left;	//指针leftNode指向node左子树的根节点
		node.left=null;	//要删除的node结点左子树置为空
		size--;	//	有效长度-1
		return leftNode;	//返回给上一级node.right=removeMax(node.right);
	}
	node.right=removeMax(node.right);	//将其左子树接到要删除结点的父亲结点的右子树上
	return node; 	//返回删除后的根节点
}
  1. 删除任意值

在这里插入图片描述

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.left=remove(node.left,e);	//递归调用其左子树,删除其中的值 更新node的左子树
		return node;	//返回节点
	}else if(e.compareTo(node.e)>0){	//删除的值比当前的结点的值大
		node.right=remove(node.rigth,e);	//递归调用其右子树,删除其中的值 更新node的右子树
		return node;	//返回节点
	}else{	//找到元素
		if(node.left==null){	//如果要删除的结点其左子树为空 和删除最小值方法一样
			Node rightNode=node.right;
			node.right=null;
			size--;
			return rightNode;
		}
		if(node.right==null){	//如果要删除的结点其右子树为空 和删除最大值方法一样
			Node leftNode=node.left;
			node.left=null;
			size--;
			return leftNode;
		}
		//当要删除的结点的左右子树都存在时
		Node  newcurRoot=minmum(node.right);	//将其右子树的最小值作为要删除结点的替换值
		newcurRoot.right=removeMin(node.right);	//将删除最小值后的node的右子树接到替换值的右子树上
		newcurRoot.left=node.left;	//将其node的左子树接到替换值的左子树上
		node.left=node.right=null;	//令其node的左右子树都为空 断开原来的联系
		return newcurRoot;	//最后返回替换值 即返回重造的以替换值为根节点这个树 返回到leftNode或rightNode
	}
}
  1. 打印
pubic StringtoString(){
	StringBuilder res=new StringBuilder();	//创建字符串变量res
	generateBSTString(root,0,res);	//内部调用generateBSTString()方法
	return res.toString();  
}
private void generateBSTString(Node node,int depth,StringBuilder res){	//res组成树的元素
	if(node==null){	//树为空
		res.append(generateDepthString(depth)+"null\n");	//字符串中添加树的深度以“--”形式显示
		return;
	}
	//前序遍历
	res.append(generateDepthString(depth)+node.e+"null\n");	
	generateBSTString(node.left,depth+1,res);
	generateBSTString(node.right,depth+1,res);
}
private String generateDepthString(int depth){	//res组成元素前面的层数 显示为“--”(与打印目录类似)
	StrinfBuilder res=new StringBuilder();
	for(int i=0;i<depth;i++){	//当前元素处于树的第几层 则打印几个“--”
		res.append("--");
	}
	return res.toString();	//返回“--..”
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值