1.二叉树:n个具有相同类型的数据元素的有限集合;左子树-根-右子树;有序。
2.二叉树的度:最大的节点度数
3.满二叉树:所有分支节点均存在左子树和右子树,且所有叶子节点均在同一层上
4.完全二叉树:存在的节点与满二叉树的节点编号相同,叶子节点只存在最后一层及倒数第二层
5.非空二叉树的i层最多有 2【i-1】个节点
6.深度为k的二叉树至多有2【k】-1个节点
7.给出完全二叉树的节点数则完全二叉树的深度可以确定
8二叉树的顺序存储结构:
完全二叉树可以存放在n个连续存储单元中(子节点与父节点之间的关系用下标进行表现)
/**Java中
* treemap,treemap类是使用红黑树实现的,红黑树是一个自平衡的二叉搜索树
* 自己实现可以采用顺序表或是链表结构进行树的存储
*/
public class HelloWorld{
public static void main(String[] args){
mytree_array mytree = new mytree_array(4);
mytree.print();
mytree.setNode(1,1,20);
mytree.print();
mytree.setNode(2,1,12);
mytree.setNode(2,2,13);
mytree.print();
mytree.setNode(3,2,14);
mytree.setNode(3,3,15);
mytree.print();
mytree.setNode(4,4,16);
mytree.print();
}
}
class mytree_array{
//如果输入直接是完全二叉树的表现形式就失去意义了,这个地方应该从头构建树
private int[] tree ;
public mytree_array(int deep){
//深度为k的二叉树至多节点数为2的k次方-1
//数组第一个节点不存,方便计算
this.tree = new int[(int)(Math.pow(2,deep))];
}
//按照层数和个数来插入节点
public int setNode(int cengshu,int geshu,int data){
//计算应该放置该节点的坐标
int index = (int)(Math.pow(2,cengshu-1))-1+geshu;
// System.out.print(index);
//判断是否该位置已经有节点
if (this.tree[index]!=0){
System.out.print("already token");
return -1;
}
//判断是否为根节点(根节点无父节点)
if (index == 1){
this.tree[index] = data;
return 1;
}
int father;
//判断是左孩子还是右孩子,根据坐标的奇偶
if (index%2 == 0){
father = index/2;
}else {
father = (index-1)/2;
}
//判断是否存在父节点
if (this.tree[father] == 0){
System.out.print("no father node");
return -1;
}else {
this.tree[index] = data;
return 1;
}
}
public void print(){
System.out.println(Arrays.toString(this.tree));
}
}
普通二叉树->补充为完全二叉树->按完全二叉树的方法进行存储(空间浪费较大)
9.二叉树的链表存储结构:
二叉链表:数据域,左孩子指针域,右孩子指针域
三叉链表:parent域,leftchild,rightchild
//之后的遍历应该是用不到查找parent的,在这里就实现二叉链表,三叉链表就是多加一个parent域
class mytree_Linkedlist{
private Node head;
class Node{
int data;
Node leftchild;
Node rightchild;
public Node(int num){
this.data = num;
this.leftchild = null;
this.rightchild = null;
}
}
public mytree_Linkedlist(){
this.head = null;
}
public void addNode(Node parent,int isleft,Node addnode){ //输入是头结点且当且树为空 if (parent == null){ if (this.head == null){ this.head = addnode; }else { System.out.print("already have root"); } }else { //输入的是一般节点 if ((isleft == 1) && (parent.leftchild == null)){ parent.leftchild = addnode; }else { if (parent.rightchild == null){ parent.rightchild = addnode; }else { System.out.print("already have rightchild"); } } } }//链表打印二叉树则涉及到遍历算法,后续实现}
10.二叉树的遍历:
递归遍历:栈实现,顺序唯一
先序遍历:根->左子树->右子树
中序遍历:左子树->根 ->右子树
后序遍历:右子树 -> 根 ->左子树
层次遍历:从上到下逐层遍历,用队列操作,根入队->访问该节点->若有左孩子则入队->若有右孩子则入队
package java_practice;
import java.util.AbstractQueue;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
/**Java中
* treemap,treemap类是使用红黑树实现的,红黑树是一个自平衡的二叉搜索树
* 自己实现可以采用顺序表或是链表结构进行树的存储
*/
class Helloworld
{
public static void main(String[] args)
{
mytree_Linkedlist mytree = new mytree_Linkedlist();
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
Node n6 = new Node(6);
mytree.addNode(null,1,n1);
mytree.addNode(n1,1,n2);
mytree.addNode(n1,0,n3);
mytree.addNode(n2,0,n4);
mytree.addNode(n3,1,n5);
mytree.addNode(n3,0,n6);
mytree.preOrder(mytree.getRoot());
System.out.print("\n");
mytree.InOrder(mytree.getRoot());
System.out.print("\n");
mytree.PostOrder(mytree.getRoot());
System.out.print("\n");
mytree.LevelOrder();
}
}
class Node{
int data;
Node leftchild;
Node rightchild;
public Node(int num){
this.data = num;
this.leftchild = null;
this.rightchild = null;
}
}
class mytree_Linkedlist{
private Node head;
public mytree_Linkedlist(){
this.head = null;
}
public void addNode(Node parent,int isleft,Node addnode){
//输入是头结点且当且树为空
if (parent == null){
if (this.head == null){
this.head = addnode;
}else {
System.out.print("already have root");
}
}else {
//输入的是一般节点
if ((isleft == 1) && (parent.leftchild == null)){
parent.leftchild = addnode;
}else {
if (parent.rightchild == null){
parent.rightchild = addnode;
}else {
System.out.print("already have rightchild");
}
}
}
}
public Node getRoot(){
return this.head;
}
//链表打印二叉树
//先序
public void preOrder(Node temp){
if (temp != null){
System.out.print(temp.data);
preOrder(temp.leftchild);
preOrder(temp.rightchild);
}
}
public void InOrder(Node temp){
if (temp != null){
InOrder(temp.leftchild);
System.out.print(temp.data);
InOrder(temp.rightchild);
}
}
public void PostOrder(Node temp){
if (temp != null){
PostOrder(temp.leftchild);
PostOrder(temp.rightchild);
System.out.print(temp.data);
}
}
//层次遍历,用队列思想,队列用java提供的LinkedList来做
public void LevelOrder() {
LinkedList<Node> que = new LinkedList<Node>();
que.addLast(this.head);
if(this.head == null){
return;
}
//当队列不空时
while (que.size()!=0){
Node now = que.removeFirst();
System.out.print(now.data);
if (now.leftchild != null){
que.addLast(now.leftchild);
}
if (now.rightchild != null){
que.addLast(now.rightchild);
}
}
}
}
//中序,从左子树返回时遇到节点就访问
public void printbystack_2(){
Stack<Node> st = new Stack<Node>();
Node temp = this.head;
while ((temp!=null) || (!st.empty())){
while (temp!=null){
st.push(temp);
temp = temp.leftchild;
}
if (!st.empty()){
Node e = st.pop();
System.out.print(e.data);
temp = e.rightchild;
}
}
}
//先序,在深入时遇到节点就访问
public void printbystack_1(){
Stack<Node> st = new Stack<Node>();
Node temp = this.head;
while ((temp!=null) || (!st.empty())){
while (temp!=null){
System.out.print(temp.data);
st.push(temp);
temp = temp.leftchild;
}
if (!st.empty()){
Node e = st.pop();
temp = e.rightchild;
}
}
}
//后序遍历需要多用空间进行记录,从右子树返回时遇到节点就访问
public static void printbystack_3(Node t) {
Stack<Node> s = new Stack<Node>();
Stack<Integer> s2 = new Stack<Integer>();
Integer i = new Integer(1);
while (t != null || !s.empty()) {
while (t != null) {
s.push(t);
s2.push(new Integer(0));
t = t.leftchild;
}
while (!s.empty() && s2.peek().equals(i)) {
s2.pop();
System.out.print(s.pop().data);
}
if (!s.empty()) {
s2.pop();
s2.push(new Integer(1));
t = s.peek();
t = t.rightchild;
}
}
}
*二叉树的先序及中序,或是后序及中序可以唯一确定一棵二叉树;
先序+中序按照书上伪代码实现的。居然真的能直接运行。
public void reConstructBinaryTreeCore(int[] pre, int[] in, int preStart, int preEnd, int inStart, int inEnd,Node tree) {
//创建当前节点
tree = new Node(pre[preStart]);
int m = inStart;
//在中序中定位根
while (in[m] != pre[preStart]){
m++;
}
//递归建立左子树
if (m == inStart){
tree.leftchild = null;
}else {
reConstructBinaryTreeCore(pre,in,preStart+1,preStart+m-inStart,inStart,m-1,tree.leftchild);
}
//递归建立右子树
if (m == inEnd){
tree.rightchild = null;
}else {
reConstructBinaryTreeCore(pre,in,preStart+m-inStart+1,preEnd,m+1,inEnd,tree.rightchild);
}
}
int[] a = {1,2,4,3,5,6};
int[] b = {2,4,1,5,3,6};
mytree.reConstructBinaryTreeCore(a, b, 0, 5, 0, 5, mytree.getRoot());
//输入分别是前序,中序,起始角标,个数,起始角标,个数,当前用于存储的节点;
中序加后序的过程是类似的,应该也可以用这个递归的思路进行。重点在于递归时脚标的确定。
public void reConstructBinaryTreeCore(int[] pre, int[] in, int preStart, int preEnd, int inStart, int inEnd,Node tree) {
tree = new Node(pre[preEnd]);
int m = inStart;
//在中序中定位根
while (in[m] != pre[preEnd]){
m++;
}
//递归建立左子树
if (m == inStart){
tree.leftchild = null;
}else {
reConstructBinaryTreeCore(pre,in,preStart,preStart+m-inStart-1,inStart,m-1,tree.leftchild);
}
//递归建立右子树
if (m == inEnd){
tree.rightchild = null;
}else {
reConstructBinaryTreeCore(pre,in,preStart+m-inStart,preEnd-1,m+1,inEnd,tree.rightchild);
}
}
11.二叉树的叶子统计:递归
public int getleefnum(Node temp){
if (temp == null){
return 0;
}
if (temp.leftchild == null & temp.rightchild == null){
return 1;
}
return getleefnum(temp.leftchild)+getleefnum(temp.rightchild);
}
二叉树的深度计算: 递归
public int getdeep(Node temp){
if (temp == null){
return 0;
}
if (temp.leftchild == null & temp.rightchild == null){
return 1;
}
int deep_1 = getdeep(temp.leftchild);
int deep_2 = getdeep(temp.rightchild);
return deep_1>deep_2?1+deep_1:1+deep_2;
}
12.线索二叉树:将二叉树以某种次序遍历使其成为线索二叉树
lchild Ltag data Rtag rchild
前驱节点:(中序为例)直接指向或是以该节点的左孩子为根节点的子树的最右最下节点
后驱节点:(中序为例)直接指向或是以该节点的右孩子为根节点的子树的最左最下节点
线索化过程:检查节点的左右指针域是否为空,若为空则修改为其前驱或后继节点的指针,可以先得到遍历顺序再进行检查和修改
13.二叉排序树:左子树节点的值小于根节点,右子树节点的值大于根节点,且均为二叉排序树
class orderTree extends Tree{
//二叉排序树的中序遍历序列应为有序序列,在这个地方对其进行了判断(手动)
ArrayList<Integer> li = new ArrayList<Integer>();
boolean index = true;
public void checkanswer(){
checkorder(this.getRoot());
System.out.print(this.index);
}
public void checkorder(Node st){
if (st!=null) {
checkorder(st.leftchild);
if (this.li.size() != 0) {
int temp = this.li.get(this.li.size() - 1);
int now = st.data;
System.out.println(temp + ":" + now);
if (temp > now) {
this.index = false;
}
this.li.add(now);
} else {
this.li.add(st.data);
}
checkorder(st.rightchild);
}
}
}
查找:与根比较。若小于则转向左子树,若大于则转向右子树
public Node getbydata(int data){
if (this.index == false){
System.out.print("not a order tree");
return null;
}
if (this.head == null){
System.out.print("null tree");
return null;
}
return compare(this.head,data);
}
public Node compare(Node compre,int data){
if (compre == null || compre.data == data){
return compre;
}
if (compre.data>data){
return compare(compre.leftchild,data);
}
else{
return compare(compre.rightchild,data);
}
}
插入:访问一个节点,若大于它,则查看其是否有右孩子,没有则新节点作为右孩子,若有则继续和其右节点比较。其余思路类似。采用记录最后访问的一个节点的思路较复杂。
public void insert(int data){
Node inse = new Node(data);
if (this.head == null){
this.head = inse;
}
Node temp = this.head;
while (temp!=null){
if (temp.data > data){
if (temp.leftchild == null){
temp.leftchild = inse;
return;
}else {
temp = temp.leftchild;
}
}
else if (temp.data<data){
if (temp.rightchild == null){
temp.rightchild = inse;
return;
}else {
temp = temp.rightchild;
}
}
else {
System.out.print("already exit");
return;
}
}
}
删除:若为叶子节点,直接删除;若单分支,删除,分支挂在父节点上;双分支,按中序遍历保持有序,中序中的前驱代替该节点,删除该前驱。
public void delete(int data){
Node need = getbydata(data);
if (need == null){
System.out.print("no such node");
return;
}
//叶子节点情况
if (need.leftchild == null & need.rightchild == null){
Node father = findfater(data);
if (father!=null){
if (father.leftchild != null && father.leftchild.data == data){
father.leftchild = null;
}else {
father.rightchild = null;
}
}else {
//此节点无父节点
this.head = null;
}
return;
}
//单分支结构
else if (need.leftchild == null){
Node father = findfater(data);
if (father!=null){
if (father.leftchild != null && father.leftchild.data == data){
father.leftchild = need.rightchild;
}else {
father.rightchild = need.rightchild;
}
}else {
//此节点无父节点
this.head = need.rightchild;
}
return;
}
else if (need.rightchild == null){
Node father = findfater(data);
if (father!=null){
if (father.rightchild != null && father.rightchild.data == data){
father.rightchild = need.leftchild;
}else {
father.leftchild = need.leftchild;
}
}else {
//此节点无父节点
this.head = need.leftchild;
}
return;
}
//左右节点均在,在节点的左分支延右指针向下找,直到右指针为空
else {
Node temp = need.leftchild;
while (temp.rightchild!=null){
temp = temp.rightchild;
}
//删除temp节点
Node father_temp = findfater(temp.data);
if (father_temp.rightchild!=null && father_temp.rightchild.data == temp.data){
if (temp.leftchild!=null){
father_temp.rightchild = temp.leftchild;
}else {
father_temp.rightchild = null;
}
}else {
if (temp.leftchild!=null){
father_temp.leftchild = temp.leftchild;
}else {
father_temp.leftchild = null;
}
}
//替换该节点
Node copy = new Node(temp.data);
copy.leftchild = need.leftchild;
copy.rightchild = need.rightchild;
Node father = findfater(data);
if (father!=null){
if (father.rightchild != null && father.rightchild.data == data){
father.rightchild = copy;
}else {
father.leftchild = copy;
}
}else {
//此节点无父节点
this.head = copy;
}
return;
}
}
public Node findfater(int data){
Node temp = this.head;
while (temp!=null){
if (temp.data == data){
return null;
}
else if (temp.data<data){
if (temp.rightchild!=null){
if (temp.rightchild.data == data){
return temp;
}else {
temp = temp.rightchild;
}
}else {
return null;
}
}
else {
if (temp.leftchild!=null){
if (temp.leftchild.data == data){
return temp;
}else {
temp = temp.leftchild;
}
}else {
return null;
}
}
}
return null;
}
14.平衡二叉树:每个节点的左右子树深度之差不超过1的二叉排序树
调整策略见 P101
15.Huffman tree(最优二叉树):带权路径长度最小
构造算法:选最小的两个,求和再比较再选最小的两个
/**
* for the test
*
* @auther:dan
* @version:1.0
* @date:2017.11.16
*/
package java_practice;
import org.omg.PortableInterceptor.INACTIVE;
import test_package.Tree;
import java.util.*;
/**Java中
* treemap,treemap类是使用红黑树实现的,红黑树是一个自平衡的二叉搜索树
* 自己实现可以采用顺序表或是链表结构进行树的存储
*/
public class HelloWorld {
public static void main(String[] args) {
Node t1 = new Node(7,"a");
Node t2 = new Node(5,"b");
Node t3 = new Node(3,"c");
Node t4 = new Node(4,"d");
List<Node> st = new ArrayList<Node>();
st.add(t1);
st.add(t2);
st.add(t3);
st.add(t4);
huffmanTree tre = new huffmanTree();
Node root = tre.create(st);
System.out.print(tre.toString(root));
List<Integer> hh= tre.getcode("d");
System.out.print(hh);
//
Node output = tre.decode(hh);
System.out.print(output);
}
}
class Node implements Comparable{
String data;
int weight;
Node leftchild;
Node rightchild;
Node parent;
Node(int weight,String data){
this.weight = weight;
this.data = data;
}
@Override
public int compareTo(Object o) {
Node other = (Node)o;
if (this.weight>other.weight){
return -1;
}
else if (this.weight<other.weight){
return 1;
}else{
return 0;
}
}
public String toString(){
return this.data;
// return String.valueOf(this.weight);
}
}
class huffmanTree{
Node head = null;
public Node create(List<Node> nodes){
while (nodes.size()>1){
//排序
Collections.sort(nodes);
//最小的两个组成叶子,生成新节点
Node left = nodes.get(nodes.size()-1);
Node right = nodes.get(nodes.size()-2);
Node partent = new Node(left.weight+right.weight,"----");
partent.leftchild = left;
partent.rightchild = right;
left.parent = partent;
right.parent = partent;
//构造新序列
nodes.remove(left);
nodes.remove(right);
nodes.add(partent);
}
this.head = nodes.get(0);
return nodes.get(0);
}
//按层次打印haffman树
public static List<Node> toString(Node root){
List<Node> nodes = new ArrayList<Node>();
Queue<Node> que = new ArrayDeque<Node>();
if (root!=null){
//插入末尾
que.offer(root);
}
while (que.isEmpty() == false){
//获取但不移除
nodes.add(que.peek());
Node temp = que.poll();
// nodes.add(temp);
if (temp.leftchild!=null){
que.offer(temp.leftchild);
}
if (temp.rightchild!=null){
que.offer(temp.rightchild);
}
}
return nodes;
}
//编码
public List getcode(String data){
List<Node> li = toString(this.head);
Node st = null;
for(Node temp :li){
if (temp.data == data){
st = temp;
}
}
if (st == null){
return null;
}
ArrayList<Integer> re = new ArrayList<Integer>();
Node temp = st.parent;
while (temp!=null){
if (st.parent.leftchild == st){
re.add(1);
}else {
re.add(0);
}
temp = temp.parent;
}
Collections.reverse(re);
return re;
}
//解码
public Node decode(List<Integer> input){
Node temp = this.head;
for (int e:input){
if (e == 1){
temp = temp.leftchild;
}else {
temp = temp.rightchild;
}
}
return temp;
}
}
16.树的存储结构
双亲孩子表示法:数组+链表
兄弟表示法:二叉链表 firstchild ,nextsibiling 记录第一个孩子节点及下一个兄弟节点
17.树转化为二叉树:对一棵树可以找到一棵二叉树与之对应,二叉树转换回来->树/森林
兄弟节点相连->保留与最左孩子的连线,其余删除(兄弟表示法)
18.树的遍历:先根遍历,后根遍历,层次遍历
19.B树:文件系统的重要管理方式,多路平衡查找树
介绍:https://www.cnblogs.com/George1994/p/7008732.html
B+树:
内部节点中,关键字的个数与其子树的个数相同,不像B树种,子树的个数总比关键字个数多1个
所有指向文件的关键字及其指针都在叶子节点中,不像B树,有的指向文件的关键字是在内部节点中。换句话说,B+树中,内部节点仅仅起到索引的作用,
在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而是继续向下搜索这个分支
优点:
B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说I/O读写次数也就降低了。
B+树的查询效率更加稳定
由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
B+树更有利于对数据库的扫描
B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以对于数据库中频繁使用的range query,B+树有着更高的性能。
20.堆:ki<=k2i,ki<=k2i+1,完全二叉树中所有非终端节点均不大于或小于左右孩子的值。大顶堆则根是最大的一个节点。
堆排序算法:输出堆顶,剩余元素再次形成一个堆。nlog2[n]。
小顶堆筛选:去掉堆顶,把最后一个元素放在堆顶,进行交换。
堆建立:从最后一个非叶子节点开始到根重复筛选过程。
/**
* for the test
*
* @auther:dan
* @version:1.0
* @date:2017.11.16
*/
package java_practice;
import org.omg.PortableInterceptor.INACTIVE;
import test_package.Tree;
import java.util.*;
/**Java中
* treemap,treemap类是使用红黑树实现的,红黑树是一个自平衡的二叉搜索树
* 自己实现可以采用顺序表或是链表结构进行树的存储
*/
public class HelloWorld {
public static void main(String[] args) {
//顺序表存储的树
int[] data = { 15, 13, 1, 5, 20, 12, 8, 9, 11 };
// 测试建堆
heap he = new heap();
// he.creat(data, data.length - 1);
// System.out.println(Arrays.toString(data));
he.heapsort(data);
System.out.print(Arrays.toString(data));
}
}
class heap{
//i是要替换的位置,n是数组最后一位,只是一个节点的调整过程
public static void fixDown(int[] data, int i, int n) {
int num = data[i];
//左儿子,因为没有头结点
int son = i * 2 + 1;
while (son <= n) {
//如果有右儿子且右儿子更小,切换到右儿子
if (son + 1 <= n && data[son + 1] < data[son])
son++;
//如果当前节点小于儿子,结束
if (num < data[son])
break;
//和儿子交换
data[i] = data[son];
i = son;
son = i * 2 + 1;
}
data[i] = num;
}
public static void creat(int[] data, int n) {
//从一半的元素开始调整,一直调整到根
for (int i = (n - 1) / 2; i >= 0; i--)
fixDown(data, i, n);
}
//第n个数向上调整的过程
public static void fixUp(int[] data, int n) {
int num = data[n];
int father = (n - 1) / 2;
// data[father] > num是进入循环的基本条件,father减到0就不会减少了
// 当n等于0时,father=0;进入死循环,所以当n==0时,需要跳出循环
while (data[father] > num && n != 0) {
data[n] = data[father];
n = father;
father = (n - 1) / 2;
}
data[n] = num;
}
// 删除,n表示堆的最后一个元素的索引
public static void delete(int[] data, int n) {
data[0] = data[n];
data[n] = -1;
fixDown(data, 0, n - 1);
}
// 增加,i表示要增加的数字,n表示增加位置的索引,是堆的最后一个元素
public static void insert(int[] data, int num, int n) {
data[n] = num;
fixUp(data, n);
}
//堆排序
public void heapsort(int[] data){
creat(data,data.length-1);
for (int i = data.length-1;i>=1;i--){
int temp = data[0];
data[0] = data[i];
data[i] = temp;
fixDown(data,0,i-1);
}
}
}