数据结构 —— 链式二叉树(C语言讲解)

devtools/2024/10/25 0:11:57/

目录

0.前言

1.链式存储的二叉树

2.二叉树的遍历

二叉树的前、中、后序遍历

前序遍历

中序遍历

后序遍历

二叉树的层序遍历

3.二叉树的其他操作

二叉树的结点个数

二叉树叶子结点个数

二叉树第k层结点个数

二叉树查找值为x的节点

二叉树的高度

判断二叉树是否是完全二叉树


0.前言

本篇文章主要讲解链式存储的二叉树,如果读者不清楚二叉树的基础知识,推荐阅读数据结构——树和二叉树简介。

对于二叉树这种数据结构的学习,重点不是增删改查,而是学习这种结构,所以,主要讲解一些链式二叉树的操作。

1.链式存储的二叉树

二叉树链式存储指的是用链表来存储二叉树。具体的做法是链表中每个结点由3个域组成,数据域和左右指针域,左右指针域分别用来存储左孩子的地址和右孩子的地址。

二叉树结点表示:

二叉树链式存储结构示意图: 

2.二叉树的遍历

由于二叉树的结构比较复杂,遍历二叉树的时候不能像遍历数组、单链表那样直观的表达;二叉树的遍历是按照某种规则依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。

  • 二叉树的遍历是二叉树最重要的操作之一,是在二叉树上进行其他操作的基础。
  • 二叉树递归结构的遍历有前序遍历、中序遍历、后续遍历
  • 二叉树非递归结构的遍历有层序遍历

二叉树的前、中、后序遍历

前序遍历

前序遍历的顺序为:根结点->左子树->右子树,对于每棵子树同样遵循该遍历顺序。

前序遍历递归代码:

void PrevOrder(BTNode* root) {if (root == NULL) {        // 访问空结点的时候返回 printf("NULL ");return;}printf("%d ", root->val);  // 访问根结点 PrevOrder(root->left);     // 遍历左子树 PrevOrder(root->right);    // 遍历右子树 
}

前序遍历递归图解:

 上图中数据走前序遍历结果为:1,2,3,4,5,6.

中序遍历

中序遍历的顺序为:左子树->根结点->右子树,对于每棵子树同样遵循该遍历顺序。

中序遍历递归代码:

void InOrder(BTNode* root)
{if (root == NULL){printf("NULL ");         // 访问空结点的时候返回 return;}InOrder(root->left);         // 遍历左子树 printf("%d ", root->val);    // 访问根结点 InOrder(root->right);        // 遍历右子树 
}

后序遍历

后序遍历的顺序为:左子树->根结点->右子树,对于每棵子树同样遵循该遍历顺序。

后续遍历递归代码:

void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");       // 访问空结点的时候返回 return;}PostOrder(root->left);     // 遍历左子树 PostOrder(root->right);    // 遍历右子树 printf("%d ", root->val);  // 访问根结点 
}

二叉树的层序遍历

二叉树除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

  • 层序遍历需要借助队列来完成。
  • 层序遍历的思想是上一层把下一层带入队列。 

层序遍历代码: 

void LevelOrder(BTNode* root)
{Que q;                    // Que是一个自定义的队列类型 QueueInit(&q);if (root)QueuePush(&q, root);  // 先往队列中放入一个结点while (!QueueEmpty(&q))   // 只要队列不为空就进入循环{BTNode* front = QueueFront(&q);    // 取队头元素,并访问 printf("%d ", front->val);if (front->left)                   // 当前节点左孩子不为空,将左孩子入队列 QueuePush(&q, front->left);if (front->right)                  // 当前节点右孩子不为空,将右孩子入队列QueuePush(&q, front->right);QueuePop(&q);         // 从队列中删除访问过的元素 }printf("\n");QueueDestroy(&q);
}

3.二叉树的其他操作

二叉树的结点个数

要求二叉树的结点个数,可以根据二叉树的递归结构来求:二叉树的结点个数 = 左子树的节点个数+右子树的结点个数+1。左子树和右子树同样需要满足上述规则。

int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

二叉树叶子结点个数

求二叉树叶子结点的个数同样可以使用递归来求解:二叉树叶子结点的个数 = 左子树的叶子结点的个数+右子树的叶子结点的个数。左子树和右子树同样需要满足上述规则。

int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL){return 1;}return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

二叉树第k层结点个数

