红黑树与平衡二叉树_百图详解红黑树,想不理解都难

之前在公司组内分享了红黑树的工作原理,今天把它整理下发出来,希望能对大家有所帮助,对自己也算是一个知识点的总结。

这篇文章算是我写博客写公众号以来画图最多的一篇文章了,没有之一,我希望尽可能多地用图片来形象地描述红黑树的各种操作的前后变换原理,帮助大家来理解红黑树的工作原理,下面,多图预警开始了。

在讲红黑树之前,我们首先来了解下下面几个概念:二叉树,排序二叉树以及平衡二叉树。

二叉树

二叉树指的是每个节点最多只能有两个字数的有序树。通常左边的子树称为左子树 ,右边的子树称为右子树 。这里说的有序树强调的是二叉树的左子树和右子树的次序不能随意颠倒。

二叉树简单的示意图如下:

59f2500a1634cef006e8d458d871e7c0.png

代码定义:

class Node {T data;Node left;Node right;
}

排序二叉树

所谓排序二叉树,顾名思义,排序二叉树是有顺序的,它是一种特殊结构的二叉树,我们可以对树中所有节点进行排序和检索。

性质
  • 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若她的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 具有递归性,排序二叉树的左子树、右子树也是排序二叉树。

排序二叉树简单示意图:

a8217217115639ca3ec986a681a5c006.png

排序二叉树退化成链表

排序二叉树的左子树上所有节点的值小于根节点的值,右子树上所有节点的值大于根节点的值,当我们插入一组元素正好是有序的时候,这时会让排序二叉树退化成链表。

正常情况下,排序二叉树是如下图这样的:

965fbd0fb859750ab4ab0f37ef1a1a52.png

但是,当插入的一组元素正好是有序的时候,排序二叉树就变成了下边这样了,就变成了普通的链表结构,如下图所示:

047067a132e7ce3ad93f620c993d3ec9.png

正常情况下的排序二叉树检索效率类似于二分查找,二分查找的时间复杂度为 O(log n),但是如果排序二叉树退化成链表结构,那么检索效率就变成了线性的 O(n) 的,这样相对于 O(log n) 来说,检索效率肯定是要差不少的。

思考,二分查找和正常的排序二叉树的时间复杂度都是 O(log n),那么为什么是O(log n) ?

关于 O(log n) 的分析下面这篇文章讲解的非常好,感兴趣的可以看下这篇文章 二分查找的时间复杂度.md),文章是拿二分查找来举例的,二分查找和平衡二叉树的时间复杂度是一样的,理解了二分查找的时间复杂度,再来理解平衡二叉树就不难了,这里就不赘述了。

继续回到我们的主题上,为了解决排序二叉树在特殊情况下会退化成链表的问题(链表的检索效率是 O(n) 相对正常二叉树来说要差不少),所以有人发明了平衡二叉树红黑树类似的平衡树。

平衡二叉树

平衡二叉数又被称为 AVL 树,AVL 树的名字来源于它的发明作者 G.M. Adelson-Velsky 和 E.M. Landis,取自两人名字的首字母。

官方定义:它或者是一颗空树,或者具有以下性质的排序二叉树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。

两个条件:

  • 平衡二叉树必须是排序二叉树,也就是说平衡二叉树他的左子树所有节点的值必须小于根节点的值,它的右子树上所有节点的值必须大于它的根节点的值。
  • 左子树和右子树的深度之差的绝对值不超过1。

红黑树

讲了这么多概念,接下来主角红黑树终于要上场了。

为什么有红黑树?

其实红黑树和上面的平衡二叉树类似,本质上都是为了解决排序二叉树在极端情况下退化成链表导致检索效率大大降低的问题,红黑树最早是由 Rudolf Bayer 于 1972 年发明的。

红黑树首先肯定是一个排序二叉树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是 RED 或 BLACK 。

Java 中实现红黑树大概结构图如下所示:

e16986036522ef6dcfe8273408336b94.png

红黑树的特性

  • 性质1:每个节点要么是红色,要么是黑色。
  • 性质2:根节点永远是黑色的。
  • 性质3:所有的叶子节点都是空节点(即null),并且是黑色的。
  • 性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点。)
  • 性质5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

