在计算机科学中,AVL树是最先发明的自平衡二叉查找树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An algorithm for the organization of information" 中发表了它。

一、AVL树的旋转规律

AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。

假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行进行的规律可归纳为下列四种情况:

1. LL型

平衡二叉树某一节点的左孩子的左子树上插入一个新的节点,使得该节点不再平衡。这时只需要把树向右旋转一次即可,如图所示,原A的左孩子B变为父结点,A变为其右孩子,而原B的右子树变为A的左子树,注意旋转之后Brh是A的左子树(图上忘在A于Brh之间标实线)

2. RR型

平衡二叉树某一节点的右孩子的右子树上插入一个新的节点,使得该节点不再平衡。这时只需要把树向左旋转一次即可,如图所示,原A右孩子B变为父结点,A变为其左孩子,而原B的左子树Blh将变为A的右子树。

3. LR型

平衡二叉树某一节点的左孩子的右子树上插入一个新的节点,使得该节点不再平衡。这时需要旋转两次,仅一次的旋转是不能够使二叉树再次平衡。如图所示,在B节点按照RR型向左旋转一次之后,二叉树在A节点仍然不能保持平衡,这时还需要再向右旋转一次。

4. RL型

平衡二叉树某一节点的右孩子的左子树上插入一个新的节点,使得该节点不再平衡。同样,这时需要旋转两次,旋转方向刚好同LR型相反。

二、AVL树的基本操作

1.插入

向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。 

在平衡的的二叉排序树Balanced BST上插入一个新的数据元素e的递归算法可描述如下:

若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增1;

若e的关键字和BBST的根结点的关键字相等,则不进行;

若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度,则将根结点的平衡因子更改为 0,BBST的深度不变; BBST的根结点的平衡因子为0(左、右子树的深度相等):则将根结点的平衡因子更改为1,BBST的深度增1;BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根结点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变;若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的 右子树深度增加(+1)时,分别就不同情况处理之。

2.删除

从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。

删除操作需要考虑的情况较多,具体见代码实现吧。

3.查找

在AVL树中查找同在一般BST完全一样的进行,所以耗费 O(log n) 时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查询而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)

三、代码实现

