二叉树(C语言版)

文章目录

  • 二叉树
    • 完全二叉树和满二叉树
    • 二叉搜索树
    • 基本操作
    • 实现
      • 代码
      • 运行结果
    • 分析
    • 红黑树
      • 2-3-4树(理论模型)
      • 红黑树(实际实现)

二叉树

树是一种层次结构,它在现实生活中是广泛存在的,比如:族谱(family tree),组织机构,目录结构等。

不过今天我们只讲二叉树。

二叉树有多重要?单就面试而言,在 leetcode 中二叉树相关的题目占据了 300 多道,近三分之一。同时,二叉树在整个算法板块中还起到承上启下的作用:不但是数组和链表的延伸,又可以作为图的基础。总之,非常重要!

那什么是二叉树?

定义:二叉树是一棵树,并且二叉树的每个结点最多有两棵子树。二叉树的子树又分为左子树和右子树。

在这里插入图片描述

完全二叉树和满二叉树

二叉树有两种特殊的形态:完全二叉树和满二叉树。

完全二叉树:若二叉树的深度为 h,除第 h 层外,其它各层(1~h-1)的结点数目都达到最大值,第 h 层的结点都连续排列在最左边,这样的二叉树就是完全二叉树。

满二叉树:每一层的结点数目都达到最大值(包括最下面一层)。

在这里插入图片描述

二叉搜索树

Binary Search Tree (BST) 又叫二叉排序树。要求树中的结点可以按照某个规则进行比较,其定义如下:

  1. .左子树中所有结点的 key 值都比根结点的 key 值小,并且左子树也是二叉搜索树。

  2. 右子树中所有结点的 key 值都比根结点的 key 值大,并且右子树也是二叉搜索树。

在这里插入图片描述

基本操作

search:若 BST 为空,则直接NULL。若 BST 非空,则和根结点比较,若和根结点相等,表明找到了。若比根结点小,则在左子树中递归查找;若比根结点大,则在右子树中递归查找。

insert:若 BST 为空,则创建结点,将其作为根结点。若 BST 非空,则和根结点比较,若和根结点相等,则返回。若比根结点小,则在左子树中递归插入;若比根结点大,则在右子树中递归插入。

delete:分三种情况处理。

  1. 如果要删除结点没有孩子,那么直接将该结点删除就行了。

    在这里插入图片描述

  2. 如果要删除结点只有一个孩子,那么需要将父亲结点对应的指针,指向它唯一 的孩子结点。

    在这里插入图片描述

  3. 如果要删除结点有两个孩子,那么我们可以找到这个结点的右子树中最小结点 (或者左子树中最大结点),把它替换到要删除的结点上,然后再删除右子树的最小结点 (或左子树的最大结点)。

    在这里插入图片描述

实现

代码

