2021-06-30

二叉树的实现及其基础遍历(前序、中序、后序)实现

1.查找二叉树的实现
定义一个查找二叉树的类:
以键值对形式存储元素,让键(泛型)继承Comparable接口,方便在插入时使得结点的键之间的比较(有序)

public class BinaryTree<Key extends Comparable<Key>, Value> {
	// 记录根结点
	private Node root;
	// 记录树中的结点个数
	private int N;
	//结点内部类
	
	//插入删除方法

	//基础遍历方法
	
}

定义内部类 结点类Node

	// 定义结点内部类
	private class Node {
		public Key key;// 存储键值对的键
		private Value value;// 存储键值对的值
		public Node left;// 左子树
		public Node right;// 右子树

		public Node(Key key, Value value, Node left, Node right) {
			this.key = key;
			this.value = value;
			this.left = left;
			this.right = right;
		}
	}

定义方法实现向树中插入键值对(结点):

	// 定义方法,向(整颗)树中插入键值对(结点)
	public void put(Key key, Value value) {
		root = put(root, key, value);// 调用重载方法向树中插入键值对(向整颗树插入时指定的树x传入根结点)
	}

	// 向指定的子树中插入键值对结点
	private Node put(Node x, Key key, Value value) {
		// 如果x子树为空,则直接将要插入的键值对赋值给x
		if (x == null) {
			N++;
			return new Node(key, value, null, null);
		}

		// 如果x子树不为空,则比较要插入的键值对的键和x结点的键的大小
		int cmp = key.compareTo(x.key);
		// <0,即要插入的键小于x结点的键,继续找x结点的左子树
		if (cmp < 0) {
			x.left = put(x.left, key, value);// 让返回的结点成为x的左子树
		} else if (cmp > 0) {
			// >0,即要插入的键大于x结点的键,继续找x结点的右子树
			x.right = put(x.right, key, value);// 让返回的结点成为x的右子树(在x==null处返回)
		} else {
			// cmp==0,则证明要插入的结点的键和x结点的键相同,直接将该结点的值替换即可
			x.value = value;
		}
		// 插入完成后将树中的元素个数加1
//		N++;//error,每次添加52行已经元素加1了
		
		// 返回插入完成后的子树结点
		return x;
	}

定义方法实现根据键查找对应结点的值:

// 查找指定键的值(从根结点开始查找)
	public Value get(Key key) {
		return get(root, key);// 在整颗树中查找,即从根结点开始查找
	}

	// 从指定子树中查找指定的键对应的值
	public Value get(Node x, Key key) {
		// 如果指定的子树为空,则直接返回null
		if (x == null) {
			return null;
		}

		// 子树不为null,则比较要查找的键和树的大小
		int cmp = key.compareTo(x.key);
		if (cmp < 0) {
			// 如果cmp<0,即要查找的键小于x结点的键,继续找x结点的左子树
			return get(x.left, key);// 超找到要找的键对应的值,直接返回值
		} else if (cmp > 0) {
			// 如果cmp>0,即要查找的键小于x结点的键,继续找x结点的左子树
			return get(x.right, key);
		} else {
			// 如果cmp=0,即要查找的键等于x结点的键,直接返回该结点的值即可
			return x.value;
		}

	}

定义方法实现 删除指定键所对应的结点:

// 删除树中指定的键的结点
	public void delete(Key key) {
		delete(root, key);
	}

	// 删除指定子树x的键的结点,并返回该树
	public Node delete(Node x, Key key) {
		//如果x为空
		if (x == null) {
			return null;
		}
		
		//如果x不为空,则比较要删除的结点的键和x的键的大小
		int cmp = key.compareTo(x.key);
		if (cmp < 0) {
			//要删除的结点的键小于x的键,则继续找x的左子树
			x.left = delete(x.left, key);//返回的结点作为x的左子树
		}else if (cmp > 0) {
			//要删除的结点的键大于x的键,则继续找x的右子树
			x.right = delete(x.right, key);//返回的结点作为x的右子树
		}else {
			//元素减1再删除
			N--;
		
			//删除的键和x的键相同,即x结点为要删除的结点(真正地删除操作)
			//如果要删除的结点的左子树为null,则将右子树返回
			if (x.left == null) {
				return x.right;
			}
			//如果要删除的结点的右子树为null,则返回左子树(即让x结点的父结点指向x的左子树)
			if (x.right == null) {
				return x.left;
			}
			
			//查找右子树中的最小结点,即最小的左子树(记录最小结点)
			Node minNode = x.right;//初始让x的右子树成为最小结点
			while(minNode.left != null) {//查找右子树中的最小结点,即最小的左子树
				minNode = minNode.left;
			}
			
			//删除右子树中的最小结点
			Node n = x.right;
			while(n.left != null) {//不是叶子结点,则继续向下找
				if (n.left.left == null && n.left.right == null) {//如果n的左子树的左子树为空,则将n的左子树删除,即置为空
					n.left = null;
				} else if (n.left.left == null && n.left.right != null) {//最小结点还有右子树 
					n.left = n.left.right;//将最小结点的右子树作为最小结点的父结点的左子树 
				}else {
					n = n.left;//变换n
				}
			}
			
			//删除最小结点后,让x结点的左子树成为该结点的左子树
			minNode.left = x.left;
			//让x结点的左子树成为该结点的左子树
			minNode.right = x.right;			
			//最后让x结点的父结点指向最小结点
			x = minNode;//直接将最小结点赋值给x结点	
		}
		return x;//返回该结点,即返回新的树x	
	}

