数据结构第五章树部分代码c

计算机考研复试面试常问问题 数据结构篇(下) 计算机考研复试面试常问问题 数据结构篇(下) 使用前需知(拒绝白嫖,如果对你有帮助,你只需点个赞就行): 注意:有人在闲鱼上盗卖我的资料,而且还有很多同学买了,请各位擦亮双眼,我是整理出来免费分享给大家的! 大家请多点赞和评论,这样后面的同学更容易百度到,不至于被人盗卖,我就不加水印影响大家使用了! 第一次整理,度不好控制,有不好的地方希望大家理解,后面可能会改进,造福今年之后的同学们! ... 阅读详情

顺序存储结构

#ifndef TREE_H
#define TREE_H

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

// 二叉树的顺序存储结构,适合于完全二叉树和满二叉树
#define MaxSize 100
typedef int ElemType;
typedef struct TreeNode{
    ElemType value;//结点中的数据元素
    bool isEmpty;//结点是否为空
}TreeNode;
// TreeNode t[MaxSize] //初始化长度为MaxSize的数组t
// for(int i=0;i<MaxSize;i++){
//     t[i].isEmpty=true;//初始化标记为空
// }

#endif
  • 顺序链表常用于存储完全二叉树或者满二叉树,如果用来表示普通二叉树,只能添加一些并不存在的空结点来让每一个结点和完全二叉树的结点向对应。

顺序链表的应用:已知一棵二叉树按照顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近的公共祖先结点的值

#include "tree.h"

// #define MaxSize 100
// 已知一棵二叉树按照顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近的公共祖先结点的值
// 按照完全二叉树的存储形式,其实就是求两个数的除法

// 正解
int Common(TreeNode t[],int i,int j){
    if(t[i].isEmpty||t[j].isEmpty)//结点不存在
        return -1;
    while(i!=j){
        if(i>j)
            i/=2;
        else
            j/=2;
    }
    return i;
}
void test127(){
    // 初始化一个顺序存储结构的树
    TreeNode T[MaxSize];
    for(int i=0;i<MaxSize;i++){
        T[i].value=i;
        T[i].isEmpty=true;
    }
    int len=10;
    // 构造一个二叉树为1-10
    for(int i=1;i<=len;i++){
        T[i].isEmpty=false;
    }
    // Common(T,1,2);
    printf("%d\n",Common(T,1,2));
    
}

链式存储结构

所有的测试用例都是:ABD##E##CF##G##

// 二叉树的链式存储
typedef struct BiTNode{
    DataType data;
    struct BiTNode *lchild,*rchild;
    // struct BiTNode *parent;//用来方便存储父节点指针,这也是所谓的三叉链表
}BiTNode,*BiTree;

创建一棵树

这里要注意的是,树的定义是递归定义的,所以创建的时候也是用递归创建的。这里使用#字符表示NULL,表示结束,并且使用的是先序法创建一个树,也即是说,输入的字符顺序和先序法获得的结点顺序是一致的。

// create
void createTree(BiTree *T){
    DataType ch;
    scanf("%c",&ch);
    // while(getchar()!='\n');//当获取一个有效字符时结束循环
    if(ch=='#'){
        *T=NULL;
        return;
    }
    else{
        *T=(BiTNode *)malloc(sizeof(BiTNode));
        (*T)->data=ch;
        createTree(&((*T)->lchild));
        createTree(&((*T)->rchild));
    }
}

void testCreate(){
    BiTree T;
    printf("请连续输入字符,中间不要有空格和换行。。。\n");
    createTree(&T);//ABD##E##CF##G##
    printf("create ok");
}

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

对先序遍历、中序遍历、后序遍历的理解,其实就是关于进入递归的三个位置(前序位置、中序位置、后序位置)的操作。可以看看这个博文

顺便提一句:递归一般都有前序位置、后序位置,可以用来做一些特殊操作,比如链表的倒置

