一、二叉树

涉及到树,一定要想到树的深度,一般越小越好。
点击查看【processon】
满二叉树:每个节点都有0或2个子节点。
完全二叉树
1、从根节点起每层必须左到右填充。
2、高度为d,除了d-1层以外,每次都是满的。
image.png

满二叉树定理:非空满二叉树,叶节点 = 内部节点 + 1
二叉树定理:非空二叉树,空子树 = 节点数 + 1

二叉树节点ADT

  1. template<class Elem> class BinNode{
  2. public:
  3. virtual Elem& val() = 0;
  4. virtual void setVal(const Elem&) = 0;
  5. virtual BinNode* left() const = 0;
  6. virtual BinNode* right() const = 0;
  7. virtual void setLeft(BinNode *) = 0;
  8. virtual void setRight(BinNode *) = 0;
  9. virtual bool isLeaf() = 0;
  10. }

1、周游(遍历)二叉树

前序遍历:根 -> 左 -> 右。
后序遍历:左 -> 右 -> 根。
中序遍历:左 -> 根 -> 右。
C++代码实现:

  1. //前序遍历
  2. template<class Elem>
  3. void preorder(BinNode<Elem>* root){
  4. if(root == NULL) return;
  5. visit(root);
  6. preorder(root->left());
  7. preorder(root->right());
  8. }
  1. //计算节点数
  2. template<class Elem>
  3. void count(BinNode<Elem>* root){
  4. if(root == NULL) return 0;
  5. return 1 + count(root->left()) + count(root->right());
  6. }

2、二叉树实现

二叉树实现(指针)

image.png

  1. template<class Elem>
  2. class BinNodePtr : public BinNode<Elem>{
  3. private:
  4. Elem val;
  5. BinNodePtr* left;
  6. BinNodePtr* right;
  7. public:
  8. BinNodePtr(){
  9. left = right = NULL;
  10. }
  11. BinNodePtr(const Elem* elem, BinNodePtr* l = NULL, BinNodePtr* r = NULL){
  12. it = elem;
  13. left = l;
  14. right = r;
  15. }
  16. ~BinNodePtr(){};
  17. Elem& val(){
  18. return val;
  19. }
  20. void setVal(const Elem& elem){
  21. val = elem;
  22. }
  23. BinNode* left() const{
  24. return left;
  25. }
  26. BinNode* right() const{
  27. return right;
  28. }
  29. void setLeft(BinNode * l){
  30. left = (BinNodePtr*) l;
  31. }
  32. void setRight(BinNode * r){
  33. right = (BinNodePtr*) r;
  34. }
  35. bool isLeaf(){
  36. return (left == NULL) && (right == NULL);
  37. }
  38. }

弊端(分支、叶节点不分)

分支节点和叶节点是两种节点。根据二叉树的实际应用经验,叶节点和分支节点的作用有很大不同,比如可能只有叶节点存储数据,可能两种节点存储的数据不同。所以分支节点和叶节点需要分开定义。
image.png

  1. class VarBinNode{
  2. public:
  3. virtual bool isLeaf() = 0;
  4. }
  5. //叶节点
  6. class LeafNode : public VarBinNode{
  7. private:
  8. Operand var;
  9. public:
  10. LeafNode(const Operand& val){ var = val; }
  11. bool isLeaf(){ return true; }
  12. Operand value(){ return var; }
  13. }
  14. //内部节点(分支节点)
  15. class IntlNode : public VarBinNode{
  16. private:
  17. VarBinNode* left;
  18. VarBinNode* right;
  19. Operator opx;
  20. public:
  21. IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r){
  22. opx = op;
  23. left = l;
  24. right = r;
  25. }
  26. bool isLeaf(){ return false; }
  27. VarBinNode* leftchild(){ return left; }
  28. VarBinNode* rightchild() { return right; }
  29. Operator value(){ return opx; }
  30. }
  31. //前序遍历
  32. void traverse(VarBinNode* subroot){
  33. if(subroot == NULL) return;
  34. if(subroot->isLeaf())
  35. cout << "Leaf:"
  36. << ((LeafNode*)subroot)->value() << endl;
  37. else{
  38. cout << "Internal:"
  39. << ((IntlNode*)subroot)->value() << endl;
  40. traverse(((IntlNode*)subroot)->leftchild());
  41. traverse(((IntlNode*)subroot)->rightchild());
  42. }
  43. }

