目录
二叉排序树的增删改查
平衡二叉树AVL
1.插入时单旋转
1)若当前节点,| 左子树高度-右子树高度 | >1,则开始准备旋转
a.若当前节点的左孩子的左孩子leftLeft高度 > leftRight ,则左单旋转
b.若当前节点的右孩子的右孩子rightRight高度 > rightLeft,则右单旋转
c. 若当前节点的左孩子的左孩子leftLeft高度 < leftRight , 则先将leftRight一支右单旋转,再整体左 单旋转
d.若当前节点的右孩子的右孩子rightRight高度 < rightLeft, 则先将rightLeft一支左单旋转,再整体
balance的代码为:
单旋转的代码为:
双旋转代码为:
红黑树
红黑树是AVL树的变种,其操作最坏情形下为log2N
1.红黑树的特征
1)根节点必须是黑色节点
2)从根节点到每一个叶子节点的路径上,都应该有相同的黑色节点
3)红色节点若有子节点,则必须两个子节点均为黑色节点
4)红黑树的高度最多为2log(N+1)
5)红黑树的优点是插入的开销相对低,而查找的效率几乎持平AVL树
2.红黑树的增删改查
B树
1.什么是M阶B树
散列表
在java中,参与散列的对象必须提供适当的equals方法与hashCode方法,散列表的核心概念为散列函数,冲突处理方法,装填因子,其中影响散列表查找效率的是装填因子,它代表散列表的饱满程度,饱满度越高,冲突可能性越大,查找的难度就越大。
处理冲突方法包括:拉链法和开放地址法,其中hashMap使用的是拉链法
1)拉链法:散列到同一位置的元素形成一个链,最新的元素放在链头,即使用头插法
2)开放地址法:包括线性探测法,平方探测法,再散列法,在频繁出现hash碰撞的情况下,拉链法肯定优于开放地址法
散列表与hashMap
hashMap采用的就是拉链法,涉及数据结构为数组(黄色部分,一格为一个桶),链表(绿色部分,一格一个节点),红黑树。
分析hashMap的同样从其数据结构开始,包括一些基本性质,基本配置,散列函数,冲突解决方法,装载因子等
基本配置:
1.二叉查找树是TreeSet和TreeMap的实现基础
1)树如何实现操作系统的文件结构
2)树如何计算算数表达式的值
3)如何利用树支持logN平均时间的各种搜索操作,以及如何达到最坏情况时间界logN,并讨论当数据被存放到磁盘上时如何实现这些操作
4)TreeSet和TreeMap类
2.树的性质
3.二叉树的先序遍历,后序遍历,中序遍历
遍历的时间复杂度均为O(n) ,递归的次数正好是栈的深度
1)先序遍历:对节点的处理工作,在其子节点之前
2)后序遍历:对节点的处理工作,在其子节点之后
3)中序遍历:左中右
4)层次遍历
5)递归算法到非递归算法的转换
由先序遍历+中序遍历,后序+中序,层序+中序 = 唯一确定二叉树
4.二叉树的存储结构
1)顺序存储,用一连串相邻的地址空间存储元素,通过计算空间内的值来确定父子关系,适用于满二叉树和完全二叉树
2)链式存储:二叉链表,拥有n个节点的二叉链表就有n+1个空指针
3)线索二叉树:每一个节点新增全局变量leftTag,rightTag,若节点有左孩子,则leftTag=0,leftChild指向左孩子
,否则leftTag=1,leftChild指向其前驱,其前驱若没有右子树,则它的rightTag置1,rightChild指向其后继,也就是该节点
中序线索二叉树建立+遍历代码,线索二叉树的遍历代码,二叉排序树的构建与增删改查
1)双亲表示法:一张顺序表分为data和parent域,但这样找到一个双亲节点的所有孩子节点就需要遍历整张表
2)孩子表示法,顺序表存储每个节点,并指向其所有孩子节点的链表头地址,这样找到双亲节点的所有孩子节点就非常简单,叶子节点没有孩子节点,但如果要找到某个孩子节点的双亲节点,就要遍历所有孩子链
3)孩子兄弟表示法,一张图,它又叫二叉链表,任何非空树都可以找到唯一一棵二叉链表与其一一对应
5.二叉查找树=二叉排序树 -> 平衡二叉树(AVL带有平衡条件的二叉查找树)-> 哈夫曼树 (三者均为二叉排序树)
1)二叉排序树的增删改查(如果预先有序的序列形成二叉排序树,其查找效率将退化为O(N),如果成为平衡二叉树,则效率最高为log2N)
2)平衡二叉树(任意节点的左子树与右子树高度差的绝对值小于1)的增删改查
a.删除分3种情况:若待删除节点 为叶子,则直接删除;只有一个子节点,则父节点直接指向该孩子;有两个子节点,则交换该节点与该节点右子树中序遍历的第一个节点(右子树最小值),然后向其右子树递归删除
b.插入分为3种情况:
单旋转
双旋转
2)左子树与右子树高度差的绝对值小于1,则称为AVL树,平衡二叉树查找效率最高,log2N;单枝二叉树最低,为N
3)如果每个叶子节点的权重X其路径长度之和最小,则称之为哈夫曼树(最优二叉树)
4)哈夫曼算法构造最优二叉树
a.新增一个空节点,从节点集合中取权值最小的两个节点作为该节点的左右孩子,该空节点的权值变为其孩子的和
b.将上述新增空节点加入节点集合中,重复步骤a
B树
1.阶为M的B树的性质
2.访问佛罗里达公民驾驶记录,主键为32个字节,为什么使用B树而不用二叉查找树
3.B树的插入与删除
红黑树
2.红黑树的增删改查
增
若新增的节点为黑色节点:这显然不行,因为一旦加入,必然有一条路径黑色节点比其他路径多
若新增的节点为红色节点:
1)若其父节点为黑色节点,则成功加入
2)若其父节点为红色节点,则违反红色节点的孩子必须是黑色节点的原则,开始颜色的改变与树的旋转,参与者为父节点P,祖父节点G,新增叶子节点X,叔父节点S
a.若叔父节点S为黑色节点(空节点也作黑色处理),则开始单旋转或双旋转,并把新的根涂为黑色
b.若叔父节点S为红色节点,把根涂为黑色会导致出现两个相连的红色节点,所以根必须是红色。此时若曾祖父也是红色,那又得继续调整,这就叫上滤
散列表
由于不论线性表还是树,其查找都需要比对,如有一种key-value关系能直接查找到值,那就是散列表
泛型
1.泛型的主要目的是规定容器能持有什么样的对象,由编译器来保证类型的正确性
2.泛型容器能持有基类时,也能持有其子类
数组
1.数组是一个高效存储,随机访问的线性表,这种性能的代价是数组只能固定大小
2.数组中可以持有基本类型
3.数组中的数据类型必须为具体类型,无法通过new T[10] 这种形式创建泛型数组,但可以创建泛型数组的引用,如T[] a ,并通过它对非泛型数组进行转型
4.协变性
a是b的子类,则f(a)是f(b) 的子类
逆变性
a是b的子类,而f(a)是f(b) 的父类
数组具有协变性,即如下是正确的
B[] bs = new A[10];
泛型集合不具有协变性,即如下是错误的,会在编译期产生异常
List<B> bs = new ArrayList<A>
5.数组常用方法
//数组复制,指定新数组长度,如果新数组大于老数组,则填充0,效率高于for循环复制 Arrays.copyOf(int[] original, int newLength) //先判断数组大小是否相等,再挨个对比内部元素a==b //如果要深入对比里面的对象内容是否相等,需要重写equals和hasCode方法 Arrays.equals() //排序,如果要按对象的某个属性进行排序,该对象需要实现Comparable接口 Arrays.sort()
排序
1.1)几种容易算法均以o(n^2)完成排序
2)部分比较复杂的算法以o(nlogn) 完成排序
3)内排序,所有操作都在内存中完成
4)外排序,由于数据量太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
5)时间复杂度:一个算法执行所消耗的时间
6)空间复杂度:运行一个程序所需要的内存大小
2.插入排序
1)插入排序由n-1趟排序组成,每插入一个数算一趟,每一趟完成一次排序
2)第N趟处理第N-1个元素,由于前N-2个元素已然有序,所以需要将第N-1个元素前移直到其正确位置,然后所有更大的元素后移一个位置
3.冒泡排序
1)比较相邻的元素,若第一个比第二个大,则交换他们两个
2)对每一对相邻的元素重复上述操作,到了最后,最大的肯定是最后一个
3)针对所有元素重复以上的步骤,除了最后那个,因为最后那个已经是最大的了
4)重复步骤1-3,直到排序完成
平均时间复杂度o(n^2),属于稳定排序,属于内部排序
4.选择排序
最简单的排序,不断从无序序列中找出最小(大)的元素,构成另外一个有序的序列,时间复杂度为o(n^2),是不稳定的
5.希尔排序
希尔排序是对插入排序的一种优化,平均时间复杂度o(nlogn),显然他是不稳定的
6.归并排序
归并排序在时间复杂度上优于选择排序,选择排序是稳定o(n^2),而归并排序是稳定o(nlogn),但它需要额外的内存空间,该算法是采用分治法的一个非常典型的应用
1)将
7.快速排序
快速排序也使用分治法,案例如下,平均时间复杂度为o(nlogn)
1)分别申请两个指针low和high,分别指向头和尾,并取low的值为基准数
2)从末端开始,若high的值>基准值,则high--,否则用high的值替换low指向的数
3)再交替为前端开始,若low的值<high的值,则low++,否则用low的值替换high的值
4)如此往复,直到low=high,则这个位置就是基准值得位置,最后小于基准值得数在基准值左边,大于基准值的数在基准值右边
5)递归处理左侧子序列和右侧子序列