前提须知
为了尽快的找到一个好实习,我不得不翻出来基础知识好好复习,并且从头到位把代码都敲了一遍!!!!。复习课程数据结构和算法:尚硅谷av54029771 ,这是尚硅谷的课程链接,我把知识总结全部做了笔记,我在下面的博客会写道,想要更全的可以私信我。本妹子超可又自恋。本文有点长,大家可以通过目录点到你需要看的地方。代码全部正确可以直接用哦!!
放张美女能让你继续看下去!!!噜啦啦啦哈哈哈哈哈我真的是不知廉耻!!!哈哈哈哈啊哈哈啊哈哈哈哈哈哈哈哈哈哈哈哈没错是我自己一位正直可爱的程序员妹子不过正文最重要!!!请看正文
二叉树
为什么需要树这种数据结构
数组的存储方式分析
- 优点,通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
- 缺点,如果要检查到具体某个值,或者插入值(按一定顺序)会整体移动,效率比较低。
- 数组扩容:每次在底层都需要创建新的数组,将原来的数组拷贝到数组,并插入新的数据
链式存储方式的分析
- 优点,在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,连接到链表中即可,删除效率也很好)。
- 缺点,在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)。
树的存储方式分析
- 能提高数据存储,读取的效率,比如利用二叉树排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
二叉树基本概念
- 如果该二叉树的所有叶子节点都在最后一层,并且节点总数=2^n-1,n为层数,则我们称为满二叉树
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
树的示意图
二叉树的遍历
前序遍历
前序遍历说明(preOrder)
- 先输出父节点,再遍历左子树和右子树
1.先输出当前节点(初始节点是root节点)
2.如果是左子节点不为空,则递归继续前序遍历
3.如果右子节点不为空,则递归继续前序遍历
前序遍历代码实现(递归方法)
//前序遍历的方法
public void preOrder(){
//先输出当前节点
System.out.print(this);
//判断左子节点,如果不为空递归遍历左子节点
if(this.left!=null){
this.left.preOrder();
}
//判断右子节点是否为空,如果不为空,遍历右子节点
if(this.right!=null){
this.right.preOrder();
}
}
中序遍历
中序遍历说明(infixOrder)
- 先遍历左子树,在输出父节点,再遍历右子树
1.如果当前节点的左节点部位空,则递归中序遍历
2.输出当前节点
3.如果当前节点的右子节点不为空,则递归继续中序遍历
中序遍历代码实现
//中序遍历方法
public void infixOrder(){
//先判断左子结点
if(this.left!=null){
this.left.infixOrder();
}
//输出该节点
System.out.print(this);
//判断右子节点
if(this.right!=null){
this.right.infixOrder();
}
}
后序遍历
后序遍历说明(postOrder)
- 先遍历左子树,再遍历右子树,最后输出父节点
1.如果当前节点的左子节点不为空,则递归后续遍历
2.如果当前节点的右子节点不为空,则递归后续遍历
3.输出当前节点
后序遍历代码实现
//后续遍历
public void postOrder(){
//判断左子节点
if(this.left!=null){
this.left.postOrder();
}
if(this.right!=null){
this.right.postOrder();
}
System.out.print(this);
}
小结:看输出父节点的顺序,就确定是前序,中序还是后序。
二叉树查找
前序查找
前序查找思路
-
先判断当前节点是否等于要查找的
-
如果相等,则返回当前节点
-
如果不等,则判断当前节点的左子节点是否位空,如果不为空,则递归前序查找
-
如果左递归前序查找找到节点,则返回,否则继续判断,当前右节点,方法同左节点。
前序查找代码实现
//前序查找
public Date preSearch(String name){
//先判断该结点
Date resDate=null;
if(this.name==name){
System.out.println("找到该结点,序号为"+this.no);
return this;
}
//然后再递归查找左子结点
if(this.left!=null){
resDate=this.left.preSearch(name);
}if(resDate!=null){
return resDate;
}
//判断右子结点
if(this.right!=null){
resDate=this.right.preSearch(name);
}
return resDate;
}
中序查找
中序查找思路
1.判断当前节点的左子节点是否为空,如果不为空则递归中序查找。
2.如果找到,则返回,没有找到,就和当前节点比较,如果是则返回当前节点,否则继续进行右递归的中序查找
3.如果右递归中序查找,找到就返回,否则返回空
中序查找代码实现
//中序查找
public Date infixSearch(String name){
Date resDate=null;
//判断左子结点
if(this.left!=null){
resDate=this.left.infixSearch(name);
}if(resDate!=null){
return resDate;
}
//判断该结点
if(this.name==name){
return this;
}
//判断右结点
if(this.right!=null){
resDate=this.right.infixSearch(name);
}
return resDate;
}
后序查找
后序查找思路
1.判断当前左节点是否位空,如果不为空,则递归查找
2.如果找到则返回,没有就判断右子节点是否为空,如果不为空,则右递归进行后续查找,如果找到,就返回。
3.就和当前系欸但进行,比如,如果是则返回,否则返回空。
后序查找代码实现
//后序查找
public Date postSearch(String name){
Date resDate=null;
//判断左子结点
if(this.left!=null){
resDate=this.left.infixSearch(name);
}if(resDate!=null){
return resDate;
}
//判断右结点
if(this.right!=null){
resDate=this.right.infixSearch(name);
}if(resDate!=null){
return resDate;
}
//判断该结点
if(this.name==name){
return this;
}
return resDate;
}
二叉树的结点删除
删除结点的两个要求
1.如果删除的系欸但是叶子节点则删除该节点
2.如果和三处的节点是非叶子节点,则删除该子树
删除结点的思路
1.考虑如果树是空数,如果只有一个节点,则等价将二叉树置空。
2.因为我们的二叉树是单项的,所以我们是判断当前节点的子节点是否需要删除。而不能去判断当前节点是不是需要删除的节点。
3.如果当前节点的左子树不为空,并且左子节点就是需要删除的节点,就将this.left=null;并且返回
4.如果当前节点的右子树不为空,并且右子节点就是需要删除的节点,就将this.right=null;并且返回
5.如果第2和第3都没有删除节点,那么我们需要向左子树进行递归删除。
6.如果第4步还没有成功,则应向右子树进行递归删除。
删除结点的代码实现
//删除结点
public void delete(String name){
//如果左子结点不为空并且是要找的结点
if(this.left!=null&&this.left.name==name){
this.left=null;
System.out.println("删除成功");
}else if(this.right!=null&&this.right.name==name){
this.right=null;
System.out.println("删除成功");
}
//如果都不是的话
else if(this.left!=null){
this.left.delete(name);
}
if(this.right!=null){
this.right.delete(name);
}
}
删除结点的练习
要求
- 如果要删除的系欸但是非叶子节点,规定如下
1.如果该非叶子节点只有一个子节点,那么就将子节点代替被删除
2.如果该非叶子节点有左子节点和右子节点,那么让左子节点代替被删除的点。
练习代码实现
- Binary类中的
//删除结点01
public void delete01(String name){
if(this.root==null){
System.out.println("该树为空,不能删除");
return;
}
if(this.root.name == name){
this.root=null;
return;
}
Date date=root.preSearch(name);
date.delete01();
}
- 底层Date类中的
//第二个代替删除
public Date delete01(){
Date res=null;
//由于是单项的,所以不能判断当前结点
//先找到最开始要删除的点
//先判断是否满足被删除的条件
//1.如果该结点有左节点,并且左节点是叶子结点。删除左节点
if(this.left!=null&&this.left.left==null&&this.left.right==null){
res=this.left;
this.left=null;
return res;
}
//2.如果该结点只有右子结点并且右子结点是叶子结点,那么删除该右子结点
if(this.left==null&&this.right!=null&&this.right.left==null&&this.right.right==null){
res=this.right;
this.right=null;
return res;
}
//一、如果有左子结点的情况下。
//1.判断是否是该结点,该结点的左节点进行递归删除
if(this.left!=null&&(this.left.left!=null||this.right!=null)){
res=this.left.delete01();
}if(res!=null){
System.out.println("成功删除"+res.no+res.name);
return res;
}
//二、右子节点的情况下
//1.判断该结点是否有左子结点
if(this.left==null&&this.right!=null&&(this.right.left!=null||this.right.right!=null)){
res=this.right.delete01();
}if(res!=null){
System.out.println("成功删除"+res.no+res.name);
return res;
}
//没有找到的情况下
return res;
}
顺序二叉树的概念
- 从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
- 要求再遍历数组arr时,仍然可以以前序、中序、后续的方法完成节点的遍历
顺序存储二叉树的特点
1.顺序二叉树通常只考虑完全二叉树
2.第n个元素的左子节点为2*n+1
3.第n个元素的右子节点为2*n+2
4.第n个元素的父节点为(n-1)/2
顺序二叉树的前中后遍历代码实现
//前序遍历数组
public void preOrder(int n,int[] array){
System.out.print(array[n]+"->");
if(n*2+1<array.length){
preOrder(n*2+1,array);
}
if(n*2+2<array.length){
preOrder(n*2+2,array);
}
}
//中序遍历数组
public void infixOrder(int n,int[] array){
if(n*2+1<array.length){
infixOrder(n*2+1,array);
}
System.out.print(array[n]+"->");
if(n*2+2<array.length){
infixOrder(n*2+2,array);
}
}
//后序遍历数组
public void postOrder(int n,int[] array){
if(n*2+1<array.length){
postOrder(n*2+1,array);
}
if(n*2+2<array.length){
postOrder(n*2+2,array);
}
System.out.print(array[n]+"->");
}
线索化二叉树
线索化二叉树基本介绍
- n个节点的二叉链表中含有n+1公式2n-(n-1)=n+1个空指针域,利用二叉链表中的空指针域,存放指向节点再某种遍历次序下的前驱和后继节点的指针(这种附加的节点称为“线索”)
- 这种附加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。根据线索性质的不同,线索二叉树可分为前序线索二叉树,中序线索二叉树和后续线索二叉树三种。
- 说明:当前线索化二叉树后,Node结点的属性left和right,有如下情况
1.left指向的是左子树,也有可能指向前驱结点
2.right指向的是右子树,也可能是指向后继结点。
- 此时还需要再结点中定义以下属性
1.private int leftType;
如果leftType ==0表示指向的是左子树,如果1则表示指向前驱结点
2.private int rightType;
如果rightType ==0表示指向的是右子树,如果1则表示指向后继结点
- 再线索二叉树中新定义
1.除了root以外,新定义pre(初始化为空)
2.为了实现线索化,需要创建要给指定当前结点的前驱结点的指针
中序线索化二叉树代码
//中序线索化线索化该树
public void threadedNode(Date node){
if(node==null)return;
//先线索化左子树
threadedNode(node.left);
//线索化当前结点
if(node.left==null){
node.left=pre;
node.leftType=1;
}
if(pre!=null&&pre.right==null){
pre.right=node;
pre.rightType=1;
}
//!!!每处理一次,让当前结点后移
pre=node;
//线索化右子树
threadedNode(node.right);
}
中序遍历线索二叉树代码
//遍历线索化树
public void threadedList(){
Date node=root;
while (node!=null) {
//先找到最左边的,输出
while (node.leftType == 0) {
node = node.left;
}
System.out.print(node);
//开始找rightType=1的,并全部输出,因为这些也都是后继结点
while (node.rightType == 1) {
System.out.print(node.right);
node = node.right;
}
//没有的话就输出下一个
node = node.right;
}
}
二叉排序树
二叉排序树基本介绍
- 二叉排序树:BST,对于二叉排序树的任何一个非叶子结点,要求左子节点的值比当前结点的值小,右子结点的值比当前结点的值大
- 特别说明:如果有相同的值,可以将该结点放在左子节点或右子节点
二叉排序树创建代码实现
package com.data;
public class BstDemo {
public static void main(String[] args) {
int[] array={3,4,1,5,2};
Bst creatBst=new Bst();
creatBst.creatBst(array);
System.out.println();
}
}
class Bst{
public BstNode root;
//创建二叉排序树
public void creatBst(int[] array){
for (int i=0;i<array.length;i++){
BstNode node=new BstNode();
node.date=array[i];
if(root==null){
root=node;
}else {
root.add(node);
}
}
}
//返回根结点
public BstNode getRoot(){
return root;
}
}
class BstNode{
int date;
BstNode left;
BstNode right;
@Override
public String toString() {
return "Bst{" +
"date=" + date +
'}';
}
//增加结点
public void add(BstNode node){
if(node.date>this.date){
if(this.right==null){
this.right=node;
}else {
this.right.add(node);
}
}
if(node.date<this.date){
if(this.left==null){
this.left=node;
}else {
this.left.add(node);
}
}
}
}
二叉排序树结点的删除
二叉排序树结点删除的三种情况
1.第一种,删除叶子结点
- 需要找到要删除的结点,targetNode
- 找到targetNode的父节点parent
- 确定是父节点的左子节点还是右子结点
- 根据前面的情况来对应删除
2.第二种,删除只有一棵子树的结点
- 需药先找到需要删除的结点 targetNode
- 找到targetNode的父节点 parent
- 确定targetNode的子节点是左子结点还是有子节点
- targetNode是parent的左子节点还是右子结点
3.第三种,删除有两个子树的结点
- 需要找到需要删除的结点targetNode
- 找到targetNode的父节点parent
- 从targetNode的右子树找到最小的结点(或者左子树找到最大的结点)
- 用一个临时变量,将最小的结点保存在temp中
- 删除最小的结点
- targetNode.value=temp
二叉排序树结点删除核心代码实现
//找到需要删除结点的上一个结点
public BstNode searchNodeParent(int date){
BstNode resNode=new BstNode();
if((this.left!=null&&this.left.date==date)||(this.right!=null&&this.right.date==date)){
return this;
}
//先判断左边
if(this.left!=null)resNode=this.left.searchNodeParent(date);
if(resNode!=null)return resNode;
if(this.right!=null)resNode=this.right.searchNodeParent(date);
return resNode;
}
//判断是否是叶子结点
public boolean isLeaf(){
if(this.left==null&&this.right==null)return true;
return false;
}
//找到子树的最小结点的父节点
public BstNode getMin(){
BstNode resNode=new BstNode();
if(this.left!=null&&this.left.left==null){
return this;
}else if(this.left!=null){
resNode=this.left.getMin();
}
return resNode;
}
//删除
public void delete(int date){
//如果是所得到父节点的左节点是要被删除的结点
if(this.left!=null&&this.left.date==date){
//1.如果他是叶子结点
if(this.left.isLeaf()){
this.left=null;//如果该左子结点是要删除的并且是叶子结点。
}
//2.如果只有一个子结点,那么就是那个结点代替他
else if(this.left.left!=null&&this.left.right==null){
this.left=this.left.left;
}
else if(this.left.left==null&&this.left.right!=null){
this.left=this.left.right;
}
//3.如果有左右子节点,那么找到右子树最小的那个结点。
else {
if(this.left.right.left==null){
this.left.date=this.left.right.date;
this.left.right=this.left.right.right;
}else{
//找到最小结点的父节点
BstNode minParent=this.left.right.getMin();
//删除结点
this.right.date=minParent.left.date;
minParent.left=null;
}
}
}
//如果是该结点的右子结点是将被删除的结点
if(this.right!=null&&this.right.date==date){
//1.如果该右子结点是要删除的并且是叶子结点。
if(this.right.isLeaf()){
this.right=null;
}
//2.如果只有一个子结点,那么就是那个结点代替他
else if(this.right.left!=null&&this.right.right==null){
this.right=this.right.left;
}
else if(this.left.left==null&&this.left.right!=null){
this.right=this.right.right;
}
//3.如果有左右子节点,那么找到右子树最小的那个结点。
else {
//找到最小结点的父节点
if(this.right.right.left==null){
this.right.date=this.right.right.date;
this.right.right=this.right.right.right;
}else{
BstNode minParent=this.right.right.getMin();
//删除结点
this.right.date=minParent.left.date;
minParent.left=null;
}
}
}
}
平衡二叉树(AVL树)
平衡二叉树的引入
- 举例{1,2,3,4,5,6}一直都是在 右子树,更像单链表,不能发挥二叉排序树优势。所以引入平衡二叉树
平衡二叉树基本介绍
- 平衡二叉树也叫平衡二叉搜索树,又被称为AVL树,可以保证查询效率较高。
- 具备以下特点:它是一棵空树或他的左右两个子树的高度差绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
获取当前树高度的代码(简单递归,由此判断旋转方向)
//获取当前树的高度
public int getHigh()
{ return Math.max(this.left==null?0:this.left.getHigh(),this.right==null?0:this.right.getHigh())+1;
}
左旋转:处理步骤
1.创建一个新结点,值等于当前根节点的值
2.把新节点的右子树设置为当前结点的右子树的左子树
3.把当前结点的值换位右子节点的值
4.把当前结点的右子树设置成右子树的右子树
5.把当前结点的左子树设置为新节点
左旋转代码实现
//左旋转
/**
1.创建一个新结点,值等于当前根节点的值
2.把新节点的右子树设置为当前结点的右子树的左子树
3.把当前结点的值换位右子节点的值
4把当前结点的右子树设置成右子树的右子树
5把当前结点的左子树设置为新节点
*/
public void leftRotation(){
int tempData=this.date;
BstNode temp=new BstNode();
temp.date=tempData;
temp.left=this.left;
temp.right=this.right.left;
this.date=this.right.date;
this.right=this.right.right;
this.left=temp;
}
右旋转:处理步骤
1.创建一个新的结点,值等于当前跟结点的值
2.把新的结点的右子树设置成当前结点的右子树
3.把新节点的左子树设置成当前结点的左子树的右子树
4.把当前结点的左子树设置成左子树的值
5.把当前结点的左子树设置成左子树的左子树
6.把当前结点的右子树设置为新结点
右旋转代码实现
//右旋转同理
public void rightRotation(){
BstNode temp=new BstNode();
temp.date=this.date;
temp.right=this.right;
temp.left=this.left.right;
this.date=this.left.date;
this.left=this.left.left;
this.right=temp;
}
多叉树
二叉树与B树
二叉树的问题分析
- 二叉树的操作效率较高,但是也存在问题
- 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的结点很多,比如1亿,就会出现如下问题
1.在构建二叉树时,需要多次进行i/o操作,海量数据存在数据库或者文件中,结点海量,构建二叉树时候,速度会有影响。
2.结点海量,也会造成二叉树高度很大,会降低操作速度。
多叉树的基本概念
- 在二叉树中,每个结点有数据项,最多有两个子节点,如果允许每个结点都可以由更多的数据项和更多的子节点,就是多叉树
- 2-3树,2-3-4树都是二叉树,多叉树通过重新组织结点,减少树的高度,能对二叉树进行优化。
- 有三个子节点叫3节点,有两个子节点叫2结点。
B树的基本介绍
- B树是通过重新组织结点,降低对树的高度,并且减少i/o读写次数来提升效率
- 文件系统及数据库系统的设计者利用了磁盘预读原理,将一个结点的大小设为等于一个页(页的大小通常为4k),这样每个结点只需要一次I/o读取就可以完全载入。
- 将树的度(值多少个分查,也可以说子节点)M设置成1024,在600亿个元素中最多只需要4次I/O操作就可以读入想要的元素,B树广泛应用于文件存储系统以及数据库系统中。
2-3树的特点
- 2-3树的所有叶子结点都在同一层。(只要是B树都满足这个条件)
- 有两个子节点的结点叫二结点,二节点要么没有子节点,要么有两个子结点
- 有三个子节点的叫三节点,三节点要么没有子节点,要么有三个子节点。
- 2-3树是由二节点树和三节点数构成的树
- 当按照规则插入一个树到某个结点时,不能满足上面三个要求,就需要拆,先向上拆,如果上层满则拆本层,拆后仍然需要满足上面三个条件
- 对于三节点的子树的值大小仍然遵守(BST二叉排序树)的规则
B树、B+树和B*树
B树的介绍
- B-tree树即B树,B:balance,平衡的意思。有人把B-tree翻译成B-树,容易让人产生误解。会以为不是这不是一种树。实际上这些都是B树。
B树的说明
- B树的阶:结点的最多子节点个数。比如2-3树的阶是3,2-3-4树阶是4
- B-树的搜索,从根节点开始,对结点内的关键字(有序)序列进行二公查找,如果命中则结束,否则进入查询关键字的所属范围的儿子结点,重复,知道所对应的儿子指针为空,或已经是叶子结点
- 关键字集合分布在整棵树中,即叶子结点和非叶子结点都存放数据
- 搜索有可能在非叶子结点结束
- 其搜索性能等价于在关键字全集内做一次二分查找
B+树的介绍
- B+树是B树的变体,也是一种多路搜索树
B+树的说明
- B+树的搜索与B树也基本相同,区别就是只有达到叶子结点才命中,其性能也等价于在关键字全集做一次二分查找
- 所有关键字都出现在叶子结点的链表中(即数据只能在叶子结点中【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
- 不可能在非叶子结点中命中
- 非叶子结点相当于是叶子结点的索引(稀疏索引)。叶子结点相当于是存储(关键字)数据的数据层
- 更适合文件索引系统
- B树和B+树各有自己的应用场景,不能说谁比谁更好。
B*树的介绍
- B*树是B+树的变体,在B+数的非根和非叶子结点在增加指向兄弟的指针
B*树的说明
- B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而B+树的块的最低使用率为B+树的1/2
- 从第1个特点我们可以看出,B*树分配新节点的概率比B+要低,空间使用率高
小结
以上对于二叉树、二叉排序树、多叉树进行了简单介绍和总结。由于不想让篇幅过长,赫夫曼树留到下一次更新。这是一场面试前的突击,要想拿到好的offer一定得基础过硬,因为各种大厂都是很看重我们的基础滴。今后我会将我整理的,学习到的分享到博客来,以及即将迎接的各种面试经历,希望在这观看的你和我都不断努力,争取拿到高薪!!!如有问题,欢迎留言