void visit(BiTree T){
    printf("%c ",T->data);
}
// 先序遍历
void PreOrder(BiTree T){
    if(T!=NULL){
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}
// 中序遍历
void InOrder(BiTree T){
    if(T!=NULL){
        InOrder(T->lchild);
        visit(T);
        InOrder(T->rchild);
    }
}
// 后序遍历
void PostOrder(BiTree T){
    if(T!=NULL){
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}
void testCreate(){
    BiTree T;
    printf("请连续输入字符,中间不要有空格和换行。。。\n");
    createTree(&T);
    printf("create ok\n");
    PreOrder(T);
    printf("\n");
    InOrder(T);
    printf("\n");
    PostOrder(T);
}

在这里插入图片描述

求树的深度——后序遍历的应用
// 求树的深度——后序遍历的应用
int treeDepth(BiTree T){
    if(T==NULL){
        return 0;
    }
    else{
        int l=treeDepth(T->lchild);
        int r=treeDepth(T->rchild);
        return l>r?l+1:r+1;
    }
}

遍历的非递归实现

把第一个递归操作变成访问左子树,把第二个递归操作变成访问右子树,其他的打印操作和递归实现是一致的。

这里在实现的时候要注意的一个点就是,入栈的不是树的一个结点,而是一整棵树,不然会出现树断开无法访问的情况。

#ifndef LISTACK_H
#define LISTACK_H
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include "tree.h"
// 链栈,不带头结点的情况
typedef BiTree TreeType;//入栈的节点是一棵树,不是树的一个节点,否则树会断层
typedef struct LinkNode{
    TreeType data;//数据域
    struct LinkNode *next;
}LinkNode,*LinkStack;

bool InitStack4(LinkStack *s);//初始化不带头结点的栈
bool Push4(LinkStack *s,TreeType x);//入栈
bool Pop4(LinkStack *s,TreeType *x);//出栈
void printfStack4(LinkStack s);//打印
bool EmptyStack4(LinkStack s);//空栈
#endif
#include "listack.h"


bool InitStack4(LinkStack *s){
    *s=NULL;//栈空,不带头结点
    return true;
}
bool EmptyStack4(LinkStack s){
    if(s==NULL){
        return true;
    }
    return false;
}
// x在这里表示的是一棵树
bool Push4(LinkStack *s,TreeType x){
    LinkNode *p=(LinkNode *)malloc(sizeof(LinkNode));
    if(p==NULL)
        return false;
    p->data=x;
    // 这一步很容易错
    p->next=(*s);
    (*s)=p;
    return true;
}
bool Pop4(LinkStack *s,TreeType *x){
    // printf("Pop-%d",x);
    if((*s)==NULL)//空表
        return false;
    LinkNode *p=*s;
    *s=(*s)->next;
    *x=p->data;
    free(p);

    return true;
}
// void printfStack4(LinkStack s){
//     // s=NULL;
//     LinkNode *p=s;
//     while(p!=NULL){
//         printf("%c->",p->data);
//         p=p->next;
//     }
//     printf("\n");
// }

//前序遍历
void PreOrder2(BiTree T){
    LinkStack S;
    InitStack4(&S);
    BiTree p=T;

    while(p!=NULL||!EmptyStack4(S)){
        if(p!=NULL){
            visit(p);
            Push4(&S,p);
            p=p->lchild;
        }else{
            Pop4(&S,&p);
            p=p->rchild;
        }
    }
    printf("\n");
}
//中序遍历
void InOrder2(BiTree T){
    LinkStack S;
    InitStack4(&S);
    BiTree p=T;

    while(p!=NULL||!EmptyStack4(S)){
        if(p!=NULL){
            Push4(&S,p);
            p=p->lchild;
        }else{
            Pop4(&S,&p);
            visit(p);
            p=p->rchild;
        }
    }
    printf("\n");
}
// 后序遍历
void PostOrder2(BiTree T){
    LinkStack S;
    InitStack4(&S);
    BiTree p=T;
    BiTree r=NULL;

    while(p!=NULL||!EmptyStack4(S)){
        if(p!=NULL){
            Push4(&S,p);
            p=p->lchild;
        }else{
            GetTop(S,&p);
            if(p->rchild!=NULL&&p->rchild!=r)//右子树存在且没有被访问过
                p=p->rchild;
            else{
                Pop4(&S,&p);
                visit(p);
                r=p;
                p=NULL;//每一次访问完一个结点都相当于遍历完以该结点为根的子树,所以要置位为NULL
            }
        }
    }
    printf("\n");
}

在这里插入图片描述

对于非递归的后序遍历算法,当访问一个结点的时候,栈中的结点恰好是p结点的所有祖先,并且由低向顶加上被访问的结点本身,刚好是从根结点到p结点的一条路径,用这个思路可以求根结点到某一个结点的路径、求两个结点的最近公共祖先等。

层次遍历

初始化一个空队列,将根节点入队列,然后出队,如果出队的结点有左右孩子,就把左右孩子入队列,以此循环一直到队列为空。

// 层次遍历
void levelOrder(BiTree T){
    LQueue Q;
    InitLQueue_withHead(&Q);
    BiTree p=NULL;
    EnLQueue_withHead(&Q,T);


    while(!LQueueEmpty_withHead(Q)){
        DeLQueue_withHead(&Q,&p);
        printf("%c ",p->data);
        if(p->lchild!=NULL)
            EnLQueue_withHead(&Q,p->lchild);
        if(p->rchild!=NULL)
            EnLQueue_withHead(&Q,p->rchild);
    }
}

在这里插入图片描述

线索二叉树

无论是先序、中序、后续的线索二叉树的实现,都要记得最后一个结点要单独处理。

#ifndef THREADTREE_H
#define THREADTREE_H
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include "tree.h"

// typedef char ElemType;
typedef struct ThreadNode{
    DataType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;//左右线索标志,tag=0,表示指向孩子,tag=1表示的是线索
}ThreadNode,*ThreadTree;

#endif
  • 创建一棵普通的树
void createThreadTree(ThreadTree *T){
    DataType ch;
    scanf("%c",&ch);
    // while(getchar()!='\n');//当获取一个有效字符时结束循环
    if(ch=='#'){
        *T=NULL;
        return;
    }
    else{
        *T=(ThreadNode *)malloc(sizeof(ThreadNode));
        (*T)->data=ch;
        (*T)->ltag=0;//创建一棵树的时候不要忘记初始化,不然可能会出错
        (*T)->rtag=0;
        createThreadTree(&((*T)->lchild));
        createThreadTree(&((*T)->rchild));
    }
}

先序线索二叉树的实现

先序线索二叉树的实现中,要注意避免死循环的情况。比如当前树的根节点(假设为A)被访问,当这个A被访问且左子树被修改为其前向结点时(也即是说A的左子树本来为NULL,然后被修改,假设其前向指针为O,就会被修改为O),按照先序遍历的逻辑是接下来会访问根节点A的左子树,访问时就会发现其左子树是当前节点的前向结点O(本来是NULL然后被修改了),程序会跳转到O,然后O访问完又跳转到A出现死循环问题,俗称“爱的魔力转圈圈”问题。解决的办法就是看标志位ltag

void ThreadVisit(ThreadNode *q){
    printf("%c ",q->data);
    if(q->lchild==NULL){
        q->lchild=pre;
        q->ltag=1;
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;
        pre->rtag=1;
    }
    pre=q;
}
// 先序线索化
void PreThread(ThreadTree T){
    if(T!=NULL){
        ThreadVisit(T);
        if(T->ltag==0)
            PreThread(T->lchild);
        PreThread(T->rchild);
    }
}
void CreatePreThread(ThreadTree T){
    pre=NULL;
    if(T!=NULL){
        PreThread(T);
        if(pre->rchild==NULL)
            pre->rtag=1;//处理遍历的最后一个结点
    }
}

中序线索二叉树的实现

#include "threadtree.h"
ThreadNode *pre=NULL;//全局变量指向当前访问的结点的前驱

void createThreadTree(ThreadTree *T){
    DataType ch;
    scanf("%c",&ch);
    // while(getchar()!='\n');//当获取一个有效字符时结束循环
    if(ch=='#'){
        *T=NULL;
        return;
    }
    else{
        *T=(ThreadNode *)malloc(sizeof(ThreadNode));
        (*T)->data=ch;
        createThreadTree(&((*T)->lchild));
        createThreadTree(&((*T)->rchild));
    }
}
// 中序线索化-是建立在已经建立了一棵二叉树的前提下实现的
void InThread(ThreadTree T){
    if(T!=NULL){
        InThread(T->lchild);
        ThreadVisit(T);
        InThread(T->rchild);
    }
}
void ThreadVisit(ThreadNode *q){
    printf("%c ",q->data);
    if(q->lchild==NULL){
        q->lchild=pre;
        q->ltag=1;
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;
        pre->rtag=1;
    }
    pre=q;
}
void CreateInThread(ThreadTree T){
    pre=NULL;
    if(T!=NULL){
        InThread(T);
        if(pre->rchild==NULL)
            pre->rtag=1;//处理遍历后的最后一个结点
    }
}
void test3(){
    ThreadTree T;
    printf("please imput a string without blank and line feed...\n");
    createThreadTree(&T);
    printf("create successfully\n");
    CreateInThread(T);
    printf("\nsuccessful!");
}

后序线索二叉树的实现

void ThreadVisit(ThreadNode *q){
    printf("%c ",q->data);
    if(q->lchild==NULL){
        q->lchild=pre;
        q->ltag=1;
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;
        pre->rtag=1;
    }
    pre=q;
}
// 后序线索化
void PostThread(ThreadTree T){
    if(T!=NULL){
        PostThread(T->lchild);
        PostThread(T->rchild);
        ThreadVisit(T);
    }
}
void CreatePostThread(ThreadTree T){
    pre=NULL;
    if(T!=NULL){
        PostThread(T);
        if(pre->rchild==NULL)
            pre->rtag=1;
    }
}

线索二叉树的遍历

中序线索二叉树可以找前驱后继,先序线索二叉树不能找前驱只能找后继,后序线索二叉树不能找后继只能找前驱。

这里说的不能,是说不重新按照遍历顺序遍历,不使用三叉链表的前提下

用例:ABD##E##CF##G##

中序
  • 后继结点遍历:正向的中序遍历

思路:找到遍历的第一个结点作为当前结点,然后不断寻找当前结点的后继结点直到没有后继结点(这不比递归来的香)

// 中序线索二叉树的遍历:找到第一个结点,然后不断寻找后继结点的过程,空间复杂度为O(1)
// 找到以p为根节点的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
    while(p->ltag==0)
        p=p->lchild;
    return p;
}
// 找到中序线索二叉树中p的直接后继
ThreadNode *Nextnode(ThreadNode *p){
    if(p->rtag==0)
        return Firstnode(p->rchild);
    else
        return p->rchild;//tag=1的时候rchild就是直接后继
}
void InOrder3(ThreadTree T){
    for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))
        printf("%c ",p->data);
}
  • 中序前向结点的遍历:实现二叉树的中序遍历的倒置