结构性开销

存储左右节点的指针占据空间(结构性开销)。
内部节点可能不存储数据,空间使用率低。

降低结构性开销:数组实现完全二叉树

完全二叉树的特性,节点的填充是有顺序的,从左到右。如果按顺序塞入数组元素,则根据数据下标即可知道父节点、左/右子节点。
设节点总数n,范围(0 ~ n - 1),节点 r 的亲属计算公式如下:

  1. Parent(r) = (r - 1) / 2; //当 r != 0 时。
  2. Leftchild(r)= 2r + 1; //当 2r + 1 < n 时。
  3. Rightchild(r) = 2r + 2; //当 2r + 2 < n 时。
  4. Leftsibling(r) = r - 1; //左兄弟,r为偶数且0 <= r <= n-1时
  5. Rightsibling(r) = r + 1; //右兄弟,r为奇数且r + 1 < n时

3、二叉查找树

Binary Search Tree,BST,二叉检索树,二叉排序树。
无序表实现的字典,插入很快(末端),查找O(n)。
数组有序表实现的字典,二分查询O(n),插入很慢。

二叉查找树,满足条件:左子树值 <= 根值 <= 右子树值
image.png

  1. template<class Key,class Elem, class KEComp, class EEComp>
  2. class BST : public Dictionary<Key, Elem, KEComp, EEComp>{
  3. private:
  4. BinNode<Elem>* root;
  5. int nodecount;
  6. private:
  7. void clearhelp(BinNode<Elem>*); //删除全部节点
  8. //在节点r下插入一个值 = t的节点。返回被插入节点的父节点。
  9. //在二叉树中插入节点
  10. //r: 被插树的root节点
  11. //t: 插入节点的节点值
  12. //ret: 被插入节点的父节点
  13. BinNode<Elem>* inserthelp(BinNode<Elem>* r, const Elem& t);
  14. //删除r节点下的最小节点,保存在t,返回被删除节点的右子树。
  15. //删除二叉树的最小节点
  16. //r: 二叉树根节点
  17. //t: 被删除节点
  18. //ret: 被删除节点的右子树
  19. //注意“删除”,只是将值抹除。例如:
  20. //删除一个有left/right节点的节点target,其实就是:
  21. //target->setValue(right子树中的最小值)
  22. BinNode<Elem>* deletemin(BinNode<Elem>* r, BinNode<Elem> *& t);
  23. //删除r节点下满足K的子节点,并保存在t。返回
  24. //删除二叉树中指定Key的节点
  25. //r: 二叉树根节点
  26. //K: 指定Key值
  27. //t: 被删除节点
  28. //ret: 二叉树不存在Key的节点,返回r,注意递归调用每个r不同
  29. // : 存在Key的节点,返回值有三种情况:
  30. // : 如果被删节点只有一个子节点,则返回子节点
  31. // : 如果被删节点有两个子节点,就返回被删节点指针,从有节点中选择最小节点的值赋值给被删节点。
  32. BinNode<Elem>* removehelp(BinNode<Elem>* r, const Key& Key, BinNode<Elem> *& t);
  33. //查找r节点下满足K的子节点,并保存在t
  34. bool findhelp(BinNode<Elem>* r, const Key& K, Elem& t) const;
  35. //中序遍历
  36. void printhelp(BinNode<Elem>*, int) const;
  37. public:
  38. BST(){
  39. root = NULL;
  40. nodecount = 0;
  41. }
  42. ~BST(){
  43. clearhelp(root);
  44. }
  45. void clearhelp(BinNode<Elem>* subroot){
  46. if(subroot == NULL) return;
  47. clearhelp(subroot->left());
  48. clearhelp(subroot->right());
  49. delete subroot;
  50. }
  51. BinNode<Elem>* inserthelp(BinNode<Elem>* subroot,const Elem& e){
  52. if(subroot == NULL) return NULL;
  53. }
  54. //一直沿着左子树找,直到没有左子树。
  55. BinNode<Elem>* deletemin(BinNode<Elem>* subroot, BinNode<Elem>* &min){
  56. if(subroot->left() == NULL){ //没有左子树,说明subroot就是最小节点
  57. min = subroot; //最小节点
  58. return subroot->right(); //返回最小节点的右子树,方便递归的时候,
  59. //把这个右子树设置成被删节点父节点的左子树。
  60. //也就是代替被删节点。
  61. }
  62. else{
  63. //这个setleft有两种情况:
  64. //
  65. //第一种情况:递归到了最左节点,也就是
  66. //deletemin返回被删节点右子树
  67. //此时的subroot是被删节点的父节点
  68. //所以setleft是把subroot的左子树设置为被删节点的右子树。
  69. //
  70. //第二种情况:递归途中,返回的就是递归节点
  71. //此时的subroot就是递归节点,deletemin返回的就是subrrot的左子节点
  72. //所以setleft其实没干什么,但是这样代码更简化,唯一作用就是上面的第一种情况。
  73. subroot->setLeft(deletemin(subroot->left(), min));
  74. //递归过程中,还没递归到最左节点,则返回递归节点
  75. return subroot;
  76. }
  77. }
  78. BinNode<Elem>* removehelp(BinNode<Elem>* subroot, const Key& K, BinNode<Elem>*& t){
  79. if(subroot == NULL) return NULL;
  80. else if(KEComp::lt(K, subroot->val()))
  81. subroot->setLeft(removehelp(subroot->left(), K, t));
  82. else if(KEComp::gt(K, subroot->val()))
  83. subroot->setRight(removehelp(subroot->right(), K, t));
  84. else{
  85. BinNode<Elem>* temp;
  86. t = subroot;
  87. if(subroot->left() == NULL)
  88. subroot = subroot->right();
  89. else if(subroot->right() == NULL)
  90. subroot = subroot->left();
  91. else{
  92. subroot->setRight(deletemin(subroot->right(), temp));
  93. Elem te = subroot->val();
  94. subroot->setVal(temp->val());
  95. temp->setVal(te);
  96. t = temp;
  97. }
  98. }
  99. return subroot;
  100. }
  101. bool findhelp(BinNode<Elem>* subroot, const Key& K, Elem& e){
  102. if(subroot == NULL) return false;
  103. else if(KEComp::lt(K, e)) return findhelp(subroot->leftchild(), K, e);
  104. else if(KEComp::gt(K, e)) return findhelp(subroot->rightchild(), K, e);
  105. else{
  106. e = subroot->val();
  107. return true;
  108. };
  109. }
  110. //中序遍历
  111. void printhelp(BinNode<Elem>* subroot, int level){
  112. if(subroot == NULL) return;
  113. printhelp(subroot->left(), level + 1);
  114. for(int i = 0; i < level; i++){
  115. cout << " ";
  116. }
  117. cout << subroot->val() << "\n";
  118. printhelp(subroot->right(), level + 1);
  119. }
  120. //以下是字典的虚函数
  121. public:
  122. void clear(){ //重新初始化
  123. clearhelp(root);
  124. root = NULL;
  125. nodecount = 0;
  126. }
  127. bool insert(const Elem& e){ //插入一个元素
  128. root = inserthelp(root, e);
  129. nodecount++;
  130. return true;
  131. }
  132. bool remove(const Key& K, Elem& e){ //根据key删除元素
  133. BinNode<Elem>* t = NULL;
  134. root = removehelp(root, K, t);
  135. if(t == NULL) return false;
  136. e = t->val();
  137. nodecount++;
  138. delete t;
  139. return true;
  140. }
  141. //按照自定义规则删除一个元素,循环执行则最终会清空字典。
  142. bool removeAny(Elem& e){
  143. if(root = NULL) return false;
  144. BinNode<Elem>* t;
  145. root = deletemin(root, t);
  146. e = t->val();
  147. delete t;
  148. nodecount++;
  149. return true;
  150. }
  151. bool find(const Key&, Elem&){ //根据key查找元素
  152. return findhelp(root, K, e);
  153. }
  154. int size(){
  155. return nodecount;
  156. }
  157. void print(){
  158. if(root == NULL) cout << "the BST is empty.\n";
  159. else printhelp(root, 0);
  160. }
  161. }

