线索二叉树概述

  二叉树虽然是非线性结构,但二叉树的遍历却为二又树的结点集导出了一个线性序列。希望很快找到某一结点的前驱或后继,但不希望每次都要对二叉树遍历一遍,这就需要把每个结点的前驱和后继信息记录下来。为了做到这一点,可在原来的二叉链表中增加一个前驱指针域(pred)和一个后继指针域(succ),分别指向该结点在某种次序下的前驱结点和后继结点。
  以中序遍历为例:

  有许多指针是空指针又没有利用。为了不浪费存储空间,利用空的leftChild域存放结点的前驱结点指针,利用空的rightChild域存放结点的后继结点指针。
  为了区别线索和子女指针,在每个结点中设置两个标志ltag和rtag。以中序线索二叉树为例,如果ltag==0,标明leftChild域中存放的是指向左子女结点的指针,否则leftChild域中是指向该结点中序下的前驱的线索;如果rtag==0,标明rightChild域中存放的是指向右子女结点的指针,否则rightChild域中是指向该结点中序下的后继的线索。
由于它们只需占用一个二进位,每个结点所需存储空间节省得多。
 

寻找当前结点在中序下的后继

 

寻找当前结点在中序序列下的前驱

 

线索二叉树的结点类

  1. //线索二叉树结点类
  2. template<typename T>
  3. struct ThreadNode //结点类
  4. {
  5. int ltag, rtag; //左右子树标志位
  6. ThreadNode<T> *leftChild, *rightChild; //左孩子和右孩子
  7. T data; //结点存储的值
  8. ThreadNode(const T item) :data(item), leftChild(NULL), rightChild(NULL), ltag(), rtag() {} //结点类的构造函数
  9. };

线索二叉树的创建

  对一个已存在的二又树按中序遍历进行线索化的算法中用到了一个指针pre,它在遍历过程中总是指向遍历指针p的中序下的前驱结点,即在中序遍历过程中刚刚访问过的结点。在做中序遍历时,只要一遇到空指针域,立即填入前驱或后继线索。

  1. //使用前序遍历创建二叉树(未线索化)
  2. void CreateTree(ThreadNode<T>* &subTree)
  3. {
  4. T item;
  5. if (cin >> item)
  6. {
  7. if (item != RefValue)
  8. {
  9. subTree = new ThreadNode<T>(item); //构造结点
  10. if (subTree == NULL)
  11. {
  12. cout << "空间分配错误!" << endl;
  13. exit();
  14. }
  15. CreateTree(subTree->leftChild); //递归创建左子树
  16. CreateTree(subTree->rightChild); //递归创建右子树
  17. }
  18. else
  19. {
  20. subTree == NULL;
  21. }
  22. }
  23. }
  24.  
  25. //中序遍历对二叉树进行线索化
  26. void createInThread(ThreadNode<T> *current, ThreadNode<T> * &pre)
  27. {
  28. if (current == NULL)
  29. {
  30. return;
  31. }
  32. createInThread(current->leftChild, pre); //递归左子树的线索化
  33. if (current->leftChild == NULL) //建立当前结点的前驱结点
  34. {
  35. current->leftChild = pre;
  36. current->ltag = ;
  37. }
  38. if (pre != NULL&&pre->rightChild == NULL) //建立当前结点的后继结点
  39. {
  40. pre->rightChild = current;
  41. pre->rtag = ;
  42. }
  43. pre = current; //用前驱记住当前的结点
  44. createInThread(current->rightChild, pre); //递归右子树的线索化
  45. }
  46.  
  47. //中序遍历对创建好的普通二叉树进行中序线索化
  48. void CreateInThread()
  49. {
  50. ThreadNode<T> *pre = NULL; //第一个结点的左子树置为NULL
  51. if (root != NULL) {
  52. createInThread(root, pre);
  53. //处理中序遍历的最后一个结点,最后一个结点的右子树置为空
  54. pre->rightChild = NULL;
  55. pre->rtag = ;
  56. }
  57. }

