实验 2:树形数据结构的实现与应用

简介: 通过实验达到:理解和掌握树及二叉树的基本概念;

1.实验目的

通过实验达到:


理解和掌握树及二叉树的基本概念;


理解和掌握二叉树的顺序存储结构、链式存储结构;


理解和掌握采用二叉链式存储结构下二叉树的各种遍历操作的思想及 其应用;


加深对堆栈、队列的概念及其典型操作思想的理解;


掌握典型二叉树操作算法的算法分析。


2. 实验题目:二叉树的建立、遍历及其应用

设树结点的元素类型为 ElemType(可以为 char 或 int),采用二叉链(或三叉 链,即双亲孩子)存储,实现以下二叉树的各种基本操作的程序:


① 编写一个创建二叉树的函数,通过文件读取方式,建立不少于 10 个结点 的二叉树 T(建议用递归方式创建);


② 给定元素 x,在二叉树中查找该元素 x,若找到则返回该结点的指针;


③ 用凹入表示法打印该二叉树(可以是图 8-2 的形式或者 8.4.3 中的逆时针 旋转 90°,一个先序,一个中序 RDL);


④ 用非递归方式先序遍历方式输出树 T 的结点;(用到栈)


⑤ 用中序或后序遍历方式输出树 T 的结点;


⑥ 用层次遍历方式输出树 T 的结点;(用到队列)


⑦ 输出树 T 的深度;


⑧ 输出树 T 的叶子结点或非叶子结点;


⑨ 主函数设计菜单,通过菜单选择相应的函数调用实现以上各项操作。(在 实验报告中请画出测试的二叉树。) 附加题:(每完成一个额外附加 5 分,上限 10 分)


① 8-32 判断该二叉树是否为完全二叉树(层次遍历);


② 8-34 根据顺序存储建立二叉链存储的二叉树(与第 1 个操作类似);


③ 哈夫曼树编码问题的设计和实现(双亲孩子表示法+flag)。


一个表达式二叉树的例子:

2.1. 数据结构设计

typedef char ElemType;
typedef struct Node {
  ElemType value;
  struct Node* left;
  struct Node* right;
}Node;

2.2. 主要操作算法设计与分析

2.2.1. 读文件建立树函数

Node* createTreeByFile();


Node* createTreeByOrder(char string[]);


Node* createNode();


返回类型:Node*;


是否有参数:无


步骤:


fopen打开文件读取字符串,此字符串是这种格式:“-+a##*b##-c##d##/e##f##”

(麻烦老师说清楚点,我都不知道你是要我以层序遍历序列构造,还是以前中序或者后中序序列构造,还是以这种来构造…)

定义一个全局变量j记录字符串被读取到哪个下标

进入函数后,判断j下标是否为“#”,是则返回NULL,如果不是,调用createNode构造一个节点root

递归调用一次,将其赋值给root左孩子

再递归调用一次,将其赋值给root右孩子

返回root

算法时间复杂度:


时间复杂度为O(N);

空间复杂度为O(log2N);

2.2.2 返回x值对应节点的指针函数

Node* findNode(Node* root, ElemType value);


返回类型:Node*;


是否有参数:二叉树的根节点,键值x


步骤:


root为NULL直接返回

root对应值为x,返回root

递归调用左孩子,返回的值不为空则返回左孩子指针

递归调用右孩子,返回的值不为空则返回右孩子指针

否则返回空,没有找到

算法时间复杂度:


时间复杂度为O(N);

空间复杂度为O(log2N);

2.2.3. 凹入法打印二叉树函数

void incurvatePrint(char preorder[], char DRLorder[]);


buildTree(preorder, DRLorder);


Node* buildByIndex(char preorder[], char inorder[], int start, int end);


void print(Node* root, int n);


int search(char inorder[], int start, int end, char key);


返回类型:无返回值;


是否有参数:有,传入前序序列与DRL序列