查插删改性能

1、findhelp、inserthelp取决于深度depth。
2、removehelp取决于被删节点的depth。
3、遍历复杂度 = O(n)。
4、log n <= 树深度depth <= n,depth越小,树越平衡。
5、算法最差O(n),最好O(log n)。
6、二叉查找树平衡度不稳定,所以性能也不稳定。

4、堆与优先队列

堆是完全二叉树,根、左、右存在相同的关系R。
完全二叉树使得平衡度最大化。

根节点:parent,左右节点:left、child。
最大值堆:parent >= max(left,right)的完全二叉树,max-heap。root最大。
最小值堆:parent <= min(left,right)的完全二叉树,min-heap。root最小。
注意和二叉查找树区分:image.png

  1. template<class Elem, class Comp>
  2. class maxheap{
  3. private:
  4. Elem* Heap; //堆的物理结构,数组,节点位置pos就是相应的数组下标。
  5. //所以0是堆顶,完全二叉树特性,n/2以后是叶节点。
  6. //建堆的情况有两种:
  7. //一:构造函数的时候,传入了一个数组的数据,然后这些数据进行建堆。
  8. //二、构造的时候是空的,然后一个一个insert进来。
  9. int size; //堆的空间大小
  10. int n; //当前元素个数
  11. void siftdown(int pos); //pos的左右子树已经是堆。
  12. //把pos节点“下沉”到正确的位置,即父节点比左右子节点大。
  13. public:
  14. maxheap(Elem* h, int num, int max); //数组h有num个元素,数组总长度是max。
  15. int heapsize() const ; //堆空间大小
  16. bool isLeaf(int pos) const; //pos是否是叶节点位置
  17. int leftchild(int pos) const; //pos是左节点?
  18. int rightchild(int pos) const; //pos是右节点?
  19. int parent(int pos) const; //pos的父节点位置
  20. bool insert(const Elem&); //插入元素,注意插入过程可不像BST
  21. //为了保持完全二叉树结构,首先插入堆最后面
  22. //然后“上浮”到正确位置(不断与父节点比较)
  23. bool removemax(Elem&); //删除根节点,注意实际上没有真的delete。
  24. //根节点和最后一个节点交换,然后新的根节点“下沉”
  25. //到正确位置,此时的最后一个节点就是删除的根节点。
  26. //在堆排序,用的上,removemax的过程其实就是排序的过程
  27. bool remove(int pos, Elem&); //删除pos位置节点
  28. //pos位置节点和最后一个节点n对换
  29. //接下来同removemax工作。
  30. void buildheap(); //Heapify contents of Heap,一个一个插入元素
  31. private:
  32. void siftdown(int pos){
  33. while(!isLeaf(pos)){
  34. int maxChildPos = leftchild(pos);
  35. int rc = rightchild(pos);
  36. if((rc < n) && Comp::lt(Heap[maxChildPos], Heap[rc]))
  37. maxChildPos = rc;
  38. if(!Comp::lt(Heap[pos], Heap[maxChildPos])) return;
  39. swap(Heap, pos, maxChildPos);
  40. pos = maxChildPos;
  41. }
  42. }
  43. public:
  44. maxheap(Elem* h, int num, int max){
  45. Heap = h;
  46. n = num;
  47. size = max;
  48. buildHeap();
  49. }
  50. int heapsize() const{ return n; }
  51. bool isLeaf(int pos) const { return (pos >= n / 2) && (pos < n); }
  52. int leftchild(int pos) const{ return pos*2 + 1; }
  53. int rightchild(int pos) const{ return pos*2 + 2; }
  54. int parent(int pos) const{ return (pos-1) / 2; }
  55. bool insert(const Elem& val){
  56. if(n >= size) return false;
  57. int curr = n++;
  58. Heap[curr] = val; //为了保持完全二叉树结构,插入堆尾,也就是数据最后一位。
  59. while((cur != 0) && (Comp::gt(Heap[curr], Heap[parent(curr)]))){
  60. //将堆尾节点“上浮”到正确位置。
  61. swap(Heap, curr, parent(curr));
  62. curr = parent(curr);
  63. }
  64. return true;
  65. }
  66. bool removemax(Elem& it){
  67. if(n == 0) return false;
  68. swap(Heap, 0, --n); // 把最后一个节点和根节点对调
  69. if(n != 0) siftdown(0); // 把新的root节点重新“下沉”
  70. it = Heap[n]; // swap之后,n位置的节点就是要删除的root节点
  71. return true;
  72. }
  73. bool remove(int pos, Elem& t){
  74. if((pos< 0) || (pos >= n)) return false;
  75. swap(Heap, pos, --n);
  76. //这是教材中的一段代码,不为什么考虑pos节点和父节点的比值,
  77. //按理说父节点肯定是大于pos节点值的。此时pos值为堆尾节点
  78. //按理说肯定是更大的,无需考虑交换的,没有明白????
  79. while((pos != 0) && (Comp::gt(Heap[pos], Heap[parent(pos)])))
  80. swap(Heap, pos, parent(pos));
  81. siftdown(pos); //将新插入的堆尾节点“下沉”到正确位置。
  82. it = Heap[n];
  83. return true;
  84. }
  85. void buildheap(){
  86. //n / 2之后是叶节点,无需移动
  87. for(int i = n / 2 - 1; i >= 0; i--)
  88. siftdown(i);
  89. }
  90. }