时间仓促,对于插入、删除操作没有就各种情况配上插图,代码里面有一些注释,可以对着代码理解。日后再研究这个的时候定配上插图。

  1. package ly.dataStructures.tree;
  2. import java.util.Comparator;
  3. /**
  4. * AVL树
  5. * @author 无间道风云
  6. * 2014.0526
  7. * @param <AnyType>
  8. */
  9. public class AvlTree<AnyType extends Comparable<? super AnyType>> {
  10. private AvlNode<AnyType> root;
  11. private Comparator<? super AnyType> cmp;
  12. /*********  AVL树节点数据结构定义   **********/
  13. private static class AvlNode<AnyType>{
  14. AnyType element;
  15. AvlNode<AnyType> left;
  16. AvlNode<AnyType> right;
  17. int height;
  18. AvlNode(AnyType theElement){
  19. this(theElement, null, null);
  20. }
  21. AvlNode(AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt){
  22. element = theElement;
  23. left = lt;
  24. right = rt;
  25. height = 0;
  26. }
  27. }
  28. public AvlTree(){
  29. root = null;
  30. }
  31. public void makeEmpty(){
  32. root = null;
  33. }
  34. public boolean isEmpty(){
  35. return root == null;
  36. }
  37. public void insert(AnyType element){
  38. root = insert(element, root);
  39. }
  40. public boolean contains(AnyType x){
  41. return contains(x, root);
  42. }
  43. public void remove(AnyType element){
  44. root = remove(element, root);
  45. }
  46. private int myCompare(AnyType lhs, AnyType rhs){
  47. if(cmp != null)
  48. return cmp.compare(lhs, rhs);
  49. else
  50. return ((Comparable)lhs).compareTo(rhs);
  51. }
  52. private boolean contains(AnyType x, AvlNode<AnyType> t){
  53. //空树处理
  54. if(t == null)
  55. return false;
  56. //正常情况处理
  57. //@方式一:对Comparable型的对象进行比较
  58. //int compareResult = x.compareTo(t.element);
  59. //@方式二:使用一个函数对象而不是要求这些项是Comparable的
  60. int compareResult = myCompare(x, t.element);
  61. if(compareResult < 0)
  62. return contains(x, t.left);
  63. else if(compareResult > 0)
  64. return contains(x, t.right);
  65. else
  66. return true;
  67. }
  68. private int height(AvlNode<AnyType> t){
  69. return t == null ? -1 : t.height;
  70. }
  71. private AvlNode<AnyType> findMin(AvlNode<AnyType> t){
  72. if(t == null)
  73. return null;
  74. if(t.left == null)
  75. return t;
  76. return findMin(t.left);
  77. }
  78. private AvlNode<AnyType> findMax(AvlNode<AnyType> t){
  79. if(t == null)
  80. return null;
  81. if(t.right == null)
  82. return t;
  83. return findMax(t.right);
  84. }
  85. private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t){
  86. if(t == null)
  87. return new AvlNode<AnyType>(x, null, null);
  88. int compareResult = myCompare(x, t.element);
  89. if(compareResult < 0){
  90. t.left = insert(x, t.left);
  91. if(height(t.left)-height(t.right) == 2){
  92. if(myCompare(x, t.left.element) < 0)     //左左情况
  93. t = rotateWithLeftChild(t);
  94. else                                    //左右情况
  95. t = doubleWithLeftChild(t);
  96. }
  97. }else if(compareResult > 0){
  98. t.right = insert(x, t.right);
  99. if(height(t.right)-height(t.left) == 2){
  100. if(myCompare(x, t.right.element) < 0)        //右左情况
  101. t = doubleWithRightChild(t);
  102. else                                        //右右情况
  103. t = rotateWithRightChild(t);
  104. }
  105. }
  106. //完了之后更新height值
  107. t.height = Math.max(height(t.left), height(t.right))+1;
  108. return t;
  109. }
  110. private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t){
  111. if(t == null)
  112. return null;
  113. int compareResult = myCompare(x, t.element);
  114. if(compareResult < 0){
  115. t.left = remove(x, t.left);
  116. //完了之后验证该子树是否平衡
  117. if(t.right != null){        //若右子树为空,则一定是平衡的,此时左子树相当对父节点深度最多为1, 所以只考虑右子树非空情况
  118. if(t.left == null){     //若左子树删除后为空,则需要判断右子树
  119. if(height(t.right)-t.height == 2){
  120. AvlNode<AnyType> k = t.right;
  121. if(k.right != null){        //右子树存在,按正常情况单旋转
  122. System.out.println("-----------------------------------------------------------------------------11111");
  123. t = rotateWithRightChild(t);
  124. }else{                      //否则是右左情况,双旋转
  125. System.out.println("-----------------------------------------------------------------------------22222");
  126. t = doubleWithRightChild(t);
  127. }
  128. }
  129. }else{                  //否则判断左右子树的高度差
  130. //左子树自身也可能不平衡,故先平衡左子树,再考虑整体
  131. AvlNode<AnyType> k = t.left;
  132. //删除操作默认用右子树上最小节点补删除的节点
  133. //k的左子树高度不低于k的右子树
  134. if(k.right != null){
  135. if(height(k.left)-height(k.right) == 2){
  136. AvlNode<AnyType> m = k.left;
  137. if(m.left != null){     //左子树存在,按正常情况单旋转
  138. System.out.println("-----------------------------------------------------------------------------33333");
  139. k = rotateWithLeftChild(k);
  140. }else{                      //否则是左右情况,双旋转
  141. System.out.println("-----------------------------------------------------------------------------44444");
  142. k = doubleWithLeftChild(k);
  143. }
  144. }
  145. }else{
  146. if(height(k.left) - k.height ==2){
  147. AvlNode<AnyType> m = k.left;
  148. if(m.left != null){     //左子树存在,按正常情况单旋转
  149. System.out.println("-----------------------------------------------------------------------------hhhhh");
  150. k = rotateWithLeftChild(k);
  151. }else{                      //否则是左右情况,双旋转
  152. System.out.println("-----------------------------------------------------------------------------iiiii");
  153. k = doubleWithLeftChild(k);
  154. }
  155. }
  156. }
  157. if(height(t.right)-height(t.left) == 2){
  158. //右子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
  159. System.out.println("-----------------------------------------------------------------------------55555");
  160. t = rotateWithRightChild(t);
  161. }
  162. }
  163. }
  164. //完了之后更新height值
  165. t.height = Math.max(height(t.left), height(t.right))+1;
  166. }else if(compareResult > 0){
  167. t.right = remove(x, t.right);
  168. //下面验证子树是否平衡
  169. if(t.left != null){         //若左子树为空,则一定是平衡的,此时右子树相当对父节点深度最多为1
  170. if(t.right == null){        //若右子树删除后为空,则只需判断左子树
  171. if(height(t.left)-t.height ==2){
  172. AvlNode<AnyType> k = t.left;
  173. if(k.left != null){
  174. System.out.println("-----------------------------------------------------------------------------66666");
  175. t = rotateWithLeftChild(t);
  176. }else{
  177. System.out.println("-----------------------------------------------------------------------------77777");
  178. t = doubleWithLeftChild(t);
  179. }
  180. }
  181. }else{              //若右子树删除后非空,则判断左右子树的高度差
  182. //右子树自身也可能不平衡,故先平衡右子树,再考虑整体
  183. AvlNode<AnyType> k = t.right;
  184. //删除操作默认用右子树上最小节点(靠左)补删除的节点
  185. //k的右子树高度不低于k的左子树
  186. if(k.left != null){
  187. if(height(k.right)-height(k.left) == 2){
  188. AvlNode<AnyType> m = k.right;
  189. if(m.right != null){        //右子树存在,按正常情况单旋转
  190. System.out.println("-----------------------------------------------------------------------------88888");
  191. k = rotateWithRightChild(k);
  192. }else{                      //否则是右左情况,双旋转
  193. System.out.println("-----------------------------------------------------------------------------99999");
  194. k = doubleWithRightChild(k);
  195. }
  196. }
  197. }else{
  198. if(height(k.right)-k.height == 2){
  199. AvlNode<AnyType> m = k.right;
  200. if(m.right != null){        //右子树存在,按正常情况单旋转
  201. System.out.println("-----------------------------------------------------------------------------aaaaa");
  202. k = rotateWithRightChild(k);
  203. }else{                      //否则是右左情况,双旋转
  204. System.out.println("-----------------------------------------------------------------------------bbbbb");
  205. k = doubleWithRightChild(k);
  206. }
  207. }
  208. }
  209. if(height(t.left) - height(t.right) == 2){
  210. //左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
  211. System.out.println("-----------------------------------------------------------------------------ccccc");
  212. t = rotateWithLeftChild(t);
  213. }
  214. }
  215. }
  216. //完了之后更新height值
  217. t.height = Math.max(height(t.left), height(t.right))+1;
  218. }else if(t.left != null && t.right != null){
  219. //默认用其右子树的最小数据代替该节点的数据并递归的删除那个节点
  220. t.element = findMin(t.right).element;
  221. t.right = remove(t.element, t.right);
  222. if(t.right == null){        //若右子树删除后为空,则只需判断左子树与根的高度差
  223. if(height(t.left)-t.height ==2){
  224. AvlNode<AnyType> k = t.left;
  225. if(k.left != null){
  226. System.out.println("-----------------------------------------------------------------------------ddddd");
  227. t = rotateWithLeftChild(t);
  228. }else{
  229. System.out.println("-----------------------------------------------------------------------------eeeee");
  230. t = doubleWithLeftChild(t);
  231. }
  232. }
  233. }else{              //若右子树删除后非空,则判断左右子树的高度差
  234. //右子树自身也可能不平衡,故先平衡右子树,再考虑整体
  235. AvlNode<AnyType> k = t.right;
  236. //删除操作默认用右子树上最小节点(靠左)补删除的节点
  237. if(k.left != null){
  238. if(height(k.right)-height(k.left) == 2){
  239. AvlNode<AnyType> m = k.right;
  240. if(m.right != null){        //右子树存在,按正常情况单旋转
  241. System.out.println("-----------------------------------------------------------------------------fffff");
  242. k = rotateWithRightChild(k);
  243. }else{                      //否则是右左情况,双旋转
  244. System.out.println("-----------------------------------------------------------------------------ggggg");
  245. k = doubleWithRightChild(k);
  246. }
  247. }
  248. }else{
  249. if(height(k.right)-k.height == 2){
  250. AvlNode<AnyType> m = k.right;
  251. if(m.right != null){        //右子树存在,按正常情况单旋转
  252. System.out.println("-----------------------------------------------------------------------------hhhhh");
  253. k = rotateWithRightChild(k);
  254. }else{                      //否则是右左情况,双旋转
  255. System.out.println("-----------------------------------------------------------------------------iiiii");
  256. k = doubleWithRightChild(k);
  257. }
  258. }
  259. }
  260. //左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
  261. if(height(t.left) - height(t.right) == 2){
  262. System.out.println("-----------------------------------------------------------------------------jjjjj");
  263. t = rotateWithLeftChild(t);
  264. }
  265. }
  266. //完了之后更新height值
  267. t.height = Math.max(height(t.left), height(t.right))+1;
  268. }else{
  269. System.out.println("-----------------------------------------------------------------------------kkkkk");
  270. t = (t.left != null)?t.left:t.right;
  271. }
  272. return t;
  273. }
  274. //左左情况单旋转
  275. private AvlNode<AnyType> rotateWithLeftChild(AvlNode<AnyType> k2){
  276. AvlNode<AnyType> k1 = k2.left;
  277. k2.left = k1.right;
  278. k1.right = k2;
  279. k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
  280. k1.height = Math.max(height(k1.left), k2.height) + 1;
  281. return k1;      //返回新的根
  282. }
  283. //右右情况单旋转
  284. private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> k2){
  285. AvlNode<AnyType> k1 = k2.right;
  286. k2.right = k1.left;
  287. k1.left = k2;
  288. k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
  289. k1.height = Math.max(height(k1.right), k2.height) + 1;
  290. return k1;      //返回新的根
  291. }
  292. //左右情况
  293. private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> k3){
  294. try{
  295. k3.left = rotateWithRightChild(k3.left);
  296. }catch(NullPointerException e){
  297. System.out.println("k.left.right为:"+k3.left.right);
  298. throw e;
  299. }
  300. return rotateWithLeftChild(k3);
  301. }
  302. //右左情况
  303. private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> k3){
  304. try{
  305. k3.right = rotateWithLeftChild(k3.right);
  306. }catch(NullPointerException e){
  307. System.out.println("k.right.left为:"+k3.right.left);
  308. throw e;
  309. }
  310. return rotateWithRightChild(k3);
  311. }
  312. }
  313. /*注明:由于删除操作考虑的情况甚多,代码中出现的打印信息主要为方便排错*/