// 前向遍历
ThreadNode *Lastnode(ThreadNode* p){
    while(p->rtag==0)
        p=p->rchild;
    return p;
}
// 寻找其前向结点
ThreadNode *Prenode(ThreadNode *p){
    if(p->ltag==0)
        return Lastnode(p->lchild);
    else
        return p->lchild;
}
void RevInOrder3(ThreadTree T){
    for(ThreadNode *p=Lastnode(T);p!=NULL;p=Prenode(p))
        printf("%c ",p->data);
    printf("\n");
}
void test3(){
    ThreadTree T;
    printf("please imput a string without blank and line feed...\n");
    createThreadTree(&T);
    printf("create successfully\n");
    CreateInThread(T);
    printf("\nsuccessful!\n");
    InOrder3(T);
    RevInOrder3(T);
}

在这里插入图片描述

先序
// 前序二叉树寻找后继结点
ThreadNode *Nextnode_pre(ThreadNode *p){
    if(p->rtag==1)
        return p->rchild;
    else{
        if(p->lchild!=NULL)
            return p->lchild;
        else
            return p->rchild;
    }
}
void PreOrder3(ThreadTree T){
    for(ThreadNode *p=T;p!=NULL;p=Nextnode_pre(p))
        printf("%c ",p->data);
    printf("\n");
}
// 前序二叉线索数寻找前向结点需要假设能找到结点的父节点(三叉链表)
// 如果p是左孩子,能找到父节点,则父节点为其前驱
// 如果p是右孩子,能找到父节点,父节点左孩子为空,则父节点为其前驱
// 如果p是右孩子,能找到父节点,且父节点的左孩子非空,则p的前驱为左孩子树中最后一个被先序遍历的节点,也就是左孩子树中最后一个右子树.因为数据结构定义问题这个先不实现了
后序
// 后序线索二叉树
// 寻找后序前驱
ThreadNode *Prenode_post(ThreadNode *p){
    if(p->ltag==1)
        return p->lchild;
    else{
        if(p->rtag==0)//说明p有右子树
            return p->rchild;
        else
            return p->lchild;
    }
}
void RevPostOrder3(ThreadTree T){
    // 寻找后序遍历的最后一个被遍历到的节点,一定就是根节点
    for(ThreadNode *p=T;p!=NULL;p=Prenode_post(p))
        printf("%c ",p->data);
    printf("\n");
}