步骤:


incurvatePrint函数内调用buildTree函数获取二叉树,传入两个序列

调用buildByIndex函数获取二叉树,传入两个序列,以及0和strlen(inorder) - 1(from,to)

前序遍历序列的第一个值,就是根节点,在DRL中找到这个根节点下标,左边为右子树,右边为左子树,分别递归调用buildByIndex函数

from > end 返回NULL

最终incurvatePrint调用print函数,传入root和0(n)用凹入法打印二叉树

每进入一层递归,传入的第二个参数都会加1

在打印root对应值之前,要递归函数,传入右孩子和n+1,之后要打印n个缩进,打印root值后打印回车符,调用递归函数,传入左孩子和n+1

算法时间复杂度:


时间复杂度为O(N2);

空间复杂度为O(log2N);

2.2.4. 非递归先序打印树函数

栈基本函数:


typedef struct Stack {
  int size;
  int capacity;
  Node** arr;
}Stack;
Stack newStack() {
  Node** arr = calloc(N, sizeof(Node*));
  Stack stack = { 0, N, arr };
  return stack;
}
void push(Stack* ps, Node* value) {
  if (ps->size == ps->capacity) {
  ps->arr = (Node**)realloc(ps->arr, 2 * ps->size * sizeof(Node*));
  ps->capacity *= 2;
  }
  ps->arr[ps->size] = value;
  (ps->size)++;
}
Node* pop(Stack* ps) {
  if (ps->size == 0) {
  printf("666,空了\n");
  return NULL;
  }
  return ps->arr[--(ps->size)];
}
int isEmptyStack(Stack* ps) {
  return ps->size == 0;
}


void preorderNormal(Node* root);


返回类型:无返回值;


是否有参数:有,传入二叉树根节点


步骤:


建立栈,打印根节点root,(为NULL则return)

定义cur为root的左孩子

进入循环,条件:栈不为空或者cur不为空

进入循环:如果左孩子不为空,压栈并打印节点对应值,cur=cur->left

循环停止cur赋值为栈弹出来的节点的右孩子

进入循环判断

结束循环,则打印结束

算法时间复杂度:


时间复杂度为O(N);

空间复杂度为O(log2N);

2.2.5. 中序遍历和后序遍历函数

void inorder(Node* root);


void postorder(Node* root);


无返回值,有参数,二叉树根节点


步骤:


根节点为NULL不打印

依次调用递归函数,传入左孩子和右孩子

中序遍历则是在两次调用之间,后序遍历则是两次调用之后

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(logN)


2.2.6. 层序遍历函数

常用队列函数:


typedef struct Queue {
  DataType* arr;
  int size;
  int capacity;
}Queue;
Queue createQueue() {
  DataType* arr = (DataType*)calloc(N, sizeof(DataType));
  Queue queue = { arr, 0, N };
  return queue;
}
void offer(Queue* pq, DataType value) {
  if (pq->size == pq->capacity) {
  pq->arr = (DataType*)realloc(pq->arr, 2 * pq->size * sizeof(DataType));
  pq->capacity *= 2;
  }
  pq->arr[pq->size] = value;
  (pq->size)++;
}
DataType poll(Queue* pq) {
  if (isEmptyQueue(pq)) {
  printf("队列空\n");
  exit(0);
  }
  DataType ret = pq->arr[0];
  (pq->size)--;
  memmove(pq->arr, pq->arr + 1, pq->size * sizeof(DataType));
  return ret;
}
DataType peek(Queue* pq) {
  if (isEmptyQueue(pq)) {
  printf("队列空\n");
  exit(0);
  }
  return pq->arr[0];
}
int isEmptyQueue(Queue* pq) {
  return pq->size == 0;
}


void levelOrder(Node* root);


无返回值,传入参数为二叉树根节点


步骤:


根节点为NULL,则return

建立队列queue,根节点入队列