测试用例如下:

  1. import static org.junit.Assert.*;
  2. import java.util.Random;
  3. import org.junit.Test;
  4. public class AvlTreeTest {
  5. private AvlTree<Integer> avlTree = new AvlTree<Integer>();
  6. @Test
  7. public void testInsert(){
  8. avlTree.insert(100);
  9. avlTree.insert(120);
  10. avlTree.insert(300);
  11. avlTree.insert(500);
  12. avlTree.insert(111);
  13. avlTree.insert(92);
  14. avlTree.insert(77);
  15. avlTree.insert(125);
  16. System.out.println(avlTree.contains(120));
  17. avlTree.remove(120);
  18. avlTree.remove(125);    //需要单旋转
  19. System.out.println(avlTree.contains(120));
  20. avlTree.insert(78);     //需要双旋转
  21. System.out.println("Insert Success !");
  22. }
  23. @Test
  24. public void testRotate(){
  25. avlTree.insert(100);
  26. avlTree.insert(90);
  27. avlTree.insert(92);
  28. avlTree.insert(78);
  29. avlTree.insert(76);
  30. System.out.println("Insert Success !");
  31. }
  32. /**
  33. * 通过较大数据进行测试,暂时还没有发现问题
  34. */
  35. @Test
  36. public void testAll(){
  37. avlTree.makeEmpty();
  38. Random random = new Random();
  39. for(int i=1;i<=1000000;i++){
  40. avlTree.insert(random.nextInt(1000000));
  41. }
  42. for(int i=2000000;i>=1000000;i--){
  43. avlTree.insert(i);
  44. }
  45. /*for(int i=700000;i>=400000;i--){
  46. avlTree.insert(i);
  47. }
  48. for(int i=100000;i<=200000;i++){
  49. avlTree.insert(i);
  50. }
  51. for(int i=400000;i<=500000;i++){
  52. avlTree.insert(random.nextInt(600000));
  53. }*/
  54. for(int i=200000;i<1400000;i++){
  55. int target = random.nextInt(1500000);
  56. if(avlTree.contains(target)){
  57. avlTree.remove(target);
  58. }
  59. }
  60. System.out.println("Insert Success !");
  61. }
  62. }