针对上面的 5 种性质,我们简单理解下,对于性质 1 和性质 2 ,相当于是对红黑树每个节点的约束,根节点是黑色,其他的节点要么是红色,要么是黑色。

对于性质 3 中指定红黑树的每个叶子节点都是空节点,而且叶子节点都是黑色,但 Java 实现的红黑树会使用 null 来代表空节点,因此我们在遍历 Java里的红黑树的时候会看不到叶子节点,而看到的是每个叶子节点都是红色的,这一点需要注意。

对于性质 5,这里我们需要注意的是,这里的描述是从任一节点,从任一节点到它的子树的每个叶子节点黑色节点的数量都是相同的,这个数量被称为这个节点的黑高。

如果我们从根节点出发到每个叶子节点的路径都包含相同数量的黑色节点,这个黑色节点的数量被称为树的黑色高度。树的黑色高度和节点的黑色高度是不一样的,这里要注意区分。

其实到这里有人可能会问了,红黑树的性质说了一大堆,那是不是说只要保证红黑树的节点是红黑交替就能保证树是平衡的呢?

其实不是这样的,我们可以看来看下面这张图:

00dbf48fba4f52e8568e1ef1ab136f90.png

左边的子树都是黑色节点,但是这个红黑树依然是平衡的,5 条性质它都满足。

这个树的黑色高度为 3,从根节点到叶子节点的最短路径长度是 2,该路径上全是黑色节点,包括叶子节点,从根节点到叶子节点最长路径为 4,每个黑色节点之间会插入红色节点。

通过上面的性质 4 和性质 5,其实上保证了没有任何一条路径会比其他路径长出两倍,所以这样的红黑树是平衡的。

其实这算是一个推论,红黑树在最差情况下,最长的路径都不会比最短的路径长出两倍。其实红黑树并不是真正的平衡二叉树,它只能保证大致是平衡的,因为红黑树的高度不会无限增高,在实际应用用,红黑树的统计性能要高于平衡二叉树,但极端性能略差。

红黑树的插入

想要彻底理解红黑树,除了上面说到的理解红黑树的性质以外,就是理解红黑树的插入操作了。

红黑树的插入和普通排序二叉树的插入基本一致,排序二叉树的要求是左子树上的所有节点都要比根节点小,右子树上的所有节点都要比跟节点大,当插入一个新的节点的时候,首先要找到当前要插入的节点适合放在排序二叉树哪个位置,然后插入当前节点即可。红黑树和排序二叉树不同的是,红黑树需要在插入节点调整树的结构来让树保持平衡。

一般情况下,红黑树中新插入的节点都是红色的,那么,为什么说新加入到红黑树中的节点要是红色的呢?

这个问题可以这样理解,我们从性质5中知道,当前红黑树中从根节点到每个叶子节点的黑色节点数量是一样的,此时假如新的黑色节点的话,必然破坏规则,但加入红色节点却不一定,除非其父节点就是红色节点,因此加入红色节点,破坏规则的可能性小一些。

接下来我们重点来讲红黑树插入新节点后是如何保持平衡的。

给定下面这样一颗红黑树:

a5ac65be31a489d24a77b5008c9338ae.png

当我们插入值为66的节点的时候,示意图如下:

c7e1ce62b2c69cef42e2e58512e79871.png

很明显,这个时候结构依然遵循着上述5大特性,无需启动自动平衡机制调整节点平衡状态。

如果再向里面插入值为51的节点呢,这个时候红黑树变成了这样。

f05ead8362ff3f4b44c5f7fb67acd012.png

这样的结构实际上是不满足性质4的,红色两个子节点必须是黑色的,而这里49这个红色节点现在有个51的红色节点与其相连。

这个时候我们需要调整这个树的结构来保证红黑树的平衡。

首先尝试将49这个节点设置为黑色,如下示意图。

dd7fcd68c5545ac7a2210c6656ecd152.png

这个时候我们发现黑高是不对的,其中 60-56-45-49-51-null 这条路径有 4 个黑节点,其他路径的黑色节点是 3 个。

接着调整红黑树,我们再次尝试把45这个节点设置为红色的,如下图所示:

