数据结构与算法——树结构


1. 二叉树

1.0 定义

1.1 性质

性质1:二叉树第 i 层上节点个数最多为 2 i-1

性质2:深度为 k 的二叉树,节点个数最多为 2 k -1

性质3:由性质2可知,包含 n 个节点的二叉树,高度至少为 log 2 (n+1)

性质4:在任意一颗二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1

推导:

在这里插入图片描述

1.2 分类

1.2.1 满二叉树 / 完美二叉树

在这里插入图片描述
满足条件:

  • 深度为k
  • 总的结点个数为 2k - 1 个结点

1.2.2 完全二叉树

在这里插入图片描述
特点:

  • 前k-1层是满二叉树
  • 第k层所有节点都在左边连续分布

1.2.3 完满二叉树

在这里插入图片描述
满足条件:

  • 除了叶子节点之外的每个节点都有2个子节点

1.2.4 二叉排序树 / 二叉查找树 / 二叉搜索树 Binary Search Tree

在这里插入图片描述
满足条件:二叉搜索树的中序遍历结果是有序数组

  • 所有子树上面的左节点的值都比根结点要小,右节点的值都比根结点要大
  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 任意结点的左右子树也都是二叉查找树

1.2.5 平衡二叉树 / AVL树 Balanced Binary Tree

它是二叉查找树最优的情况。它很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多

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

AVL树
在这里插入图片描述
失衡
在这里插入图片描述

LL
在这里插入图片描述
RR
在这里插入图片描述

LR
在这里插入图片描述

RL
在这里插入图片描述

参考链接:https://www.cnblogs.com/skywang12345/p/3576969.html

1.2.6 红黑树

一种自平衡二叉查找树, 通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保从根到叶子节点的最长路径不会是最短路径的两倍,用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决

红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多

  • 性质1:每个节点要么是黑色,要么是红色。
  • 性质2:根节点是黑色。
  • 性质3:每个叶子节点(NIL)是黑色。
  • 性质4:每个红色结点的两个子结点一定都是黑色。
  • 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

参考链接:https://www.jianshu.com/p/e136ec79235c

1.3 树的遍历

		    1				前序遍历:1、2、3、4、5、6
		   / \
		  2   5				中序遍历:3、2、4、1、5、6
		 / \   \
		3   4   6			后序遍历:3、4、2、6、5、1
  • 准备工作
public class Solution {
	public static void main(String[] args) {
		TreeNode root=new TreeNode(1);
		TreeNode node1=new TreeNode(2);
		TreeNode node2=new TreeNode(3);
		TreeNode node3=new TreeNode(4);
		TreeNode node4=new TreeNode(5);
		TreeNode node5=new TreeNode(6);
		root.left=node1;
		root.right=node4;
		node1.left=node2;
		node1.right=node3;
		node4.left=null;
		node4.right=node5;		
	}	
}
class TreeNode{
	int val;
	TreeNode left;
	TreeNode right;
	TreeNode(int val){
		this.val=val;
	}
}

1.3.1 前序遍历

  • 思路:对任意子树,先访问根节点,遍历左子树,再遍历右子树
  • 代码实现

LeetCode144. 二叉树的前序遍历

递归实现

public static void preOrder(TreeNode root){
	if(root== null)
		return;
	else{
		System.out.println(root.val);
		preOrder(root.left);
		preOrder(root.right);
	}
}

非递归实现1

public static void preOrder(TreeNode root){
	List<Integer> res=new ArrayList<>();
    if(root==null){
         return res;
    }
    TreeNode cur=root;
    Stack<TreeNode> stack=new Stack<>();
    while(true){
        while(cur != null) {
            stack.push(cur);
            res.add(cur.val);
            cur = cur.left;
        } 
        //不管有没有右节点
        TreeNode temp = stack.pop();
        //有右节点的话就去遍历右节点
        if (temp.right != null) {
            cur = temp.right;
        }
        if(stack.empty()&& cur==null) break;
    }
    return res;
}

非递归实现2:颜色标记法优化(未遍历过的节点,将节点入栈;遍历过,将节点值入栈),颜色标记法参考LeetCode题解

