1.栈
栈(stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶 (top)。它是后进先出(LIFO)的。对栈的基本操作只有 push(进栈)和 pop(出栈)两种, 前者相当于插入,后者相当于删除最后的元素。
2.队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的 后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为 队尾,进行删除操作的端称为队头。
3.链表
链表是一种数据结构,和数组同级。比如,Java 中我们使用的 ArrayList,其实现原理是数组。而 LinkedList 的实现原理就是链表了。链表在进行循环遍历时效率不高,但是插入和删除时优势明显。
4.散列表
散列表(Hash table,也叫哈希表)是一种查找算法,与链表、树等算法不同的是,散列表算法 在查找时不需要进行一系列和关键字(关键字是数据元素中某个数据项的值,用以标识一个数据 元素)的比较操作。 散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必 须要在数据元素的存储位置和它的关键字(可用 key 表示)之间建立一个确定的对应关系,使每个 关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定 关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用 h(key)表示)。
用的构造散列函数的方法有:
(1)直接定址法: 取关键字或关键字的某个线性函数值为散列地址。 即:h(key) = key 或 h(key) = a * key + b,其中 a 和 b 为常数。
(2) 数字分析法:
(3) 平方取值法: 取关键字平方后的中间几位为散列地址。
(4) 折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
(5) 除留余数法:取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址, 即:h(key) = key MOD p p ≤ m
(6)随机数法:选择一个随机函数,取关键字的随机函数值为它的散列地址, 即:h(key) = random(key)
5.树
树的术语:
- 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
- 根:树顶端的节点称为根,一棵树只有一个根,如果要把节点和边的集合称为树,那么从根到其他任何一个节点 都必须有且只有一条路径。
- 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点。
- 叶节点:没有子节点的节点称为叶节点,也叫叶子节点。
- 子树:每个节点都可以作为子树的根,它和它所有的子节点,子节点的子节点都包含在在子树中。
节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。 - 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0,(从上往下看)。
- 高度:对于任意节点n,n的高度在从n到一片树叶的最长路径长,所有树叶的高度为0,(从下往上看)。
- 度:结点拥有的子树数目称为结点的度。
数据结构中有很多种类的树结构。例如:普通二叉树、完全二叉树、满二叉树、线索二叉树、哈夫曼树、二叉搜索树(排序树)、平衡二叉树、AVL平衡二叉树、红黑树、B树、B+树、堆
5.1二叉树
最多有两棵子树的树被称为二叉树
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
5.2排序二叉树
如果普通二叉树每个节点满足:左子树所有节点值小于它的根节点值,且右子树所有节点值 大于它的根节点值,则这样的二叉树就是排序二叉树或二叉搜索树。
5.2.1插入操作
首先要从根节点开始往下找到自己要插入的位置(即新节点的父节点);具体流程是:新节点与当前节点比较,如果相同则表示已经存在且不能再重复插入;如果小于当前节点,则到左子树中寻找,如果左子树为空则当前节点为要找的父节点,新节点插入到当前节点的左子树即可;如果 大于当前节点,则到右子树中寻找,如果右子树为空则当前节点为要找的父节点,新节点插入到 当前节点的右子树即可。
5.2.2删除操作
删除操作主要分为三种情况,即要删除的节点无子节点,要删除的节点只有一个子节点,要删除 的节点有两个子节点。
1.对于要删除的节点无子节点可以直接删除,即让其父节点将该子节点置空即可。
2.对于要删除的节点只有一个子节点,则替换要删除的节点为其子节点。
3. 对于要删除的节点有两个子节点,则首先找该节点的替换节点(即右子树中最小的节点), 接着替换要删除的节点为替换节点,然后删除替换节点。
5.2.3查询操作
查找操作的主要流程为:先和根节点比较,如果相同就返回,如果小于根节点则到左子树中 递归查找,如果大于根节点则到右子树中递归查找。因此在排序二叉树中可以很容易获取最 大(最右最深子节点)和最小(最左最深子节点)值。
(下图是错误的不是二叉树)
5.2.4遍历
二叉搜索树遍历节点:
遍历树是根据一种特定的顺序访问树的每一个节点,比较常用的有前序遍历,中序遍历,后续遍历,而二叉搜索树最常用的是中序遍历。
中序遍历:左子树–》根节点–》右子树
前序遍历:根节点–》左子树–》右子树
后序遍历:左子树–》右子树–》根节点
5.2.5应用
二分查找时间复杂度
public static void main(String[] args) {
int[] arr = {1, 2, 3, 56, 77, 21, 7, 10};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
System.out.println(dichotomizingSearch(arr,9));
}
private static int dichotomizingSearch(int[] arr, int value) {
int low = 0; //1
int high = arr.length - 1;//1
while (low <= high) {
int middle = (low + high) / 2;
if (value == arr[middle]) {
return middle;
}
if (value > arr[middle]) {
low = middle + 1;
}
if (value < arr[middle]) {
high = middle - 1;
}
}
return -1;
}
n/2^k = 1
k = logn
T(n) = logn * 5 + 3 = logn = O(logn)
优点:相对于暴力算法,其性能还是非常不错的,每次迭代查询可以排除掉一半的结果。
缺点:必须是有序数组,性能才能不错
二叉树时间复杂度
N/(2^k)>=1
O(log2(N))=>O(log N)
优点:不用必须是有序数组
缺点:斜树的情况下,类似链表,性能比较低
二叉搜索树退化成线性链表的问题?
5.2.6 AVL树
AVL树特点:
1.具有二叉树全部特性
2.每个节点的左子树与右子树的高度差至多等于1
平衡二叉树基于本身特点就可以保证大量节点不会偏向于一边。插入或者删除的时候,会发生左旋,右旋操作,使这棵树再次左右保持一定的平衡
5.3红黑树
背景:
虽然平衡树解决了二叉叉查找树退化为近似链表的缺点,能够把查找时间控制在O(logN),不过还不是最佳。
因为平衡二叉树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在太严格,导致每次插入删除节点的时候,几乎都会破坏平衡二叉树的第二规则。进而我们都需要通过左旋和右旋来进行调整,使其再次成为一颗符合要求的平衡树。显然在,插入删除很频繁的业务场景中,平衡树需要频繁的进行调整,这会使平衡树的性能大打折扣,为了解决这个问题有了红黑树。
简介:
R-B Tree,全称是 Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
5.3.1红黑树特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
根据性质5可以推出:如果一个节点存在黑子节点,那么该节点肯定有两个子节点
红黑树并不是一个完美平衡二叉查找树,从上图可以看出根节点90的右子树要略高于左子树,但是左子树和右子树的黑节点的层数是相等的。也即任意一个节点到每个叶子结点的路径都包含数量相同的黑节点(即性质5)。所以我们称红黑树这种平衡为黑色完美平衡。
5.3.2左旋
对 x 进行左旋,意味着,将“x 的右孩子”设为“x 的父亲节点”;即,将 x 变成了一个左节点(x 成为了y的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。
5.3.2右旋
对 x 进行右旋,意味着,将“x 的左孩子”设为“x 的父亲节点”;即,将 x 变成了一个右节点(x 成了为 y 的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。
5.3.2新增
粗略归纳步骤:
1.查找插入位置
2.自平衡
第一步: 将红黑树当作一颗二叉查找树,将节点插入。
第二步:将插入的节点着色为"红色"。 根据被插入节点的父节点的情况,可以将"当节点 z 被着色为红色节点,并插入二叉树"划分为三 种情况来处理。
1.情况说明:被插入的节点是根节点。 处理方法:直接把此节点涂为黑色。
2.情况说明:被插入的key值已存在,直接覆写value。
3.情况说明:被插入的节点的父节点是黑色。 处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
4.情况说明:被插入的节点的父节点是红色且是祖父节点的左子节点。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为 3 种情况(Case)。
(1)当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色
①将“父节点”设为黑色。
②将“叔叔节点”设为黑色。
③将“祖父节点”设为红色。
④将“祖父节点”设为“当前节点(红色节点)”,之后继续对“当前节点”进行操作。
(2)当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
①将“父节点”设为黑色。
②将“祖父节点”设为红色。
③以“祖父节点”为支点进行右旋。
(3)当前节点的父亲节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
①将“父节点”作为“新的当前节点”。
②以“新的当前节点”为支点进行左旋。
第三步:通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。