5、Huffman编码树

5.1、构建Huffman树

image.pngimage.png
类设计:
HuffNode:哈弗曼树的节点基类(内部节点、叶节点)。
LeafNode:叶节点,存储权重和字母值。
IntlNode:内部节点,存储子节点权重和。
FreqPair:字母-频率数的数据对,叶节点保存值用。
HHComp:哈弗曼树的compare函数(比较根权重)
HuffTree:哈弗曼树的类

buildHuff:通过一个有序链表来对不断构建的HuffTree进行保存和排序。顺序是从HHComp的方法排列。

  1. template<class Elem>
  2. class HuffNode{
  3. public:
  4. virtual int weight() = 0;
  5. virtual bool isLeaf() = 0;
  6. virtual HuffNode* left() const = 0;
  7. virtual HuffNode* right() const = 0;
  8. virtual void setLeft(HuffNode*) = 0;
  9. virtual void setRight(HuffNode*) = 0;
  10. }
  11. //Huffman树的叶节点,保存字幕
  12. template<class Elem>
  13. class LeafNode : public HuffNode<Elem>{
  14. private:
  15. FreqPair<Elem>* it; //存储字母Elem、和他出现次数(权重)对。
  16. public:
  17. LeafNode(const Elem& val, int freq){
  18. it = new FreqPair<Elem>(val, freq);
  19. }
  20. int weight(){ return it->weight() }
  21. FreqPair<Elem>* val(){ return it; }
  22. bool isLeaf(){ return true; }
  23. virtual void setLeft(HuffNode*){}
  24. virtual void setRight(HuffNode*){}
  25. virtual HuffNode* left() const{ return NULL; }
  26. virtual HuffNode* right() const{ return NULL; }
  27. }
  28. //Huffman树的内部节点。
  29. template<class Elem>
  30. class IntlNode : public HuffNode<Elem>{
  31. private:
  32. HuffNode<Elem>* lc;
  33. HuffNode<Elem>* rc;
  34. int wgt;
  35. public:
  36. IntlNode(HuffNode<Elem>* l, HuffNode<Elem>* r){
  37. wgt = l->weight() + r->weight();
  38. lc = l; rc = r;
  39. }
  40. int weight() { return wgt; }
  41. bool isLeaf(){ return false; }
  42. HuffNode<Elem>* left() const { return lc; }
  43. HuffNode<Elem>* right() const { return rc; }
  44. void setLeft(HuffNode<Elem>* b) { lc = (HuffNode*)b; }
  45. void setRight(HuffNode<Elem>* b) { rc = (HuffNode*)b; }
  46. }
  47. template<class Elem>
  48. class FreqPair{
  49. private:
  50. Elem it;
  51. int freq;
  52. public:
  53. FreqPair(const Elem& e, int f){ it = e; freq = f; }
  54. ~FreqPair(){}
  55. int weight(){ return freq; }
  56. Elem& val() { return it; }
  57. }
  58. template<class Elem>
  59. class HuffTree{
  60. private:
  61. HuffNode<Elem>* theRoot;
  62. public:
  63. HuffTree(Elem& val, int freq){
  64. theRoot = new LeafNode<Elem>(val, freq);
  65. }
  66. HuffTree(HuffTree<Elem>* l, HuffTree<Elem>* r){
  67. theRoot = new IntlNode<Elem>(l->root(), r->root());
  68. }
  69. ~HuffTree(){};
  70. HuffNode<Elem>* root(){ return theRoot; }
  71. int weight(){ return theRoot->weight(); }
  72. }
  73. template<class Elem>
  74. class HHComp{
  75. public:
  76. static bool lt(HuffTree<Elem>* x, HuffTree<Elem>* y){
  77. return x->weight() < y->weight();
  78. }
  79. static bool eq(HuffTree<Elem>* x, HuffTree<Elem>* y){
  80. return x->weight() == y->weight();
  81. }
  82. static bool gt(HuffTree<Elem>* x, HuffTree<Elem>* y){
  83. return x->weight() > y->weight();
  84. }
  85. }
  86. template<class Elem>
  87. HuffTree<Elem>* buildHuff(SLList<HuffTree<Elem>*, HHCompare<Elem> >* fl){
  88. HuffTree<Elem> *temp1, *temp2, *temp3;
  89. for(fl->setStart(); fl->leftLength() + fl->rightLength() > 1; fl->setStart()){St
  90. //第二个setStart是因为循环中remove链表节点
  91. //需要setStart重新定位栅栏到头部。
  92. fl->remove(temp1);
  93. fl->remove(temp2);
  94. temp3 = new HuffTree<Elem>(temp1, temp2);
  95. fl->insert(temp3);
  96. delete temp1;
  97. delete temp2;
  98. }
  99. return temp3;
  100. }