public static List<Integer> preOrder(TreeNode root) {
	List<Integer> res=new ArrayList<>();
 	if(root==null){
        return res;
    }
 	TreeNode cur=root;
    Stack<Object> stack=new Stack<>();
    stack.push(cur);
    while(!stack.empty()) {
   	 	Object o=stack.pop();
   	 	if(o instanceof TreeNode) {
   		 	TreeNode node=(TreeNode) o;
   		 	//因为前序遍历是根节点--左节点--右节点
   		 	//即出栈顺序为根节点--左节点--右节点,入栈顺序相反
   		 	if(node.right!=null) stack.push(node.right);
   		 	if(node.left!=null) stack.push(node.left);
   		 	stack.push(node.val); 
		}else {
   			res.add((int)o);
   	 	}   	 
    }
    return res;  
}

1.3.2 中序遍历

  • 思路:对任意子树,先遍历左子树,访问根节点,再遍历右子树
  • 代码实现

LeetCode94. 二叉树的中序遍历

递归实现

public static void inOrder(TreeNode biTree){
	if(biTree == null)
		return;
	else{
		inOrder(biTree.left);
		System.out.println(biTree.val);
		inOrder(biTree.right);
	}
}

非递归实现1

public static List<Integer> inOrder(TreeNode biTree){
	List<Integer> res=new ArrayList<>();
    if(root==null){
        return res;
    }
    TreeNode cur=root;
    Stack<TreeNode> stack=new Stack<>();
    while(true){
        while(cur != null) {
            stack.push(cur);
            cur = cur.left;
        } 
        //不管有没有右节点
        TreeNode temp = stack.pop();
        res.add(temp.val);
        //有右节点的话就去遍历右节点
        if (temp.right != null) {
            cur = temp.right;
        }
        if(stack.empty() && cur==null) break;
    }
    return res;
}

非递归实现2:颜色标记法优化(未遍历过的节点,将节点入栈;遍历过,将节点值入栈),颜色标记法参考LeetCode题解

public static List<Integer> inOrder(TreeNode root) {
	List<Integer> res=new ArrayList<>();
 	if(root==null){
        return res;
    }
 	TreeNode cur=root;
    Stack<Object> stack=new Stack<>();
    stack.push(cur);
    while(!stack.empty()) {
   	 	Object o=stack.pop();
   	 	if(o instanceof TreeNode) {
   		 	TreeNode node=(TreeNode) o;
   		 	//因为中序遍历是左节点--根节点--右节点
            //即出栈顺序为左节点--根节点--右节点,入栈顺序相反
            if(node.right!=null) stack.push(node.right);
            stack.push(node.val); 
            if(node.left!=null) stack.push(node.left); 
		}else {
   			res.add((int)o);
   	 	}   	 
    }
    return res;  
}

1.3.3 后序遍历

  • 思路:对任意子树,先遍历左子树,再遍历右子树,访问根节点
  • 代码实现

LeetCode145. 二叉树的后序遍历

递归实现

public static void postOrder(TreeNode root){
	if(root== null)
		return;
	else{
		postOrder(root.left);
		postOrder(root.right);
		System.out.println(root.val);
	}
}

非递归实现1:逆序+前序遍历,使用LinkedList实现

public static List<Integer> postOrder(TreeNode root){
	LinkedList<TreeNode> stack = new LinkedList<>();
    LinkedList<Integer> res= new LinkedList<>();
    if (root == null) {
      	return res;
    }

    stack.add(root);
    while (!stack.isEmpty()) {
    	TreeNode node = stack.pollLast();
      	res.addFirst(node.val);
      	if (node.left != null) {
        	stack.add(node.left);
      	}
      	if (node.right != null) {
        	stack.add(node.right);
      	}
    }
    return res;				
}

非递归实现2:破坏树结构

public List<Integer> postOrder(TreeNode root) {
    List<Integer> res=new ArrayList<>();
    if(root==null){
        return res;
    }
    TreeNode cur=root;
    //1.每拿到一个节点就把它保存在栈中
    //2.继续对这个节点的左子树重复过程1,直到左子树为空
    //3.因为保存在栈中的节点都遍历了左子树 但是没有遍历右子树,所以对栈中节点出栈并对它的右子树重复过程1
    //4.直到遍历完所有节点
    Stack<TreeNode> stack=new Stack<>();
    while(true){
        while(cur != null) {
            stack.push(cur);
            cur = cur.left;
        } 
        //栈顶节点
        TreeNode temp = stack.peek();
        //没有右节点
        if (temp.right == null) {
            res.add(temp.val);
            stack.pop();
        }
        //有的话就去遍历右节点
        else {
            cur = temp.right;
            temp.right = null;
        }
        if(stack.empty()) break;
    }
    return res;
}