中序线索化二叉树的成员函数

  1. //寻找中序下第一个结点
  2. ThreadNode<T> * First(ThreadNode<T> *current)
  3. //返回以*current为根的中序线索二叉树中序遍历的第一个结点
  4. {
  5. ThreadNode<T> *p = current;
  6. while (p->ltag == )
  7. {
  8. p = p->leftChild; //循环找到最左下角结点
  9. }
  10. return p;
  11. }
  12.  
  13. //寻找中序下的后继结点
  14. ThreadNode<T>* Next(ThreadNode<T>* current)
  15. {
  16. ThreadNode<T>* p = current->rightChild;
  17. if(current->rtag==)
  18. {
  19. return First(p);
  20. }
  21. else
  22. {
  23. return p;
  24. }
  25. }
  26.  
  27. //寻找中序下最后一个结点
  28. ThreadNode<T> * Last(ThreadNode<T> *current)
  29. //返回以*current为根的中序线索二叉树中序遍历的最后一个结点
  30. {
  31. ThreadNode<T> *p = current;
  32. while (p->rtag==)
  33. {
  34. p = p->rightChild;
  35. }
  36. return p;
  37. }
  38.  
  39. //寻找结点在中序下的前驱结点
  40. ThreadNode<T>* Prior(ThreadNode<T>* current)
  41. {
  42. ThreadNode<T>* p = current->leftChild;
  43. if (current->ltag==)
  44. {
  45. return Last(p);
  46. }
  47. else
  48. {
  49. return p;
  50. }
  51. }

中序线索化二叉树上执行中序遍历的算法

  先利用First()找到二又树在中序序列下的第一个结占,抑它作为当前结点,然后利用求后继结点的运算Next()按中序次序逐个访问,直到二叉树的最后一个结点。

  1. //中序线索化二叉树上执行中序遍历的算法
  2. void InOrder(ThreadNode<T>* p)
  3. {
  4. for (p=First(root);p!=NULL;p=Next(p))
  5. {
  6. cout << p->data<<" ";
  7. }
  8. cout << endl;
  9. }

中序线索化二叉树上实现前序遍历的算法

  前序序列中的第一个结点即二又树的根,因此从根结点开始前序遍历二叉树。若当前结点有左子女,则前序下的后继结点即为左子女结点,否则,若当前结点有右子女,则前序后继即为右子女结点。对于叶结点,则沿着中序后继线索走到一个有右子女结点的结点,这个右子女结点就是当前结点的前序后继结点。

  1. void PreOrder(ThreadNode<T>* p)
  2. {
  3. while (p!=NULL)
  4. {
  5. cout << p->data<<" "; //先访问根节点
  6. if (p->ltag==)
  7. {
  8. p = p->leftChild; //有左子树,即为后继
  9. }
  10. else if(p->rtag==) //否则,有右子树,即为后继
  11. {
  12. p = p->rightChild;
  13. }
  14. else //无左右子树
  15. {
  16. while (p!=NULL&&p->rtag==) //检测后继线索
  17. {
  18. p = p->rightChild; //直到找到有右子树的结点
  19. }
  20. if (p!=NULL)
  21. {
  22. p = p->rightChild; //该结点的右子树为后继
  23. }
  24. }
  25. }
  26. cout << endl;
  27. }

中序线索化二叉树后序遍历的算法

  首先从根结点出发,寻找在后序序列中的第一个结点。寻找的方法是从根出发,沿着左子女链一直找下去,找到左子女不再是左子女指针的结点,再找到该结点的右子女,在以此结点为根的子树上再重复上述过程,直到叶结点为止。接着,从此结点开始后序遍历中序线索二又树。在遍历过程中,每次都先找到当前结点的父结点,如果当前结点是父结点的右子女,或者虽然当前结点是父结点的左子女,但这个父结点没有右子女,则后序下的后继即为该父结点;否则,在当前结点的右子树(如果存在)上重复执行上面的操作。这种后序遍历过程必须搜寻父结点,并确定当前结点与其父结点的关系,即是左子女还是右子女。

  1. //中序线索二叉树的后序遍历算法
  2. void PostOrder(ThreadNode<T>* p)
  3. {
  4. ThreadNode<T>* t = p;
  5. while (t->ltag==||t->rtag==) //寻找后续第一个结点
  6. {
  7. if(t->ltag==)
  8. {
  9. t = t->leftChild;
  10. }
  11. else if(t->rtag==)
  12. {
  13. t = t->rightChild;
  14. }
  15. }
  16. cout << t->data<<" "; //访问第一个结点
  17. while ((p=Parent(t))!=NULL) //每次都先找到当前结点的父结点
  18. {
  19. //若当前结点是父节点的右子树或者当前结点是左子树,但是这个父节点没有右子树,则后续下的后继为改父节点
  20. if (p->rightChild==t||p->rtag==)
  21. {
  22. t = p;
  23. }
  24. //否则,在当前结点的右子树(如果存在)上重复执行上面的操作
  25. else
  26. {
  27. t = p->rightChild;
  28. while (t->ltag==||t->rtag==)
  29. {
  30. if (t->ltag==)
  31. {
  32. t = t->leftChild;
  33. }
  34. else if (t->rtag==)
  35. {
  36. t = t->rightChild;
  37. }
  38. }
  39. }
  40. cout << t->data << " ";
  41. }
  42. }