0fb85596c2daaf87435b1ab84e725923.png

这个时候我们发现问题又来了,56-45-43 都是红色节点的,出现了红色节点相连的问题。

于是我们需要再把 56 和 43 设置为黑色的,如下图所示。

c4e2f68a32eda7c7c3e2c8e8789510c6.png

于是我们把 68 这个红色节点设置为黑色的。

f596ef2fae6b2dda95f9ff7a9b23a1d0.png

对于这种红黑树插入节点的情况下,我们可以只需要通过变色就可以保持树的平衡了。但是并不是每次都是这么幸运的,当变色行不通的时候,我们需要考虑另一个手段就是旋转了。

例如下面这种情况,同样还是拿这颗红黑树举例。

c7e1ce62b2c69cef42e2e58512e79871.png

现在这颗红黑树,我们现在插入节点65。

2a1543927cc3a846c242f70d3a201bdd.png

我们尝试把 66 这个节点设置为黑色,如下图所示。

371eb9662a87224360a9be08d35ef2f8.png

这样操作之后黑高又出现不一致的情况了,60-68-64-null 有 3 个黑色节点,而60-68-64-66-null 这条路径有 4 个黑色节点,这样的结构是不平衡的。

或者我们把 68 设置为黑色,把 64 设置为红色,如下图所示:

7ed8e727e1a564b58a0f853e47860248.png

但是,同样的问题,上面这颗红黑树的黑色高度还是不一致,60-68-64-null 和 60-68-64-66-null 这两条路径黑色高度还是不一致。

这种情况如果只通过变色的情况是不能保持红黑树的平衡的。

红黑树的旋转

接下来我们讲讲红黑树的旋转,旋转分为左旋和右旋。

左旋

文字描述:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点。

文字描述太抽象,接下来看下图片展示。

首先断开节点PL与右子节点G的关系,同时将其右子节点的引用指向节点C2;然后断开节点G与左子节点C2的关系,同时将G的左子节点的应用指向节点PL。

0fefb8f61154d9df251d04d3dc3ac90d.png

接下来再放下 gif 图,希望能帮助大家更好地理解左旋,图片来自网络。

630908574bded1db1d78ff39ff162e2e.gif

右旋

文字描述:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点。

右旋的图片展示:

首先断开节点G与左子节点PL的关系,同时将其左子节点的引用指向节点C2;然后断开节点PL与右子节点C2的关系,同时将PL的右子节点的应用指向节点G。

08f5843582680c80ff2c45fce7279ee9.png

右旋的gif展示(图片来自网络):

d7292eed305934aa00de929a1a4ee886.gif

介绍完了左旋和右旋基本操作,我们来详细介绍下红黑树的几种旋转场景。

左左节点旋转(插入节点的父节点是左节点,插入节点也是左节点)

如下图所示的红黑树,我们插入节点是65。

20ec470c0cc8e43340783047ff9ee0ab.png

操作步骤如下可以围绕祖父节点 69 右旋,再结合变色,步骤如下所示:

30f013601f36e87b19f858a1c38a8151.png

左右节点旋转(插入节点的父节点是左节点,插入节点是右节点)

还是上面这颗红黑树,我们再插入节点 67。

20ec470c0cc8e43340783047ff9ee0ab.png

这种情况我们可以这样操作,先围绕父节点 66 左旋,然后再围绕祖父节点 69 右旋,最后再将 67 设置为黑色,把 69 设置为红色,如下图所示。

f2289e4b1cf4aa5a84fa04a28609adcf.png

右左节点旋转(插入节点的父节点是右节点,插入节点左节点)

如下图这种情况,我们要插入节点68。

a3d74156f6d7a5a24d47814a50266fe3.png

这种情况,我们可以先围绕父节点 69 右旋,接着再围绕祖父节点 66 左旋,最后把 68 节点设置为黑色,把 66 设置为红色,我们的具体操作步骤如下所示。

dd4101ea326dec02aa7f62d0c351171b.png

右右节点旋转(插入节点的父节点是右节点,插入节点也是右节点)

还是来上面的图来举例,我们在这颗红黑树上插入节点 70 。

