C语言-实现顺序二叉树和平衡二叉树AVL

news2024/10/7 6:40:52

1. 结构体

在实现二叉树之前先看下结构体的一些使用方法

数组是保存一系列相同的数据。在实际问题中,一组数据往往有很多种不同的数据类型。例如,登记学生的信息,可能需要用到 char型的姓名,int型或 char型的学号,int型的年龄

///第一种方式
struct Person{
    int age;
    char name[20];
};
//之后定义变量
struct Person a, b;
 
///第二种方式(声明的同时定义)
struct Person {
    int age;
    char name[20];
}a, b;
 
///第三种方式(不需要提供结构体名字,直接定义)
struct {
    int age;
    char name[20];
}a, b;

访问结构成员

访问其中的各个元素,用结构成员运算符点(.)。即访问成员的一般形式是:结构变量名 . 成员名
如 a.name 表示学生 a 的姓名。

结构成员的初始化

1)结构体的成员逐个赋值

struct Person stu1;      //定义结构体变量
strcpy(stu1.name, "Jack");
stu1.age= 18;

2)整体赋值

struct Person stu1 = {"jack", 12};

也可以在初始化的时候赋值

struct Person {                 //声明结构体 Person
    char name[20];               
    int age;                   
}stu = {"Mike", 20};        //注意初始化值的类型和顺序要与结构体声明时成员的类型和顺序一致

也可以按照任意的顺序使用指定初始化:

struct Person st = { .name = "Smith",
                          .age= 18 };

3)整体拷贝

struct Person stu1 = {"jack_mike", 12};
struct Person stu2;
stu2 = stu1;

如上每次使用结构体均需要写struct,可以使用typedef 简化书写

typedef struct Person {
    char name[20];
    int age;
}Person;
// 定义一个结构体
    Person stu1 = {"jack", 12};

结构成员指针

结构变量作为函数的参数传递时,将整个结构体变量拷贝为副本,传送的空间开销比较大,特别是当成员有数组的时候,程序效率较低。所以可以考虑使用结构体指针。

通过结构指针间接访问成员值:

访问的一般形式:
(*结构指针变量). 成员名 或 结构指针变量 -> 成员名

typedef struct Person {
    char name[20];
    int age;
}Person;

int main(){
    Person stu1 = {"jack", 12};
    Person* stuP = &stu1;
// (*stuP).name
    printf("Person name: %s \n", (*stuP).name);
//  stuP->name
    printf("Person name: %s \n", stuP->name);

    return 0;
}

2. 二叉树

2-1)树的基本概念

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

二叉树

二叉树是由一个根节点加上两棵别称为左子树和右子树的二叉树组成;二叉树不存在度大于2的结点

2-2)二叉树的创建与递归遍历

依据前序遍历建二叉树,树结构如上图所示:

参数是二级指针时,需要传入左节点的地址,需要改变指针的指向

#include <stdio.h>
#include <stdlib.h>
 
typedef struct treeNode{
    struct treeNode* left;
    struct treeNode* right;
    char data;
}treeNode;
 
treeNode* mallocTreeNode(char data){
    treeNode* node = (treeNode*)malloc(sizeof(treeNode));
    node->data = data;
    return node;
}
 
// 使用递归的方法创建一棵二叉树,不用return 这里使用二级指针
void createTree(treeNode** node, char* src, int* index){
    char ch = *(src + *index);
    (*index)++;
    printf("ch is %c \n", ch);
    if (ch != '#'){
        // 根左右,前序遍历二叉树
        *node = mallocTreeNode(ch);
        createTree(&((*node)->left), src, index);
        createTree(&((*node)->right), src, index);
    }else{
        *node = NULL;
    }
}
 
treeNode* createTree2(char* src, int* index){
    char ch = *(src + *index);
    (*index)++;
    printf("ch is %c \n", ch);
    if (ch != '#'){
        // 根左右,前序遍历二叉树
        treeNode* node = mallocTreeNode(ch);
        node->left = createTree2(src, index);
        node->right = createTree2(src, index);
        return node;
    }
    return NULL;
}

main 函数