非递归实现3:不破坏树结构

public static List<Integer> postOrder(TreeNode root) {
	 List<Integer> res=new ArrayList<>();
     if(root==null){
         return res;
     }
     TreeNode cur=root;
     Stack<TreeNode> stack=new Stack<>();
     HashSet<TreeNode> set=new HashSet<>();//存放遍历过的节点
     while(true){
         while(cur != null) {
             stack.push(cur);
             cur = cur.left;
         } 
         //栈顶节点
         TreeNode temp = stack.peek();
         //没有右节点
         if (temp.right == null) {
             res.add(temp.val);
             set.add(temp);
             stack.pop();
         }
         //有的话就去遍历右节点
         else {
        	 //右节点没被访问过
        	 if(!set.contains(temp.right))
        		 cur = temp.right;
        	 else {
        		 res.add(temp.val);
        		 set.add(temp);
	             stack.pop();
        	 }
         }
         if(stack.empty()) break;
     }
     return res;
}

非递归实现4:颜色标记法优化(未遍历过的节点,将节点入栈;遍历过,将节点值入栈),颜色标记法参考LeetCode题解

public static List<Integer> postOrder(TreeNode root) {
	List<Integer> res=new ArrayList<>();
 	if(root==null){
        return res;
    }
 	TreeNode cur=root;
    Stack<Object> stack=new Stack<>();
    stack.push(cur);
    while(!stack.empty()) {
   	 	Object o=stack.pop();
   	 	if(o instanceof TreeNode) {
   		 	TreeNode node=(TreeNode) o;
   		 	//因为后序遍历是左节点--右节点--根节点
            //即出栈顺序为左节点--右节点--根节点,入栈顺序相反
            stack.push(node.val); 
            if(node.right!=null) stack.push(node.right);
            if(node.left!=null) stack.push(node.left);  
		}else {
   			res.add((int)o);
   	 	}   	 
    }
    return res;  
}

1.3.4 层序遍历

LeetCode102. 二叉树的层序遍历

已知二叉树结构:

         1
	    / \
	   2   3
	  / \ / \ 
 	 4  5 6  7

输出:

[
	[1],
	[2,3],
	[4,5,6,7]
]

非递归实现:颜色标记法优化(未遍历过的节点,将节点入栈;遍历过,将节点值入栈),颜色标记法参考LeetCode题解

1.3.5 已知任意两种遍历求树结构+第三种遍历结果

1.3.5.1 已知前序遍历+中序遍历求后序遍历

LeetCode105. 从前序与中序遍历序列构造二叉树

假设树中没有重复的元素,给出:

  • 前序遍历 preorder = [3,9,20,15,7]
  • 中序遍历 inorder = [9,3,15,20,7]

思路分析:

  • 前序遍历第一个节点3是根节点

  • 中序遍历3左面是左子树,右面是右子树,9是3的左节点

  • 20是3的右节点,20左面是左子树15(即左节点),20右面是右子树(即右节点)

          3
         / \
        9  20
          /  \
         15   7
    

假设树中没有重复的元素,给出:

  • 前序遍历 preorder = [1,2,4,5,3,6,7]
  • 中序遍历 inorder = [4,2,5,1,6,3,7]

思路分析:

  • 前序遍历第一个节点1是根节点

  • 中序遍历1左面(4,2,5)是左子树,右面(6,3,7)是右子树

  • 2属于左子树,2是1的左节点,2的左子树4(即左节点),右子树5(即右节点)

  • 1的左子树遍历完毕

  • 3属于右子树,3是1的右节点,3的左子树6(即左节点),右子树7(即右节点)

           1
          / \
         2   3
        / \ / \ 
       4  5 6  7
    

代码实现:

在这里插入代码片
1.3.5.2 已知中序遍历+后序遍历求前序遍历

LeetCode106. 从中序与后序遍历序列构造二叉树

假设树中没有重复的元素,给出:

  • 中序遍历 inorder = [9,3,15,20,7]
  • 后序遍历 postorder = [9,15,7,20,3]