在中序线索二叉树中求父节点

  中序线索化二叉树后序遍历的算法中用到了求父节点的算法,程序中包括两条查找父结点的路径。第一种选择是从当前结点走到树上层的一个中序前驱(不一定是直接前驱),然后向右下找父结点。第二种选择是从当前结点走到树上层的一个中序后继(不一定是直接后继),然后向左下找父结点。以下通过一个具体的例子来说明为什么不可以只采用一种方法。
 

  例如上图寻找结点’*’的父结点的两条路径。一条路径是从结点’*’沿左子女链走到’b',然后顺中序前驱线索走到’+’,而’+”就是’*’的父结点。另一条路径是从结点’*’沿右子女链走到’d',然后顺中序后继线索走到’一’,再向左子女方向走到结点’+’,找到结点’*’的父结点。对于此例,无论第一条路径还是第二条路径都可以找到父结点。但情况不总是这样。例如:在找结点’+’的父结点,从’+’沿左子女链将走到结点’a',而’a'无中序前驱线索,因此这条路径失败,但通过另一条路径找到了结点’+”的父结点’一’。说明了只采用一种方法是不行的。
  程序实现是先试探第一条路径,如果走到中序序列的第一个结点而告失败,则改换后一条路径寻找父结点。只有找根结点的父结点,这两种方法才都会失败。因为从根结点沿左子女链一定走到中序序列的第一个结点,沿右子女链一定走到中序序列的最后一个结点。然而,根结点根本就无父结点,所以这种特例在开始就排除了。

  1. //在中序线索化二叉树中求父节点
  2. ThreadNode<T>* Parent(ThreadNode<T>* t)
  3. {
  4. ThreadNode<T>* p;
  5. if(t==root) //根节点无父节点
  6. {
  7. return NULL;
  8. }
  9. for (p = t; p->ltag == ; p = p->leftChild); //求*t为根的中序下的第一个结点p
  10. //情况1
  11. if (p->leftChild!=NULL) //当p左子树指向不为空
  12. {
  13. //令p为p的左子树指向的结点,判断此结点是否并且此节点的左右子树结点的指向都不为t,再将p为p的右孩子结点
  14. for (p = p->leftChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->rightChild);
  15. }
  16. //情况2
  17. //如果上面的循环完了,由于是p==NULL结束的循环,没有找到与t相等的结点,就是一直找到了中序线索化的第一个结点了,这时候这种就要用到情况2的方法
  18. if (p==NULL||p->leftChild==NULL)
  19. {
  20. //找到*t为根的中序下的最后一个结点
  21. for (p = t; p->rtag == ; p = p->rightChild);
  22. //让后让他指向最后一个结点指向的结点,从这个结点开始,以此判断它的左孩子孩子和右孩子是否和t相等
  23. for (p = p->rightChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->leftChild);
  24. }
  25. return p;
  26. }

完整代码

