树及其表示方法
查找的分类
静态查找:在固定的集合中,只涉及查找操作,不涉及其他的添加,删除操作。
动态查找:在动态可变的数组中,涉及查找,插入,删除操作。
静态查找实现
//在表T[1]->T[n]中查找关键字为K的元素
//确定返回值为检查到的元素的位置(为int 类型)
int Search(int List [],int K,int N)
{
for(int i =0;i<N;i++)
{
return List[i]==k? i:NULL;
}
}
//通过设置哨兵的方式
//通过返回的位置是0或者非0来判断能否该表中是否有要查找的数值
int Search(StaticTable *TBL,int K)
{
//设置哨兵,如果检测到哨兵位置而返回哨兵的位置的数值,那么证明所查序列中没有K关键值
TBL->Element[0] = K;
//如果TBL的Element[i]指向的元素一直不是k的话循环执行for,值到满足循环为止,退出循环,最后输出i的数值为K关键字所在静态链表的位置
for(int i = TBL->Length;TBL->Element[i]!=K;i--);
return i
}
执行二分查找其N个元素必须满足有序排列:比如有小到大,连续存放
二分查找算法思路描述:首先就是将我们要查找的序列缩小为原来的1/2,然后将中间数值与目标数值作比较,如果小于中间数值,那么后1/2序列删除,说明关键子所在的位置可能在前面的1/2序列中,也可能在本序列中不存在这个关键字,两种情况。算法复杂度减少了很多logN
//执行二分查找(折半查找,效率高)
int BinarySearch(StaticTable *TBL,int K)
{
int start = 1;//切记静态表结构第一个元素我们规定下标首个数字是1
int last =TBL->Length;
while(start<=last)
{
int mid = (start+last)/2;
if (TBL->Element[mid]>K)//关键字小于链表中间数值的情况,去除后面数组,继续查找前半部分
{
right = mid-1;
}
else if(TBL->Element<K)//关键字大于链表中间数值的情况,去除后面数组,继续查找前半部分
{
left = mid+1;
}
else
{
return mid;//关键字等于链表中间数值的情况,直接返回
}
}
else//未查找到
{
return NULL;
}
}
树的定义
树: n(n≥0)个结点构成的有限集合。n=0表示为空树,n>0表示非空树。
根:每棵树都有一个根。用“R”表示。
树是每个其余结点不相互连接的有限集合。然后每个几何本身又是一棵子树。
子树互不相交,每一棵子树都有一个父结点;N个结点有N-1条边。
树的术语
- 结点的度:就是该结点直接相连的子树的个数。
- 树的度:该树结构中结点连接的的最大子树的个数。
- 叶结点:没有子树的结点(度为0的结点)。
- 父结点:有子树的结点是其子树的父结点。
- 子结点:A是结点那么A直接相连的就是A的子结点。A是B的父结点,B是A的子结点。(孩子结点)
- 兄弟结点:具有相同父结点的各个结点。
- 路径和路径长度:路径是由父结点到子结点的路程;路径长度:从父结点到子结点的路径中所经过的结点的个数。
- 祖先结点:沿树根到某一结点的路径上所经过的所有结点都是该结点的祖先结点。
- 子孙结点:某一结点的子树中的所有结点都是该结点的子孙结点。
- 结点的层次:规定根结点所在的层数为1层其他任意结点的层数都是该结点所对应的父结点的层数+1。
- 树的深度:结点所在的最大层次。
结点表示法中: :其中孩子和兄弟对应的域都是指针域。
二叉树及存储结构
二叉树T:一个有穷的结点的集合。这个集合可以为空,若为非空,那么二叉树由该结点以及其左子树Tl和右子树Tr的两个相互不相交的二叉树构成。
特殊二叉树:
- 完美二叉树:所有结点除了子结点以外,所有结点都有它的左子树以及右子树。(满二叉树)
- 完全二叉树:有n个结点的二叉树,对树中结点按照从上到下,从左到右的顺序编号,编号为i的结点与满二叉树的结点的编号相同,但是可以存在最后一层的最后一个结点树不满的情况(叶结点除外)。
二叉树的性质:
- 一个二叉树的第i层的最大的节点数为2(i-1)。
- 深度为K的二叉树的最大结点的总数:2k-1
- 对于任何非空的二叉树,若n0表示叶结点的个数,n2是度为2的非叶子结点,那么二者满足的关系:n0+n2=2n2+1=>n0=n2+1;
二叉树的操作
数据对象:一个有穷的结点的集合。由根结点,及其左右的子二叉树组成。
操作集合:
- 创建二叉树
BinaryTree CreatBinaryTree();
- 判断二叉树是否为空:
Boolean IsEmpty(BinaryTree BT);
- 遍历,按照某顺序访问每个结点:
void Traversal(BinaryTree BT)
常用的遍历方法:
先序遍历:从根结点访问左结点最后访问右结点的顺序。void PreOderTraversal(BinaryTree BT)
中序遍历:从左结点再访问根结点最后访问右结点的顺序。void InOderTraversal(BinaryTree BT)
后序遍历:从左结点访问右结点最后访问根结点的顺序。void PostOderTraversal(BinaryTree BT)
层次遍历:“从上到下”,“从左到右”依次序进行遍历。void LevelOderTraversal(BinaryTree BT)
二叉树的存储结构
顺序存储
完全二叉树:从上到下,从左到右顺序存储。
n个结点的完全二叉树的父子结点的关系:
- 非根结点(i):那么它的父结点的序号为i/2。最后会取整。
- 结点(序号为i):那么它的左孩子的结点的位置是:2i;右孩子的位置2i+1;
链表存储二叉树:
//定义二叉树存储结点的结构
typedef struct TreeNode *BinTree;//此时BinTree代表一个指针
typedef BinTree positionl
struct TreeNode
{
ElementType Data;
BinTree Left;
BinTree Right;
}
二叉树的遍历
先序遍历
遍历过程:
- 访问根结点
- 先序遍历访问左子树
- 后序遍历访问右子树
void PreOrderTraversal(BinTree BT)
{
//首先判断二叉树究竟是否为空
if(BT)
{
printf(BT->Data);//根结点的数值
PreOrderTraversal(BT->left);
PreOrderTraversal(BT->right);
}
}
中序遍历
遍历过程:
4. 先访问根结点的左子树
5. 在访问根结点
6. 最后访问根结点的右子树
void InOrderTraversal(BinTree BT)
{
if(BT)
{
InOrderTraversal(BT->left);
printf(BT->Data);
PreOrderTraversal(BT->right);
}
}
后序遍历
遍历过程:
7. 先访问根结点的左子树
8. 再访问根结点的右子树
9. 最后在访问根结点
void PostOrderTraversal(BinTree BT)
{
if(BT)
{
InOrderTraversal(BT->left);
PreOrderTraversal(BT->right);
printf(BT->Data);
}
}
二叉树非递归遍历
中序遍历非递归遍历算法
我们采用的基本思路是:使用堆栈
自我思想默背描述:遇到一个根结点,我们将该结点压入栈,然后不断访问它的左子树,直到左子树达到叶子节点,开始将左子树的数据读出(弹出栈),然后将该根节点的数据弹出栈(读出),然后再访问它的右子树。达到中序遍历的非递归目的
官方算法(数据结构浙江大学):遇到一个结点,就把它压入栈,并且去遍历它的左子树;当左子树遍历结束的时候,从栈顶弹出这个结点并访问它;然后再按照中序遍历去访问该结点的右子树
void InOrderTraversal(BinTree BT)
{
BinTree T =BT;
Stack s = CreatStack(MaxSize);//创建初始栈来存放压入栈的结点
//树不空或者栈不空的时候,我们执行
while(T||!IsEmpty(s))
{
while (T)
{
Push(s,T);//将当前结点(父结点)压入栈
T=T->left;//持续访问它的左子树
}
while(!IsEmpty(s))//到达最左侧叶子结点跳出上一个while,输出该结点
{
T = Pop(s,T);
printf(T);
T=T->right;//开始遍历右侧右子树
}
}
}
先序遍历非递归遍历算法
typedef BinaryTree Node *BinTree;
void PreOrderTraversal(BinTTree BT)
{
BinTree T =BT;
Stack s = CreatStack(MaxSize);//创建初始栈来存放压入栈的结点
while(T||!IsEmpty(s))//树不空
{
while(T)
{
printf("%5d",T->Data);//访问打印结点
push(s,T);
T=T->left;
}
if(!IsEmpty(s))
{
T=(struct TNode*)Pop(s);
T = T->right;
}
}
}
后序遍历非递归遍历算法
typedef BinaryTree Node *BinTree;
void PostOrderTraversal(BinTTree BT)
{
BinTree T =BT;
Stack s = CreatStack(MaxSize);//创建初始栈来存放压入栈的结点
BinTree pr = null;//用于记录上一次访问的结点
while(T||!IsEmpty(s))//树不空
{
while(T!=NULL)
{
Push(s,T);
T= T->left;
}
else(!IsEmpty(s))
{
T = Pop(s,t);
if(T->right == null||pre == T->right)
{
printf(T);
pre = T;
T = null;
}
else
{
Push(s,T);
T = T->right;
}
}
}
}
宝藏来源
已经明白
层序遍历
核心思想:队列实现遍历从根结点开始,然后执行循环,首先将根结点入队,根结点出队(访问),将其对应的左右结点入队。
层序遍历的基本步骤:
- 从队列中取出一个根结点
- 访问该根节点,输出
- 将该根节点的左右结点入队(在非空的条件下,先左结点入队,再右结点入队)
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
Q = CreatQueue(MaxSize);
BinTree T=BT;
Add(Q,T);
while(!IsEmpty(s))
{
if(!T->left || !T->right)
{
T=Delet(Q);
printf(T);
if(T->left) Add(T->left);
if(T->right) Add(T->right);
}
}
}
两种遍历确定二叉树必须包括中序遍历
例如先序和中序遍历确定一棵二叉树:
首先根据先序遍历第一个结点为根结点
根据根结点再中序遍历中分割出两个子序列
对左子树和右子树分别递归使用相同的方法继续分解