3.1 树与树的表示
查找元素的方法:
静态顺序查找 把要查找的元素放在数组的0号位置作为哨兵,当循环退出时,根据返回的值是否为零来判断是否找到要查找的元素。
二分查找 元素必须按顺序存储,每次都找中间即可。
typedef struct LNode *List;
struct LNode{
ElementType Data[MaxSize];
int length = MaxSize - 1;
};
二分查找的实现。
int BinarySearch(List Tbl, ElementType K){
int left, right, mid;
left = 1;
right =Tbl->length;
while(left <= right){
mid = (left + right)/2;
if(Tbl->Data[mid] > K)right = mid - 1;
else if(Tbl->Data[mid] < K)left = mid + 1;
else return mid;
}
return NotFound;
}
如上所示,二分查找可以在数组里面直接做,但是,数组里面添加或者删除数据的时候不方便,得一个一个挪动,这时候把数据按照查找树的方式组织起来,也是二分查找,添加和删除也很方便。这就是查找树的优点。
二分查找的方法形成了一个判定树,判定树上存的就是二分查找中的各个mid值(即数据集的全体元素),元素在树上的层数就是找到它需要查找到的次数。进而,我们可以把数据直接按照判定树的方式存起来,这样不但查找很方便,插入删除也很方便,这就是查找树。
二叉树的遍历和增删是树结构的核心内容,要搞懂这两部分。
树中的每个节点都有一条向上的边,所以一棵树有n个节点,那么它就有n-1条边。
树的表示: 无论是数组还是链表,直接存储一般树的时候都有困难,因而我们采用“儿子—兄弟”表示法,就是把所有的节点都设计成数据加两个指针,其中一个指针指向自己的儿子,另一个指针指向自己的下一个兄弟。\
用儿子—兄弟表示法表示出来的树每个节点都只有两个子树,这就是二叉树。
typedef struct TNode *Position;
struct TNode{
ElementType Data;
Position Left;
Position Right;
};
3.2 二叉树及存储结构
二叉树有两类常见的:完美二叉树(满二叉树)、完全二叉树(最后缺几个元素)
重要:n0=n2 + 1;往上看一共有n0+n1+n2-1条边,往下看有n1+2*n2条边。(n0表示没有儿子的节点)
类型名称: 二叉树
对象数据集: 有穷节点的集合。若不为空,由一个根节点和其左右二叉子树构成。
操作集:
Boolean IsEmpty(Position T);
void Traversal(Position T); //按照某种特定的顺序遍历
Position CreatTree(); //创建一个二叉树
其中遍历操作包括:先序遍历、中序遍历、后序遍历、层序遍历。
void PreOrderTraversal(Position T);
void InOrderTraversal(Position T);
void PostOrderTraversal(Position T);
void LevelOrderTraversal(Position T);
对二叉树做遍历操作之前要先解决二叉树如何存储:一是数组、二是链表
- 数组(会浪费一些空间,树比较满的时候比较合适):
二叉树存储可以使用数组,但是数组只能存储完全二叉树,对于非完全二叉树必须补全成完全二叉树,然后按照从上到下,从左到右的顺序编号存储,第i个节点的父节点是i/2(去掉小数部分),左儿子是2i,右儿子是2i+1。 - 链表
如果用链表,可以用统一的形式存储,比起非完全二叉树数组较为节省空间。
typedef struct TreeNode *BinTree
struct TreeNode{
ElementType Data;
BingTree Left;
BingTree Right;
};
3.3 二叉树的遍历
遍历分为递归方法和非递归方法,递归方法很好理解,主要是非递归方法。
递归方法(根据根节点访问的次序,对应的把那三句话调整顺序即可。):
- 先序遍历(先访问根,再访问左子树,再访问右子树)
void PreOrderTraversal(BinTree T){
if(T){
printf("%d",T->Data);
PreOrderTraversal(T->Left);
PreOrderTraversal(T->Right);
}
}
- 中序遍历(左,根,右)
void InOrderTraversal(BinTree T){
if(T){
InOrderTraversal(T->Left);
printf("%d",T->Data);
InOrderTraversal(T->Right);
}
}
- 后序遍历(左,右,根)
void PostOrderTraversal(BinTree T){
if(T){
PostOrderTraversal(T->Left);
PostOrderTraversal(T->Right);
printf("%d",T->Data);
}
}
非递归方法(要使用栈):
- 中序遍历
void InOrderTraversal(BinTree T){
Stack S = CreatStack(MaxSize);
while( T || !IsEmpty(S) ){
while(T){
Push(S,T);
T = T->Left;
}
T = Pop(S);
printf("%d",T->Data);
T = T->Right;
}
}
- 先序遍历
void PreOrderTraversal(BinTree T){
Stack S = CreatStack(MaxSize);
while( T || !IsEmpty(S) ){
while(T){
Push(S,T);
printf("%d",T->Data);
T = T->Left;
}
T = Pop(S);
T = T->Right;
}
}
//--------------还有一种先序遍历的实现,这是我自己想的,应该是对的(别的遍历就算了,我想不出来……)
void PreOrderTraversal2(BinTree T){
Stack S = CreatStack(MaxSize);
//先把根节点压到栈里
push(S,T);
while(!isEmpty(S)){
T = pop(S);
printf(T);
if(T->right != null){
push(T->right)
}
if(T->left != null){
push(T->left);
}
}
}
- 后序遍历
void PostOrderTraversal(BTree T) {
Stack S = CreatStack(MaxSize);
while (T || !isEmpty(S))
{
while (T) {
push(S, T);
T = T->left;
}
//peek方法只查看栈顶元素,并不弹出
if (peek(S)->tag)
printf(pop(S));
else {
T = peek(S)->right;
peek(S)->tag = true;
}
}
}
整个遍历还是比较简单的,递归就不用说了,怎么想的就怎么写,非递归是用了三次访问的原理,push是第一次访问,pop是第二次访问,如果要做后续遍历栈元素要附加一个标记值标记是否已经查看过该元素的 右儿子,如果查看过了就出栈,否则就进入右儿子。
————————麻个鸡的分割线—————————
- 层序遍历
用队列实现,每出队一个节点,就把它的左右儿子入队
void LevelOrderTraversal(BinTree T){
Queue Q = CreatQueue(MaxSize);
if(T) AddQ(Q,T);
while( !IsEmpty(Q) ) {
T = DeleteQ(Q);
printf("%d",T->Data);
if( T->Left ) AddQ( Q, T->Left );
if( T->Right ) AddQ( Q, T->Right );
}
}