// 寻找后续后继——基于父节点来实现
// 如果p是右孩子,其后继便是父节点
// 如果p是左孩子且没有兄弟节点,则其后继为父节点
// 如果p是左孩子且右孩子非空,其后继为右兄弟中第一个被后序遍历的结点
// 如果p是跟结点,则p是没有后序后继的,且父节点的左孩子非空,则p的前驱为左孩子树中最后一个被先序遍历的节点,也就是左孩子树中最后一个右子树.因为数据结构定义问题这个先不实现了
后序
// 后序线索二叉树
// 寻找后序前驱
ThreadNode *Prenode_post(ThreadNode *p){
    if(p->ltag==1)
        return p->lchild;
    else{
        if(p->rtag==0)//说明p有右子树
            return p->rchild;
        else
            return p->lchild;
    }
}
void RevPostOrder3(ThreadTree T){
    // 寻找后序遍历的最后一个被遍历到的节点,一定就是根节点
    for(ThreadNode *p=T;p!=NULL;p=Prenode_post(p))
        printf("%c ",p->data);
    printf("\n");
}

// 寻找后续后继——基于父节点来实现
// 如果p是右孩子,其后继便是父节点
// 如果p是左孩子且没有兄弟节点,则其后继为父节点
// 如果p是左孩子且右孩子非空,其后继为右兄弟中第一个被后序遍历的结点
// 如果p是跟结点,则p是没有后序后继的