二叉树第k层的结点个数 = 左子树第k-1层的结点个数+右子树第k-1层的结点个数。左子树和右子树同样需要满足上述规则。

int TreeKLevel(BTNode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1){return 1;}return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}

二叉树查找值为x的节点

查找一个值为x的结点,我们采用前序遍历,先访问根,再遍历左子树和右子树。

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL)return NULL;if (root->val == x)            //访问当前结点return root;BTNode* ret = NULL;ret = TreeFind(root->left, x); // 去左子树中找if (ret)return ret;ret = TreeFind(root->right, x);// 去右子树中找if (ret)return ret;return NULL;    
}

二叉树的高度

二叉树的高度 = 左右子树高度大的那个 + 1。左右子树同时满足该规则。

int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);   // 求出左子树的高度 int rightHeight = TreeHeight(root->right); // 求出右子树的高度 // 二叉树的高度 = 左右子树高度大的那个 + 1 return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

判断二叉树是否是完全二叉树

判断二叉树是否是完全二叉树,主要借助层序遍历的思想 ——上一层带下一层,以及完全二叉树的特点 —— 所有结点之间不会出现空。

当第一个循环结束之后,判断队列中是否还有非空结点,如果有,这棵二叉树就不是完全二叉树;如果没有,这棵二叉树就是完全二叉树。

// 判断二叉树是否是完全二叉树
int TreeComplete(BTNode* root)
{Que q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);QueuePop(&q);}// 已经遇到空节点,如果队列中后面的节点还有非空,就不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

http://www.ppmy.cn/devtools/128544.html

相关文章

【mysql进阶】2-4. mysql 系统库

mysql System Schema (mysql系统库) Mysql Schema是⼀个系统库,表中存储了MySQL服务器运⾏时所需的信息。⼴义上,mysqlschema包含存储数据库对象元数据的数据字典和⽤于其他操作⽬的的系统表。数据字典表和系统表位于数据⽬录下⼀个名为 mysql.ibd 的表…

【MySQL】详解MySQL数据类型

一、数据类型 各类型的数值范围: 在MySQL中,整型可以指定是有符号的和无符号的,默认是有符号的。 可以通过UNSIGNED来说明某个字段是无符号的。对于int类型可能存放不下的数据,尽量不使用unsigned,unsigned int 同样可…

Rust编程语言变量的所有权(ownership)

文章目录 什么是所有权所有权规则转让所有权变量与数据交互的方式(一):移动变量与数据交互的方式(二):克隆只在栈上的数据:拷贝所有权与函数返回值与作用域引用和借用可变引用悬垂引用(Dangling References)引用的规则什么是所有权 所有权(ownership)是Rust 的核心功能之一…

GISBox vs CesiumLab:哪款GIS工具更适合你的项目?

在地理信息系统(GIS)领域,越来越多的用户开始关注GIS工具箱的选择,其中GISBox和CesiumLab是两款备受推崇的产品。那么,哪一款更适合你的需求呢?本文将从功能、使用体验和应用场景等方面,对GISBo…

怎么快速在ppt中添加文本框?2个常用的ppt使用技巧盘点!

文本是ppt中最基本的元素,也是我们呈现想法和观点最常使用的媒介,想在ppt中添加文本,必然离不开文本框,那ppt如何添加文本框呢? 对于Powerpoint(简称ppt)而言,可以点击菜单栏的 “插…

智慧升级,知识无界:十大搭建知识库软件助你前行

在知识爆炸的时代,如何高效地管理、整合与利用信息,成为了个人与企业发展的核心竞争力。智慧升级,意味着我们不仅要掌握丰富的知识,更要学会运用工具,让知识无界流通,助力个人成长与企业创新。以下是精心挑…

制作sdk

制作 java-sdk 的两种方式_java sdk-CSDN博客 平时maven工程里 pom 中的引用的依赖就是别人开发好的 sdk 包;工作中为了方便一些开发也需要自定义开发 sdk 包, 精华; 一、两种方式 我们平时引用 sdk 有两种方式: pom 依赖引用…

神经网络model训练时loss=nan【原因总结】

一、Loss functions 中含 F.log_softmax()函数 原因: 由于在计算log_softmax(x)时, 出现log(0)的情况。 解决方法: 给log_softmax的参数x添加一个很小的数: out=F.log_softmax(x+1e-10).二、loss_function(x)函数参数中出现nan 原因: 网络的生成features x 中含有nan. 解…