1、查找问题:分静态查找和动态查找
2、同样n个元素的查找,二分查找比顺序查找快很多。
判定树深度:[log2 n]+1,还可计算平均查找次数,11个节点仅为4.
3、树:根与子树,要求子树是不能相交的。
于是树的特点:
除根节点外,每个节点只有1个父节点。
n个节点,n-1条线。
树是连通的,且是保证所有节点联通但同时线条树最少的状态。
4、节点的度
树的度 取最大的节点度
5、用什么表示树?
链表 (兄弟-儿子表示法):
每一个元素1个数据+2个指针(firstChild+nextSibling)
转化为二叉树:度为2的树
一共2n个指针,有效n-1个,闲置n+1个。
6、二叉树:
有左右之分
斜二叉树
完美二叉树(满二叉树)k层,最多(2^k-1)个节点
完全二叉树(若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。)
总节点数=n0+n1+n2
总边数=n0+n1+n2-1=n1+2*n2
求解:n0=n2+1
推广到m叉树
n0+n1+……+nm -1=1*n1+2*n2+……+m*nm;
移项,得:
n0=n2+2*n3……+(m-1)*nm + 1
深度为k,最大节点数2^k-1个,最后一层最大节点数2^(k-1)个。
7、二叉树的存储
1)数组
元素之间的联系通过下标体现。
完全二叉树:很适合用数组(下标1开始),
因为有如下规律:

一般二叉树:差的不多也可以补齐然后用数组。
2)链表
一般二叉树,链表更适合
1个元素包含3个字段,1个是数据,另外2个指针,left和right
typedef struct TreeNode* BinTree;
struct TreeNode{
ElementType Data;
BinTree left;
BinTree right;
}8、二叉树操作集
createBinTree
isEmpty
traversal
其中traversal又分
preOrder
inOrder
postOrder
levelOrder
9、二叉树前/中/后序遍历基于链表的递归实现。
1)前序遍历 根左右
void preOrderTraversal(BinTree BT){
if (BT)
{
printf("%d",BT -> Data);
preOrderTraversal(BT->left);
preOrderTraversal(BT->right);
}
}2)中序遍历 左根右
void inOrderTraversal(BinTree BT){
if (BT)
{
preOrderTraversal(BT->left);
printf("%d",BT -> Data);
preOrderTraversal(BT->right);
}
}3)后序遍历 左右根
void postOrderTraversal(BinTree BT){
if (BT)
{
preOrderTraversal(BT->left);
printf("%d",BT -> Data);
preOrderTraversal(BT->right);
}
}10、二叉树前/中/后序遍历基于链表的非递 归实现。
递归写起来简单,能不能不用递归,比如:用堆栈实现中序堆栈?
【思路】
中序,左中右,中是中间的一个节点,左右是树。
因此节点作为左节点时并不能马上就访问,因为它也是子树的根。所以作为根要放在堆栈里,等左边访问完。访问左边时,子树左节点同样是子子树的根,同样放堆栈里。
什么时候可以结束呢?
这个节点作为根放入堆栈,一查发现左孩子不存在,就可以弹出来了。弹出来这个是根,这时确定的是左树已经访问完,所以马上访问根,然后下一步访问自己的右树。
为了访问右树,把右树的根压栈……
弹一个是自己,如果再弹一个应该是自己的父亲。但自己访问完应该去访问自己的右树,自己作为根的这棵树结束以后,才把自己看成儿子,去访问那棵树。所有不会马上弹第二个,访问右树的过程也是把这棵树的根节点压尽,到底反弹的过程。等右树访问完,右树所有的节点都已经在堆栈里压进去又出来过,堆栈的现场同访问右树前没有变化,就可以回去弹出根了。
堆栈保存的是子树的根!
【规则】
第一次碰到实际上都是跟左右的顺序。
每轮循环有一个当前节点。
当前节点如果不为NULL,有2种,左孩子为空和不为空。
如果左孩子不为空,就把当前节点换成它的左孩子,同时把自己保持在堆栈里,过一会回来方便找右节点。
左孩子为空,堆栈顶pop一个,刚好是当前节点(第2次碰到),然后当前节点换成它的右节点。
如果右节点为空,所以下一轮当前节点可能是NULL,意味着某几个层次的右子树、同时也作为某一个层次的左子树都处理完了,这时pop出下一个根(左子树的根)。
外层循环的意义在于,弹出一个根节点并在此之前保证它的左子树已经访问完毕。
堆栈也用链表表示,栈顶在链表头,参考:https://zhuanlan.zhihu.com/p/50379824
typedef struct SNode* Stack
struct SNode{
ElementType item;
struct SNode* left;
struct SNode* right;
};创建堆栈:
Stack createStack(){
Stack s=(Stack)malloc(sizeof(struct SNode));
s->Next=NULL;
return s;
}利用堆栈实现中序遍历
void inOrderTraversal(BinTree BT){
Stack s =createStack();
BinTree bt=BT;
while(bt||!isEmpty(s))
{
while(bt){
push(s, bt);
bt=bt->left;
}
if(!isEmpty(s)){
Stack tmp=pop(s)
if(tmp){
printf("%d",tmp->Data);
t=tmp->right;
}
}
}利用堆栈实现先序遍历 根左右
void inOrderTraversal(BinTree BT){
Stack s =createStack();
BinTree bt=BT;
while(bt||!isEmpty(s))
{
while(bt){
printf("%d",bt->Data);
push(s, bt);//push for the right later
bt=bt->left;
}
if(!isEmpty(s)){
Stack tmp=pop(s)
if(tmp){
t=tmp->right;
}
}
}利用堆栈实现后序遍历 左右根(较复杂)
1)法一:
作为根节点入栈时打上is_first标记。在左树结束后本来有一次出栈机会,但看到标记线不出(或者这里的统一出,出了看到标记不对再压回去),同时把下一个要处理的节点置为右儿子。等右子树结束后悔再次有pop的机会,这时才真正的访问。
void inOrderTraversal(BinTree BT){
Stack s =createStack();
BinTree bt=BT;
while(bt||!isEmpty(s))
{
while(bt){
//printf("%d",bt->Data);
bt->is_first=True;
push(s, bt);//push for the right later
bt=bt->left;
}
if(!isEmpty(s)){
Stack tmp=pop(s)
if(tmp){
if(!is_first){
printf("%d",tmp->Data);
p=NULL;
}
else{
tmp->is_first=false;
push(s, tmp);
bt=tmp->right;
}
}
}2)法二:每一轮循环都去栈顶去一个元素看看,对于当前节点,如果有孩子且孩子还没被访问到,就不出栈,反而把右边和左边的孩子先后压栈;否则(),自己轻松出栈。
void PostOrderTraversal(BinTree BT)
{
Stack S=CreateStack(MaxSize);
BinTree pre,cur;
pre=NULL;
cur=NULL;
Push(S,BT);
while(!IsEmpty(S))
{
cur=S->Data[Top];
if((cur->Left==NULL&&cur->Right==NULL)||(pre!=NULL&&(cur->Left==pre||cur->Right==pre)))//没有子节点或者pre是自己的子节点是自己的子节点
{
printf("%5d",cur->Data);
Pop(S);
pre=cur;
}
else
{
if(cur->Right!=NULL)
Push(S,cur->Right);
if(cur->Left!=NULL)
Push(S,cur->Left);
}
}
}3)利用2个堆栈,把根左右转化为右左根
先序的访问顺序是root, left, right 假设将先序左右对调,则顺序变成root, right, left,暂定称之为“反序”。
后序遍历的访问顺序为left, right,root ,刚好是“反序”结果的逆向输出。
如果有了先序的代码+这个思路,改一下代码不难。
void InOrderTraversal( BinTree BT )
{
BinTree T BT;
Stack S = CreatStack( MaxSize ); /*创建并初始化堆栈S*/
Stack Q = CreatStack( MaxSize ); /*创建并初始化堆栈Q,用于输出反向*/
while( T || !IsEmpty(S) ){
while(T){ /*一直向右并将沿途结点压入堆栈*/
Push(S,T);
Push(Q,T);/*将先序遍历到的结点压栈,用于反向*/
T = T->Right; /*转向右子树*/
}
if(!IsEmpty(S)){
T = Pop(S);
T = T->Left; /*转向左子树*/
}
}
while( !IsEmpty(Q) ){
T = Pop(Q);
printf(“%5d”, T->Data);
}
}11、二叉树层序遍历基于队列的非递归实现。
二叉树的遍历-》二维转化为一维,这个顺序是自己定夺的。
一个父节点有2个子节点,访问左边以及左边子树以后,怎么再去找右边或者自己?
需要想办法保存自己或者右边。
前序中序后序-》堆栈-》保存自己
层序-》队列-》保存右边
以上前中后的左右不是左右节点,而是左右子树-》找到左儿子也不敢当场访问,保存起来先。
层序遍历是一层一层看完,从上往下是顺序的,找到就当场访问,没有保存起来一会儿回来访问的需求。所以没有堆栈。
至于用队列,是因为这一层的所有节点都访问以后他们的子节点才会被访问,加入这一层4个节点,第1、2个节点自己是找到就当场访问,但子女没有,要等第4个访问完,所以就需要保存这些父节点。而子节点访问的顺序与父节点一致,所以用队列,先进先出,刚好。
层序遍历的思路:
找到父节点就当场访问,然后把父节点的左右孩子放进待访问的队列,等排队到了自然能访问,在此之前前面有和父节点同一层的节点需要访问。
程序从1个根节点启动。除了第1个是手工指定,以后的父节点其实都是xxx的子节点,找到父节点的时机都是排队到队头。所以主要都是在操作队列。
根节点为了与程序相适应,也准备从队头访问。为此只需要手工放入队列,然后就可以进入从队列取数-子节点入队的循环。
步骤:
创建队列,把根节点加入队列
然后进入循环,在每一轮循环里,从队头取出一个数,访问,然后把左右节点加入队尾,如果左右节点存在的话。
队列为空则循环结束
void inOrderTraversal(BinTree BT){
if (!BT->Data)return ;
Queue q=createQueue();
add(q,BT->data);
while(!q.isEmpty()){
ElementType x=delete(q);
printf(x->data);
if(x->left)
add(q,x->left);
if(x->right)
add(q,x->right);
}
}12、层序创建二叉树
和层序遍历有相同但也有所差别。遍历时访问了父亲同时把2儿子入队,因为儿子已经存在;创建是创建父亲同时把父亲入队,到时候如果还有输入就动态创建子节点,没有的话就不创建了。
遍历结束的时机是队列空掉,创建结束的时机是输入结束。
创建二叉树存在同一的问题:如果这一层4个节点,需要填充完这一层4个才去下一层,开始填充第1个元素的左右孩子?
但链表表示,其实对4这个数字并没有记载,并不清楚这一层多少个数。换个思路,其实需要记载的不一定是数字4,而是最终的它们的子节点——所以如果可以在父节点访问时就创建好子节点并保存,到时候去取,就不用关心到底多少个。
这个保存适合队列,保证顺序,不关心数量——队列里是待填的空格,每次上面一行有了一个元素,必然意味着下面需要画左右2个空格。空格产生了先放着,等到上一次都填充完就来填充它。事实上,上一层的空格也是上上一层造成的,这样保证了应有的顺序。且排队的空格都是上一层产生,这保证了队列里不会有其他多余的东西,可以放心用。
其实左右空格是上层父节点产生的,所以……可以直接保存父节点,出队时1个父节点再去创建2个子节点,并同时完成赋值。
除了根节点,其他节点都是子节点其实。可以去区分父节点子节点似乎是徒劳的,一个节点两重身份。入队时是作为别人的子节点,自身的Data已经在父节点出队时填充好,但需要排队完善子节点;出队时作为父节点,完善自己的子节点。
BT createBinTree()
{int x;
scanf("%d", &x);
if(!x) return NULL;
Queue q=createQueue();
BT bt= (BT)malloc(sizeof(struct TreeNode));
bt->Data=x;
bt->left=bt->right=NULL;
add(q, bt);
while(!isEmpty(q))
{BT bt=delete(q);
scanf("%d", &x);
if(x){
BT left= (BT)malloc(sizeof(struct TreeNode));
left->left=left->right=NULL;
left->Data=x;
bt->left=left;
add(left,q);
}
else
{
bt->left=NULL;
}
scanf("%d", &x);
if(x){
BT right= (BT)malloc(sizeof(struct TreeNode));
right->left=right->right=NULL;
right->Data=x;
bt->left=left;
add(right,q);
}
else
{
bt->right=NULL;
}
return bt;
}