线索二叉树

  1. //线索二叉树
  2. template<typename T>
  3. struct ThreadNode //结点类
  4. {
  5. int ltag, rtag; //左右子树标志位
  6. ThreadNode<T> *leftChild, *rightChild; //左孩子和右孩子
  7. T data; //结点存储的值
  8. ThreadNode(const T item) :data(item), leftChild(NULL), rightChild(NULL), ltag(), rtag() {} //结点类的构造函数
  9. };
  10.  
  11. template<typename T>
  12. class ThreadTree
  13. {
  14.  
  15. public:
  16. //构造函数(普通)
  17. ThreadTree() :root(NULL) {}
  18.  
  19. //指定结束标志RefValue的构造函数
  20. ThreadTree(T value) :RefValue(value), root(NULL) {}
  21.  
  22. //使用前序遍历创建二叉树(未线索化)
  23. void CreateTree() { CreateTree(root); }
  24.  
  25. //中序遍历对创建好的普通二叉树进行中序线索化
  26. void CreateInThread()
  27. {
  28. ThreadNode<T> *pre = NULL; //第一个结点的左子树置为NULL
  29. if (root != NULL) {
  30. createInThread(root, pre);
  31. //处理中序遍历的最后一个结点,最后一个结点的右子树置为空
  32. pre->rightChild = NULL;
  33. pre->rtag = ;
  34. }
  35. }
  36. //线索化二叉树上执行中序遍历的算法
  37. void InOrder() { InOrder(root); }
  38. //中序线索化二叉树上实现前序遍历的算法
  39. void PreOrder() { PreOrder(root); }
  40. //中序线索二叉树的后序遍历算法
  41. void PostOrder() { PostOrder(root); }
  42. private:
  43. //使用前序遍历创建二叉树(未线索化)
  44. void CreateTree(ThreadNode<T>* &subTree)
  45. {
  46. T item;
  47. if (cin >> item)
  48. {
  49. if (item != RefValue)
  50. {
  51. subTree = new ThreadNode<T>(item); //构造结点
  52. if (subTree == NULL)
  53. {
  54. cout << "空间分配错误!" << endl;
  55. exit();
  56. }
  57. CreateTree(subTree->leftChild); //递归创建左子树
  58. CreateTree(subTree->rightChild); //递归创建右子树
  59. }
  60. else
  61. {
  62. subTree == NULL;
  63. }
  64. }
  65. }
  66. //中序遍历对二叉树进行线索化
  67. void createInThread(ThreadNode<T> *current, ThreadNode<T> * &pre)
  68. {
  69. if (current == NULL)
  70. {
  71. return;
  72. }
  73. createInThread(current->leftChild, pre); //递归左子树的线索化
  74. if (current->leftChild == NULL) //建立当前结点的前驱结点
  75. {
  76. current->leftChild = pre;
  77. current->ltag = ;
  78. }
  79. if (pre != NULL&&pre->rightChild == NULL) //建立当前结点的后继结点
  80. {
  81. pre->rightChild = current;
  82. pre->rtag = ;
  83. }
  84. pre = current; //用前驱记住当前的结点
  85. createInThread(current->rightChild, pre); //递归右子树的线索化
  86. }
  87.  
  88. //寻找中序下第一个结点
  89. ThreadNode<T> * First(ThreadNode<T> *current) //返回以*current为根的中序线索二叉树中序遍历的第一个结点
  90. {
  91. ThreadNode<T> *p = current;
  92. while (p->ltag == )
  93. {
  94. p = p->leftChild; //循环找到最左下角结点
  95. }
  96. return p;
  97. }
  98.  
  99. //寻找中序下的后继结点
  100. ThreadNode<T>* Next(ThreadNode<T>* current)
  101. {
  102. ThreadNode<T>* p = current->rightChild;
  103. if(current->rtag==)
  104. {
  105. return First(p);
  106. }
  107. else
  108. {
  109. return p;
  110. }
  111. }
  112.  
  113. //寻找中序下最后一个结点
  114. ThreadNode<T> * Last(ThreadNode<T> *current) //返回以*current为根的中序线索二叉树中序遍历的最后一个结点
  115. {
  116. ThreadNode<T> *p = current;
  117. while (p->rtag==)
  118. {
  119. p = p->rightChild;
  120. }
  121. return p;
  122. }
  123. //寻找结点在中序下的前驱结点
  124. ThreadNode<T>* Prior(ThreadNode<T>* current)
  125. {
  126. ThreadNode<T>* p = current->leftChild;
  127. if (current->ltag==)
  128. {
  129. return Last(p);
  130. }
  131. else
  132. {
  133. return p;
  134. }
  135. }
  136. //在中序线索化二叉树中求父节点
  137. ThreadNode<T>* Parent(ThreadNode<T>* t)
  138. {
  139. ThreadNode<T>* p;
  140. if(t==root) //根节点无父节点
  141. {
  142. return NULL;
  143. }
  144. for (p = t; p->ltag == ; p = p->leftChild); //求*t为根的中序下的第一个结点p
  145. //情况1
  146. if (p->leftChild!=NULL) //当p左子树指向不为空
  147. {
  148. //令p为p的左子树指向的结点,判断此结点是否并且此节点的左右子树结点的指向都不为t,再将p为p的右孩子结点
  149. for (p = p->leftChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->rightChild);
  150. }
  151. //情况2
  152. //如果上面的循环完了,由于是p==NULL结束的循环,没有找到与t相等的结点,就是一直找到了中序线索化的第一个结点了,这时候这种就要用到情况2的方法
  153. if (p==NULL||p->leftChild==NULL)
  154. {
  155. //找到*t为根的中序下的最后一个结点
  156. for (p = t; p->rtag == ; p = p->rightChild);
  157. //让后让他指向最后一个结点指向的结点,从这个结点开始,以此判断它的左孩子孩子和右孩子是否和t相等
  158. for (p = p->rightChild; p != NULL&&p->leftChild != t&&p->rightChild != t; p = p->leftChild);
  159. }
  160. return p;
  161. }
  162.  
  163. //中序线索化二叉树上执行中序遍历的算法
  164. void InOrder(ThreadNode<T>* p)
  165. {
  166. for (p=First(root);p!=NULL;p=Next(p))
  167. {
  168. cout << p->data<<" ";
  169. }
  170. cout << endl;
  171. }
  172. //中序线索化二叉树上实现前序遍历的算法
  173. void PreOrder(ThreadNode<T>* p)
  174. {
  175. while (p!=NULL)
  176. {
  177. cout << p->data<<" "; //先访问根节点
  178. if (p->ltag==)
  179. {
  180. p = p->leftChild; //有左子树,即为后继
  181. }
  182. else if(p->rtag==) //否则,有右子树,即为后继
  183. {
  184. p = p->rightChild;
  185. }
  186. else //无左右子树
  187. {
  188. while (p!=NULL&&p->rtag==) //检测后继线索
  189. {
  190. p = p->rightChild; //直到找到有右子树的结点
  191. }
  192. if (p!=NULL)
  193. {
  194. p = p->rightChild; //该结点的右子树为后继
  195. }
  196. }
  197. }
  198. cout << endl;
  199. }
  200. //中序线索二叉树的后序遍历算法
  201. void PostOrder(ThreadNode<T>* p)
  202. {
  203. ThreadNode<T>* t = p;
  204. while (t->ltag==||t->rtag==) //寻找后续第一个结点
  205. {
  206. if(t->ltag==)
  207. {
  208. t = t->leftChild;
  209. }
  210. else if(t->rtag==)
  211. {
  212. t = t->rightChild;
  213. }
  214. }
  215. cout << t->data<<" "; //访问第一个结点
  216. while ((p=Parent(t))!=NULL) //每次都先找到当前结点的父结点
  217. {
  218. //若当前结点是父节点的右子树或者当前结点是左子树,但是这个父节点没有右子树,则后续下的后继为改父节点
  219. if (p->rightChild==t||p->rtag==)
  220. {
  221. t = p;
  222. }
  223. //否则,在当前结点的右子树(如果存在)上重复执行上面的操作
  224. else
  225. {
  226. t = p->rightChild;
  227. while (t->ltag==||t->rtag==)
  228. {
  229. if (t->ltag==)
  230. {
  231. t = t->leftChild;
  232. }
  233. else if (t->rtag==)
  234. {
  235. t = t->rightChild;
  236. }
  237. }
  238. }
  239. cout << t->data << " ";
  240. }
  241. }
  242.  
  243. private:
  244. //树的根节点
  245. ThreadNode<T> *root;
  246. T RefValue;
  247. };