进入循环,条件:队列不为空

Node* tmp接受出队列元素

打印tmp的值,将tmp的左孩子如队列,右孩子入队列(如果为NULL则不用)

直到出循环

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(log2N)


2.2.7. 输出树的深度

int getHeight(Node* root);


返回int深度,传入二叉树根节点


步骤:


root为NULL返回

调用两次递归函数,依次传入左孩子和右孩子

返回两个递归函数的返回值中的最大值加1

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(log2N)


2.2.8. 输出树 T 的叶子结点或非叶子结点

void printLeafNode(Node* root);


void printNotLeaf(Node* root);


无返回值,传入二叉树根节点


步骤:


root为NULL,return

root不为NULL,

printLeafNode函数如果root左右孩子都为NULL,则打印

printNotLeaf函数中如果root左右孩子不全为NULL,则打印

调用两次递归函数,依次传入左孩子和右孩子

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(log2N)


2.2.9. 判断该二叉树是否为完全二叉树

int isCompleteTree(Node* root)


返回int是与否,传入二叉树根节点


步骤:


如果root为NULL,返回1

定义队列queue,根节点入队列

进入循环,循环条件是queue不为空队列

Node* cur接受出队列元素

如果cur不为NULL,左右孩子入队列

否则,一直出队列,如果队列中出现非NULL值,则返回0,否则返回1

若循环结束,返回1

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(log2N)


2.2.10. 层序遍历序列构造二叉树(用2.2.9的类似操作)

Node* createTreeByLevelOrder(char string[]);


返回二叉树根节点,传入字符数组


步骤:


只要数组不为空,就先入队数组首元素,并用这个值创建二叉树的root。

然后进入循环,循环条件为队列不为空,取出队头元素,队头出队。

只要数组还有元素,就先给刚刚拿出的对头元素创建左孩子,然后左孩子入队。

同上,再创建右孩子,右孩子入队。

结束一次循环。回到2

复杂度分析:


时间复杂度:O(N)


空间复杂度:O(log2N)


2.2.11. main函数


int main() {
  printf("===================================\n");
  Node* root = createTreeByFile();
  printf("===================================\n");
  Node* XTree = findNode(root, '*');
  printf("===================================\n");
  printf("凹入法打印:\n");
  incurvatePrint("-+a*b-cd/ef", "f/e-d-c*b+a");
  printf("===================================");
  printf("\n非递归前序:");
  preorderNormal(root);
  printf("\n递归前序:");
  preorder(root);
  printf("\nXTree前序:");
  preorder(XTree);
  printf("\n===================================");
  printf("\n递归中序:");
  inorder(root);
  printf("\n递归后序:");
  postorder(root);
  printf("\n===================================");
  printf("\n层序:");
  levelOrder(root);
  printf("\n===================================");
  printf("\n深度:%d", getHeight(root));
  printf("\n===================================");
  printf("\n叶子节点:");
  printLeafNode(root);
  printf("\n非叶子节点:");
  printNotLeaf(root);
  printf("\n===================================\n");
  printf("root%s完全二叉树", isCompleteTree(root) ? "是" : "不是");
  printf("\n===================================\n");
  levelOrder(createTreeByLevelOrder("123456789"));
}


2.3. 程序运行过程及结果


5. 总结

在这个过程中遇到很多问题,例如空指针异常,结果与预计结果不符

但是只要好好调试,总是能解决问题

对于递归的重点就是转化为子问题,整体性的思想

非递归实现则需要结合栈或者队列!

6. 附录:源代码


s:103
+关注
目录
打赏
0
0
0
0
34
分享
相关文章
|
2天前
|
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
21 3
 算法系列之数据结构-Huffman树
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
2月前
|
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
72 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
2月前
|
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
60 12
|
2月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
58 10
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
58 2
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
107 5
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
106 1
|
4月前
|
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
387 9
|
4月前
|
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
64 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等