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

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

写在前面

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

Ⅰ. 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

相关文章
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
376 10
 算法系列之数据结构-二叉树
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
432 12
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
232 10
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
603 3
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
数据采集 存储 算法
【C++数据结构——图】图的遍历(头歌教学实验平台习题) 【合集】
本文介绍了图的遍历算法,包括深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先遍历通过递归方式从起始节点深入探索图,适用于寻找路径、拓扑排序等场景;广度优先遍历则按层次逐层访问节点,适合无权图最短路径和网络爬虫等应用。文中提供了C++代码示例,演示了如何实现这两种遍历方法,并附有测试用例及结果,帮助读者理解和实践图的遍历算法。
757 0
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
364 59
|
9月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
194 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
704 77
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。