5.2、Huffman编码

点击查看【processon】

二、一般树(general tree)

前序、后序遍历和二叉树差不对,中序遍历一般用不到,因为子节点数量不确定。

1、ADT(右兄弟表示法)

一个(最左节点),以及下一个(右邻近)兄弟节点。即可遍历全部节点。

  1. template<class Elem>
  2. class GTNode{
  3. public:
  4. GTNode(const Elem&);
  5. ~GTNode();
  6. Elem value();
  7. bool isLeaf();
  8. GTNode* parent();
  9. GTNode* leftmost_child(); //最左子节点
  10. GTNode* right_sibling(); //右邻近 兄弟节点
  11. void setValue(Elem&);
  12. void insert_first(GTNode<Elem>*); //插入到最左边子节点左边
  13. void insert_next(GTNode<Elem>*); //插入到最右边节点右边
  14. void remove_first(); //同上
  15. void remove_next(); //同上
  16. }
  17. template<class Elem>
  18. class GenTree{
  19. private:
  20. void printHelp(GTNode*); //打印树
  21. public:
  22. GenTree();
  23. ~GenTree();
  24. void clear();
  25. GTNode* root();
  26. void newroot(Elem&, GTNode<Elem>*, GTNode<Elem>*);
  27. void print();
  28. private:
  29. // 前序遍历
  30. void printHelp(GTNode* subroot){
  31. if(subroot->isLeaf()) cout << "Leaf: ";
  32. else cout << "Internal: ";
  33. cout << subroot->value() << "\n";
  34. for(GTNode<Elem>* temp = subroot->leftmost_child(); temp != NULL;
  35. temp = temp->right_sibling())
  36. printhelp(temp);
  37. }
  38. }

