感谢每一个能看到最后的过路人,衷心感谢。~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在介绍树的概念之前,先来看一下树的四种遍历方式以此更直观了解树
void InorderTraversal( BinTree BT ) { if( BT ) { InorderTraversal( BT->Left ); /* 此处假设对BT结点的访问就是打印数据 */ printf("%d ", BT->Data); /* 假设数据为整型 */ InorderTraversal( BT->Right ); } }//中序遍历 void PreorderTraversal( BinTree BT ) { if( BT ) { printf("%d ", BT->Data ); PreorderTraversal( BT->Left ); PreorderTraversal( BT->Right ); } }//先序遍历 void PostorderTraversal( BinTree BT ) { if( BT ) { PostorderTraversal( BT->Left ); PostorderTraversal( BT->Right ); printf("%d ", BT->Data); } }//后序遍历 void LevelorderTraversal ( BinTree BT ) { Queue Q; BinTree T; if ( !BT ) return; /* 若是空树则直接返回 */ Q = CreatQueue(); /* 创建空队列Q */ AddQ( Q, BT ); while ( !IsEmpty(Q) ) { T = DeleteQ( Q ); printf("%d ", T->Data); /* 访问取出队列的结点 */ if ( T->Left ) AddQ( Q, T->Left ); if ( T->Right ) AddQ( Q, T->Right ); } }//层次遍历
那到底什么是树呢到底
在对树有一个直观的了解后我们再来了解下树的几个定义以及基本术语
为了方便在计算机中表示出树以及应用,我们引入二叉树的概念,
在介绍二叉树的之前呢,我们也一样先直观地看一下二叉树的代码构成
typedef struct TNode *Position; typedef Position BinTree; /* 二叉树类型 */ struct TNode{ /* 树结点定义 */ ElementType Data; /* 结点数据 */ BinTree Left; /* 指向左子树 */ BinTree Right; /* 指向右子树 */ };
为何要引入二叉树,优势在哪,结构又是怎样,我们下面慢慢来揭晓
具体存储结构如下
从这里我们可以看到二叉树可以以链表的形式把树给串到一块。
同时节点的子父关系我们也可以用指针把它们给联系起来。
再来看一下二叉树的详细定义
在介绍完二叉树之后,我们可以接着来学二叉搜索树了
那我们要用二叉搜索树来实现哪些操作呢
看完这些概括,下面我们来具体看一下这些操作具体是怎么来实现的吧
先来看一下查找吧
具体的代码实现如下,尾递归和迭代
我们再来看看实际操作中如何来查找二叉树的最大值和最小值吧
先来看看最大值和最小值在树中充当着一个怎样的角色
具体操作的迭代与递归算法
接下来我们再来认识一个新的概念-完全二叉树
在了解二叉树的几个特点之后,我们再来认识一下二叉树的一些性质
在前面的随笔中,我们有谈到抽象数据类型,接下来
我们看看二叉树的抽象数据类型是怎样的吧
这其实就是我们一开头看到的几种遍历,我们再来看一下具体的实现过程吧
学完各种遍历,我们再来看几个二叉树遍历的实例
接下来我们再来学习一下二叉树的存储结构
一般的二叉树也可以转换成完全二叉树的,如下图所示
但是直接转会造成很多空间上的浪费以至于降低效率
我们再来看一个完整过程的利用二叉树解决问题案例-树的同构问题
我们该怎么来解读这道题呢
看完输入样例,我们很自然而然地会思考这个二叉树到底该怎么搭建呢
然后我们下一步就要开始分析整个程序框架的搭建了
具体的实现如下
最后按照逻辑把这些整理到下面就大功告成了
怎么样,对树的初步了解感觉如何。
了解完树的概念和最基本的操作后
接下来,我们会对树更进一步的深入认识
---------------------------------------------------------------------------------------------------------------------------------------------------------
下面我们就来介绍二叉搜索树
那到底什么是二叉搜索树呢
二叉搜索树的操作主要包括查找、插入和删除,查找是后两个的基础
我们先来看一下代码的直观实现
BinTree Insert( BinTree BST, ElementType X ) { if( !BST ){ /* 若原树为空,生成并返回一个结点的二叉搜索树 */ BST = (BinTree)malloc(sizeof(struct TNode)); BST->Data = X; BST->Left = BST->Right = NULL; } else { /* 开始找要插入元素的位置 */ if( X < BST->Data ) BST->Left = Insert( BST->Left, X ); /*递归插入左子树*/ else if( X > BST->Data ) BST->Right = Insert( BST->Right, X ); /*递归插入右子树*/ /* else X已经存在,什么都不做 */ } return BST; } BinTree Delete( BinTree BST, ElementType X ) { Position Tmp; if( !BST ) printf("要删除的元素未找到"); else { if( X < BST->Data ) BST->Left = Delete( BST->Left, X ); /* 从左子树递归删除 */ else if( X > BST->Data ) BST->Right = Delete( BST->Right, X ); /* 从右子树递归删除 */ else { /* BST就是要删除的结点 */ /* 如果被删除结点有左右两个子结点 */ if( BST->Left && BST->Right ) { /* 从右子树中找最小的元素填充删除结点 */ Tmp = FindMin( BST->Right ); BST->Data = Tmp->Data; /* 从右子树中删除最小元素 */ BST->Right = Delete( BST->Right, BST->Data ); } else { /* 被删除结点有一个或无子结点 */ Tmp = BST; if( !BST->Left ) /* 只有右孩子或无子结点 */ BST = BST->Right; else /* 只有左孩子 */ BST = BST->Left; free( Tmp ); } } } return BST; }
我们先一步一步拆解,查找作为后两个的基础当然至关重要
先来看看查找吧
查找我们可以分为静态查找和动态查找
静态查找我们又有顺序查找和二分查找两种实现方式
我们再来看看二分查找的例子
了解完二叉搜索树的遍历和查找操作,
我们再来看看它是怎么来实现插入和删除操作的吧
插入
删除
最后我们来看一下利用二叉搜索树
来解决判断是否同一搜索树的问题
typedef struct TreeNode *Tree; struct TreeNode{ int v; Tree Left,Right; int flag; }; Tree MakeTree(int N) { Tree T; int i,V; scanf(''%d'',&V); T=NewNode(V); for(i=1;i){ scanf(''%d'',&V); T=Insert(T,V); } return T; } Tree NewNode(int V) { Tree T=(Tree)malloc(sizeof(struct TreeNode)); T->v=V; T->Left=T->Right=NULL; T->flag=0; return T; } Tree Insert(Tree T,int V) { if(!T)T=NewNode(V); else{ if(V>T->v) T->Right=Insert(T->Right,V); else T->Left=Insert(T->Left,V); } return T; } int check(Tree T,intV) { if(T->flag){ if(V v)return check(T->Left,V); else if(V>T->v)return check(T->Right,V); else return 0; } else{ if(V==T->v){ T->flag=1; return 1; } else return 0; } } int Judge(Tree T,int N) { int i,V,flag=0; //flag:0代表目前还一致,1代表已经不一致 scanf(''%d'',&V); if(V!=T->v) flag=1; else T->flag=1; for(i=1;i ){ scanf(''%d'',&V); if((!flag)&&(!check(T,V)))flag=1; } if(flag) return 0; else return 1; } void ResetT(Tree T) //清楚T中各节点的flag标记 { if(T->Left) ResetT(T->Left); if(T->Right) ResetT(T->Right); T->flag=0; } void FreeTree(Tree T) //释放T的空间 { if(T->Left) FreeTree(T->Left); if(T->Right) FreeTree(T->Right); free(T); } int main() { int N,L,i; Tree T; scanf(''%d'',&N); while(N){ scanf(''%d'',&L); T=MakeTree(N); for(i=0;i ){ if(Judge(T,N))printf(''Yes\n''); else printf(''No\n''); ResetT(T); //清楚T中标记的flag } FreeTree(T); scanf(''%d'',&N); } return 0; }
到此二叉搜索树就告一段落啦,你收获了多少呢
-----------------------------------------------------------------------------------------------------------------
二叉搜索树之后,我们来学习下一个新概念-平衡二叉树
同样我们也是先来同过代码直观地了解下这个AVL平衡二叉树
typedef struct AVLNode *Position; typedef Position AVLTree; /* AVL树类型 */ struct AVLNode{ ElementType Data; /* 结点数据 */ AVLTree Left; /* 指向左子树 */ AVLTree Right; /* 指向右子树 */ int Height; /* 树高 */ }; int Max ( int a, int b ) { return a > b ? a : b; } AVLTree SingleLeftRotation ( AVLTree A ) { /* 注意:A必须有一个左子结点B */ /* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */ AVLTree B = A->Left; A->Left = B->Right; B->Right = A; A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1; B->Height = Max( GetHeight(B->Left), A->Height ) + 1; return B; } AVLTree DoubleLeftRightRotation ( AVLTree A ) { /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */ /* 将A、B与C做两次单旋,返回新的根结点C */ /* 将B与C做右单旋,C被返回 */ A->Left = SingleRightRotation(A->Left); /* 将A与C做左单旋,C被返回 */ return SingleLeftRotation(A); } /*************************************/ /* 对称的右单旋与右-左双旋请自己实现 */ /*************************************/ AVLTree Insert( AVLTree T, ElementType X ) { /* 将X插入AVL树T中,并且返回调整后的AVL树 */ if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */ T = (AVLTree)malloc(sizeof(struct AVLNode)); T->Data = X; T->Height = 0; T->Left = T->Right = NULL; } /* if (插入空树) 结束 */ else if ( X < T->Data ) { /* 插入T的左子树 */ T->Left = Insert( T->Left, X); /* 如果需要左旋 */ if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 ) if ( X < T->Left->Data ) T = SingleLeftRotation(T); /* 左单旋 */ else T = DoubleLeftRightRotation(T); /* 左-右双旋 */ } /* else if (插入左子树) 结束 */ else if ( X > T->Data ) { /* 插入T的右子树 */ T->Right = Insert( T->Right, X ); /* 如果需要右旋 */ if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 ) if ( X > T->Right->Data ) T = SingleRightRotation(T); /* 右单旋 */ else T = DoubleRightLeftRotation(T); /* 右-左双旋 */ } /* else if (插入右子树) 结束 */ /* else X == T->Data,无须插入 */ /* 别忘了更新树高 */ T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1; return T; }
我们先通过一个例子来演示一下
那到底什么是平衡二叉树呢,又改怎么判断是还是不是呢
思考一个问题:平衡二叉树的高度能达到logN吗
(ps:利用斐波那契序列)
平衡二叉树的调整(RR旋转为例)和插入操作的具体步骤如下
主要通过旋转来进行平衡二叉树的调整
旋转又可以分为 LL旋转 RR旋转 LR旋转 RL旋转 四种类型
最后平衡二叉树还要注意平衡因子这个点
至此,平衡二叉树我们也告一段落了
-------------------------------------------------------------------------
接下来我们介绍一下堆,不过这可不是我们之前所说的堆栈里的堆
为什么这么说呢,我们先来引入几个概念
一般我们会怎样来实现队列的优先级的呢,下面列举一些常见的方法
还记不记得我们之前稍微提过一下的完全二叉树的概念了,现在就派上用场了
好啦好啦,到目前为止我们可算引入堆的概念了(铺垫好像稍稍有点略长 噗)
我们再来认识下最大堆和最小堆吧(最大和最小永远是对难兄难弟呢)
数据结构我们都鼓励用抽象数据类型来表示的
堆当然也不会例外 具体如下
还是老样子,我们先构思以及搭建起框架
具体代码实现起来如下