树的存储结构

#ifndef TREEPLUS_H
#define TREEPLUS_H

// 双亲表示法
#define MAX_TREE_SIZE 100
typedef char ElemType;
typedef struct{
    ElemType data;
    int parent;
}PTNode;
typedef struct{
    PTNode nodes[MAX_TREE_SIZE];
    int len;//表示结点的数量
}PTree;


// 孩子表示法
struct CTNode{
    int child;//孩子结点在数组中的位置
    struct CTNode *next;//下一个孩子
};//用来表示孩子链表
typedef struct{
    ElemType data;
    struct CTNode *firstChild;//第一个孩子
}CTBox;//数组的一个元素
typedef struct{
    CTBox nodes[MAX_TREE_SIZE];
    int n,r;//结点的数量和根的位置
}CTree;//一整个数组

// 孩子兄弟表示法(链式存储)——树和二叉树相互转化的问题
typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild,*nextsibling;//第一个孩子和右兄弟指针
}CSNode,*CSTree;
#endif

树的遍历

  • 树的先根遍历

因为涉及到具体要使用的存储结构,所以这里给的是伪代码。

对数的先根遍历和这棵树对应的二叉树的先序序列相同

void PreOrder4(TreeNode *R){
    if(R!=NULL){
        visit(R);//访问根结点
        while(R还有下一个子树T){
            PreOrder4(T);//先根遍历下一棵子树
        }
    }
}
  • 后根遍历

如果树非空,先依次对每棵子树进行后根遍历,最后再访问根节点

对树的后跟遍历和这棵树对应的二叉树的中序序列相同

void PostOrder4(TreeNode *R){
    if(R!=NULL){
        while(R还有下一棵子树T)
            PostOrder4(T);
        visit(R);//访问根结点
    }
}

先根遍历和后跟遍历并称为深度优先遍历

  • 树的层次优先遍历——也叫广度优先遍历
和二叉树一样,也是使用队列来辅助进行遍历的,思路是:
首先根节点入队列
根节点出队列,如果出队的结点有孩子结点,就把孩子结点入队列
重复以上操作直到队列为空

森林的遍历

  • 森林的先序遍历——两层的递归遍历

森林是由n棵互不相交的树的集合,没棵树去掉根节点后,其余各个子树又组成了森林。因此森林是用树来递归定义的,基于这个想法,森林的先序遍历如下

如果森林非空,则
    访问森林中第一棵树的根节点
    先序遍历第一棵树中根节点的子树森林
    先序遍历除第一棵树之后剩余的树构成的森林

森林的先序遍历和其对应的每一棵树的分别的先序遍历得到的结果是相同的

也可以先把森林转换为对应的二叉树,对二叉树进行先序遍历,得到的结果相同

简单来讲,先序遍历没有什么特别的,就代码特别难写

  • 森林的中序遍历——两层的递归遍历
如果森林非空
    中序遍历森林中的第一棵树的根节点的子树森林
    访问第一棵树的根节点
    中序遍历除第一棵树之后剩余的树构成的森林

森林的中序遍历相当于对森林的每一棵树的后序遍历得到的结果

也可以先转化为对应的二叉树,对二叉树进行中序遍历,得到的结果也相同

森林树二叉树
先序先序先序
中序后序中序

哈夫曼树

  • 带权路径长度

结点的权:有某种现实含义的数值

结点的带权路径长度:从树的根节点到该结点的路劲长度(经过的边数)与该结点上权值的乘积

树的带权路径长度:树中所有叶节点的带权路径长度之和WPL

在这里插入图片描述

  • 哈夫曼树

在含有n个带权叶节点的二叉树中,其中带权路径长度WPL最小的二叉树称为哈夫曼树,也称为最优二叉树

  • 构造哈夫曼树

每次选择结点权重最小的两个数作为兄弟节点,把他们权重的和作为他们的根结点。再把这个根结点和其他没有被选择的最小权值的结点构造一棵树,一次类推直到全部的结点都被选择。

构造出的哈夫曼树是不唯一的,其编码也是不唯一的

在这里插入图片描述

  • 哈夫曼编码——前缀编码,无歧义

哈夫曼树可以用来进行数据的压缩

并查集——双亲表示法

将各个元素划分为互不相交的子集

用代码来表示同一个集合的元素:同一个集合的元素构成一棵树,这些树构成一个森林

查询操作:从指定元素出发,找到根节点,时间复杂度为O(n),查询的时间复杂度与树的高度有关,所以优化的思路是,当合并的时候,小树合并成大树

#define SIZE 13
int UFSets[SIZE];//集合元素数组