思路分析:

  • 后序遍历最后一个节点3是根节点

  • 中序遍历根节点3左侧(9)是左子树,右侧是右子树(15,7,20),9是3的左节点

  • 后序遍历右子树(15,7,20)中最后一个节点20是右子树的根节点

  • 中序遍历20左侧(15)是左子树(即左节点),右侧(7)是右子树(即右节点)

          3
         / \
        9  20
          /  \
         15   7
    
1.3.5.3 已知前序遍历+后序遍历求中序遍历

LeetCode889. 根据前序和后序遍历构造二叉树

假设树中没有重复的元素,给出:

  • 前序遍历 preorder = [1,2,4,5,3,6,7]
  • 后序遍历 postorder = [4,5,2,6,7,3,1]
  • 无法保证得到的二叉树结构唯一

思路分析:

  • 前序遍历第一个节点1是根节点,也是后序遍历最后一个节点

  • 假设2是1的左节点,即2是1的左子树的根节点

  • (4,5)可能分别是2的左右子节点,或者是2的右节点,如下图所示

  • 同理对前序遍历中1的右子树(3,6,7),会有多种结果

          1						1
         / \				   / \
        2   3				  2   3
       / \ / \				  \  / \
      4  5 6  7				   4 6  7
      						    \
      							 5
    

2. 多叉树

2.1 B树 / B-树

是一种自平衡的多路搜索树(并不是二叉的),能够保证数据有序。同时它还保证了在查找、插入、删除等操作时性能都能保持在O(logn),为大块数据的读写操作做了优化,同时它也可以用来描述外部存储(支持对保存在磁盘或者网络上的符号表进行外部查找)

在这里插入图片描述

2.2 B+树

B+的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找

在这里插入图片描述


参考链接

常用数据结构——树

3. 哈夫曼编码

  • 频率表 A:60、B:45、C:13、D:69、E:14、F:5、G:3

  • 找出字符中最小的两个,小的在左边,大的在右边,组成二叉树。在频率表中删除此次找到的两个数,并加入此次最小两个数的频率和

在这里插入图片描述

  • 频率表 A:60、B:45、C:13、D:69、E:14、FG:8
  • 最小的是 FG:8与C:13,因此如图,并返回FGC的和:21给频率表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

哈夫曼树原理,及构造方法

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构与算法分析》实验报告 姓名 学号_ _____ __年 __月__ __日 上机题目:以静态链表为存储结构,编写给定权值{7,19,2,6,32,3}构造哈夫曼树的 算法。 (输出以存储结构表示或以树型显示(90度旋转)) 需求分析 1. 输入数据必须为int的整形数据,其数值范围为:-~47 2. 输出的数据格式为:%d 3. 测试数据的数据为:{7,19,2,6,32,3} 详细设计 1. 该程序采用顺序表的存储结构,其数据结构定义如下: #define n 6 #define m 2*n-1 #define max 100typedef struct {int data; int lchild,rchild,prnt; }hufmtree; 所用数据类型中每个操作的伪码算法如下: 创建哈夫曼树 Program hufm(hufmtree t[m]) FOR i=0;i<m;i++ TO t[i].data=0; t[i].lchild=0; t[i].rchild=0; t[i].prnt=0; End FOR 输入结点值 FOR i=n;i<m;i++ TO p1=0;p2=0; small1=max;small2=max FOR j=0;j<=i-1;j++ TO IFt[j].prnt?=0 IF(t[j].data<small1) small2=small1; small1=t[j].data; p2=p1; p1=j;} ELSE IF(t[j].data<small2) small2=t[j].data; p2=j; t[p1].prnt=i+1; t[p2].prnt=i+1; t[i].lchild=p1+1; t[i].rchild=p2+1; t[i].data=t[p1].data+t[p2].data; END IF END FOR END Hufman 调试分析 1. 调试过程中主要遇到哪些问题?是如何解决的? 开始的时候main函数的数据结构类型定义的与主函数不同,而且缺少返回值,导致最 后的结果陷入死循环,通过看书,向同学询问,得以解决。 2. 经验和体会 哈夫曼树又称最优二叉树,此次实验创建哈夫曼树算法,虽然依旧犯了不少错误,但 仍解决了。在其中学习了很多,对树有了更深的了解。 测试结果 附件 见 058詹奇.Cpp ----------------------- 数据结构与算法分析实验报告全文共3页,当前为第1页。 数据结构与算法分析实验报告全文共3页,当前为第2页。 数据结构与算法分析实验报告全文共3页,当前为第3页。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值