2.实现二叉树的基础遍历
先序中序和后续的区别在于根结点的次序,先序遍历即先打印根结点(根-左-右),中序即(左-根-右),后续(左-右-根)
注:本文代码都为递归方法,非递归方法可使用栈来实现(类似于……可以说是模拟递归吧,后续整理了的话会单放一篇,因为相比起来递归更容易理解和实现一些)
先序遍历
先序遍历,即先输出根结点——再左子结点——再右子结点。
思路:获取树中的所有结点,按照先根后左子树再右子树的逻辑,存储到队列或列表中,最后遍历队列或列表输出。此处使用递归遍历法,将获取到的结点存储在队列中。

//获取指定的树x的所有键,并放到队列中(先序遍历)
	public Queue<Key> preErgodic(){
		Queue<Key> keys = new Queue<>();//创建队列,存储树的结点的键
		preErgodic(root,keys);//调用重载方法,查找树中的所以键
		return keys;//返回存储了树中节点的键的队列
	}

	public void preErgodic(Node x, Queue keys) {
		//判断x树是否为空
		if (x == null) {
			return;
		}
		
		//不为空则将x(先根结点)结点的键存入队列中
		keys.enqueue(x.key);
		
		//判断x的左子树(再左子树)是否为空
		if (x.left != null) {
			//不为空则递归遍历左子树
			preErgodic(x.left, keys);
		}
		
		//(最后右子树)判断x的右子树是否为空
		if (x.right != null) {
			//不为空则递归遍历右子树
			preErgodic(x.right,keys);
		}		
	}

中序遍历
同理,中序遍历为:先左子树——再根结点——最后右子树

//获取指定的树x的所有键,并放到队列中(中序遍历)
	public Queue<Key> midErgodic(){
		//创建队列对象
		Queue<Key> keys = new Queue<>();
		//调用重载方法实现中序遍历
		midErgodic(root,keys);
		return keys;
	}

	public void midErgodic(Node x, Queue keys) {//先左子树、再根,再右子树
		//先判断x树是否为空
		if (x == null) {
			return;
		}
		
		//先判断x的左子树是否为空,不为空则递归遍历左子树
		if (x.left != null) {
			midErgodic(x.left,keys);
		}
		
		//将当前结点的键存入队列中
		keys.enqueue(x.key);
		
		//判断x的右子树是否为空,不为空则递归遍历右子树
		if (x.right != null) {
			midErgodic(x.right, keys);
		}	
	}

后续遍历
后序遍历为:先左子树——再右子树——最后根结点

//后续遍历
	public Queue<Key> afterErgodic(){
		//创建队列对象
		Queue<Key> keys = new Queue<>();
		//调用重载方法实现中序遍历
		afterErgodic(root,keys);
		return keys;
	}

	public void afterErgodic(Node x, Queue keys) {
		//判断x是否为空
		if (x == null) {
			return;
		}
		//不为空
		//判断x左子树是否为空,不为空则递归遍历左子树
		if (x.left != null) {
			afterErgodic(x.left, keys);
		}
		
		//再判断x的右子树是否为空,不为空则递归遍历右子树
		if (x.right != null) {
			afterErgodic(x.right, keys);
		}
		
		//将当前结点x的键存入队列中
		keys.enqueue(x.key);
		
	}

基础遍历测试
如树:在这里插入图片描述

先序遍历结果:E B A D C G F H
在这里插入图片描述
中序遍历:A B C D E F G H
后续遍历:A C D B F H G E

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个更复杂的 SQL 查询语句,它使用了多个子查询和窗口函数: WITH -- 定义一个子查询,获取销售额排名前10的产品 top_products AS ( SELECT product_id, SUM(sales) AS total_sales FROM orders WHERE order_date BETWEEN '2021-01-01' AND '2021-06-30' GROUP BY product_id ORDER BY total_sales DESC LIMIT 10 ), -- 定义一个子查询,获取销售额排名前10的客户 top_customers AS ( SELECT customer_id, SUM(sales) AS total_sales FROM orders WHERE order_date BETWEEN '2021-01-01' AND '2021-06-30' GROUP BY customer_id ORDER BY total_sales DESC LIMIT 10 ), -- 定义一个窗口函数,计算每个客户的销售额排名 customer_sales_rank AS ( SELECT customer_id, SUM(sales) AS total_sales, ROW_NUMBER() OVER (ORDER BY SUM(sales) DESC) AS sales_rank FROM orders WHERE order_date BETWEEN '2021-01-01' AND '2021-06-30' GROUP BY customer_id ) -- 最终查询,获取纽约市销售额排名前10的客户,以及他们购买的销售额排名前10的产品 SELECT customers.id AS customer_id, customers.name AS customer_name, products.id AS product_id, products.name AS product_name, SUM(orders.sales) AS total_sales FROM orders -- 连接顾客信息 INNER JOIN customers ON orders.customer_id = customers.id -- 连接产品信息 INNER JOIN products ON orders.product_id = products.id -- 仅查询纽约市的客户 WHERE customers.city = 'New York' -- 仅查询销售额排名前10的客户 AND customers.id IN (SELECT customer_id FROM top_customers) -- 仅查询销售额排名前10的产品 AND products.id IN (SELECT product_id FROM top_products) -- 仅查询客户销售额排名前10的订单 AND customers.id IN (SELECT customer_id FROM customer_sales_rank WHERE sales_rank <= 10) GROUP BY customers.id, customers.name, products.id, products.name ORDER BY customers.id, total_sales DESC, products.id;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值