//初始化并查集
void Initial(int S[]){
    for(int i=0;i<SIZE;i++)
        S[i]=-1;
}

//find操作
int Find(int S[],int x){
    while(S[x]>=0)
        x=S[x];
    return x;
}

合并结合:其中一棵树称为另一棵树的子树就可以了——时间复杂度为O(1)

void Union(int S[],int Root1,int Root2){
    if(Root1==Root2)
        return;
    S[Root2]=Root1;
}

并查集的优化

  • 优化union操作,优化的思路是小树合并成大树

优化后的合并操作——我们使得根节点对应的数组值变成其对应集合数量的相反数,比如一棵树的数量为10,那么其根节点对应的数组值为-10。

void Union2(int S[],int Root1,int Root2){
    if(Root1==Root2)
        return;
    if(S[Root1]>S[Root2]){
        S[Root1]+=S[Root2];
        S[Root2]=Root1;
    }else{
        S[Root2]+=S[Root1];
        S[Root1]=Root2;
    }
}

用数学归纳法知,优化后的树的高度不会超过(log2)n+1

  • 优化find操作

优化的结果是压缩路径,思路是先找到根节点,再将查找路径上的所有结点都挂接到根结点下面,优化后的代码如下

int find(int S[],int x){
    int root=x;
    while(S[root]>=0)
        root=s[root];
    while(x!=root){
        int t=S[x];//t指向x的父节点
        S[x]=root;//x直接挂接到根节点下面
        x=t;
    }
    return root;
}

每次find操作后,再找根,再压缩路径,使得树的高度不超过O(α(n)),α(n)是一个增长很缓慢的树函数,对于常见的n的值,通常α(n)≤4,因此优化后的并查集的find和union操作时间开销都很低

在这里插入图片描述

别再手动写JSON了!用Node-RED OPC UA节点5分钟搞定楼宇温湿度数据采集 本文介绍了如何利用Node-RED和OPC UA节点快速实现楼宇温湿度数据的自动化采集与JSON格式转换。通过可视化工具Node-RED,开发者无需编写复杂代码,5分钟内即可完成从传感器到数据输出的全流程配置,大幅提升智慧楼宇和工业物联网项目的实施效率。 阅读详情

相关推荐

如何使用ArcGIS自动矢量化建筑

在显示的重分类对话框内,输入栅格选择提取单波段后的栅格,重分类分为三个层级,旧值分别为100-249、250-251和252-255,对应新值为1、2和3,旧值的最小和最大值不用修改,保持和原有的一致。同样在ArcToolbox内点击Spatial Analyst工具点击“重分类\重分类”,在重分类对话框内输入栅格选择之前重分类过后的图片,将旧值3的新值设置为1,这样分类后就只有两类。在显示的新建Shapefile对话框内,名称命名为折线,要素类型选择折线,点击编辑,设置坐标系为WGS 1984。

2165

数据结构树-C语言实现

一.树是什么? 这个自己百度吧,不是此文重点。需了解基本概念才能更好理解代码。 二.开发环境 VC++ 6.0 比较老的一个环境,单文件测试。 三.主要记录 参考https://blog.youkuaiyun.com/huihuituboshu/article/details/79620056 设计。 但是发现代码还是有一些问题的,所以调试了一下,解解bug,包括以下方面: 1.原文并不是基于C语言写的,其中调用了vector.然后百度了一下,大概就是一个动态数组,考虑到程序中的动态添加节点信息问题,所以自己用链表实

nalanxiaojie的博客 985

手机热点+Advanced IP Scanner:5分钟搞定树莓派无屏SSH连接(附详细配置截图)

本文详细介绍如何利用手机热点和Advanced IP Scanner工具快速实现树莓派的无屏SSH连接。通过简单的WiFi配置和IP扫描,5分钟内即可完成连接,特别适合外出调试或空间有限的工作环境。文章包含详细步骤、配置截图和常见问题解决方案,帮助开发者高效管理树莓派设备。

ik678901234的博客 955

数据结构——树的C语言实现

文章目录1.什么是树?2.如何判断是否是树?3.树的基本术语4.树的表示方法5.二叉树的定义6.特殊二叉树7.二叉树的性质8.二叉树的抽象数据类型定义9.二叉树的存储结构9.1顺序存储9.2 链表存储10.二叉树的遍历10.1 二叉树的遍历方法1.递归遍历2.层序遍历10.2 二叉树遍历的C语言实现1.递归遍历的实现2.层序遍历的实现 1.什么是树? 2.如何判断是否是树? 3.树的基本术语 4.树的表示方法 5.二叉树的定义 6.特殊二叉树 7.二叉树的性质 8.二叉树的抽象数据类型定义