2、ADT(父指针表示法)

每个节点只保存父节点指针,虽然没有右兄弟法存储的信息多,但是足够解决一些有用问题。比如:
1、节点是否在同一树中(FIND,找(最)父节点)
2、归并两个集合(UNION)
称为UNION/FIND算法。

  1. //array[k] = v,v是k的父节点
  2. class GENtree{
  3. private:
  4. int* array;
  5. int size;
  6. int FIND(int curr) const;
  7. public:
  8. Gentree(int sz);
  9. ~Gentree();
  10. void UNION(int a, int b); //并入a、b节点,使用他们有相同的最顶级根节点。
  11. bool differ(int a, int b); //a、b是否有相同的最顶级根节点
  12. }
  13. private:
  14. int FIND(int curr) const{ //找到最顶层的根节点
  15. while(array[curr] != ROOT) curr = array[curr];
  16. return curr;
  17. }
  18. //路径压缩合并
  19. int FIND(int curr) const{
  20. if(array[curr] == ROOT) return curr;
  21. return array[curr] = FIND(array[curr]);
  22. }
  23. public:
  24. Gentree(int sz){
  25. size = sz;
  26. array = new int[sz];
  27. for(int i = 0;i < sz;i++)
  28. array[i] = ROOT;
  29. }
  30. ~Gentree(){ delete[] array };
  31. //这里直接让b节点存储a节点的位置
  32. void UNION(int a, int b){ //这里直接让b节点存储a节点的位置
  33. int root1 = FIND(a);
  34. int root2 = FIND(b);
  35. if(root2 != root1) array[root2] = root1;
  36. }
  37. bool differ(int a, int b){ return FIND(a) == FIND(b); }
  38. }

