目录

一、基本概念

1.定义

2.基本术语

3.性质(重点!!)

二、二叉树

1.定义

2.特殊二叉树

1.满二叉树

2.完全二叉树

3.二叉排序树

4.平衡二叉树

3.性质

4.存储结构

1.顺序存储

2.链式存储

三、二叉树的遍历和线索二叉树

1.二叉树的遍历

1.先序遍历(NLR)

2.中序遍历(LNR)

3.后序遍历(LRN)

4.层次遍历

5.递归与非递归算法转换

6.遍历序列构成二叉树

2.线索二叉树

1.概念

2.三种线索二叉树

3.线索二叉树找前驱/后继

四、树和森林

1.树的存储结构

1.双亲表示法

2.孩子表示法

3.孩子兄弟表示法

2.树、森林与二叉树的转换

1.树->二叉树

2.森林->二叉树

3.二叉树->树

3.树和森林的遍历

1.树的先根遍历

2.树的后根遍历

3.树的层次遍历

4.森林的先序遍历

5.森林的中序遍历

五、树与二叉树的应用

1.哈夫曼树

2.哈夫曼编码

3.并查集

六、课后习题(持续更新中~)


一、基本概念

1.定义

n个节点的有限集合,n=0时为空树,是一种递归的分层结构,满足:

1.有且仅有一个根节点

2.n>0时其余节点可分为m个互不相交的有限集,每个集合又单独成为一个树称为子树。

3.除根节点外所有节点都有且仅有一个前驱,所有节点都有零个或多个后继。

2.基本术语

    1.节点间关系描述:①祖先:节点K的父亲及父亲的父亲balabala那条线上一串全是K的祖先;②子孙:祖先那条中K扮演的角色;③双亲:直接祖先,即K的前驱节点;④孩子:节点的后继节点,上一条中K扮演的角色;⑤兄弟:同双亲的另几个节点。

2.度:一个节点的孩子个数叫该节点的度,树中节点最大度数叫树的度(度>0为非终端节点)。

3.分支节点:度大于0的节点。

4.节点的深度:从根节点自顶向下逐层累加。

节点的高度:从叶节点自底向上逐层累加。

树的高度(或深度):节点中最大层数。

5.有序树和无序树:有序树从左到右是有序的,不可交换,否则为无序树。

6.路径和路径长度路径由两节点间所经过的节点序列构成;路径长度为路径上所经过的边的个数

7.森林:m棵互不相交的树的集合,树将根节点去掉也能形成新的森林。

3.性质(重点!!)

1.节点数 = 总度数+1

2.度为m的树&m叉树

度为m的树 m叉树
任意节点的度<=m 任意节点的度<=m
至少一个节点度=m 允许所有节点度<m
一定是非空树,至少m+1个节点 可以是空树

3.度为m的树第 i 层最多个节点

m叉树第 i 层最多个节点(i>=1)

4.高度为h的m叉树最多个节点。

    5.高度为h的m叉树至少有h个节点(一串)。

高度为h、度为m的树至少有h+m-1个节点(一串串底下缀一堆)。

6.具有n个节点的m叉树的最小高度为 [向上取整]

7.总结: 

m叉树 度为m的树
第i层最多节点数
最多节点数 (高h)

 (高h)

最少节点数 h(高h) h+m-1

二、二叉树

1.定义

一种特殊的逻辑结构,特点是每个节点至多只有两棵子树,且子树有左右之分,和树相同是一种递归形式定义的结构,是有序树

对比二叉树和度为2的有序树的区别:

二叉树  度为2的有序树
可为空 至少三个节点
节点次序固定,无论有几个孩子,孩子的左右属性都不改变 孩子左右顺序是相对另一个孩子而言,若仅有一个孩子则没有左右之分

2.特殊二叉树

1.满二叉树

高为h,含有2^h-1个节点的二叉树,即树每层都有最多的节点,不存在度为1的节点。对满二叉树按层编号,则编号为 i 的节点左孩子编号为 2i ,右孩子编号为 2i+1

2.完全二叉树

高度为h,有n个节点,且编号1~n与完全二叉树相符合。

性质:① i<=[n/2] (向下取整)为分支节点,否则为叶节点。(重要!!)