自平衡二叉(查找树/搜索树/排序树) binary search tree的更多相关文章

  1. 1.红黑树和自平衡二叉(查找)树区别 2.红黑树与B树的区别

    1.红黑树和自平衡二叉(查找)树区别 1.红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单. 2.平衡 ...

  2. 笔试算法题(58):二分查找树性能分析(Binary Search Tree Performance Analysis)

    议题:二分查找树性能分析(Binary Search Tree Performance Analysis) 分析: 二叉搜索树(Binary Search Tree,BST)是一颗典型的二叉树,同时任 ...

  3. 算法:非平衡二叉搜索树(UnBalanced Binary Search Tree)

    背景 很多场景下都需要将元素存储到已排序的集合中.用数组来存储,搜索效率非常高: O(log n),但是插入效率比较低:O(n).用链表来存储,插入效率和搜索效率都比较低:O(n).如何能提供插入和搜 ...

  4. 《数据结构与算法分析——C语言描述》ADT实现(NO.03) : 二叉搜索树/二叉查找树(Binary Search Tree)

    二叉搜索树(Binary Search Tree),又名二叉查找树.二叉排序树,是一种简单的二叉树.它的特点是每一个结点的左(右)子树各结点的元素一定小于(大于)该结点的元素.将该树用于查找时,由于二 ...

  5. 【遍历二叉树】07恢复二叉搜索树【Recover Binary Search Tree】

    开一个指针数组,中序遍历这个二叉搜索树,将节点的指针依次保存在数组里, 然后寻找两处逆序的位置, 中序便利里BST得到的是升序序列 ++++++++++++++++++++++++++++++++++ ...

  6. [Swift]LeetCode669. 修剪二叉搜索树 | Trim a Binary Search Tree

    Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that a ...

  7. LeetCode 669. 修剪二叉搜索树(Trim a Binary Search Tree)

    669. 修剪二叉搜索树 669. Trim a Binary Search Tree 题目描述 LeetCode LeetCode669. Trim a Binary Search Tree简单 J ...

  8. LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)

    题目描述 给定一个二叉树,判断其是否是一个有效的二叉搜索树. 假设一个二叉搜索树具有如下特征: 节点的左子树只包含小于当前节点的数. 节点的右子树只包含大于当前节点的数. 所有左子树和右子树自身必须也 ...

  9. 66. 有序数组构造二叉搜索树[array to binary search tree]

    [本文链接] http://www.cnblogs.com/hellogiser/p/array-to-binary-search-tree.html [题目] 编写一个程序,把一个有序整数数组放到二 ...