int main(){
    printf("this is binary tree test =====\n");
    char* src = "ABD##E##C##";
    // Node* root = create_tree();
    treeNode* root = NULL;
    int index = 0;
    // createTree(&root, src, &index);
    root = createTree2(src, &index);
    printf("root left= %c \n", root->left->data);
 
    return 0;
}

递归遍历二叉树:前序遍历、中序遍历、后续遍历

// 前序遍历二叉树: 中左右
void printPreOrderTree(treeNode* root){
    if (root != NULL){
        printf(" %c - ", root->data);
        printPreOrderTree(root->left);
        printPreOrderTree(root->right);
    } 
}
 
// 中序遍历二叉树: 左中右
void printInOrderTree(treeNode* root){
    if (root != NULL){
        printInOrderTree(root->left);
        printf(" %c - ", root->data);
        printInOrderTree(root->right);
    } 
}
 
// 后序遍历二叉树: 左右中
void printBackOrderTree(treeNode* root){
    if (root != NULL){
        printBackOrderTree(root->left);
        printBackOrderTree(root->right);
        printf(" %c - ", root->data);
    } 
}

二叉树的非递归遍历可以看这篇博文

5-3)顺序二叉树

顺序二叉树的中序遍历是有顺序的。

#include <stdio.h>
#include <stdlib.h>
 
typedef struct treeNode{
    struct treeNode* left;
    struct treeNode* right;
    int data;
}treeNode;
 
treeNode* mallocTreeNode(int data){
    treeNode* node = (treeNode*)malloc(sizeof(treeNode));
    node->data = data;
    return node;
}
 
// 中序遍历二叉树: 左中右
void printInOrderTree(treeNode* root){
    if (root != NULL){
        printInOrderTree(root->left);
        printf(" %d - ", root->data);
        printInOrderTree(root->right);
    } 
}
 
// 由于需要改变头部节点的值 // -> 运算符优先级比 & 大
void orderedTreeInsert(treeNode** node, int data){
    if(*node == NULL){
        *node = mallocTreeNode(data);
        (*node)->left = NULL;
        (*node)->right = NULL;
    }else if (data > (*node)->data ){
        
        orderedTreeInsert(&(*node)->right, data);
    }else if(data < (*node)->data){
        orderedTreeInsert(&(*node)->left, data);
    }else{
        // 如果2 个值相等,则不插入,忽略
        return;
    }
}
 
// 需要找到最底层的 node 先free,然后一直往上递归。类似于后序遍历。左右根
void freeNode(treeNode** node){
    if (*node != NULL){
        freeNode(&(*node)->left);
        freeNode(&(*node)->right);
        printf("free node: %d \n", (*node)->data);
        free(*node);
        *node = NULL;
    }
}
 
int main(){
    printf("this is ordered tree == \n");
    const int SIZE = 7;
    int data[7] = {4, 6, 3, 7, 2, 9, 1};
    treeNode* root = NULL;
    for (int i=0; i< SIZE; i++){
        orderedTreeInsert(&root, data[i]);
    }
    printInOrderTree(root);
    printf("\n");
    freeNode(&root);
 
    return 0;
}

main函数创建顺序二叉树

int main(){
    int data[7] = {4, 6, 3, 7, 2, 9, 1};
    int size= sizeof(data) / sizeof(int);
    treeNode* root = NULL;
    for (int i=0; i< size; i++){
        orderedTreeInsert(&root, data[i]);
    }
    printInOrderTree(root);
    printf("\n");
    freeNode(&root);
 
    return 0;
}

5-4)二叉平衡树 AVL 树

二叉排序树可以在一定程度上提高查找(搜索)的效率,但仍然会出现特殊情况,让二叉排序树失效。例如,将序列{1,2,3,4,5,6, 7}中的元素依次插入到二叉排序树中,会得到右斜树,这就相当于一个单链表了,搜索效率降低为O(n)。

上述的二叉排序树输出的中序遍历和后续遍历的值为如下,可以看出退化成了一个链表了。

1 -  2 -  3 -  4 -  5 -  6 -  7 - 
back print 
 7 -  6 -  5 -  4 -  3 -  2 -  1 -