这个UNION涉及到如何合并,当然要使树的深度尽可能低,有两种方法:

重量权衡合并

在UNION的时候,“小”树合并到“大”树上,大小说的是树的深度。

路径压缩

每次FIND的时候,递归FIND,让一个节点指向ROOT,其他节点指向这个节点。

3、树的实现

方法一、节点数组,子节点链表

数组保存树的节点,每个节点保存:
一个子节点链表
一个父节点。
这样的话,难找兄弟节点
image.png

方法二:左子节点、右兄弟节点

数组保存树节点。
每个节点保存:
父节点
最左子节点
右侧兄弟节点
image.png

改进2:“动态子节点数组”表示法

image.png
每个节点保存一个数组指针,数组存储的是子节点指针。在子节点创建的时候就知道了所有的子节点,创建的数组由可利用空间管理,当节点插入子节点时,数组又刚好满了,就从可利用空间要一个更大一点的数组,原来的数组回收。

方法3:“动态节点”表示法

树的节点由链表存储,每个节点又保存一个子节点链表。

方法4:“化森林为二叉”

image.png

4、K叉树

每个节点都固定有K个子节点。K = 2时,就是二叉树。
image.png

5、顺序树表示法

按前序遍历把节点顺序存储起来,这样节省空间,但访问耗时。
数据结构_二叉树、一般树 - 图13