②叶节点只能出现在层次最大的两层,最大层中叶节点都排列在该层最左侧。

③有0个或1个度为1的节点,且度为1的节点只有左孩子无右孩子

④编号为 i 的节点若为叶节点或只有左孩子,则编号大于 i 的节点均为叶节点。

⑤若n为奇数,则每个分支节点都有左右两个孩子;若n为偶数,则编号最大的分支节点(编号n/2)只有左孩子而没有右孩子,其余分支节点左右孩子都有(原因:n0 = n2+1,n = n0+n1+n2,n奇数n1偶 = 0;n偶n1奇 = 1

⑥ i>1时双亲节点为[i/2](向下取整),i 偶数时双亲为 i/2,i 奇数时双亲 (i-1)/2

⑦ 2i<=n时节点 i 左孩子编号 2i,否则无左孩子;2i+1<=n时,节点 i 的右孩子编号 2i+1,否则无右孩子。

⑧ i 所在层次深度为[log2(i)](向下取整)+1

3.二叉排序树

关键字大小:左子树<根节点<右子树,子树中依旧递归遵守该定义。

4.平衡二叉树

树上任意一个节点的左子树和右子树深度差不超过1。

3.性质

①n0 = n2 + 1

②二叉树第 i 层至多有2^(i-1)个节点;m叉树第 i 层至多有m^(i-1)个节点。

③高为h的二叉树至多2^h-1个节点(满二叉树);高为h的m叉树最多个节点。

4.存储结构

1.顺序存储

用一组连续地址的数组,按照从上到下、从左到右的顺序依次存储完全二叉树中各个节点,比较适用于完全二叉树和满二叉树。对于一般二叉树,可通过设置空节点,让每个节点与完全二叉树相对照。

存储时最好从下标为1的空间开始,这样计算根节点和左右孩子下标关系会比较方便。

#define MaxSize 10
struct TreeNode{ElemType value;bool isEmpty;      //判断节点是否为空
};TreeNode t[MaxSize];for(int i=0;i<MaxSize;i++)t[i].isEmpty = true;    //初始化

       若非完全二叉树顺序对应存储则无法从节点编号反应节点间逻辑关系,因此使用一个二维数组,一行存放数据,另一行存放双亲信息,根节点双亲指针 = -1,非根节点双亲指针 = 父节点在数组的下标,利用下标建立节点与双亲的关系。

2.链式存储

节点结构包含数据域 data、左指针域 lchild、右指针域 rchild。

在含有n个节点的二叉链表中,含有n+1个空链表

struct ElemType{int value;
};typedef struct BiTNode{ElemType data;struct BiTNode *lchild, *rchild;//struct BiTNode *parent;      //三叉链表便于查找父节点
}BiTNode, *BiTree;//初始化根节点
BiTree root = NULL;
root = (BiTree)malloc(sizeof(BiTNode));
root->data = {1};
root->lchild = NULL;
root->rchild = NULL;//插入新节点
BiTNode *p = (BiTNode *)malloc(sizeof(BiTNode));
p->data = {2};
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p;

三、二叉树的遍历和线索二叉树

1.二叉树的遍历

二叉树遍历是按某条搜索路径访问树中的各个节点,使每个节点都被访问一次,其中对根节点N、左子树L和右子树R的搜索方法包括NLR、LNR、LRN和层次遍历。

typedef struct BiTNode{ElemType data;struct BiTNode *lchild,*rchild;
}BiTNode, *BiTree;

1.先序遍历(NLR)

判空,非空情况下做以下递归:

①访问根节点

②先序遍历左子树

③先序遍历右子树

//先序遍历void PreOrder(BiTree T){if(T!=NULL){visit(T);PreOrder(T->lchild);PreOrder(T->rchild);}
}

2.中序遍历(LNR)

判空,非空情况下做以下递归:

①中序遍历左子树

②访问根节点

③中序遍历右子树

//中序遍历void InOrder(BiTree T)
{if(T!=NULL){InOrder(T->lchild);visit(T);InOrder(T->rchild);}
}

3.后序遍历(LRN)

判空,非空情况下做以下递归:

①后序遍历左子树

②后序遍历右子树

③访问根节点