// BST.h
typedef char K;typedef struct tree_node {K key;struct tree_node* left;struct tree_node* right;
} TreeNode;typedef struct {TreeNode* root;
} BST;// API
BST* bst_create();
void bst_destroy(BST* tree);void bst_insert(BST* tree, K key);
TreeNode* bst_search(BST* tree, K key);
void bst_delete(BST* tree, K key);void bst_preorder(BST* tree); // 前序
void bst_inorder(BST* tree); // 中序
void bst_postorder(BST* tree); // 后序
void bst_levelorder(BST* tree); // 层序遍历
// BST.c
#include "BST.h"
#include <stdlib.h>
#include <stdio.h>BST* bst_create() {BST* bst = (BST*)malloc(sizeof(BST));bst->root = NULL;return bst;
}void freeTraversal(TreeNode* node) {// 终止条件if (node == NULL) {return;}freeTraversal(node->left); // 前freeTraversal(node->right); // 后printf("已释放结点 %d\n", node->key);free(node); // 中
}void bst_destroy(BST* tree) {TreeNode* root = tree->root;// 1.释放所有结点freeTraversal(root);// 2.释放BSTfree(tree);printf("已释放BST\n");
}void bst_insert(BST* tree, K key) {if (tree->root == NULL) { // 空树,直接作为根结点插入TreeNode* newNode = (TreeNode*)calloc(1, sizeof(TreeNode));newNode->key = key;tree->root = newNode;return;}// 迭代法TreeNode* cur = tree->root;while (cur) {if (key > cur->key) {// 大于if (cur->right == NULL) {TreeNode* newNode = (TreeNode*)calloc(1, sizeof(TreeNode));newNode->key = key;cur->right = newNode;return;} else {cur = cur->right;}}else if (key < cur->key) {// 小于if (cur->left == NULL) {TreeNode* newNode = (TreeNode*)calloc(1, sizeof(TreeNode));newNode->key = key;cur->left = newNode;return;} else {cur = cur->left;}}else return;} // cur == NULL
}TreeNode* bst_search(BST* tree, K key) {TreeNode* cur = tree->root;while (cur) {if (key > cur->key)cur = cur->right;else if (key < cur->key)cur = cur->left;else {printf("找到结点,结点值 %d\n", cur->key);return cur;}}printf("没找到结点\n");return NULL;
}TreeNode* deleteOneNode(TreeNode* targetNode) {if (targetNode == NULL) return NULL;if (targetNode->right == NULL) { // 右空 左空或不空TreeNode* tmpNode = targetNode->left;free(targetNode);return tmpNode;}// 右不空,左不空或空TreeNode* cur = targetNode->right;while (cur->left) {cur = cur->left;} // 右子树的最左结点 cur->left == NULLcur->left = targetNode->left;TreeNode* tmpNode = targetNode->right;free(targetNode);return tmpNode;
}void bst_delete(BST* tree, K key) {TreeNode* cur = tree->root;TreeNode* prev = NULL;while (cur) {if (cur->key == key) {if (prev == NULL) {// 要删除根节点tree->root = deleteOneNode(cur);}else if (prev->left && prev->left->key == key) {prev->left = deleteOneNode(cur);}else if (prev->right && prev->right->key == key) {prev->right = deleteOneNode(cur);}break;}prev = cur;if (key > cur->key) cur = cur->right;if (key < cur->key) cur = cur->left;}}/* **************************************** */
/*               深度优先遍历                 */
/* **************************************** */// 前序遍历(递归法)
void preorder_traversal(TreeNode* node) {// 终止条件if (node == NULL) {return;}printf(" %d", node->key); // 中preorder_traversal(node->left); // 左preorder_traversal(node->right); // 后
}void bst_preorder(BST* tree) {TreeNode* rootNode = tree->root;preorder_traversal(rootNode);
}// 中序遍历(递归法)
void inorder_traversal(TreeNode* node) {// 终止条件if (node == NULL) {return;}inorder_traversal(node->left); // 前printf(" %d", node->key); // 中inorder_traversal(node->right); // 后
}void bst_inorder(BST* tree) {TreeNode* rootNode = tree->root;inorder_traversal(tree->root);
}// 后序遍历(递归法)
void postorder_traversal(TreeNode* node) {// 终止条件if (node == NULL) {return;}postorder_traversal(node->left); // 前postorder_traversal(node->right); // 后printf(" %d", node->key); // 中
}void bst_postorder(BST* tree) {TreeNode* rootNode = tree->root;postorder_traversal(tree->root);
}/* **************************************** */
/*               广度优先遍历                 */
/* **************************************** */// 实现队列(基于链表)
typedef TreeNode* V;typedef struct que_node{V node;struct que_node* next;
} queNode;typedef struct {queNode* front;queNode* rear;int size;
} Queue;Queue* create_queue() {Queue* que = (Queue*)malloc(sizeof(Queue));que->front = NULL; // 队头que->rear = NULL; // 队尾que->size = 0;return que;
}void push_queue(Queue* q, V val) {queNode* newNode = (queNode*)malloc(sizeof(queNode));newNode->node = val;newNode->next = NULL;if (q->front == NULL) { // 空队列q->front = newNode;q->rear = newNode;q->size++;return;}q->rear->next = newNode;q->rear = newNode;q->size++;
}V pop_queue(Queue* q) {if (q->front == NULL) {return NULL;}queNode* tmpNode = q->front;if (q->size == 1)q->rear = NULL;q->front = tmpNode->next;q->size--;return tmpNode->node;
}V peek_queue(Queue* q) {if (q->front == NULL) {return NULL;}return q->front->node;
}bool is_empty(Queue* q) {return (q->front == NULL);
}/* 层序遍历(迭代法) */
// 层序遍历二叉树
void bst_levelorder(BST* tree) {Queue* que = create_queue();TreeNode* cur = tree->root;if (cur == NULL)return;push_queue(que, cur);while (!(is_empty(que))) {int num = que->size;while (num--) {cur = que->front->node;printf(" %d", que->front->node->key);pop_queue(que);if (cur->left) {push_queue(que, cur->left);}if (cur->right) {push_queue(que, cur->right);}}} 
}
// main.c
#include "BST.h"
#include <stdio.h>int main(void) {BST* bst = bst_create();bst_insert(bst, 1);bst_insert(bst, 2);bst_insert(bst, 5);bst_insert(bst, 7);bst_insert(bst, 5);bst_insert(bst, 9);bst_insert(bst, 4);// bst_search(bst, 5);// bst_search(bst, 100);printf("前序遍历结果:");bst_preorder(bst);printf("\n");printf("中序遍历结果:");bst_inorder(bst);printf("\n");printf("后序遍历结果:");bst_postorder(bst);printf("\n");printf("层序遍历结果:");bst_levelorder(bst);printf("\n");printf("已删除结点2\n");bst_delete(bst, 2);printf("已删除结点5\n");bst_delete(bst, 5);printf("前序遍历结果:");bst_preorder(bst);printf("\n");printf("中序遍历结果:");bst_inorder(bst);printf("\n");printf("后序遍历结果:");bst_postorder(bst);printf("\n");printf("层序遍历结果:");bst_levelorder(bst);printf("\n");bst_destroy(bst);
}

运行结果

在这里插入图片描述

分析

BST 增加、删除和查找的效率取决于它的高度 h。

insert:O(h)

search:O(h)

delete:O(h)

有 n 个结点的二叉树,高度最低时 h = log2n (完全二叉树),高度最高时 h = n (退化成单链表)。但是我们上面实现的 BST 并不能保证树的高度为 O(logn),更糟糕的是,随着动态的插入和删除元素,整棵树会慢慢地向一边倾斜。

要想保证二叉树增加,查找,删除的时间复杂度为 O(logn),我们需要在添加结点和删除结点后,做一些调整操作,以保证二叉树的平衡。

常见的平衡二叉树有:AVL树、红黑树、伸展树、树堆等。其中应用最广,名气最大的当属红黑树了。

红黑树

2-3-4树(理论模型)

2-3-4 树有以下两个性质:

  • 2-3-4 树,在普通的二叉查找树上进行了扩展,它允许一个结点包含多个 key 值。

    2-结点:一个 key 值,两个孩子。

    3-结点:两个 key 值,三个孩子。

    4-结点:三个 key 值,四个孩子

  • 2-3-4树可以动态地保持完美平衡。

    所谓完美平衡,就是从根结点到任意一个叶子结点的路径都是一样长的。

在这里插入图片描述

查找

2-3-4 树的查找和普通 BST 的查找方式几乎一致。

在这里插入图片描述

插入

2-3-4 树的插入和普通 BST 相比,稍微复杂一点。

  • 如果是在2-结点中插入,直接将2-结点转换为3-结点。

    在这里插入图片描述

  • 如果是在3-结点中插入,直接将3-结点转换为4-结点。

在这里插入图片描述

  • 但是,如果是在4-结点中插入,该怎么办呢?

    在这里插入图片描述

这就需要将4-结点进行分裂了。

在这里插入图片描述

但是,如果父结点也是4-结点,又该怎么办呢?

有两种解决方案:(不变式:当前结点肯定不是4-结点。)

a. 自底向上(Bayer, 1972) 用同样的方法分裂父结点

如果需要,我们会沿着查找路径自底向上依次分裂4-结点

b. 自顶向下(Guibas-Sedgewick, 1978)

在查找插入位置的时候,遇到4-结点就分裂

找到插入位置的时候,就可以直接插入了(当前结点肯定不是4-结点)

在这里插入图片描述

2-3-4树生长的例子在这里插入图片描述

在这里插入图片描述

性能分析

2-3-4树增加,删除,查找操作的时间复杂度取决于它的高度 h.

  • 最坏情况:h = log2N [所有结点都是2-结点]
  • 最好情况:h = log4N = (1/2)log2N [所有结点都是4-结点]

2-3-4树的性能是非常优秀的,举个例子

  • N = 100万时,2-3-4树的高度在 10 到 20 之间。
  • N = 10亿时,2-3-4树的高度在 15 到 30 之间。

实现

那我们如何实现 2-3-4 树呢?如果为2-结点,3-结点,4-结点分别编写不同的结点类型,则处理起来会非常麻烦,我们不得不处理各个结点类型之间得转换。那 2-3-4 树有没有一种更好得实现方式呢?

有的,答案就是红黑树!

红黑树(实际实现)

我们可以用普通的 BST 来表示 2-3-4 树。可是,我们该如何表示3-结点和4-结点呢?

红黑树给出了一个很好的解决方案,我们可以用"红色"的边来表示3-结点和4-结点。如下图所示:

在这里插入图片描述

3-结点有两种表示形式,而4-结点只有一种表示形式。

可是"边"是不存在的呀,它只是逻辑上的一个结构,我们又该如何表示边的颜色呢?

孩子结点到父亲结点的边是唯一的,所以我们可以用孩子结点的颜色,来表示孩子结点到父亲结点的边的颜色

typedef struct treenode_s {int val;struct treenode_s* left;struct treenode_s* right;bool color;
}

这就是红黑树!

我们来看一看经典教科书(算法导论)对红黑树的定义:

一棵红黑树是满足下面红黑性质的二叉搜索树:
1. 每个结点或者是红色的,或者是黑色的
2. 根结点是黑色的
3. 叶子结点 (Nil) 是黑色的 (注:在算法导论中,叶子结点指得是 NULL 结点)
4. 如果一个结点是红色的,则它的两个子结点都是黑色的 (4-node 只有一种编码方式)
5. 对每个结点,从该结点到其所有后代叶子结点的路径上,包含相同数目的黑色结点。(黑高平衡, 2-3-4树是一个完美平衡的树)

2-3-4 树和红黑树之间是有一张对应关系的,不过这种对应关系不是 1-1 的 (3-结点可以倾向任意一边)。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/71132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIA项目实践---ACL访问控制列表相关知识和配置过程

十 ACL访问控制列表 1 策略的概念 在网络连通之后&#xff0c; 把所有为了追求控制而实现的技术都叫策略 2 访问控制 在路由器流量流入或者流出的接口上&#xff0c;匹配流量&#xff0c;执行相应的动作。&#xff08;流量流入或者流出的接口并不是一个固定的概念而是一个相对的…

3-初始化项目

在文件UIStaticHelper配置路径 YIUI自动化工具 在Tools->YIUI自动化工具即可看到面板。有6个功能&#xff0c;如下所示。 在运行的过程中&#xff0c;用绑定代替反射是因为手机运行放射是开销比较大的&#xff0c;所以用绑定代替反射&#xff0c;在发布前UI如果有改动&…

人工智能的现状与未来发展趋势分析

引言 人工智能(AI)作为21世纪最具革命性的技术之一,正在深刻改变我们的生活和工作方式。从早期的概念提出到如今的广泛应用,AI经历了漫长而曲折的发展历程。本文旨在全面分析AI的现状,并探讨其未来发展趋势,以期为读者提供一个清晰而深入的视角。 一、人工智能的发展历…

基于Spring Boot的家电销售展示平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

栈与队列(C语言版)

文章目录 栈与队列1. 栈基本操作实现(基于链表)代码运行结果 应用场景 2. 队列基本操作实现代码运行结果 应用场景 栈与队列 1. 栈 栈是一种操作受限的线性结构。操作受限体现在&#xff0c;栈只能在一端添加和删除元素&#xff0c;符合后进先出 ( LIFO ) 的特性&#xff0c;…

第二节 字符串、列表

字符串 字符串的切片 使用[ : ]截取&#xff0c;左闭右开&#xff0c;示例&#xff1a; str"nihao" print(str[0:3]) 第一个参数省略默认从头开始&#xff0c;第二个参数省略默认截取到末尾 当第二个参数或第三个参数为-1时&#xff0c;从右向左取值&#xff0c…

【NLP 22、语言模型 language model】

有时候我也想听听&#xff0c;我在你心里&#xff0c;是什么样子 —— 25.1.12 一、什么是语言模型 语言是灵活的&#xff0c;也是有规律的 了解一门语言的人可以判断一句话是否“合理” 通俗来讲&#xff0c;语言模型用来评价一句话(句子可以看作是字的组合)是否“合理”或…

【CSS进阶】常见的页面自适应的方法

在前端开发中&#xff0c;自适应布局&#xff08;Responsive Design&#xff09;是一种让网页能够适应不同屏幕尺寸、设备和分辨率的技术。常见的自适应布局方法包括 流式布局、弹性布局&#xff08;Flexbox&#xff09;、栅格布局&#xff08;Grid&#xff09;、媒体查询&…

通过openresty和lua实现随机壁纸

效果&#xff1a; 图片存放路径&#xff1a; /home/jobs/webs/imgs/ ├── default/ │ ├── image1.jpg │ ├── image2.png ├── cats/ │ ├── cat1.jpg │ ├── cat2.gif ├── dogs/ │ ├── dog1.jpg访问http://demo.com/imgs/default 随机返回…

CAS单点登录(第7版)20.用户界面

如有疑问&#xff0c;请看视频&#xff1a;CAS单点登录&#xff08;第7版&#xff09; 用户界面 概述 概述 对 CAS 用户界面 &#xff08;UI&#xff09; 进行品牌化涉及编辑 CSS 样式表以及一小部分相对简单的 HTML 包含文件&#xff0c;也称为视图。&#xff08;可选&…

安全测试中的身份认证与访问控制深度解析

第一部分:基本概念与核心问题 1. 身份认证与访问控制基础 1.1 身份认证三要素 知识因素(密码、PIN码)持有因素(硬件令牌、手机)生物因素(指纹、面部识别)1.2 访问控制模型 DAC(自主访问控制)MAC(强制访问控制)RBAC(基于角色的访问控制)2. 关键安全机制 2.1 会话…

AI 编程工具—Cursor 进阶篇 数据分析

AI 编程工具—Cursor 进阶篇 数据分析 上一节课我们使用Cursor 生成了北京房产的销售数据,这一节我们使用Cursor对这些数据进行分析,也是我们尝试使用Cursor 去帮我们做数据分析,从而进一步发挥Cursor的能力,来帮助我们完成更多的事情 案例一 房产销售数据分析 @北京202…

算法1-1 玩具谜题

题目描述 小南有一套可爱的玩具小人&#xff0c;它们各有不同的职业。 有一天&#xff0c;这些玩具小人把小南的眼镜藏了起来。小南发现玩具小人们围成了一个圈&#xff0c;它们有的面朝圈内&#xff0c;有的面朝圈外。如下图&#xff1a; 这时 singer 告诉小南一个谜题&…

Java函数计算冷启动从8s到800ms的优化实录

在函数计算场景中,冷启动性能对用户体验至关重要。本文将分享如何将 Java 函数计算的冷启动时间从 8 秒优化到 800 毫秒,包括具体的技术手段和代码示例。 一、背景介绍 函数计算是一种事件驱动的计算服务,用户只需上传代码,无需管理服务器。但在实际使用中,函数计算的冷…

《AI大模型开发笔记》deepseek提示词技巧

为什么你的 AI 助手总是答非所问&#xff1f; 「写篇产品分析」 → 收到一堆不知所云的文字 「做个竞品对比」 → 得到几页没有重点的废话 揭秘&#xff1a;不是 AI 不够聪明&#xff0c;而是你的指令太“高冷”&#xff01; 一、新手进阶&#xff1a; 5 大法则&#xff0c;让…

二、OpenSM排障----实战生产

目录 一、确认 OpenSM 服务端故障的步骤 1. 检查客户端与服务器的连通性 2. 检查客户端 InfiniBand 接口状态 3. 检查子网管理器状态 4. 检查拓扑信息 5. 检查路由表 二、客户端日志位置及查看方法 1. 系统日志 2. OpenSM 客户端日志 3. 内核日志 4. 性能计数器日志…

[矩形绘制]

矩形绘制 真题目录: 点击去查看 E 卷 200分题型 题目描述 实现一个简单的绘图模块,绘图模块仅支持矩形的绘制和擦除 当新绘制的矩形与之前的图形重叠时,对图形取并集当新擦除的矩形与之前的图形重叠时,对图形取差集给定一系列矩形的绘制和擦除操作,计算最终图形的面积。 …

在软件产品从开发到上线过程中,不同阶段可能出现哪些问题,导致软件最终出现线上bug

在软件产品从开发到上线的全生命周期中&#xff0c;不同阶段都可能因流程漏洞、技术疏忽或人为因素导致线上问题。以下是各阶段常见问题及典型案例&#xff1a; 1. 需求分析与设计阶段 问题根源&#xff1a;业务逻辑不清晰或设计缺陷 典型问题&#xff1a; 需求文档模糊&#…

渲染相机设置 pyrender cameralib

目录 cameralib 设置相机 numpy获取相机参数: pyrender设置相机: hmr2渲染设置 multi_hmr获取cam_t cameralib 设置相机 cameralib安装教程: cameralib 安装-CSDN博客 import cameralibcamera = cameralib.Camera.from_fov(fov_degrees=55, imshape=(720,1280))intri…

ros:ur机械臂初识

这是用来可视化的launch文件 比如&#xff0c;我运行 roslaunch ur_description view_ur3.launch ur3模型 ur3e模型 ur5模型 ur5e模型 ur10模型 ur20模型 ur30模型 后来我搜了一下 UR5 和 UR10 都是由 Universal Robots&#xff08;简称 UR&#xff09;生产的协作机器人&…