zxr916的博客 1198

Amesim管路仿真进阶:如何用阻力管实现不规则管路的高精度模拟

本文深入探讨了在Amesim仿真中,如何利用阻力管(Orifice with Table)元件,特别是TFORT0子模型,实现对不规则管路的高精度模拟。通过直接输入基于测试或CFD的流量-压降数据,该方法能有效弥补标准管路模型在复杂流道、内部结构复杂等场景下的不足,为液压、热管理等系统仿真提供精准可靠的部件模型。

cicd6pipeline的博客 671

深度解析跨境支付之业务架构

详解跨境支付之业务架构

weixin_47493863的博客 1880

数据结构算法——1067. 目录树

题目 input: 7 b c ab\cd a\bc ab\d a\d\a a\d\z\ 思路 善用STL 代码 #include<bits/stdc++.h> using namespace std; struct folder { string name; map<string,folder*> folder_child; set<string> file_child; }; void print(const folder* root,

JamSlade的博客 1041

ROS2初级面试题汇总

总结和收集了一些关于ros2的问题和解答记录

qq_57633442的博客 5576

数据结构C语言实现-5—树

Table of Contents 二叉树顺序结构实现 二叉树链式结构实现 线索二叉树 理论部分见:数据结构理论基础-5—树 二叉树顺序结构实现 #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #def...

toutuo 1258

【毕设实战】基于YOLOv8的HRSC2016船舶智能检测系统开发全指南(含可视化UI界面,打造海事领域高分毕设)

亲爱的同学,通过这篇教程,你已经掌握了“基于YOLOv8的HRSC2016微小船只检测系统”从数据集处理、模型训练到Tkinter界面开发的全流程。这个课题既贴合“海事智能化”的产业战略,又涵盖了“目标检测、深度学习、界面开发”等多个毕设评分点,非常适合作为计算机或海事工程专业的毕业设计。在实践中,你可能会遇到“海面背景复杂导致船只漏检”“微小船只识别精度不足”“UI与模型推理同步卡顿”等挑战——别担心,这些都是技术成长的必经之路。

know_F0727的博客 131