测试函数

主函数

  1. int main(int argc, char* argv[])
  2. {
  3. //abc##de#g##f###
  4. ThreadTree<char> tree('#');
  5. tree.CreateTree();
  6. tree.CreateInThread();
  7. tree.InOrder();
  8. tree.PreOrder();
  9. tree.PostOrder();
  10. }

线索二叉树的详细实现(C++)的更多相关文章

  1. 【PHP数据结构】完全二叉树、线索二叉树及树的顺序存储结构

    在上篇文章中,我们学习了二叉树的基本链式结构以及建树和遍历相关的操作.今天我们学习的则是一些二叉树相关的概念以及二叉树的一种变形形式. 完全二叉树 什么叫完全二叉树呢?在说到完全二叉树之前,我们先说另 ...

  2. 数据结构《9》----Threaded Binary Tree 线索二叉树

    对于任意一棵节点数为 n 的二叉树,NULL 指针的数目为  n+1 , 线索树就是利用这些 "浪费" 了的指针的数据结构. Definition: "A binary ...

  3. 线索二叉树Threaded binary tree

    摘要   按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排序为一个线性序列.在该序列中,除第一个结点外每个结点有且仅有一个直接前驱结点:除最后一个结点外每一个结点有且仅有一个直接后继结点.这 ...

  4. 遍历二叉树 traversing binary tree 线索二叉树 threaded binary tree 线索链表 线索化

    遍历二叉树   traversing binary tree 线索二叉树 threaded binary tree 线索链表 线索化 1. 二叉树3个基本单元组成:根节点.左子树.右子树 以L.D.R ...

  5. 树和二叉树->线索二叉树

    文字描述 从二叉树的遍历可知,遍历二叉树的输出结果可看成一个线性队列,使得每个结点(除第一个和最后一个外)在这个线形队列中有且仅有一个前驱和一个后继.但是当采用二叉链表作为二叉树的存储结构时,只能得到 ...

  6. 图解中序遍历线索化二叉树,中序线索二叉树遍历,C\C++描述

    body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...

  7. 【Java】 大话数据结构(9) 树(二叉树、线索二叉树)

    本文根据<大话数据结构>一书,对Java版的二叉树.线索二叉树进行了一定程度的实现. 另: 二叉排序树(二叉搜索树) 平衡二叉树(AVL树) 二叉树的性质 性质1:二叉树第i层上的结点数目 ...

  8. 数据结构之线索二叉树——C语言实现

     线索二叉树操作 (1) 线索二叉树的表示:将每个节点中为空的做指针与右指针分别用于指针节点的前驱和后续,即可得到线索二叉树. (2) 分类:先序线索二叉树,中序线索二叉树,后续线索二叉树 (3) 增 ...

  9. 后序线索二叉树中查找结点*p的后继

    在后序线索二叉树中查找结点*p的后继: 1.若结点*p为根,则无后继:2.若结点*p为其双亲的右孩子,则其后继为其双亲:3.若结点*p为其双亲的左孩子,且双亲无右子女,则其后继为其双亲:4.若结点*p ...

