数据结构与算法(六)二叉树

简介

二叉树是树的一种,数还包括红黑树、2-3-4树。为什么要用树呢?因为它结合了数组和链表两种数据结构的优点。众所周知,链表有查询慢插入删除快的特点,数组查询快删除添加慢。树结构能完美的解决这个问题。

什么是树

(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点通过连接它们的组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树的常用术语

  • 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
  • 根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
  • 子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
  • 叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、G都是叶子节点。
  • 子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
  • 节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
  • 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
  • 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;

二叉树

树的每个节点最多只能有两个子节点的树称之为二叉树。二叉树每个节点的子节点成为左节点和右节点。左节点的值要小于父节点,右节点的值要大于等于父节点。

 

Java模拟实现二叉树

Node.java

public class Node {
	
	int data;
	Node leftChild;
	Node rightChild;

	public Node(int data) {
		this.data = data;
	}

	public void display() {
		System.out.println(data);
	}

}

BinaryTree.java

public class BinaryTree {
	/**
	 * 根节点
	 */
	private Node root;
	/**
	 * insert:插入节点
	 */
	public boolean insert(int data) {
		Node newNode = new Node(data);
		if (root == null) {//当前树为空树,没有任何节点
			root = newNode;
			return true;
		} else {
			Node current = root;
			Node parentNode = null;
			while (current != null) {
				parentNode = current;
				if (current.data > data) {//当前值比插入值大,搜索左子节点
					current = current.leftChild;
					if (current == null) {//左子节点为空,直接将新值插入到该节点
						parentNode.leftChild = newNode;
						return true;
					}
				} else {
					current = current.rightChild;
					if (current == null) {//右子节点为空,直接将新值插入到该节点
						parentNode.rightChild = newNode;
						return true;
					}
				}
			}
		}
		return false;
	}
	/**
	 * preOrder:前序遍历
	 */
	public void preOrder(Node current) {
		if (current != null) {
			System.out.print(current.data + " ");
			preOrder(current.leftChild);
			preOrder(current.rightChild);
		}
		}
	/**
	 * infixOrder:中序遍历
	 */
	public void infixOrder(Node current) {
		if (current != null) {
			infixOrder(current.leftChild);
			System.out.print(current.data + " ");
			infixOrder(current.rightChild);
		}
		}
	/**
	 * postOrder:后序遍历
	 */
	public void postOrder(Node current) {
		if (current != null) {
			postOrder(current.leftChild);
			postOrder(current.rightChild);
			System.out.print(current.data + " ");
		}
		}
	/**
	 * findMax:返回最大值
	 */
	public Node findMax() {
		Node current = root;
		Node maxNode = current;
		while (current != null) {
			maxNode = current;
			current = current.rightChild;
		}
		return maxNode;
	}
	/**
	 * findMin:返回最小值
	 */
	public Node findMin() {
		Node current = root;
		Node minNode = current;
		while (current != null) {
			minNode = current;
			current = current.leftChild;
		}
		return minNode;
	}
	/**
	 * find查找节点
	 */
	public Node find(int key) {
		Node current = root;
		while (current != null) {
			if (current.data > key) {
				current = current.leftChild;
			} else if (current.data < key) {
				current = current.rightChild;
			} else {
				return current;
			}
		}
		return null;
	}
	/**
	 * delete删除指定节点
	 */
	public boolean delete(int key) {
		Node current = root;
		Node parent = root;
		boolean isLeftChild = false;
		//查找删除值,找不到直接返回false
		while (current.data != key) {
			parent = current;
			if (current.data > key) {
				isLeftChild = true;
				current = current.leftChild;
			} else {
				isLeftChild = false;
				current = current.rightChild;
			}
			if (current == null) {
				return false;
			}
		}
		//如果当前节点没有子节点
		if (current.leftChild == null && current.rightChild == null) {
			if (current == root) {
				root = null;
			} else if (isLeftChild) {
				parent.leftChild = null;
			} else {
				parent.rightChild = null;
			}
			return true;

			//当前节点有一个子节点,右子节点
		} else if (current.leftChild == null && current.rightChild != null) {
			if (current == root) {
				root = current.rightChild;
			} else if (isLeftChild) {
				parent.leftChild = current.rightChild;
			} else {
				parent.rightChild = current.rightChild;
			}
			return true;
			//当前节点有一个子节点,左子节点
		} else if (current.leftChild != null && current.rightChild == null) {
			if (current == root) {
				root = current.leftChild;
			} else if (isLeftChild) {
				parent.leftChild = current.leftChild;
			} else {
				parent.rightChild = current.leftChild;
			}
			return true;
		} else {
			//当前节点存在两个子节点
			Node successor = getSuccessor(current);
			if (current == root) {
				root = successor;
			} else if (isLeftChild) {
				parent.leftChild = successor;
			} else {
				parent.rightChild = successor;
			}
			successor.leftChild = current.leftChild;
		}
		return false;
	}
	public Node getSuccessor(Node delNode) {
		Node successorParent = delNode;
		Node successor = delNode;
		Node current = delNode.rightChild;
		while (current != null) {
			successorParent = successor;
			successor = current;
			current = current.leftChild;
		}
		//后继节点不是删除节点的右子节点,将后继节点替换删除节点
		if (successor != delNode.rightChild) {
			successorParent.leftChild = successor.rightChild;
			successor.rightChild = delNode.rightChild;
		}
		return successor;
	}

	public static void main(String[] args) {
		BinaryTree bt = new BinaryTree();
		bt.insert(50);
		bt.insert(20);
		bt.insert(10);
		bt.insert(30);
		bt.insert(60);
		bt.insert(100);
		bt.infixOrder(bt.root);
		System.out.println();
		bt.find(30).display();
		bt.delete(20);
		bt.infixOrder(bt.root);
	}
}

执行结果

 

哈夫曼(Huffman)编码

我们知道计算机里每个字符在没有压缩的文本文件中由一个字节(比如ASCII码)或两个字节(比如Unicode,这个编码在各种语言中通用)表示,在这些方案中,每个字符需要相同的位数。

有很多压缩数据的方法,就是减少表示最常用字符的位数量,比如英语中,E是最常用的字母,我们可以只用两位01来表示,2位有四种组合:00、01、10、11,那么我们可以用这四种组合表示四种常用的字符吗?

答案是不可以的,因为在编码序列中是没有空格或其他特殊字符存在的,全都是有0和1构成的序列,比如E用01来表示,X用01011000表示,那么在解码的时候就弄不清楚01是表示E还是表示X的起始部分,所以在编码的时候就定下了一个规则:每个代码都不能是其它代码的前缀。

哈夫曼编码

二叉树中有一种特别的树——哈夫曼树(最优二叉树),其通过某种规则(权值)来构造出一哈夫曼二叉树,在这个二叉树中,只有叶子节点才是有效的数据节点(很重要),其他的非叶子节点是为了构造出哈夫曼而引入的!
哈夫曼编码是一个通过哈夫曼树进行的一种编码,一般情况下,以字符:‘0’与‘1’表示。编码的实现过程很简单,只要实现哈夫曼树,通过遍历哈夫曼树,规定向左子树遍历一个节点编码为“0”,向右遍历一个节点编码为“1”,结束条件就是遍历到叶子节点!因为上面说过:哈夫曼树叶子节点才是有效数据节点!

 我们用01表示S,用00表示空格后,就不能用01和11表示某个字符了,因为它们是其它字符的前缀。在看三位的组合,分别有000,001,010,100,101,110和111,A是010,I是110,为什么没有其它三位的组合了呢?因为已知是不能用01和11开始的组合了,那么就减少了四种选择,同时011用于U和换行符的开始,111用于E和Y的开始,这样就只剩下2个三位的组合了,同理可以理解为什么只有三个四位的代码可用。

所以对于消息:SUSIE SAYS IT IS EASY

哈夫曼编码为:100111110110111100100101110100011001100011010001111010101110

哈夫曼解码

如果收到上面的一串哈夫曼编码,怎么解码呢?消息中出现的字符在哈夫曼树中是叶节点,也就是没有子节点,如下图:它们在消息中出现的频率越高,在树中的位置就越高,每个圆圈外面的数字就是频率,非叶节点外面的数字是它子节点数字的和。

每个字符都从根开始,如果遇到0,就向左走到下一个节点,如果遇到1,就向右。比如字符A是010,那么先向左,再向右,再向左,就找到了A,其它的依次类推。

 

总结

1、树是由边(直线)和节点(圆)组成

2、树的根是最顶端节点,没有父节点

3、二叉树中节点最多两个

4、搜索二叉树时,所有的左子节点都比父节点小,所有右子节点都比父节点大

5、树执行查找、插入、删除的时间复杂度都是O(logN)

6、节点用来存储树中的保存对象

7、中序变量二叉树返回的数据是有序的。

 

/* * 基于链表节点实现二叉树节点 */ package dsa; public class BinTreeNode implements BinTreePosition { protected Object element;//该节点中存放的对象 protected BinTreePosition parent;//父亲 protected BinTreePosition lChild;//左孩子 protected BinTreePosition rChild;//右孩子 protected int size;//后代数目 protected int height;//高度 protected int depth;//深度 /**************************** 构造方法 ****************************/ public BinTreeNode() { this(null, null, true, null, null); } public BinTreeNode( Object e,//节点内容 BinTreePosition p,//父节点 boolean asLChild,//是否作为父节点的左孩子 BinTreePosition l,//左孩子 BinTreePosition r)//右孩子 { size = 1; height = depth = 0; parent = lChild = rChild = null;//初始化 element = e;//存放的对象 //建立与父亲的关系 if (null != p) if (asLChild) p.attachL(this); else p.attachR(this); //建立与孩子的关系 if (null != l) attachL(l); if (null != r) attachR(r); } /**************************** Position接口方法 ********************************/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } /**************************** BinTreePosition接口方法 *************************/ //判断是否有父亲(为使代码描述简洁) public boolean hasParent() { return null != parent; } //返回当前节点的父节点 public BinTreePosition getParent() { return parent; } //设置当前节点的父节点 public void setParent(BinTreePosition p) { parent = p; } //判断是否为叶子 public boolean isLeaf() { return !hasLChild() && !hasRChild(); } //判断是否为左孩子(为使代码描述简洁) //若当前节点有父亲,而且是左孩子,则返回true;否则,返回false public boolean isLChild() { return (hasParent() && this == getParent().getLChild()) ? true : false; } //判断是否有左孩子(为使代码描述简洁) public boolean hasLChild() { return null != lChild; } //返回当前节点的左孩子 public BinTreePosition getLChild() { return lChild; } //设置当前节点的左孩子(注意:this.lChild和c.parent都不一定为空) public void setLChild(BinTreePosition c) { lChild = c; } //判断是否为右孩子(为使代码描述简洁) //若当前节点有父亲,而且是右孩子,则返回true;否则,返回false public boolean isRChild() { return (hasParent() && this == getParent().getRChild()) ? true : false; } //判断是否有右孩子(为使代码描述简洁) public boolean hasRChild() { return null != rChild; } //返回当前节点的右孩子 public BinTreePosition getRChild() { return rChild; } //设置当前节点的右孩子(注意:this.rChild和c.parent都不一定为空) public void setRChild(BinTreePosition c) { rChild = c; } //返回当前节点后代元素的数目 public int getSize() { return size; } //在孩子发生变化后,更新当前节点及其祖先的规模 public void updateSize() { size = 1;//当前节点 if (hasLChild()) size += getLChild().getSize();//左子树的规模 if (hasRChild()) size += getRChild().getSize();//右子树的规模 if (hasParent()) getParent().updateSize();//递归更新各个真祖先的规模记录 } //返回当前节点的高度 public int getHeight() { return height; } //在孩子发生变化后,更新当前节点及其祖先的高度 public void updateHeight() { height = 0;//先假设没有左、右孩子 if (hasLChild()) height = Math.max(height, 1+getLChild().getHeight());//左孩子 if (hasRChild()) height = Math.max(height, 1+getRChild().getHeight());//右孩子 if (hasParent()) getParent().updateHeight();//递归更新各个真祖先的高度记录 } //返回当前节点的深度 public int getDepth() { return depth; } //在父亲发生变化后,更新当前节点及其后代的深度 public void updateDepth() { depth = hasParent() ? 1+getParent().getDepth() : 0;//当前节点 if (hasLChild()) getLChild().updateDepth();//沿孩子引用逐层向下, if (hasRChild()) getRChild().updateDepth();//递归地更新所有后代的深度记录 } //按照中序遍历的次序,找到当前节点的直接前驱 public BinTreePosition getPrev() { //若左子树非空,则其中的最大者即为当前节点的直接前驱 if (hasLChild()) return findMaxDescendant(getLChild()); //至此,当前节点没有左孩子 if (isRChild()) return getParent();//若当前节点是右孩子,则父亲即为其直接前驱 //至此,当前节点没有左孩子,而且是左孩子 BinTreePosition v = this;//从当前节点出发 while (v.isLChild()) v = v.getParent();//沿左孩子链一直上升 //至此,v或者没有父亲,或者是父亲的右孩子 return v.getParent(); } //按照中序遍历的次序,找到当前节点的直接后继 public BinTreePosition getSucc() { //若右子树非空,则其中的最小者即为当前节点的直接后继 if (hasRChild()) return findMinDescendant(getRChild()); //至此,当前节点没有右孩子 if (isLChild()) return getParent();//若当前节点是左孩子,则父亲即为其直接后继 //至此,当前节点没有右孩子,而且是右孩子 BinTreePosition v = this;//从当前节点出发 while (v.isRChild()) v = v.getParent();//沿右孩子链一直上升 //至此,v或者没有父亲,或者是父亲的左孩子 return v.getParent(); } //断绝当前节点与其父亲的父子关系 //返回当前节点 public BinTreePosition secede() { if (null != parent) { if (isLChild()) parent.setLChild(null);//切断父亲指向当前节点的引用 else parent.setRChild(null); parent.updateSize();//更新当前节点及其祖先的规模 parent.updateHeight();//更新当前节点及其祖先的高度 parent = null;//切断当前节点指向原父亲的引用 updateDepth();//更新节点及其后代节点的深度 } return this;//返回当前节点 } //将节点c作为当前节点的左孩子 public BinTreePosition attachL(BinTreePosition c) { if (hasLChild()) getLChild().secede();//摘除当前节点原先的左孩子 if (null != c) { c.secede();//c脱离原父亲 lChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //将节点c作为当前节点的右孩子 public BinTreePosition attachR(BinTreePosition c) { if (hasRChild()) getRChild().secede();//摘除当前节点原先的右孩子 if (null != c) { c.secede();//c脱离原父亲 rChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //前序遍历 public Iterator elementsPreorder() { List list = new List_DLNode(); preorder(list, this); return list.elements(); } //中序遍历 public Iterator elementsInorder() { List list = new List_DLNode(); inorder(list, this); return list.elements(); } //后序遍历 public Iterator elementsPostorder() { List list = new List_DLNode(); postorder(list, this); return list.elements(); } //层次遍历 public Iterator elementsLevelorder() { List list = new List_DLNode(); levelorder(list, this); return list.elements(); } /**************************** 辅助方法 ****************************/ //在v的后代中,找出最小者 protected static BinTreePosition findMinDescendant(BinTreePosition v) { if (null != v) while (v.hasLChild()) v = v.getLChild();//从v出发,沿左孩子链一直下降 //至此,v或者为空,或者没有左孩子 return v; } //在v的后代中,找出最大者 protected static BinTreePosition findMaxDescendant(BinTreePosition v) { if (null != v) while (v.hasRChild()) v = v.getRChild();//从v出发,沿右孩子链一直下降 //至此,v或者为空,或者没有右孩子 return v; } //前序遍历以v为根节的(子)树 protected static void preorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 list.insertLast(v);//访问v preorder(list, v.getLChild());//遍历左子树 preorder(list, v.getRChild());//遍历右子树 } //中序遍历以v为根节的(子)树 protected static void inorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 inorder(list, v.getLChild());//遍历左子树 list.insertLast(v);//访问v inorder(list, v.getRChild());//遍历右子树 } //后序遍历以v为根节的(子)树 protected static void postorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 postorder(list, v.getLChild());//遍历左子树 postorder(list, v.getRChild());//遍历右子树 list.insertLast(v);//访问v } //层次遍历以v为根节的(子)树 protected static void levelorder(List list, BinTreePosition v) { Queue_List Q = new Queue_List();//空队 Q.enqueue(v);//根节点入队 while (!Q.isEmpty()) { BinTreePosition u = (BinTreePosition) Q.dequeue();//出队 list.insertLast(u);//访问v if (u.hasLChild()) Q.enqueue(u.getLChild()); if (u.hasRChild()) Q.enqueue(u.getRChild()); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值