【霍罗维兹数据结构】二叉树前中后序遍历 | 层序遍历 | 复制二叉树 | 判断两个二叉树全等 | 可满足性问题

简介: 【霍罗维兹数据结构】二叉树前中后序遍历 | 层序遍历 | 复制二叉树 | 判断两个二叉树全等 | 可满足性问题

写在前面

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历,就是按照某种特定的规则,一次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。 访问节点所做的操作要看具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其他运算的基础。

Ⅰ. BINARY TREE TRAVERSALS

0x00 概念

经常出现的一个操作是遍历树,也就是说,对树上的每个节点都精确访问一次。

完全遍历会对树中的信息产生一个线性顺序。

当遍历一棵树时,我们希望以同样的方式对待每个节点和它的子树。

假设对于树中的每个节点:

代表向左移动

代表访问该节点(eg. 打印出数据字段)

代表向右移动。

则存在 6 种可能的遍历组合:

—— 中序遍历(inorder traversal)

—— 后序遍历(postorder traversal)

—— 前序遍历(preorder traversal)

二叉树的三种遍历,与表达式的  三种形式,不乏紧密、自然的联系。

(图5.15)

0x01 中序遍历 - Inorder Traversal

void inorder(tree_pointer ptr)
/* inorder tree traversal */
{
  if (ptr) {
    inorder(ptr->left_child);
    printf("%d", ptr->data);
    inorder(ptr->right_child);
  }
}

图5.15 按顺序输出:

0x02 前序遍历 - Preorder Traversal

void preorder(tree_pointer ptr)
/* preorder tree traversal */
{
  if (ptr) {
    printf("%d", ptr->data);
    preorder(ptr->left_child);
    preorder(ptr->right_child);
  }
}

图5.15 按顺序输出:

0x03 后序遍历 - Postorder Traversal

void postorder(tree_pointer ptr)
/* postorder tree traversal */
{
  if (ptr) {
    postorder(ptr->left_child);
    postorder(ptr->right_child);
    printf("%d", ptr->data);
  }
}

图5.15 按顺序输出:

0x04 非递归(循环)中序遍历 - Iterative Inorder Traversal

图5.16 含蓄地表现了 Program5.1 的堆叠和拆垛的过程。

一个没有动作的节点表示该节点被添加到堆栈中, - 而一个有printf动作的节点表示该节点被从堆栈中移除。 注意: - 左边的节点被堆叠起来,直到到达一个空节点, - 然后该节点被从堆叠中移除, - 该节点的右边子节点被堆叠起来

void iter_inorder(tree_pointer node)
{
  int top = -1; /* initialize stack */
  tree_pointer stack[MAX_STACK_SIZE];
  for (; ; ) {
    for (; node; node = node->left_child)
      add(&top, node); /* add to stack */
    node = delete(&top); /* delete from stack */
    if (!node) break; /* empty stack */
    printf("%d", node->data);
    node = node->right_child;
  }
}

🔑 iter_inorder 的分析:令 是树中结点个数。iterInorder 把每个节点入栈一次、出栈一次,所以时间复杂度为 ,空间复杂度取决于树的深度,也是

0x05 层序遍历 - Level Order Traversal

📚 层序遍历(Level Traversal):设二叉树的根节点所在的层数为1的情况下,从二叉树的根节点出发,首先访问第1层的树根节点,然后再从左到右访问第2层上的节点。接着是第3层的节点……以此类推,自上而下、从左向右地逐层访问树的节点。

层序遍历需要利用队列来实现遍历。

void level_order(tree_pointer ptr)
/* level order tree traversal */
{
  int front = rear = 0;
  tree_pointer queue[MAX_QUEUE_SIZE];
  if (!ptr) return; /* empty tree */
  addq(front, &rear, ptr);
  for (; ; ) {
    ptr = deleteq(&front, rear); /*empty list returns NULL*/
    if (ptr) {
      printf("%d", ptr->data);
      if (ptr->left_child)
        addq(front, &rear, ptr->left_child);
      if (ptr->right_child)
        addq(front, &rear, ptr->right_child);
    }
    else break;
  }
}

Ⅱ. 其他二叉树操作

0x00 复制二叉树

根据二叉树的定义,以及前中后序的递归实现,可以很容易地编写其他二叉树操作的C程序。

以常用的二叉树复制操作为例,程序 5.6 中的 copy 函数为实现代码。程序结构与刚才的 postorder 很接近,它实际上是根据它的代码修改而得到的,改动很少。

[Program 5.6] 复制二叉树

tree_pointer copy(tree_pointer original)
/* this function returns a tree_pointer to an exact copy
of the original tree */
{
  tree_pointer temp;
  if (original) {
    temp = (tree_pointer)malloc(sizeof(node));
    if (IS_FULL(temp)) {
      fprintf(stderr, "The memory is full\n");
      exit(1);
    }
    temp->left_child = copy(original->left_child);
    temp->right_child = copy(original->right_child);
    temp->data = original->data;
    return temp;
  }
  return NULL;
}

0x01 判断两个二叉树全等 - Testing For Equality Of Binary Trees

两个全等的二叉树定义为两者的结构相等,并且对应数据域的内容相等。

int equal(tree_pointer first, tree_pointer second)
/* function returns FALSE if the binary trees first and
second are not equal, otherwise it returns TRUE */
{
  return ((!first && !second) || (first && second &&
    (first->data == second->data) &&
    equal(first->left_child, second->left_child) &&
    equal(first->right_child, second->right_child))
}

0x03 可满足性问题(The Satisfiability Problem)

考虑由遍历 和操作符 构成的演算公式集合。

变量只有两种取值 true 和 flase。

公式满足如下条件:

(1)一个变量是一个表达式

(2)如果 x 和 y 是表达式, 则 是表达式

(3)操作符的优先级从高到低为 ,但括号可以改变运算顺序。

这三条基本规则可以生成命题演算的所有演算公式,如 "蕴含" 可用 表示。

对于给定的公式,是否存在一个变量集合的赋值,使该公式的求值结果为 true。

[program 5.8] 求解可满足性问题的第一个程序

for (all 2n possible combinations) {
  generate the next combination;
  replace the variables by their values;
  evaluate the expression;
  if (its value is true) {
    printf(<combination>);
    return;
  }
}
printf("No satisfiable combination\n");

<Evaluating an propositional formula>

为了评为了评估一个表达式,我们可以按后序遍历树,评估子树,直到整个表达式被简化为一个单一的值。 这对应于算术表达式的后缀评估。

[program 5.8] 后序求值

void post_order_eval(tree_pointer node)
{
  /* modified postorder traversal to evaluate
  a propositional calculus tree */
  if (node) {
    post_order_eval(node->left_child);
    post_order_eval(node->right_child);
    switch (node->data) {
    case not : node->value =
      !node->right_child->value;
      break;
    caseand : node->value =
      node->right_child->value && node->left_child->value;
    break;
    case or : node->value =
      node->right_child->value || node->left_child->value;
      break;
    case true: node->value = TRUE;
      break;
    case false: node->value = FALSE;
    }
  }
}

参考资料

Fundamentals of Data Structures in C

相关文章
|
13天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
62 8
|
1月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
18 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
1月前
|
存储 算法 搜索推荐
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
这篇文章主要介绍了顺序存储二叉树和线索化二叉树的概念、特点、实现方式以及应用场景。
20 0
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
|
1月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
1月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
24 0
|
14天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
5天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
14 1
|
8天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
11天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
13天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
40 4