常用数据逻辑结构:
集合——数据元素间除“同属于一个集合”外,无其它关系
线性结构——一个对一个,如线性表、栈、队列
树形结构——一个对多个,如树
图形结构——多个对多个,如图
树的定义:
树(tree)是由n(n≥0)个结点组成的有限集合T。n=0的树称为空树;对n>0的树,有: (1)仅有一个特殊的结点称为根(root)结点,根结点没有前驱结点; (2)当n>1时,除根结点外其余的结点分为m(m>0)个互不相交的有限集合T1,T2,…,Tm,其中每个集合Ti本身又是一棵树,称之为根的子树( subtree)。
结点(node):由数据元素和构造数据元素之间关系的指针组成
结点的度:结点所拥有的子树的个数
树的度:树中所有结点的度的最大值
叶结点:度为0的结点,也称作终端结点
分支结点:度不为0的结点,除叶结点之外的其余结点。
结点的层次:从根结点到树中某结点所经路径上的分支数
规定树中根结点的层次为1,其余结点的层次等于其双亲结点的层次加1。 若某结点在第l(l≧1)层,则其子结点在第l+1层。
树的深度:树中所有结点的层次的最大值
森林:m(m≥0)棵树的集合
从根结点开始,到达某结点p所经过的所有结点成为结点p的层次路径(有且只有一条)。 结点p的层次路径上的所有结点(p除外)称为p的祖先(ancester) 。 以某一结点为根的子树中的任意结点称为该结点的子孙结点(descent)。
孩子(child)结点:树中一个结点的子树的根结点 双亲(parent)结点:若树中某结点有孩子结点,则这个结点就称作它的孩子结点的双亲结点 兄弟(sibling)结点:具有相同的双亲结点的结点
无序树:树中任意一个结点的各孩子结点之间的次序构成 无关紧要的树 有序树:树中任意一个结点的各孩子结点有严格排列次序的树
二叉树的性质:
性质1: 在二叉树的第i层上至多有2i-1个结点;第i层上至少有 1 个结点
性质2: 深度为k的二叉树至多有2k-1个结点;深度为k时至少有 k 个结点
性质3:对于任何一棵二叉树,若2度的结点数有n2个,则叶子数n0必定为n2+1 (即n0=n2+1)
满二叉树:一棵深度为k 且有2k -1个结点的二叉树。(特点:每层都“充满”了结点);如
完全二叉树:深度为k 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k 的满二叉树中编号从1至n的结点一一对应
满二叉树的特点: ◆ 基本特点是每一层上的结点数总是最大结点数。 ◆ 满二叉树的所有的支结点都有左、右子树。 ◆ 可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行。 完全二叉树(Complete Binary Tree):如果深度为k,由n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应,该二叉树称为完全二叉树。 或深度为k的满二叉树中编号从1到n的前n个结点构成了一棵深度为k的完全二叉树。 其中 2k-1 ≦ n≦2k-1 。
完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例。 完全二叉树的特点: 若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树的最大层次为l,则其左子树的最大层次为l或l+1。
性质4: 具有n个结点的完全二叉树的深度必为log2n+1
性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2。
二叉链表:
typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild; //左右孩子指针
}BiNode,*BiTree;
在n个结点的二叉链表中,有 n+1 个空指针域
分析:必有2n个链域。除根结点外,每个结点有且仅有一个双亲,所以只会有n-1个结点的链域存放指针,指向非空子女结点。空指针数目=2n-(n-1)=n+1
三叉链表:
typedef struct TriTNode{
TelemType data;
struct TriTNode *lchild,*parent,*rchild;
}TriTNode,*TriTree;
遍历:顺着某一条搜索路径巡访二叉树中的结点,使 得每个结点均被访问一次,而且仅被访问一次
若规定先左后右,则只有前三种情况: DLR —— 先(根)序遍历, LDR —— 中(根)序遍历, LRD —— 后(根)序遍历。
先序遍历二叉树的操作定义: 若二叉树为空,则空操作;否则 (1) 访问根结点; (2) 先序遍历左子树; (3) 先序遍历右子树。
中序遍历二叉树的操作定义: 若二叉树为空,则空操作;否则 (1) 中序遍历左子树; (2) 访问根结点; (3) 中序遍历右子树。
后序遍历二叉树的操作定义: 若二叉树为空,则空操作;否则 (1) 后序遍历左子树; (2) 后序遍历右子树; (3) 访问根结点。
1.先序遍历二叉树:
递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 ⑴ 访问根结点; ⑵ 先序遍历左子树(递归调用本算法); ⑶ 先序遍历右子树(递归调用本算法)。
void PreorderTraverse(BTNode *T)
{ if (T!=NULL)
{ visit(T->data) ; /* 访问根结点 */
PreorderTraverse(T->Lchild) ;
PreorderTraverse(T->Rchild) ;
}
}
非递归算法 设T是指向二叉树根结点的指针变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T; ⑴ 访问p所指向的结点; ⑵ q=p->Rchild ,若q不为空,则q进栈; ⑶ p=p->Lchild ,若p不为空,转(1),否则转(4); ⑷ 退栈到p ,转(1),直到栈空为止。
#define MAX_NODE 50
void PreorderTraverse( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T, *q ;
int top=0 ;
if (T==NULL) printf(“ Binary Tree is Empty!\n”) ;
else { do
{ visit( p-> data ) ; q=p->Rchild ;
if ( q!=NULL ) stack[++top]=q ;
p=p->Lchild ;
if (p==NULL) { p=stack[top] ; top-- ; }
}
while (p!=NULL) ;
}
}
中序遍历:递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 ⑴ 中序遍历左子树(递归调用本算法); ⑵ 访问根结点; ⑶ 中序遍历右子树(递归调用本算法)。
中序遍历的递归算法
void InorderTraverse(BTNode *T)
{ if (T!=NULL)
{ InorderTraverse(T->Lchild) ;
visit(T->data) ; /* 访问根结点 */
InorderTraverse(T->Rchild) ;
}
}
非递归算法 设T是指向二叉树根结点的指针变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T ⑴ 若p不为空,p进栈, p=p->Lchild ; ⑵ 否则(即p为空),退栈到p,访问p所指向的结点; ⑶ p=p->Rchild ,转(1); 直到栈空为止。
#define MAX_NODE 50
void InorderTraverse( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T ;
int top=0 , bool=1 ;
if (T==NULL) printf(“ Binary Tree is Empty!\n”) ;
else { do
{ while (p!=NULL)
{ stack[++top]=p ; p=p->Lchild ; }
if (top==0) bool=0 ;
else { p=stack[top] ; top-- ;
visit( p->data ) ; p=p->Rchild ; }
} while (bool!=0) ;
}
}
后序遍历:
递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 ⑴ 后序遍历左子树(递归调用本算法); ⑵ 后序遍历右子树(递归调用本算法) ; ⑶ 访问根结点 。
void PostorderTraverse(BTNode *T)
{ if (T!=NULL)
{ PostorderTraverse(T->Lchild) ;
PostorderTraverse(T->Rchild) ;
visit(T->data) ; /* 访问根结点 */
}
}
非递归算法 在后序遍历中,根结点是最后被访问的。因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问,而要先遍历其左子树,此时根结点进栈。当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到到该根结点时,才能被访问。 因此,设立一个状态标志变量tag :
其次,设两个堆栈S1、S2 ,S1保存结点,S2保存结点的状态标志变量tag 。S1和S2共用一个栈顶指针。 设T是指向根结点的指针变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T; ⑴ 第一次经过根结点p,不访问: p进栈S1 , tag 赋值0,进栈S2,p=p->Lchild 。 ⑵ 若p不为空,转(1),否则,取状态标志值tag : ⑶ 若tag=0:对栈S1,不访问,不出栈;修改S2栈顶元素值(tag赋值1) ,取S1栈顶元素的右子树,即p=S1[top]->Rchild ,转(1); ⑷ 若tag=1:S1退栈,访问该结点; 直到栈空为止。
#define MAX_NODE 50
void PostorderTraverse( BTNode *T)
{ BTNode *S1[MAX_NODE] ,*p=T ;
int S2[MAX_NODE] , top=0 , bool=1 ;
if (T==NULL) printf(“Binary Tree is Empty!\n”) ;
else { do
{ while (p!=NULL)
{ S1[++top]=p ; S2[top]=0 ;
p=p->Lchild ;
}
if (top==0) bool=0 ;
else if (S2[top]==0)
{ p=S1[top]->Rchild ; S2[top]=1 ; }
else
{ p=S1[top] ; top-- ;
visit( p->data ) ; p=NULL ;
/* 使循环继续进行而不至于死循环 */ }
} while (bool!=0) ;
}
}
层次遍历二叉树,是从根结点开始遍历,按层次次序“自上而下,从左至右”访问树中的各结点。 为保证是按层次遍历,必须设置一个队列,初始化时为空。 设T是指向根结点的指针变量,层次遍历非递归算法是: 若二叉树为空,则返回;否则,令p=T,p入队; ⑴ 队首元素出队到p; ⑵访问p所指向的结点; ⑶将p所指向的结点的左、右子结点依次入队。直到队空为止
#define MAX_NODE 50
void LevelorderTraverse( BTNode *T)
{ BTNode *Queue[MAX_NODE] ,*p=T ;
int front=0 , rear=0 ;
if (p!=NULL)
{ Queue[++rear]=p; /* 根结点入队 */
while (front<rear)
{ p=Queue[++front]; visit( p->data );
if (p->Lchild!=NULL)
Queue[++rear]=p; /* 左结点入队 */
if (p->Rchild!=NULL)
Queue[++rear]=p; /* 左结点入队 */
}
}
}
#define MAX_NODE 50
typedef struct BTNode
{ char data ;
struct BTNode *Lchild , *Rchild ;
}BTNode ;
BTNode *Create_BTree(void)
/* 建立链式二叉树,返回指向根结点的指针变量 */
{ BTNode *T , *p , *s[MAX_NODE] ;
char ch ; int i , j ;
while (1)
{ scanf(“%d”, &i) ;
if (i==0) break ; /* 以编号0作为输入结束 */
else
{ ch=getchar() ;
p=(BTNode *)malloc(sizeof(BTNode)) ;
p–>data=ch ;
p–>Lchild=p–>Rchild=NULL ; s[i]=p ;
if (i==1) T=p ;
else
{ j=i/2 ; /* j是i的双亲结点编号 */
if (i%2==0) s[j]->Lchild=p ;
else s[j]->Rchild=p ;
}
}
}
return(T) ;
}
按先序遍历方式建立 对一棵二叉树进行“扩充”,就可以得到有该二叉树所扩充的二叉树。有两棵二叉树T1及其扩充的二叉树T2如图所示。
算法:二叉树的扩充方法是:在二叉树中结点的每一个空链域处增加一个扩充的结点(总是叶子结点,用方框“□”表示)。对于二叉树的结点值: ◆ 是char类型:扩充结点值为“?”; ◆ 是int类型:扩充结点值为0或-1; 下面的算法是二叉树的前序创建的递归算法,读入一棵二叉树对应的扩充二叉树的前序遍历的结点值序列。每读入一个结点值就进行分析: ◆ 若是扩充结点值:令根指针为NULL; ◆ 若是(正常)结点值:动态地为根指针分配一个结点,将该值赋给根结点,然后递归地创建根的左子树和右子树。
#define NULLKY ‘?’
#define MAX_NODE 50
typedef struct BTNode
{ char data ;
struct BTNode *Lchild , *Rchild ;
}BTNode ;
BTNode *Preorder_Create_BTree(BTNode *T)
/* 建立链式二叉树,返回指向根结点的指针变量 */
{ char ch ;
ch=getchar() ; getchar();
if (ch==NULLKY)
{ T=NULL; return(T) ; }
else
{ T=(BTNode *)malloc(sizeof(BTNode)) ;
T–>data=ch ;
Preorder_Create_BTree(T->Lchild) ;
Preorder_Create_BTree(T->Rchild) ;
return(T) ;
}
}
求二叉树的叶子结点数 可以直接利用先序遍历二叉树算法求二叉树的叶子结点数。只要将先序遍历二叉树算法中vist()函数简单地进行修改就可以。
算法实现:
#define MAX_NODE 50
int search_leaves( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T;
int top=0, num=0;
if (T!=NULL)
{ stack[++top]=p ;
while (top>0)
{ p=stack[top--] ;
if (p->Lchild==NULL&&p->Rchild==NULL) num++ ;
if (p->Rchild!=NULL )
stack[++top]=p->Rchild;
if (p->Lchild!=NULL )
stack[++top]=p->Lchild;
}
}
return(num) ;
}
求树的深度:
#define MAX_NODE 50
int search_depth( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T;
int front=0 , rear=0, depth=0, level ;
/* level总是指向访问层的最后一个结点在队列的位置 */
if (T!=NULL)
{ Queue[++rear]=p; /* 根结点入队 */
level=rear ; /* 根是第1层的最后一个节点 */
while (front<rear)
{ p=Queue[++front];
if (p->Lchild!=NULL)
Queue[++rear]=p; /* 左结点入队 */
if (p->Rchild!=NULL)
Queue[++rear]=p; /* 左结点入队 */
if (front==level)
/* 正访问的是当前层的最后一个结点 */
{ depth++ ; level=rear ; }
}
}
}