随机推荐

  1. 动态权限<三>华为小米特殊机制

    动态权限对于谷歌来说从android6.0引入,对于国内的rom来说,这个题目不是好的选择题.因为大多数时候由于使用群众的层次不同,有些人在乎隐私的泄露,而更多的人却并不关心,使用了动态权限,增加了用 ...

  2. 部署jenkins问题

    总结:配置的url,jenkins部署的ip必须有开放,否则发布会超时失败

  3. 弄啥嘞?热爱你的Bug

    有人喜欢创造世界,他们做了开发者:有的人喜欢开发者,他们做了测试员.什么是软件测试?软件测试就是一场本该在用户面前发生的灾难提前在自己面前发生了,这会让他们生出一种救世主的感觉,拯救了用户,也就拯救者 ...

  4. Centos7+安装python3+wkhtmltoPdf+pdfkit

    前言 这几天要做一个将HTML转化为PDF的小功能.期间经历了颇多的挫折,刚开始是通过java做的,后来发现java库做这个事情实在是效果不理想,前端做好了样式转完之后会出现很多问题.后来我想起来py ...

  5. 基础:enctype 包含上传input时必须(解决图片上传不成功问题)

    今天在做一个上传图片的时候,死活就是看不到传过去的值..对比了写法没发现问题,后来抱着试试看的心,查看下了 from里的写法.发现缺少了enctype.不了解这个用法,特意百度了下. enctype ...

  6. 在HTML中引用JavaScript中的变量

    和上次的代码几乎一样,但这次是引用已经写好的变量.主要功能和用法如下: document对象的getElementId方法得到HTML元素. HTML元素的value属性可以用来设置变量的值. 02. ...

  7. Visual Studio的框选代码区块功能

    要从Visual Studio里复制代码粘贴到其他地方,会因为对齐的问题,造成粘贴的时候,代码左边带有大量的空格. 而VS有一个很好的功能就是框选功能,使用方法是,将光标放置在要框选代码的最左边,然后 ...

  8. Kaggle 广告转化率预测比赛小结

    20天的时间参加了Kaggle的 Avito Demand Prediction Challenged ,第一次参加,成绩离奖牌一步之遥,感谢各位队友,学到的东西远比成绩要丰硕得多.作为新手,希望每记 ...

  9. 图解 Go 并发

    你很可能从某种途径听说过 Go 语言.它越来越受欢迎,并且有充分的理由可以证明. Go 快速.简单,有强大的社区支持.学习这门语言最令人兴奋的一点是它的并发模型. Go 的并发原语使创建多线程并发程序 ...

  10. last命令详解

    基础命令学习目录首页 原文链接:https://blog.csdn.net/jerry_1126/article/details/54427119 [root@xiaoma /root] test!# ...