随机推荐

  1. 浅谈C#委托的用法-delegate[转]

    一.委托的概念 委托和类一样是一种用户自定义类型,它存储的就是一系列具有相同签名和返回类型的方法的地址,调用委托的时候,它所包含的所有方法都会被执行. 借用百度上的一句话概括:委托是一个类,它定义了方 ...

  2. C# 读取Excel到DataTable两种方式对比

    方式一 OLEDB读取 数据库引擎 优点:读取速度快,依据sheet排序读取 缺点:对于Excel版本依赖强,无法读取指定sheet 错误提示:本地计算机未指定 Microsoft.ACE.OLEDB ...

  3. Codeforces Round #616 (Div. 2) B. Array Sharpening

    t题目链接:http://codeforces.com/contest/1291/problem/B 思路: 用极端的情况去考虑问题,会变得很简单. 无论是单调递增,单调递减,或者中间高两边低的情况都 ...

  4. HDU1548 奇怪的电梯(bfs求最少)

    There is a strange lift.The lift can stop can at every floor as you want, and there is a number Ki(0 ...

  5. 假期学习【十】首都之窗百姓信件JavaWweb+Echarts图表展示

    今天主要对昨天爬取的数据进行处理,处理后用Echart图表展示, 效果如下:

  6. 记录 Docker 的学习过程 (dockerfile自动制作镜像)

    自动制作镜像 通过编写dockerfile来自动创建镜像 #vi Dockerfile #编辑dockerfile文件,一定要以这个名字命名 #cat Dockerfile #导入哪个基础镜像FROM ...

  7. Git学习笔记-上传一个新的项目到GitHub上

    前提: 已有Github账号,已在Github上建立了仓库,已在Github上配置了SSH,已上传过一些项目到Github上 目标: 目前有一个新的项目,需要上传到github上 我的做法记录: 1. ...

  8. android 代码实现模拟用户点击、滑动等操作

    /** * 模拟用户点击 * * @param view 要触发操作的view * @param x 相对于要操作view的左上角x轴偏移量 * @param y 相对于要操作view的左上角y轴偏移 ...

  9. 利用Marshal来管理非托管资源

    void MarshalChartDemo() { string name = "xuwei"; IntPtr pName = Marshal.AllocHGlobal(name. ...

  10. day30 NFS服务器概述

    02. NFS存储服务概念介绍 NFS是Network File System的缩写,中文意思是网络文件共享系统, 它的主要功能是通过网络(一般是局域网)让不同的主机系统之间可以共享文件或目录 存储服 ...