//后序遍历void PostOrder(BiTree T)
{if(T!=NULL){PostOrder(T->lchild);PostOrder(T->rchild);visit(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;}
}

三种遍历方式中,每个节点都访问且只访问一次(递归是会路过3次,但不重复访问),因此三种遍历方法时间复杂度都为O(n)。同时,递归时发现递归工作栈的深度恰好为树的深度,因此最坏情况下节点数为n的二叉树树高为n,空间复杂度为O(n)

4.层次遍历

算法思想:

①初始化一个辅助队列

②根节点入队

③若队列非空,则队头节点出队,访问该节点,将节点的左右孩子节点入队

④重复上述过程③直到队列为空。

//二叉树节点
typedef struct BiTNode{char data;struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;//链式队列节点
typedef struct LinkNode{BiTNode *data;struct LinkNode *next;
}LinkNode;typedef struct{LinkNode *front, *rear;    //队头队尾
}LinkQueue;//层次遍历void LevelOrder(BiTree T)
{LinkQueue(Q);InitQueue(Q);BiTree p;EnQueue(Q,T);while(!IsEmpty(Q)){DeQueue(Q, p);visit(p);if(p->lchild!=NULL)EnQueue(Q, p->lchild);if(p->rchild!=NULL)EnQueue(Q, p->rchild);}
}

5.递归与非递归算法转换

借助栈的思想分析中序遍历访问过程:

①沿着根的左孩子依次入栈,直到左孩子为空

②栈顶元素出栈并访问:若右孩子为空继续执行;若右孩子非空,将右子树转执行①

中序遍历非递归算法

void InOrder2(BiTree T)
{InitStack(S);BiTree p=T;while(p || !IsEmpty(S)){if(p){Push(S,p);p = p->lchild;}else{Pop(S,p);visit(p);p = p->rchild;}}
}

先序遍历非递归算法

void PreOrder2(BiTree T)
{InitStack(S);BiTree p=T;while(p || !IsEmpty(S)){if(p){visit(p);Push(S, p);p = p->lchild;}else{Pop(S, p);p = p->rchild;}}
}

后序遍历非递归算法思想(相对较复杂):

①沿根的左孩子,直到左孩子为空

后序遍历非递归算法

//后序遍历非递归算法
void PostOrder2(BiTree T)
{InitStack(S);BiTNode *p = T;BiTNode *r = NULL;while(P || !IsEmpty(S)){if(p){Push(S, p);p = p->lchild;}else{GetTop(S, p);if(p->rchild && p->rchild!=r)    //若右子树存在且未被访问{p = p->rchild;}else{Pop(S, p);visit(p->data);r = p;                       //记录最近被访问过的节点p = NULL;                    //节点访问过后,重置p指针}}}
}

后序遍历非递归访问时,访问到一个p节点,栈中节点恰好是p节点的全部祖先,从栈底到栈顶节点再加上p节点,刚好构成从根节点到p节点的一条路径

应用:求根到某节点的路径、求两个节点的最近公共祖先等。

6.遍历序列构成二叉树

需明确,给定一个前序/中序/后序遍历序列,该序列可能对应多个二叉树,因此单个序列无法确定构建一颗二叉树,因此需要将不同序列进行结合,共同进行二叉树构建,但注意,前序、后序、层序的两两组合是无法唯一确定一颗二叉树的。

核心:利用前序/后序/层序确定根节点,再结合中序中根节点所在位置判断左右子树,再利用这种思想确定各子树中的根节点和下一层子树,最终建立出整个二叉树。

1.前序+中序遍历序列

前序:根节点、左子树、右子树

中序:左子树、根节点、右子树

前序确定第一个出现的节点为根节点,对比中序中根节点位置确定该二叉树第一层左右子树内容,再利用前序分别确定第一层子树的根节点,结合中序确定第二层左右子树内容,依此进行以上步骤直到建成整个二叉树。

2.后序+中序遍历序列

  后序:左子树、右子树、根节点

  中序:左子树、根节点、右子树

和第一种类似,由后序先确定根节点,再利用中序确定左右子树内容,依此划分各层次根节点和子树直到叶子节点。

3.层序+中序遍历序列

层次:逐层从左到右输出

中序:左子树、根节点、右子树

利用层次遍历得到根节点,利用中序确定下一层的左右子树内容,再利用层次遍历确定第二层子树的根节点,结合中序对二叉树节点进行进一步划分,最终得到整个二叉树。

2.线索二叉树

1.概念

传统的二叉树仅能体现出父子关系,不能直接得到节点在遍历中的前驱或后继,因此线索二叉树利用链表中的 n+1个空指针(叶子节点每个有2个空指针,度为1的节点每个有1个空指针)来存放指向其前驱或后继的指针,来加快查找节点前驱和后继的速度

//线索二叉树节点typedef struct ThreadNode{ElemType data;struct ThreadNode *lchild, *rchild;int ltag, rtag;                        //左、右线索标示
}ThreadNode, *ThreadTree;//tag标志为0时标示指针指向孩子;为1时标示指针指向“线索”

2.三种线索二叉树

1.先序线索化(爱滴魔力转圈圈)

思想和中序一样,看下面(写文章的时候先写的中序dbq)

ThreadNode *pre = NULL;//前序遍历二叉树,一边遍历一边线索化
void PreThread(ThreadTree T)
{if(T!=NULL){visit(T);PreThread(T->lchild);PreThread(T->rchild);}
}void visit(ThreadNode *q)
{if(q->lchild == NULL){q->lchild = pre;q->ltag = 1;}if(pre != NULL && pre->rchild == NULL){pre->rchild = q;pre->rtag = 1;}pre = q;
}//前序线索化二叉树T
void CreatePreThread(ThreadTree T)
{pre = NULL;if(T!=NULL){PreThread(T);if(pre->rchild == NULL)pre->rtag = 1;        //处理遍历的最后一个节点}
}

2.中序线索化

附设指针pre指向刚刚访问过的节点,指针p指向正在访问的节点,即让pre指向p的前驱。在中序遍历的过程中,检查p的左指针是否为空,若为空就将他指向pre;检查pre的右指针是否为空,若为空就将它指向p

ThreadNode *pre = NULL;//中序遍历二叉树,一边遍历一边线索化
void InTread(ThreadTree T)
{if(T!=NULL){InThread(T->lchild);visit(T);InThread(T->rchild);}
}void visit(ThreadNode *q)
{if(q->lchild == NULL){q->lchild = pre;  //pre全局变量q->ltag = 1;      //一定记得修改指针状态}if(pre != NULL && pre->rchild == NULL){pre->rchild = q;pre->rtag = 1;}pre = q;              //节点后移
}//中序线索话二叉树T
void CreateInThread(ThreadTree T)  //这个函数和书上有一点不同,但核心思想是一样的
{pre = NULL;if(T!=NULL){InThread(T);if(pre->rchild == NULL)pre->rtag = 1;        //处理遍历的最后一个节点}
}

3.后序线索化

同理

ThreadNode *pre = NULL;//后序遍历二叉树,一边遍历一边线索化
void PostThread(ThreadTree T)
{if(T!=NULL){PostThread(T->lchild);PostThread(T->rchild);visit(T);}
}void visit(ThreadNode *q)
{if(q->lchild == NULL){q->lchild = pre;q->ltag = 1;}if(pre != NULL && pre->rchild == NULL){pre->rchild = q;pre->rtag = 1;}pre = q;
}//后序线索化二叉树T
void CreatePostThread(ThreadTree T)
{pre = NULL;if(T!=NULL){PostThread(T);if(pre->rchild == NULL)pre->rtag = 1;        //处理遍历的最后一个节点}
}

3.线索二叉树找前驱/后继

1.中序线索二叉树

            在中序线索二叉树中找到指定节点*p的中序后继next:

① p->rtag == 1, next = p->rchild

② p->rtag == 0,next = p的右子树中最左下节点,代码:

//找到以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);elsereturn p->rchild;    //rtag == 1直接返回后继线索
}//对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T)
{for(ThreadNode *p = Firstnode(T); p!=NULL; p=Nextnode(p))visit(p);
}

            在中序线索二叉树中找到指定节点*p的中序前驱pre:

① p->ltag == 1, pre = p->lchild

② p->ltag == 0,pre = p的左子树中最右下节点,代码:

//找到以p为根的子树中,最后一个被中序遍历的节点
ThreadNode *Lastnode(ThreadNode *p)
{//循环找到最右下节点(不定为叶节点)while(p->rtag == 0)p = p->rchild;return p;
}//在中序线索二叉树中找到节点p的前驱节点
ThreadNode *Prenode(ThreadNode *p)
{//左子树中最右下节点if(p->ltag == 0)return Lastnode(p->lchild);elsereturn p->lchild;
}//对中序线索二叉树进行逆向中序遍历
void RevInorder(ThreadNode *T)
{for(ThreadNode *p = Lastnode(T); p!=NULL; p=Prenode(p))visit(p);
}

2.先序线索二叉树

            1.在先序线索二叉树中找到指定节点*p的先序后继next

① p->rtag == 1, next = p->rchild

② p->rtag == 0,next = 左孩子(没有就右孩子)

            2.在先序线索二叉树中找到指定节点*p的先序前驱pre

① p->ltag == 1, pre = p->lchild

② p->ltag == 0,可能做不到,只能用土办法从头进行遍历,或改用三叉链表

i 如果能找到p的父节点且p为左孩子(或为右孩子,左孩子为空),则前驱为p的父节点

ii 如果p为右孩子且左孩子非空,则p的前驱为左兄弟子树中最后一个被先序遍历的节点

iii 如果p为根节点,则无前驱

3.后序线索二叉树

1.在后序线索二叉树中找到指定节点*p的后序后继next

① p->rtag == 1, next = p->rchild

② p->rtag == 0,只能用土办法从头开始遍历,后序遍历中左右子树否为根的前驱,不可能是后继:

i 如果能找到p的父节点,p是右孩子(或没有右孩子时的左孩子),则p的后继是其父节点

ii 如果能找到p的父节点,p是左孩子且有右兄弟,则p的后继为右兄弟子树中第一个被后序遍历的节点

iii 如果p是根节点,则p没有后继节点。

            2.在后序线索二叉树中找到指定节点*p的后序前驱pre

① p->ltag == 1, pre = p->lchild

② p->ltag == 0,pre = 右孩子(没有就左孩子)

这部分重点在于理解,千万不要死记硬背

四、树和森林

1.树的存储结构

1.双亲表示法

本质是顺序存储,详见上文顺序存储中二维数组存储法(一行存数据,一行存双亲的数组下标)。

#define MAX_TREE_SIZE 100    //节点结构体
typedef struct{ElemType data;int parent;              //双亲位置域
}PTNode;typedef struct{             //树结构体PTNode nodes[MAX_TREE_SIZE];int n;                  //节点个数
}PTree;

双亲表示法也可用于表示森林,即另每个树的根节点位置域都置-1,其他元素位置域对应存放双亲的数组下标。

优点:找父节点方便

缺点:找孩子不方便

适合找父亲节点多而找孩子节点少的情况,如:并查集。

2.孩子表示法

本质是链式存储,用数组顺序存储各个节点,每个节点保存数据元素、孩子链表表头指针。

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;                 //节点数和根的位置
}

孩子法存储森林需要记录多个根位置,较冗杂。

 优点:找孩子方便

缺点:找父亲麻烦,只能遍历每个链表

适合找孩子多而找父亲少的情景,如:服务流程树。

3.孩子兄弟表示法

利用二叉链表实现,每个节点内保存数据元素和两个指针,但两个指针指向为前一个孩子和右边的第一个兄弟,与二叉树两个指针指向不同。

typedef struct CSNode{ElemType data;struct CSNode *firstchild, *nextsibling;  //第一个指向第一个孩子,第二个指向右边第一个兄弟
}CSNode, *CSTree;

孩子兄弟表示法存储树或森林,从存储视角看形态与二叉树类似,相对更方便。

2.树、森林与二叉树的转换

1.树->二叉树

①在二叉树中画出一个根节点

②按“树的层序”依此处理每个节点:如果当前节点有孩子就把所有孩子节点用右指针串起来,并把第一个孩子挂在该节点左指针下方

③进行步骤②,直到各节点左指针指向第一个孩子,右指针连接最近的一个右兄弟为止。

2.森林->二叉树

将各树的根节点视为同级的兄弟,其他和树到二叉树的转换方式一样。

3.二叉树->树

①画出树的根节点

②从根节点开始,按“树的层序”恢复每个节点的孩子

4.二叉树->森林

步骤与二叉树到树的转变相同,只不过第一串右子树要拆成不同的几棵树

3.树和森林的遍历

1.树的先根遍历

若树非空,先访问根节点,再依此对每棵子树进行先根遍历

树的先根遍历序列与这棵树相应二叉树的先序序列相同

2.树的后根遍历

若树非空,先依此对每棵子树进行后根遍历,再访问其根节点。

树的后根遍历序列与这棵树相应二叉树的中序序列相同

3.树的层次遍历

①若树非空,则根节点入队

②若队列非空,队头元素出队并访问,同时将该元素的孩子依此入队

③重复②直到队列为空

4.森林的先序遍历

若森林非空:

①访问森林中第一棵树的根节点

②先序遍历第一棵树中根节点的子树森林

③先序遍历除去第一棵树之后剩余的树构成的森林

效果等同于依此对各个树进行先根遍历

5.森林的中序遍历

若森林非空:

①中序遍历森林中第一棵树的根节点的子树森林

②访问第一棵树中根节点

③中序遍历除去第一棵树之后剩余的树构成的森林

效果等同于依此对各个树进行后根遍历

内容总结:

森林 二叉树
先根遍历 先序遍历 先序遍历
后根遍历 中序遍历 中序遍历

五、树与二叉树的应用

1.哈夫曼树

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

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

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

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

2.哈夫曼编码

固定长度编码:每个字符用相等长度的二进制位表示。

可变长度编码:允许对不同字符用不等长的二进制位表示,若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码

哈夫曼编码:字符集中每个字符作为一个叶子节点,各个字符出现的频度作为节点权值,根据创建的哈夫曼树确定各字符的哈夫曼编码。

3.并查集

用互不相交的树表示多个“集合”,存储方法为双亲表示法

:从指定元素开始查询其所在树的根节点,比对确定两个元素是否在同一集合内。

:将一棵树成为另一棵树的子树,即利用指针将根节点直接挂到另一棵树的根节点上。

#define SIZE 13int UFSets[SIZE];    //集合元素数组//初始化并查集
void Initial(int S[])
{for(int i=0; i<SIZE; i++)S[i] = -1;
}//Find————查操作,找到x所属集合    最坏时间复杂度:O(h)
int Find(int S[], int x)
{while(S[x]>=0){x = S[x];    //循环找x的根}return x;
}//Union————并操作,将两个集合合并   时间复杂度:O(1)  全部合并:O(n^2)
void Union(int S[], int Root1, int Root2)
{if(Root1 == Root2)  return;S[Root2] = Root1;
}

Union优化思路:每次Union操作创建树时尽可能不让树变高

①用根节点绝对值表示树的节点总数

②Union操作让小树合并到大树

优化后Find操作最坏时间复杂度为:O(log2(n)),树高不超过[log2(n)]+1

//Union————并操作,小树合并到大树   时间复杂度:O(1)  全部合并:O(nlog2(n))
void Union(int S[], int Root1, int Root2)
{if(Root1 == Root2)  return;if(S[Root2]>S[Root1]){S[Root1] += S[Root2];S[Root2] = Root1;}else{S[Root2] += S[Root1];S[Root1] = Root2;}
}

Find优化思路压缩路径,先找到根节点,再将查找路径上所有节点都挂到根节点下。

优化后Union操作整体时间复杂度:O(na[n])

//Find————查操作,先找到根节点再压缩路径    最坏时间复杂度:O(a(n))   a(n)增长缓慢
int Find(int S[], int x)
{int root = x;while(S[root]>=0){root = S[root];    //循环找根}while(x!=root)         //压缩路径{int t=S[x];S[x] = root;x = t;}return root;
}

核心思想:让树变矮

六、课后习题(持续更新中~)

408数据结构考研笔记——第五章树与二叉树(重点)相关推荐

  1. 408数据结构考研笔记——第七章查找(命题重点)

    考点:折半查找的过程.构造判定树.分析平均查找时间             二叉排序树.平衡二叉树和红黑树了解概念.性质和相关操作             B树--插入删除和查找操作:B+树了解基本概 ...

  2. 【数据结构总结】第五章 树和二叉树(非线性结构)

    第五章 树和二叉树(非线性结构) 提示:本文主要是以思维导图的形式概括数据结构第一章的精华内容,基本不会用到文字性的内容,目的是为了给大家梳理每个重要的知识点的相关概念,方便大家在复盘的时候快速阅读和 ...

  3. 考研数据结构笔记——第五章 树和二叉树

    文章目录: 一:二叉树 1.性质 2.二叉树的存储结构 2.1 二叉树顺序存储 2.2 二叉树链式存储

  4. 数据结构(C语言第2版) 课后习题答案之第五章 树和二叉树

    目录 第5章  树和二叉树 一.选择题 二.应用题 (1)试找出满足下列条件的二叉树 (2)设一棵二叉树的先序序列: A B D F C E G H ,中序序列: B F D A G E H C (3 ...

  5. 王道数据结构课代表 - 考研数据结构 第五章 树和二叉树 究极精华总结笔记

    本篇博客是考研期间学习王道课程 传送门 的笔记,以及一整年里对数据结构知识点的理解的总结.希望对新一届的计算机考研人提供帮助!!!   关于对 树和二叉树 章节知识点总结的十分全面,涵括了<王道 ...

  6. 王道408数据结构——第五章 树与二叉树

    文章目录 一.树的基本概念 树的性质 二.二叉树 满二叉树 完全二叉树 二叉排序树 平衡二叉树 二叉树的性质 完全二叉树的性质 三.二叉树的储存结构 顺序储存 链式存储 四.树的储存方式 双亲表示法 ...

  7. C语言数据结构【手抄版】第五章 树和二叉树【上篇】

    注意:文中彩色代码均在Visual Studio 2022编译器中编写,本文为C语言数据结构手抄版,文中有部分改动,非原创. 目录 5.1.树的基本概念和术语 1.树的定义 2.树的表示法 3.基本术 ...

  8. C语言数据结构【手抄版】第五章 树和二叉树【尾篇】二叉树C实现

    封装二叉树算法 字符串工具,规避了了数字类型字符串比较不准确的BUG,string.h: #pragma once #ifdef __cplusplus extern "C" { ...

  9. 天勤数据结构笔记——第六章 树与二叉树(代码)

    二叉树的链式存储结构 typedef struct BTNode{char data;struct BTNode *lchild;struct BTNode *rchild; }BTNode; 先序遍 ...

最新文章

  1. mac微软雅黑字体_【字体字重】常见设计稿字体对应字重
  2. 文件包含漏洞检测工具fimap
  3. 机器学习案例学习【每周一例】之 Titanic: Machine Learning from Disaster
  4. 027_jdbc-mysql几个常用的日期类型
  5. long转string mybatis_Spring+Mybatis类型转换的问题,oracle数据库中有一个clob类型,怎样在查询以后转换为String类型?...
  6. 移动安全-iOS(一)
  7. Android滑动到顶部悬停
  8. BCD码干什么用的?
  9. vb ftp linux,一个功能比较完整VB FTP程序+源码
  10. 快速批量执行redis命令
  11. 第一期:GIS基本原理与arcgis软件
  12. caxa明细表导入零件材质_caxa中如何使明细表风格应用到任一个图纸中
  13. VBA代码片之计算加权平均分
  14. 第二十七篇 -- 学习第四十五天打卡20190810
  15. cursor游标讲解
  16. 如何选择一台适合个人使用的云服务器?
  17. 记录手机root后安装xposed框架后变砖无法开机的问题
  18. 3dmax模型单位转换问题解决方法
  19. oracle,MyIbats sql [17004] 无效的列类型
  20. electron项目打包报错

热门文章

  1. 移动互联网的营销新策略
  2. centos8 yum安装提示All mirrors were tried
  3. 黑叔推荐:6个上班摸鱼的网址!
  4. 关于Tensorflow用于深度神经网络训练中的一些问题
  5. ubuntu 18.04安装QGIS (2.18/3.16)
  6. [菜鸟向]使用ZeroTier实现内网穿透,校外连接实验室服务器
  7. 现代大学英语精读第二版(第一册)学习笔记(原文及全文翻译)——13A - The Greatest Invention(最伟大的发明)
  8. 输入两个字符串,从第一个字符串中删除第二个字符串中的字符
  9. 我的2013——青春的躁动
  10. 《游戏学习》Java版仿windows扫雷小游戏源码