a3d74156f6d7a5a24d47814a50266fe3.png

我们可以这样操作围绕祖父节点 66 左旋,再把旋转后的根节点 69 设置为黑色,把 66 这个节点设置为红色。具体可以参看下图:

6f7b508b9af72094d7ceb89b16f5cbe2.png

红黑树在 Java 中的实现

Java 中的红黑树实现类是 TreeMap ,接下来我们尝试从源码角度来逐行解释 TreeMap 这一套机制是如何运作的。

// TreeMap中使用Entry来描述每个节点static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left;Entry<K,V> right;Entry<K,V> parent;boolean color = BLACK;...}
复制代码

TreeMap 的put方法。

public V put(K key, V value) {//先以t保存链表的root节点Entry<K,V> t = root;//如果t=null,表明是一个空链表,即该TreeMap里没有任何Entry作为rootif (t == null) {compare(key, key); // type (and possibly null) check//将新的key-value创建一个Entry,并将该Entry作为rootroot = new Entry<>(key, value, null);size = 1;//记录修改次数加1modCount++;return null;}int cmp;Entry<K,V> parent;// split comparator and comparable pathsComparator<? super K> cpr = comparator;//如果比较器cpr不为null,即表明采用定制排序if (cpr != null) {do {//使用parent上次循环后的t所引用的Entryparent = t;//将新插入的key和t的key进行比较cmp = cpr.compare(key, t.key);//如果新插入的key小于t的key,t等于t的左边节点if (cmp < 0)t = t.left;//如果新插入的key大于t的key,t等于t的右边节点    else if (cmp > 0)t = t.right;else//如果两个key相等,新value覆盖原有的value,并返回原有的valuereturn t.setValue(value);} while (t != null);}else {if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;do {parent = t;cmp = k.compareTo(t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}//将新插入的节点作为parent节点的子节点Entry<K,V> e = new Entry<>(key, value, parent);//如果新插入key小于parent的key,则e作为parent的左子节点if (cmp < 0)parent.left = e;//如果新插入key小于parent的key,则e作为parent的右子节点elseparent.right = e;//修复红黑树fixAfterInsertion(e);size++;modCount++;return null;}
复制代码
//插入节点后修复红黑树
private void fixAfterInsertion(Entry<K,V> x) {x.color = RED;//直到x节点的父节点不是根,且x的父节点是红色while (x != null && x != root && x.parent.color == RED) {//如果x的父节点是其父节点的左子节点if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//获取x的父节点的兄弟节点Entry<K,V> y = rightOf(parentOf(parentOf(x)));//如果x的父节点的兄弟节点是红色if (colorOf(y) == RED) {     //将x的父节点设置为黑色setColor(parentOf(x), BLACK);//将x的父节点的兄弟节点设置为黑色setColor(y, BLACK);//将x的父节点的父节点设为红色setColor(parentOf(parentOf(x)), RED);x = parentOf(parentOf(x));}//如果x的父节点的兄弟节点是黑色else {   //TODO 对应情况第二种,左右节点旋转//如果x是其父节点的右子节点if (x == rightOf(parentOf(x))) {//将x的父节点设为xx = parentOf(x);//右旋转rotateLeft(x);}//把x的父节点设置为黑色setColor(parentOf(x), BLACK);//把x的父节点父节点设为红色setColor(parentOf(parentOf(x)), RED);rotateRight(parentOf(parentOf(x)));}}//如果x的父节点是其父节点的右子节点else {//获取x的父节点的兄弟节点Entry<K,V> y = leftOf(parentOf(parentOf(x)));//只着色的情况对应的是最开始例子,没有旋转操作,但是要对应多次变换//如果x的父节点的兄弟节点是红色  if (colorOf(y) == RED) {//将x的父节点设置为黑色setColor(parentOf(x), BLACK);//将x的父节点的兄弟节点设为黑色setColor(y, BLACK);//将X的父节点的父节点(G)设置红色setColor(parentOf(parentOf(x)), RED);//将x设为x的父节点的节点x = parentOf(parentOf(x));}//如果x的父节点的兄弟节点是黑色else {//如果x是其父节点的左子节点if (x == leftOf(parentOf(x))) {//将x的父节点设为xx = parentOf(x);//右旋转rotateRight(x);}//将x的父节点设为黑色setColor(parentOf(x), BLACK);//把x的父节点的父节点设为红色setColor(parentOf(parentOf(x)), RED);rotateLeft(parentOf(parentOf(x)));}}}//将根节点强制设置为黑色root.color = BLACK;
}
复制代码

TreeMap的插入节点和普通的排序二叉树没啥区别,唯一不同的是,在TreeMap 插入节点后会调用方法fixAfterInsertion(e)来重新调整红黑树的结构来让红黑树保持平衡。

我们重点关注下红黑树的fixAfterInsertion(e)方法,接下来我们来分别介绍两种场景来演示fixAfterInsertion(e)方法的执行流程。

第一种场景:只需变色即可平衡

同样是拿这颗红黑树举例,现在我们插入节点 51。

c7e1ce62b2c69cef42e2e58512e79871.png

当我们需要插入节点51的时候,这个时候TreeMap 的 put 方法执行后会得到下面这张图。

f05ead8362ff3f4b44c5f7fb67acd012.png

接着调用fixAfterInsertion(e)方法,如下代码流程所示。

b147d3d7d7e69c458ac3992ad18b451c.png

当第一次进入循环后,执行后会得到下面的红黑树结构。

22f90e647e8ce84ec82585c756d46d36.png

在把 x 重新赋值后,重新进入 while 循环,此时的 x 节点为 45 。

1849333f287ece9f22b7a4e65ff4d1f4.png

执行上述流程后,得到下面所示的红黑树结构。

ef92e19ee8eb0d78cf96b84681b2870c.png

这个时候x被重新赋值为60,因为60是根节点,所以会退出 while 循环。在退出循序后,会再次把根节点设置为黑色,得到最终的结构如下图所示。

2b8e05495ce4da61f12de0fd1b2cc238.png

最后经过两次执行while循环后,我们的红黑树会调整成现在这样的结构,这样的红黑树结构是平衡的,所以路径的黑高一致,并且没有红色节点相连的情况。

第二种场景 旋转搭配变色来保持平衡

接下来我们再来演示第二种场景,需要结合变色和旋转一起来保持平衡。

给定下面这样一颗红黑树:

5d8b1f2d2c4f6e4b9abfae5ddda6fabb.png

现在我们插入节点66,得到如下树结构。

f60d46ba247e4715731e044802e3c082.png

同样地,我们进入fixAfterInsertion(e)方法。

76f3bf72c7f549c940363693f4ba96ca.png

e4a92b3d5d968a8494ca510f34774afb.png

最终我们得到的红黑树结构如下图所示:

7893e61223beaba4f737895870e76f53.png

调整成这样的结构我们的红黑树又再次保持平衡了。

演示 TreeMap 的流程就拿这两种场景举例了,其他的就不一一举例了。

红黑树的删除

因为之前的分享只整理了红黑树的插入部分,本来想着红黑树的删除就不整理了,有人跟我反馈说红黑树的删除相对更复杂,于是索性还是把红黑树的删除再整理下。

删除相对插入来说,的确是要复杂一点,但是复杂的地方是因为在删除节点的这个操作情况有很多种,但是插入不一样,插入节点的时候实际上这个节点的位置是确定的,在节点插入成功后只需要调整红黑树的平衡就可以了。

但是删除不一样的是,删除节点的时候我们不能简单地把这个节点设置为null,因为如果这个节点有子节点的情况下,不能简单地把当前删除的节点设置为null,这个被删除的节点的位置需要有新的节点来填补。这样一来,需要分多种情况来处理了。

删除节点是根节点

直接删除根节点即可。

删掉节点的左子节点和右子节点都是为空

直接删除当前节点即可。

删除节点有一个子节点不为空

这个时候需要使用子节点来代替当前需要删除的节点,然后再把子节点删除即可。

给定下面这棵树,当我们需要删除节点69的时候。

f6930f6f0443495543c95f5587b342fd.png

首先用子节点代替当前待删除节点,然后再把子节点删除。

b2992027961dc9f0a92997629414b382.png

最终的红黑树结构如下面所示,这个结构的红黑树我们是不需要通过变色+旋转来保持红黑树的平衡了,因为将子节点删除后树已经是平衡的了。

e4d90146ba4a2831b957c3f3a1af92ca.png

还有一种场景是当我们待删除节点是黑色的,黑色的节点被删除后,树的黑高就会出现不一致的情况,这个时候就需要重新调整结构。

还是拿上面这颗删除节点后的红黑树举例,我们现在需要删除节点67。

68625a2fc441b47fd0cb919b4c34ca8b.png

因为67 这个节点的两个子节点都是null,所以直接删除,得到如下图所示结构:

d959827d4bc2adb532565d7c368aa29a.png

这个时候我们树的黑高是不一致的,左边黑高是3,右边是2,所以我们需要把64节点设置为红色来保持平衡。

df7893511a74cb26c8e05b4a106bdd05.png

删除节点两个子节点都不为空

删除节点两个子节点都不为空的情况下,跟上面有一个节点不为空的情况下也是有点类似,同样是需要找能替代当前节点的节点,找到后,把能替代删除节点值复制过来,然后再把替代节点删除掉。

  • 先找到替代节点,也就是前驱节点或者后继节点
  • 然后把前驱节点或者后继节点复制到当前待删除节点的位置,然后在删除前驱节点或者后继节点。

那么什么叫做前驱,什么叫做后继呢? 前驱是左子树中最大的节点,后继则是右子树中最小的节点。

前驱或者后继都是最接近当前节点的节点,当我们需要删除当前节点的时候,也就是找到能替代当前节点的节点,能够替代当前节点肯定是最接近当前节点。

在当前删除节点两个子节点不为空的场景下,我们需要再进行细分,主要分为以下三种情况。

第一种,前驱节点为黑色节点,同时有一个非空节点

如下面这样一棵树,我们需要删除节点64:

04c1dc30dcede240dbc39e8cec0dba44.png

首先找到前驱节点,把前驱节点复制到当前节点:

77e062ba662cbd111b36b5d8c1ff7cbd.png

接着删除前驱节点。

4138c8b7dd2c41971b05a1fd131ce66e.png

这个时候63和60这个节点都是红色的,我们尝试把60这个节点设置为红色即可使整个红黑树达到平衡。

0944bebc959296f20688a1dde81c4003.png

第二种,前驱节点为黑色节点,同时子节点都为空

前驱节点是黑色的,子节点都为空,这个时候操作步骤与上面基本类似。

如下操作步骤:

86cbc0e6ad93f20aaec374818573e783.png

因为要删除节点64,接着找到前驱节点63,把63节点复制到当前位置,然后将前驱节点63删除掉,变色后出现黑高不一致的情况下,最后把63节点设置为黑色,把65节点设置为红色,这样就能保证红黑树的平衡。

第三种,前驱节点为红色节点,同时子节点都为空

给定下面这颗红黑树,我们需要删除节点64的时候。

f61ceb0bfd17dad17ed8ea2eea65e1dd.png

同样地,我们找到64的前驱节点63,接着把63赋值到64这个位置。

3b753f81d7f1dbe85ae7e177eca43de8.png

然后删除前驱节点。

8f28989b10f0661e0def33239d31ebcf.png

删除节点后不需要变色也不需要旋转即可保持树的平衡。

终于把红黑树的基本原理部分写完了,用了很多示意图,这篇文章是在之前分享的 ppt 上再整理出来,我觉得自己应该算是把基本操作讲明白了,整理这篇文章前前后后用了近一周左右,因为平时上班,基本上只有周末有时间才有时间整理,如有问题请留言讨论。

如果您觉得写得还可以,请您帮忙点个赞,您的点赞真的是对我最大的支持,也是我能继续写下去的动力,感谢。

转自:https://juejin.im/post/5df4aa...

怒求一波赞

能坚持看到这儿的都是努力学习的人,我们相信,努力奋斗终将会使我们过上自己想要的生活。

我会努力更新原创干货,也会收集一些精品文章,供大家日常学习。不论如何,如果大家觉得在我这儿能学到点东西,在这儿厚着脸皮的向大家求个赞,求个关注,求个分享。我一定不会辜负大家,为大家的学习之路添加更多精彩的文章。

创作不易,坚持不易,大家的支持是我最大的动力,再次谢谢大家。

下面这篇文章,是我收集的5000G的精品VIP视频的部分目录,都是会免费分享给大家的,大家可以点进去看看是否有自己需要的,如果没有,大家也可以通过公众号或者微信私聊我,我也会尽力去收集。

java架构师:作为Java开发,我是如何在一年之内,让自己的月薪爆炸式提升!!​zhuanlan.zhihu.com
87e5f072a0980c7692b7ba5915b6b483.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/538831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux 父子进程结束,Linux下让父进程结束后,子进程自动结束

在多进程编程的时候&#xff0c;经常会遇到这样的情况。父进程创建了一堆子进程&#xff0c;当遇到错误或者操作失误的时候把父进程关闭了&#xff0c;但是子进程还在跑&#xff0c;不得不一个一个地杀死子进程&#xff0c;或者使用ps,grep,awk,kill来配合批量杀死。之前在写 x…

android:showAsAction 无效

我想要的效果 但actionbar上的搜索菜单不显示 在androidstudio里&#xff0c;android:showAsAction"always"标红 根据提示&#xff0c;需要加入 xmlns:app"http://schemas.android.com/apk/res-auto" 加入后依然无效 正确的加入方式是&#xff1a;

Exchange_Server_2013在Windows_2008_R2部署

Exchange Server 2013可以部署在Windows Server 2012的平台&#xff0c;也可以部署在Windows Server 2008 R2的平台。如果部署在Windows Server 2008 R2平台要求操作系统版本为Windows Server 2008 R2 SP1的版本。如下拓扑图&#xff1a;在本架构中有两台服务器&#xff0c;都安…

建立副本名称冲突_包的建立(一)

这次的内容&#xff0c;涉及到 R 语言包的建立。事实上&#xff0c;CRAN 提供的官方参考指南&#xff0c;并不适合快速阅读&#xff0c;且内容繁杂。比较适合作为后期提高的 教材。而 http://r-pkgs.had.co.nz/ 上 的教程则更适合作为 R 包编写的帮助指南。这里&#xff0c;仅仅…

Android 多选列表

原文&#xff1a;http://blog.csdn.net/wljun739/article/details/37655209 点击阅读原文 ----------------------------------------------------------- 1、activity_main.xml[java] view plaincopy<LinearLayout xmlns:android"http://schemas.android.com/apk/res/…

python自带的编辑器怎么换行_Python3基础 print 自带换行功能

镇场诗&#xff1a; ———大梦谁觉&#xff0c;水月中建博客。百千磨难&#xff0c;才知世事无常。 ———今持佛语&#xff0c;技术无量愿学。愿尽所学&#xff0c;铸一良心博客。 —————————————————————————————————————————— 1 …

查看db2数据库名linux,【名说】DB2数据库备份与恢复(linux环境)

lslinux 下备份db2数据库1.SSH方式&#xff1a;登录db2数据库(因为是linux环境 &#xff0c; putty就不错)2.进入备份文件夹&#xff1a;cd /home/backup/db2 list application | grep 数据库名//(可能会有一些连接进程&#xff0c;有则全部杀掉)//杀进程&#xff1a;db2 "…

leetcode 回文数

2019独角兽企业重金招聘Python工程师标准>>> 判断一个整数是否是回文数。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向右…

安装ae显示安装程序无法初始化_adobe CC 2015/2017安装失败(adobe cc安装不了的解决办法)...

adobe CC 2015/2017安装失败(adobe cc安装不了的解决办法)书法字体2015.06.18Adobe Application ManagerAdobe Creative Cloud 2015/2017全系统软件已经可以从官网下载了&#xff0c;相信又将有一大波设计师会更新安装adobe CC 2015/2017软件。本着尝鲜的精神&#xff0c;本人也…

Hadoop控制输出文件命名

原文地址&#xff1a;http://blog.csdn.net/zuochanxiaoheshang/article/details/8769198 点击阅读原文 --------------------------------------------------- Hadoop 控制输出文件命名 在一般情况下&#xff0c;Hadoop 每一个 Reducer 产生一个输出文件&#xff0c;文件以 …

office高级应用与python综合案例教程_office高级应用与python综合案例实验指导--详细介绍...

随着社会经济的发展&#xff0c;现代信息技术逐渐改变着人们的工作和生活方式。为使学生掌握办公自动化软件高级应用的技能&#xff0c;了解Python程序基础知识&#xff0c;综合运用办公自动化软件分析和解决实际问题&#xff0c;编者编写了本书。 本书围绕高等学校培养应用型人…

linux系统的安全机制有哪些内容,系统安全机制

AG351.SELINUXSElinux 是一个强制访问控制系统,它为每个进程与文件都打上一个安全上下文标签,而 selinux 通过这个标签对系统访问控制进行管理。2.针对车载产品对于启动安全、平台运行安全、通信安全三个主要领域有着特 殊 很 高 的 要 求 , 为 此 Quectel 结 合 了 Qualcomm 给…

移动端video播放时不弹出页面层

移动端视频在播放时会主动弹出页面&#xff0c;有的浏览器不会。对那些会的浏览器进行处理&#xff1a; 直接加上下面三个属性即可&#xff0c;兼容方面就不说了&#xff0c;微信上是很ok的。 <video x5-playsinline"" playsinline"" webkit-playsinlin…

1.计算机语言发展史

第一代 计算机语言 第二代 汇编语言 第三代 高级语言 面向过程&#xff1a;c&#xff0c;fortan&#xff0c;cobol&#xff0c;pascal&#xff0c;ada 面向对象&#xff1a;c&#xff0c;java&#xff0c;c# 计算机语言&#xff1a; 01010100010111000 010101010000 00…

定题信息服务是从什么角度_信息管理练习题2

1.文件的目录结构是网页在服务器上的存放状况。(对)2、网络信息指引库存放的是有关主题的数据库或服务器地址。(对)3、数据库组织方式是将超文本与多媒体技术结合起来的组织方式。(错)4、按信息的组织方式划分&#xff0c;搜索引擎则可以分为目录式搜索引擎(Yahoo)、索引式搜索…

python判断是否为完全数_Python识别完美数

完美数 完美数(perfect number&#xff0c;又称完全数)指&#xff0c;它所有的真因子(即除了自身以外的因子)和&#xff0c;恰好等于它自身。 第一个完美数&#xff1a;6&#xff0c; 第二个完美数&#xff1a;28&#xff0c; 第三个完美数&#xff1a;496&#xff0c; 第四个完…

linux嵌入式做智能家居,嵌入式系统在智能家居中的应用

汪家乐利用嵌入式系统来构建智能家居系统&#xff0c;使得用户可以根据实际需求来进行操作&#xff0c;不仅可以提高生活水平&#xff0c;并且与其他系统相比&#xff0c;其在运行上具有更高的稳定性。本文对嵌入式系统在智能家居中应用要点进行了简单分析。【关键词】嵌入式系…

前端路由的两种实现原理

2019独角兽企业重金招聘Python工程师标准>>> History API 这里不细说每一个 API 的用法&#xff0c;大家可以看 MDN 的文档&#xff1a;https://developer.mozilla.org... 重点说其中的两个新增的API history.pushState 和 history.replaceState 这两个 API 都接收三…

2.JAVA简史

SUN公司 --美国SUN&#xff08;Stanford university network&#xff09;公司 --在中国大陆的正式中文名&#xff1a;太阳计算机系统&#xff08;中国&#xff09;有限公司 --在中国台湾中文名&#xff1a;升阳电脑公司 JAVA为什么被发明&#xff1f; --是sun公司Green项目…

es统计有多少个分组_ES 24 - 如何通过Elasticsearch进行聚合检索 (分组统计)

1 普通聚合分析1.1 直接聚合统计(1) 计算每个tag下的文档数量, 请求语法:GET book_shop/it_book/_search{"size": 0, // 不显示命中(hits)的所有文档信息"aggs": {"group_by_tags": {// 聚合结果的名称, 需要自定义(复制时请去掉此注释)"te…