• 数据结构——二叉树线索化遍历(前中后序遍历)


    二叉树线索化

    线索化概念:

    为什么要转换为线索化

            二叉树线索化是一种将普通二叉树转换为具有特殊线索(指向前驱和后继节点)的二叉树的过程。这种线索化的目的是为了提高对二叉树的遍历效率,特别是在不使用递归或栈的情况下进行遍历。

            将二叉树线索化的主要目的是为了提高对二叉树的遍历效率以及节省存储空间。线索化使得在不使用递归或栈的情况下可以更快速地进行遍历,特别是在特定顺序的遍历时,如前序、中序或后序遍历。  

    1. 提高遍历效率:线索化后,可以在常量时间内找到节点的前驱和后继节点,从而实现更高效的遍历。这对于需要频繁遍历大型二叉树或需要在树的中间部分执行插入和删除操作时特别有用。

    2. 无需递归或栈:线索化的二叉树允许你在遍历时省去递归或栈的开销,因为你可以沿着线索直接访问节点的前驱和后继,从而降低了内存和时间复杂度。

    3. 节省存储空间:线索化可以用较少的额外存储空间来实现。通常,只需为每个节点添加一个或两个指针来存储线索信息,而不需要额外的数据结构(如堆栈)来辅助遍历。

    4. 支持双向遍历:线索化的二叉树可以支持双向遍历,即可以在给定节点的前向和后向方向上遍历树。这在某些应用中很有用,例如双向链表的操作。

    5. 节省计算资源:在某些特定的应用场景中,通过线索化可以避免重复计算,因为可以直接访问前驱和后继节点,而无需再次搜索或遍历

    树的遍历

            先给定一棵树,然后对他进行每种遍历:

            前序遍历:

            前序遍历,就是先遍历根节点在遍历左子树,在遍历右子树;

            他的顺序就是:根节点->左子树->右子树

            根据递归先遍历了根节点,然后递归左子树,回溯后在进行遍历右子树的一个过程;

            例如上图开始前序遍历:

            遍历根节点50,然后递归左子树,34,现在把34看为根节点继续递归左子树,28,然后把28看作根节点,继续遍历左子树,19,然后把19看为根节点继续遍历,然后左子树为空,开始回溯,回溯到19,遍历右子树也为空,继续回溯到结点28,遍历右子树,31;然后通过这种思想一直进行遍历最总遍历完整棵子树;

            前序遍历结果:50,34,28,19,31,41,81,72,95

            中序遍历:

            顺序是:左子树->根节点->右子树

            其实在理解了前序遍历后,中序遍历也差不多的,刚才是先记录了根节点,现在开始,先一直递归遍历左子树,递归到没有左子树的时开始记录,比如上图递归到19,然后回溯,28,遍历右子树31,回溯34,遍历右子树41,回溯50,遍历右子树;然后最终的结果就是:

            19,28,31,34,41,50,72,81,95;

            中序遍历就是,找到子树的最左边的那个结点,然后回溯到它的父节点,然后遍历他父节点的右子树,然后到右子树中又去找它的最左的结点,这样一直经过这样的操作,最终完成中序遍历。

            后序遍历:

            顺序是:左子树->右子树->根节点

            先是左子树,那就先找到左子树中的最左边的结点,19,然后回溯,遍历右子树,然后再右子树中找最左边的结点,31,回溯28,然后在回溯;这样的一个过程;

            最终结果就是:

            19,31,28,41,34,72,95,81,50

            后序遍历,看着比中序和后序遍历难理解,其实只要懂得了递归回溯的那个过程,就思路回非常的清晰;然后大概的过程就是先遍历左子树,找到左子树中最左边的结点,回溯,然后遍历右子树,遍历右子树的过程也是先遍历右子树中的左子树,然后再进行遍历右子树的右子树,最后来遍历他们的根节点;

            3种遍历的代码实现:  

    1. void pre_orderNode(Node *root) {前序
    2. if (!root) return ;
    3. printf("%d ", root->data);//先输出根节点
    4. pre_orderNode(root->lchild);//遍历左子树
    5. pre_orderNode(root->rchild);//遍历右子树
    6. return ;
    7. }
    8. void in_orderNode(Node *root) {中序
    9. if (!root) return ;
    10. in_orderNode(root->lchild);//先遍历左子树
    11. printf("%d ", root->data);//在打印根节点的值
    12. in_orderNode(root->rchild);//在遍历右子树
    13. return ;
    14. }
    15. void post_orderNode(Node *root) {后序
    16. if (!root) return ;
    17. post_orderNode(root->lchild);//先遍历左子树
    18. post_orderNode(root->rchild);//在遍历右子树
    19. printf("%d ", root->data);//最终打印根节点值
    20. return ;
    21. }

            可以去理解一下代码,尝试在纸上或者脑子里执行以下代码,模拟运行以下;

    二叉树前序线索化:

            

            如图上图一个简单的二叉树,现在是将这个二叉树进行前序线索化,那么前序遍历的顺序是根节点->左子树->右子树;

            那么根节点就应该在条线的头部,然后再去遍历左子树和右子树,那么这个数的前序遍历结果是50,34,79;

            

            那么就可以将二叉树样来看,50是34的前驱,79是34的后继,前驱就是在遍历在他前面的,后继就是在他后面遍历的;

            现在我们将每个结点的指向两个子孩子指针改变为指向他的前驱和后继:

            通过这样的转换,那这棵树就转换为一条双向链表了:

            其中会发下50和79的会右一颗子树指向NULL,那么就需要用一个变量来记录,他是否右前驱或者后继;

            就是这样的一个转换,也证明了概念种的节省空间,不用递归遍历,可以双向遍历,提高了遍历效率;

            下面是代码实现:

    1. #include
    2. #include
    3. #include
    4. #define NORMAL 0
    5. #define THRENA 1
    6. //这里左儿子记录前驱,右儿子记录后继
    7. typedef struct Node {
    8. int val, ltag, rtag;//val结点的值,ltag记录是否有前驱,rtag记录是否有后继
    9. struct Node *lchild, *rchild;
    10. } Node;
    11. Node *getNewNode(int val) {//获取新的结点
    12. Node *root = (Node *)malloc(sizeof(Node));
    13. root->lchild = root->rchild = NULL;
    14. root->rtag = root->ltag = NORMAL;//NORMAL表示还未有前驱或后继
    15. root->val = val;
    16. return root;
    17. }
    18. Node *insert(Node *root, int val) {//添加结点,组成普通的二叉树
    19. if (!root) return getNewNode(val);
    20. if (root->val == val) return root;
    21. if (root->val > val) root->lchild = insert(root->lchild, val);
    22. else root->rchild = insert(root->rchild, val);
    23. return root;
    24. }
    25. void build_Thread(Node *root) {//建立线索化
    26. if (!root) return ;
    27. static Node *pre = NULL;//使用静态变量使得pre值不随程序递归而改变
    28. Node *left = root->lchild;//记录当前结点左右儿子
    29. Node *right = root->rchild;
    30. if (root->ltag == NORMAL) {//当前结点没有前驱结点
    31. root->lchild = pre;//给当前结点赋值前驱结点
    32. root->ltag = THRENA;//标记有前驱结点
    33. }
    34. if (pre && pre->rtag == NORMAL) {//如果它的前驱结点没有后继,并且前驱结点不为NULL
    35. pre->rchild = root;//将前驱结点的后继附上当前结点
    36. pre->rtag = THRENA;//标记前驱结点有后继了
    37. }
    38. pre = root;//pre等于当前递归的结点
    39. build_Thread(left);//递归左子树
    40. build_Thread(right);//在递归右子树
    41. return ;
    42. }
    43. void output(Node *root) {//遍历线索化二叉树
    44. if (!root) return ;
    45. Node *p = root;
    46. while (p) {
    47. printf("%d ", p->val);
    48. if (p->rtag == THRENA) p = p->rchild;//说明当前结点有后继直接往右节点也就是后继结点继续遍历
    49. else if (p->rtag == NORMAL) break;//如果当前结点没有后继结束遍历
    50. }
    51. return ;
    52. }
    53. void preface(Node *root) {//普通前序递归遍历
    54. if (!root) return ;
    55. printf("%d ", root->val);
    56. if (root->ltag == NORMAL) preface(root->lchild);
    57. if (root->rtag == NORMAL) preface(root->rchild);
    58. return ;
    59. }
    60. void clearNode(Node *root) {//回收空间
    61. if (!root) return ;
    62. if (root->ltag == NORMAL) clearNode(root->lchild);
    63. if (root->rtag == NORMAL) clearNode(root->rchild);
    64. free(root);
    65. return ;
    66. }
    67. int main() {
    68. srand(time(0));//获取当期计算时间,获取随机数
    69. Node *root = NULL;
    70. for (int i = 0; i < 10; i++) {
    71. int val = rand() % 100;
    72. root = insert(root, val);
    73. }
    74. preface(root);//先打印普通前序遍历
    75. putchar(10);//换行
    76. build_Thread(root);//建立线索化
    77. output(root);//输出前序线索化
    78. putchar(10);
    79. clearNode(root);
    80. return 0;
    81. }

    二叉树中序线索化:

           

            中序遍历顺序:左子树->根节点->右子树

            那么结果借是34,50,79

            转换线索化,那么34就是50的前驱,50就是34的后继;和前序遍历的是一样的;

            直接来看代码实现:

    1. #include
    2. #include
    3. #include
    4. #define NORMAL 0 //表示没有
    5. #define THRENA 1 //表示有
    6. //这里左儿子记录前驱,右儿子记录后继
    7. typedef struct Node {
    8. int val, ltag, rtag;///val结点的值,ltag记录是否有前驱,rtag记录是否有后继
    9. struct Node *lchild, *rchild;
    10. } Node;
    11. Node *getNewNode(int val) {///获取新的结点
    12. Node *p = (Node *)malloc(sizeof(Node));
    13. p->val = val;
    14. p->lchild = p->rchild = NULL;
    15. p->ltag = p->rtag = NORMAL;//NORMAL表示还未有前驱或后继
    16. return p;
    17. }
    18. Node *insert(Node *root, int val) {//添加结点,组成普通的二叉树
    19. if (!root) return getNewNode(val);
    20. if (root->val == val) return root;
    21. if (root->val > val) root->lchild = insert(root->lchild, val);
    22. else root->rchild = insert(root->rchild, val);
    23. return root;
    24. }
    25. void in_order(Node *root) {//普通递归中序遍历
    26. if (!root) return ;
    27. if (root->ltag == NORMAL) in_order(root->lchild);
    28. printf("%d ", root->val);
    29. if (root->rtag == NORMAL) in_order(root->rchild);
    30. return ;
    31. }
    32. void build_Thread(Node *root) {//建立线索化
    33. if (!root) return ;
    34. static Node *pre = NULL;//使用静态变量使得pre值不随函数递归过程改变而改变
    35. build_Thread(root->lchild);//由于是中序遍历,先递归到最左结点
    36. //中间过程就是想当于根节点
    37. if (root->ltag = NORMAL) {
    38. root->lchild = pre;
    39. root->ltag = THRENA;
    40. }
    41. if (pre && pre->rchild == NORMAL) {
    42. pre->rchild = root;
    43. pre->rtag = THRENA;
    44. }
    45. pre = root;
    46. build_Thread(root->rchild);//然后再遍历右子树
    47. return ;
    48. }
    49. Node *most_left(Node *root) {//找到最左结点
    50. Node *temp = root;
    51. while (temp && temp->ltag == NORMAL && temp->lchild != NULL) temp = temp->lchild;
    52. return temp;
    53. }
    54. void output(Node *root) {
    55. if (!root) ;
    56. Node *p = most_left(root);//从最左结点开始遍历
    57. while (p) {
    58. printf("%d ", p->val);
    59. if (p->rtag == THRENA) p = p->rchild;//如果后继存在进行遍历后继
    60. else p = most_left(p->rchild);//找到右子树的最左结点继续遍历
    61. }
    62. return ;
    63. }
    64. void clear(Node *root) {
    65. if (!root) return ;
    66. if (root->ltag == NORMAL) clear(root->lchild);
    67. if (root->rtag == NORMAL) clear(root->rchild);
    68. free(root);
    69. return ;
    70. }
    71. int main() {
    72. srand(time(0));
    73. Node *root = NULL;
    74. for (int i = 0; i < 10; i++) {
    75. int val = rand() % 100;
    76. root = insert(root, val);
    77. }
    78. in_order(root);
    79. putchar(10);
    80. build_Thread(root);
    81. output(root);
    82. putchar(10);
    83. clear(root);
    84. return 0;
    85. }

            二叉树后序线索化:

            直接上代码演示,如果前面两种你都弄懂了那么,后序理解起来也非常容易:

    1. #include
    2. #include
    3. #include
    4. #define NORMAL 0
    5. #define THRENA 1
    6. typedef struct Node {
    7. int val, ltag, rtag;
    8. struct Node *lchild, *rchild;
    9. } Node;
    10. Node *getNewNode(int val) {
    11. Node *root = (Node *)malloc(sizeof(Node));
    12. root->lchild = root->rchild = NULL;
    13. root->rtag = root->ltag = NORMAL;
    14. root->val = val;
    15. return root;
    16. }
    17. Node *insert(Node *root, int val) {
    18. if (!root) return getNewNode(val);
    19. if (root->val == val) return root;
    20. if (root->val > val) root->lchild = insert(root->lchild, val);
    21. else root->rchild = insert(root->rchild, val);
    22. return root;
    23. }
    24. void build_Thread(Node *root) {
    25. if (!root) return ;
    26. static Node *pre = NULL;
    27. build_Thread(root->lchild);
    28. build_Thread(root->rchild);
    29. if (root->ltag == NORMAL) {
    30. root->lchild = pre;
    31. root->ltag = THRENA;
    32. }
    33. if (pre && root->rtag == NORMAL) {
    34. pre->rchild = root;
    35. pre->rtag = THRENA;
    36. }
    37. pre = root;
    38. return ;
    39. }
    40. Node *most_left(Node *root) {
    41. while (root && root->ltag == THRENA && root->lchild) root = root->lchild;
    42. return root;
    43. }
    44. void output(Node *root) {
    45. if (!root) return ;
    46. Node *p = most_left(root);
    47. while (p) {
    48. printf("%d ", p->val);
    49. if (p->rtag == THRENA) p = p->rchild;
    50. else if (p->rtag == NORMAL) break;
    51. }
    52. return ;
    53. }
    54. void back_order(Node *root) {
    55. if (!root) return ;
    56. if (root->ltag == NORMAL) back_order(root->lchild);
    57. if (root->rtag == NORMAL) back_order(root->rchild);
    58. printf("%d ", root->val);
    59. return ;
    60. }
    61. void preface(Node *root) {
    62. if (!root) return ;
    63. printf("%d(", root->val);
    64. if (root->ltag == NORMAL) preface(root->lchild);
    65. printf(",");
    66. if (root->rtag == NORMAL) preface(root->rchild);
    67. printf(")");
    68. return ;
    69. }
    70. void clearNode(Node *root) {
    71. if (!root) return ;
    72. if (root->ltag == NORMAL) clearNode(root->lchild);
    73. if (root->rtag == NORMAL) clearNode(root->rchild);
    74. free(root);
    75. return ;
    76. }
    77. int main() {
    78. srand(time(0));
    79. Node *root = NULL;
    80. for (int i = 0; i < 10; i++) {
    81. int val = rand() % 100;
    82. root = insert(root, val);
    83. }
    84. preface(root);
    85. putchar(10);
    86. back_order(root);
    87. putchar(10);
    88. build_Thread(root);
    89. output(root);
    90. putchar(10);
    91. clearNode(root);
    92. return 0;
    93. }

  • 相关阅读:
    MySQL笔记之Checkpoint机制
    数组的定义与使用
    Halcon知识:盘点一下计量对象【1】
    python网页爬虫xpath应用
    scrapy+scrapyd+gerapy 爬虫调度框架
    这么多年你还在怕正则吗?
    js中的深拷贝浅拷贝
    吴恩达深度学习笔记(二)——浅层神经网络
    每日一个设计模式之【代理模式】
    Android Studio的安装 环境搭建
  • 原文地址:https://blog.csdn.net/qq_58240849/article/details/132739522