cefshap(C#)定义浏览器的右键菜单

菜单效果:复制(自带),搜索文本,打开链接,复制链接,刷新,检查,关于 public class MenuHandler : IContextMenuHandler { //public event Action<string> PopupRequest; void IContextMenuHandler.OnBeforeContextMenu(IWebBrowser browserControl, IBrows

无线智能模块 可视DIY智能专家 1706

PTA 数据结构 7-9 目录树

输入格式 输入首先给出正整数N(≤104),表示ZIP归档文件中的文件和目录的数量。随后N行,每行有如下格式的文件或目录的相对路径和名称(每行不超过260个字符): 路径和名称中的字符仅包括英文字母(区分大小写); 符号“\”仅作为路径分隔符出现; 目录以符号“\”结束; 不存在重复的输入项目; 整个输入大小不超过2MB。 输出格式 假设所有的路径都相对于root目录。从root目录开始,在输出时每个目录首先输出自己的名字,然后以字典序输出所有子目录,然后以字典序输出所有文件。注意,在输出

weixin_52450397的博客 1707

数据结构之博弈树搜索(深度优先搜索)

数据结构之博弈树搜索(深度优先搜索)

LucianaiB的博客 1527

从零开始学数据结构系列之第五章《B树的代码部分》

以上就是所有内容,对大家有用的话点个关注!​ B树的插入操作只能在。

铁匠匠匠的博客 1422

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

从零开始学数据结构系列之第五章《B树的总体代码》

以上就是所有内容,对大家有用的话点个关注!

铁匠匠匠的博客 1198

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

用C/C++构建自己的Redis——第五章、Redis中的AVL树实现

在Redis数据库中,有序集合是一种核心的数据结构,它允许存储具有关联分数的唯一元素。为了实现这种数据结构,Redis采用了AVL树,一种自平衡的二叉搜索树,以确保数据的快速插入、删除和查找操作。这一章我们将一起深入探讨AVL树在Redis有序集合中的应用,包括其节点定义、插入和删除操作、以及如何通过旋转操作保持树的平衡。此外,文章还将介绍如何通过一系列测试案例来验证AVL树的正确性和性能,确保其在实际应用中的可靠性。

m0_46295679的博客 1417

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数据结构第五章:树与二叉树知识总结

在数据结构的学习中,第五章主要围绕树相关的内容展开,其知识密度和难度都有进一步提升。

m0_57836225的博客 881

《数据结构》C语言版 (清华严蔚敏考研版) 全书知识梳理

写在前面: 恰逢期末复习,用了几天时间结合老师勾画的重点以及课件教材等,将全书重要内容做了个大整合。一方面便于自己复习记忆,另一方面po出来让更多需要的人也可以做个参考。 同类梳理: 《数据库系统概论》第五版(王珊版)全书知识梳理 《计算机组成原理》第五版(唐朔飞考研版) 全书知识梳理 《数据结构》C语言版 (清华严...

12 26 25 的博客 22万+

25版王道数据结构课后习题详细分析 第五章树与二叉树 5.4树、森林

给定一棵树的先根遍历序列和后根遍历序列,能否唯一确定一棵树?若能,请举例说明将下面一个由3棵树组成的森林转换为二叉树。 已知某二叉树的先序序列和中序序列分别为 ABDEHCFIMGJKL和 DBHEAIM FCGKLJ, 请画出这棵二叉树,并画出二叉树对应的森林。 编程求以孩子兄弟表示法存储的森林的叶结点数。 以孩子兄弟链表为存储结构,请设计递归算法求树的深度。下列关于树的说法中,正确的是利用二叉链表存储森林时,根结点的右指针是

2406_86301373的博客 1203

数据结构第五章——树与二叉树

树与二叉树

qq_54122504的博客 1152

25版王道数据结构课后习题详细分析 第五章树与二叉树 5.5树与二叉树的应用

在有n个叶结点的哈夫曼树中,非叶结点的总数是( )给定整数集合{3,5,6,9,12},与之对应的哈夫曼树是( )下列编码中,( )不是前缀码设哈夫曼编码的长度不超过 4,若已对两个字符编码为1和 01,则还最多可对( )个 字符编码一棵哈夫曼树共有215个结点,对其进行哈夫曼编码,共能得到( )个不同的码字设某哈夫曼树有5个叶结点,则该哈夫曼树的高度最高可以是( )。 以下对于哈夫曼树的说法中,错误的是( ) 下列关于哈夫曼树的说法中,错误的是( )

2406_86301373的博客 726

数据结构复盘——第五章:树

涉及:树的基本术语、树的家族关系、基本的二叉树、二叉树的存储结构、二叉树的基本性质、二叉树的遍历、线索二叉树、双亲表示法、孩子表示法、孩子兄弟表示法、树、森林与二叉树的转换、数和森林的遍历、二叉排序树的定义、二叉排序树的插入、二叉排序树的删除、二叉排序树的查找、平衡二叉树的定义、最小平衡二叉树公式、平衡二叉树的插入、平衡二叉树的删除、哈夫曼树的定义、哈夫曼树的构造、哈夫曼编码

时生的博客 641

2022版王道数据结构算法题C语言代码实现-第5章-树与二叉树

概述 这是2022版王道数据结构的第5章——树与二叉树的算法大题的C语言代码实现,主要分为二叉树,树和树与二叉树的应用三部分。代码基本都经过了简单的测试,基本上不会有太大问题。 编译环境为gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0,文件目录结构如下: ch5 ├── 5-3-binaryTree.c ├── 5-4-tree.c ├── 5-5-application.c ├── application_test.c ├── binaryTree_test.c ├── q

DanielSYC的博客 1078

【数据结构】严蔚敏版--学习&&复习笔记

----------------------------------------------------------- 写在前面: emmmmm,这个数据结构系列博客我会不定时持续更新,会在本文下边给出导航链接 关于实现形式的不同。在本书中给出的伪代码使用了C++部分内容,我在实现这本书中的算法时,我会使用C...

1900的博客 7485

数据结构学习第五章树和二叉树

1.1定义1.2基本术语1.3二叉树定义特点:①每个结点最多有两个孩子(二叉树中不存在度大于2的结点)。②子树有左右之分,次序不能颠倒。③二叉树可以是空集合,跟可以有空的左子树或者空的右子树。:二叉树不是树的特殊情况,二叉树的子树要区分左子树和右子树,而树无需区分。例子:具有三个节点的二叉树有五种不同形态。树有两种形态1.4二叉树的5种形态。

weixin_46016581的博客 1958

2023考研王道数据结构知识梳理

写在前面: 本系列参考书目: 王道 《数据结构》 《数据结构》(C语言版)是为“数据结构”课程编写的教材,是很多学校数据结构课程的指定教材也是经典教材,同时也是考研数据结构的必选书目。 本系列根据课程重难点 整合此书精华部分,以求在尽可能短的时间内掌握相应知识,希望能够让你有所收获。 后续还会进行更进一步的优化整理,欢迎关注,尽请期待。 如有错误以及可以改进的地方欢迎在下方评论区留言! ...

蒟蒻 9159
上一篇: 数据结构第四章字符串部分代码c
下一篇: 数据结构应用题第五章树代码c
@@老胡
博客等级 码龄7年 291粉丝 184原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值