平衡二叉树的性质:

  • 可以是空树。
  • 假如不是空树,任何⼀个结点的左子树与右子树都是平衡⼆叉树,并且 高度之差的绝对值不超过 1

二叉平衡树的构建

  1. 首先判断当前节点的值与插入的节点值的大小,递归插入到左、右节点中
  2. 如果是插入到右节点,先判断左右子树是否是平衡的,如果不平衡的话,则判断如下:
  • -1)如果当前插入的值大于节点的右节点值的话,则表示是 RR形式的,需要左旋
  • -2)如果当前插入的值小于节点的右节点值的话,则表示是 RL 形式的,先对当前节点的右节点进行右旋,更新节点,再对根部节点左旋。
  1. 如果是插入到左节点,先判断左右子树是否是平衡的,如果不平衡的话,则判断如下:
  • -1)如果当前插入的值小于节点的左节点值的话,则表示是 LL 形式的,需要右旋
  • -2)如果当前插入的值大于于节点的左节点值的话,则表示是 LR 形式的,先对当前节点的左节点进行左旋,更新节点,再对根部节点右旋。

RR形式,左旋


void RRrotation(treeNode* node, treeNode** root){
    // 缓存 tmp,因为tmp 会最终作为新的头部节点
    treeNode* tmp = node->right;
    node->right = tmp->left;
    tmp->left = node;
    // 更新tmp 和 node 的节点的高度
    node->height = MAX(getTreeHeight(node->left), getTreeHeight(node->right)) + 1;
    tmp->height = MAX(getTreeHeight(tmp->left), getTreeHeight(tmp->right)) + 1;
    // 更新根部节点
    *root = tmp;

LL形式,右旋

void LLrotation(treeNode* node, treeNode** root){
    treeNode* tmp = node->left;
    node->left = tmp->right;
    tmp->right = node;
    node->height = MAX(getTreeHeight(node->left), getTreeHeight(node->right)) + 1;
    tmp->height = MAX(getTreeHeight(tmp->left), getTreeHeight(tmp->right)) + 1;
    // 更新根部节点
    *root = tmp;
}

右孩子的左子树 RL

右孩子的左子树 插入导致失衡

左孩子的右子树 LR

左孩子的右子树 插入导致失衡

#include <stdio.h>
#include <stdlib.h>
 
#define MAX(a, b) ((a > b) ? (a) : (b))
 
typedef struct treeNode{
    struct treeNode* left;
    struct treeNode* right;
    int height;
    int data;
}treeNode;
 
treeNode* mallocTreeNode(int data){
    treeNode* node = (treeNode*)malloc(sizeof(treeNode));
    node->data = data;
    node->height = 0;
    return node;
}
 
int getTreeHeight(treeNode* node){
    return node ? node->height : 0;
}
 
void avlTreeInsert(treeNode** node, int data){
    if (*node == NULL){
        *node = mallocTreeNode(data);
        (*node)->left = NULL;
        (*node)->right = NULL;
    }else if(data > (*node)->data){
        // 如果插入的节点要大于当前的节点,插入到右节点
        avlTreeInsert(&(*node)->right, data);
        // 再对节点进行旋转
        int leftHeight = getTreeHeight((*node)->left);
        int rightHeight = getTreeHeight((*node)->right);
        // 如果是左右子树是不平衡的树
        if (rightHeight-leftHeight == 2){
            // 如果当前节点还大于右边最大的节点,则进行左旋
            // 找到首个节点即可。
            if (data > (*node)->right->data){
                RRrotation(*node, node);
            }else{
                // RL 形式的树,先对node->right 节点右旋,然后对node 左旋
                RRrotation((*node)->right, &(*node)->right);
                LLrotation(*node, node);
            }
        }
    }else if(data < (*node)->data){
        avlTreeInsert(&(*node)->left, data);
        int leftHeight = getTreeHeight((*node)->left);
        int rightHeight = getTreeHeight((*node)->right); 
        if (leftHeight-rightHeight == 2){
            if (data < (*node)->left->data){
                // LL 形式节点
                LLrotation(*node, node);
            }else{
                // LR 形式的树,先对node->left 节点左旋,然后对更新后的node 节点右旋
                LLrotation((*node)->left, &(*node)->left);
                RRrotation(*node, node);
            }
        }       
    }else{
        return;
    }
    (*node)->height = MAX(getTreeHeight((*node)->left), getTreeHeight((*node)->right)) + 1;
}
 
void orderPrintTree(treeNode* root){
    if (root != NULL){
        // 中序遍历: 左根右
        orderPrintTree(root->left);
        printf(" -%d ", root->data);
        orderPrintTree(root->right);
    }
}
 
void printBackOrderTree(treeNode* root){
    if (root != NULL){
        printBackOrderTree(root->left);
        printBackOrderTree(root->right);
        printf(" %d - ", root->data);
    } 
}
 
 
int main(){
 
    int arr[] = {1, 2, 3, 4, 5, 6, 7};
    int size = sizeof(arr) / sizeof(int);
    treeNode* root = NULL;
    for (int i=0; i< size; i++){
        avlTreeInsert(&root, arr[i]);
    }
    orderPrintTree(root);
    printf("back order === \n");
    printBackOrderTree(root);
 
    return 0;
}

二叉平衡树的搜索的时间复杂度为 O(log2n)

二叉平衡树博客惨参考

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

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

相关文章

4.6--计算机网络之TCP篇之TCP的基本认识--(复习+深入)---好好沉淀,加油呀

1.TCP 头格式有哪些&#xff1f; 序列号&#xff1a; 在建立连接时由计算机生成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送一次数据&#xff0c;就「累加」一次该「数据字节数」的大小。 用来解决网络包乱序问题。 确认应答号&#xff1a; …

MySQL优化——Explain分析执行计划详解

文章目录前言一. 查看SQL执行频率二. 定位低效率执行SQL三. explain分析执行计划3.1 id3.2 select_type3.3 table3.4 type3.5 key3.6 rows3.7 extra四. show profile分析SQL前言 在应用的的开发过程中&#xff0c;由于初期数据量小&#xff0c;开发人员写 SQL 语句时更重视功能…

酷炫的3D智慧工厂不会打造?这40+可视化大屏模板千万别错过!

数字化已成为各行各业的重要趋势&#xff0c;因为数字化能够带来精准的效益提升。小米公司最近推进了黑灯工厂业务。”24小时熄灯操作&#xff0c;全程无人工干预”&#xff0c;这是小米智能工厂的重要特征。走进工厂&#xff0c;闪烁的灯带提示生产正在进行&#xff0c;机器通…

【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…

【ChatGPT】Prompt Engineering入门

Prompt Engineering入门一、什么是 Prompt Engineering&#xff1f;二、我们还需要学习 PE 吗&#xff1f;三、Prompt基础原则一、什么是 Prompt Engineering&#xff1f; 简单的理解它是给 AI 模型的指令。它可以是一个问题、一段文字描述&#xff0c;甚至可以是带有一堆参数的…

数据结构 - 排序 | C (插入、希尔、选择、堆、冒泡)

目录0.排序思维导图&#xff08;总&#xff09;一、插入排序1.直接插入排序思路分析代码实现时间复杂度2.希尔排序思路分析代码实现时间复杂度二、选择排序1.选择排序思路分析代码实现时间复杂度2.堆排序思路分析代码实现时间复杂度三、交换排序冒泡排序思路分析代码实现时间复…

如何基于vue实现倒计时效果

如何基于vue实现倒计时效果基于vue2 element实现画面效果代码基于vue2 element 实现画面效果 代码 <template><div><div class"Box"><div style"position: relative;"><el-progress type"circle" :width"30…

pytorch注意力机制

pytorch注意力机制 最近看了一篇大佬的注意力机制的文章然后自己花了一上午的时间把按照大佬的图把大佬提到的注意力机制都复现了一遍&#xff0c;大佬有一些写的复杂的网络我按照自己的理解写了几个简单的版本接下来就放出我写的代码。顺便从大佬手里盗走一些图片&#xff0c…

Vue3使用触摸滑动插件(Swiper)

Vue2使用触摸滑动插件&#xff08;Swiper&#xff09; 参考文档&#xff1a; Swiper官方 Swiper Vue Swiper Demos 本文使用的是最新版本&#xff1a;Swiper9.2.2 安装插件&#xff1a;yarn add swiper 本文基于Swiper插件进行封装&#xff0c;主要实现两种形式的轮播图展示…

电脑自带远程桌面和远程控制软件哪个好?

随着科技的不断发展和普及&#xff0c;越来越多的公司和个人开始关注远程控制软件的使用。我们常常需要在不同的地方工作&#xff0c;但工作需要的文件和软件却只能在一个地方使用&#xff0c;这时候远程控制电脑软件就变得尤为重要咯。但是&#xff0c;许多人分不清楚&#xf…

Windows远程连接工具有哪些

Windows远程连接工具&#xff0c;一般称为远程桌面软件&#xff0c;更准确的叫远程访问软件或远程控制软件&#xff0c;可以让你从一台电脑远程控制另一台电脑。远程桌面软件允许您控制连接的计算机&#xff0c;就好像它就在您面前一样。 远程桌面工具可用于技术支持、远程工作…

单TYPE-C口 可支持快充又可传输USB2.0数据方案

虽然现在有不少厂商也采用了Type-C接口&#xff0c;但是只作为一个充电接口&#xff0c;对于跨时代的type-c接口来说&#xff0c;多少有点大材小用&#xff0c; 那么有没有办法&#xff0c;让一个type-c接口既可以充电&#xff0c;又可以接OTG&#xff1f;比如不充电的时候可以…

Python一行命令搭建HTTP服务器并外网访问 - 内网穿透

文章目录1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置4.公网访问测试5.结语转载自远程内网穿透的文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透」 1…

第二章:HTML CSS 网页开发基础(二)

CSS概述 CSS全称&#xff1a;Cascading Style Sheet&#xff0c;可以对文字进行重叠&#xff0c;定位。主要实现页面美化。 一、CSS规则 CSS样式表中包括了3部分&#xff1a;选择符、属性、属性值 选择符{属性:属性值;} 选择符&#xff1a;也可以称为选择器&#xff0c;所有…

Java实现输入行数打印取缔字符,打印金字塔三角形的两个代码程序

目录 前言 一、实现输入行数&#xff0c;打印取缔字符 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 二、打印金字塔三角形 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图​​​​​​​ 前言 1.因多重原因&#xff0c;本博文有…

【BlazePose】《BlazePose: On-device Real-time Body Pose tracking》

arXiv-2020 文章目录1 Background and Motivation2 Advantages / Contributions3 Method4 Experiments5 Conclusion&#xff08;own&#xff09;1 Background and Motivation 人体关键点存在的难点&#xff1a;a wide variety of poses, numerous degrees of freedom, and occ…

JavaWeb—Maven

目录 1.什么是Maven 2.Maven的作用 3.Maven概述 3.1Maven介绍 3.2 Maven模型 3.3 Maven仓库 1.什么是Maven Maven是Apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 官网&#xff1a;Maven – Welcome to Apache Mavenhttps://maven.apache.o…

vscode 终端集成bash

windows 版本的 vs code 终端默认是没有集成bash的&#xff0c;虽然也能在vscode 终端可以提交git&#xff0c;但是没有高亮&#xff0c;没有提示&#xff0c;很不方便&#xff0c;这时候就需要我们将bash集成到vs code的终端&#xff0c;就可以愉快的使用git的分支高亮&#x…

阿里云蔡英华:云智一体,让产业全面迈向智能

4月11日&#xff0c;在2023阿里云峰会上&#xff0c;阿里云智能首席商业官蔡英华表示&#xff0c;算力的飞速发展使数字化成为确定&#xff0c;使智能化成为可能。阿里云将以云计算为基石&#xff0c;以AI为引擎&#xff0c;参与到从数字化迈向智能化的划时代变革中。 基于服务…

资深PM赞不绝口的【9种项目管理图】

好用的项目管理工具可以帮助项目经理掌握项目进度&#xff0c;更好的拆分任务&#xff0c;节约时间。 今天给大家安排上&#xff0c;助力大家在项目交付路上更顺畅&#xff0c;早日以高质量交付结果&#xff0c;找到百万年薪工作。 ​项目管理甘特图扫描Q群二维码下载Q群5330…