1. 先来了解一下二叉树
1)二叉树的概念:一棵二叉树是节点的有限集合,该集合可以为空,也可以是一个根节点加上两颗分别称为左子树和右子树的二叉树组成。
2)二叉树的特点:每个节点最多有两颗子树,即二叉树不存在度大于2的结点
二叉树的子树有左右之分,其子树的次序不能颠倒
2满二叉树&&完全二叉树
满二叉树:在一颗二叉树中,所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上
完全二叉树:如果一颗具有N个节点的二叉树的结构与满二叉树的前N个节点的节点相同,则成为完全二叉树
3.二叉树的性质
1)若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 (i>0)个结点
2)若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大 结点数是 (k>=0)
3)对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数 为 n2,则有n0=n2+1
4)具有n个结点的完全二叉树的深度k为log2(n+1) 上取整
5)对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序 对所有节点从0开始编号,则对于序号为i的结点有:
1. 若i>0,双亲序号:(i-1)/2; i=0,i为根节点编号,无双亲结点
2. 若2i+1<n,左孩子序号:2i+1,否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,否则无右孩子
4.二叉树的基本操作
(a)二叉树的遍历:前中后三种遍历方式
前序遍历:先访问根节点,在访问左子树,最后遍历右子树,所以次序为A、B、D、C、E、F
中序遍历:先访问左子树,再访问根节点,最后访问右子树,所以次序为D、B、A、E、C、F
后序遍历:先访问左子树,在访问右子树,最后访问根节点,所以次序为D、B、E、F、C、A
所以根据三种遍历方式的特性,我们可以写出他们的递归算法
// 前序遍历递归 void PreOrder(PBTNode pRoot) { if(pRoot) { printf("%c ",pRoot->_data); PreOrder(pRoot->_pLeft); PreOrder(pRoot->_pRight); } } // 中序遍历递归 void InOrder(PBTNode pRoot) { if(pRoot) { InOrder(pRoot->_pLeft); printf("%c ",pRoot->_data); InOrder(pRoot->_pRight); } } // 后序遍历递归 void PostOrder(PBTNode pRoot) { if(pRoot) { PostOrder(pRoot->_pLeft); PostOrder(pRoot->_pRight); printf("%c ",pRoot->_data); } }
那么既然有递归算法,那么对应的非递归算法怎么写呢?
这里我们借助栈来实现:
前序遍历的非递归算法:
让根节点先入栈,利用一个指针来保存栈顶元素,,将它出栈,然后看栈顶是否有左子树和右子树,如果有就让入栈,循环上述过程
// 前序遍历非递归 void PreOrderNor(PBTNode pRoot) { Stack s; PBTNode pCur = NULL; StackInit(&s); StackPush(&s,pRoot); if(NULL == pRoot) return; while(!StackEmpty(&s)) { pCur = StackTop(&s);//用pCur来标记栈顶元素 printf("%c",pCur->_data); StackPop(&s); if(pCur->_pLeft) StackPush(&s,pCur->_pLeft); else if(pCur->_pRight) StackPush(&s,pCur->_pRight); } }
中序遍历非递归算法:
也是借助栈,先让一个指针指向根节点,然后入栈,然后一直沿着左子树走,每遇到一个节点就入栈,最后走完入栈后,栈顶元素就是最左边的结点D,然后依次出栈,最后右子树叶金星上述过程就ok
//中序遍历非递归 void InOrderNor(PBTNode pRoot) { Stack s; PBTNode pCur = NULL; StackInit(&s); if(NULL == pRoot) return; pCur = pRoot; while(!StackEmpty(&s) || pCur) { while(pCur) { StackPush(&s,pCur); pCur = pCur->_pLeft; } pCur = StackTop(&s); printf("%c",pCur->_data); StackPop(&s); pCur = pCur->_pRight; } }
后序遍历非递归算法:
后序遍历一直沿左边走,并将途中遇到的所有结点依次入栈,直到走到最后的一个左结点为止,然后出栈,这时栈顶元素变为B,此时看B是否有无右结点,如果有就入栈,当然了值得注意的是当我们走到C的时候,我们发现他有右子树F,当我们从F返回到C时,相当于C被访问两次(因为之前访问E时已访问过一次),就会不符合,所以需要另外一个指针来保存最近访问过的结点
//后序遍历非递归 void PostOrderNor(PBTNode pRoot) { PBTNode pCur = pRoot; PBTNode pPre = NULL;//标记最近访问过的节点 PBTNode pTop = NULL;//标记栈顶元素 Stack s; if(NULL == pRoot) return; StackInit(&s); while(pCur || !StackEmpty(&s)) { while(pCur) { StackPush(&s,pCur); pCur = pCur->_pLeft; } pTop = StackTop(&s); if(NULL == pTop->_pRight || pPre == pTop->_pRight) { printf("%c",pTop->_data); pPre = pTop; StackPop(&s); } else pCur = pTop->_pRight; } }
(b)二叉树的层序遍历
按照·二叉树的层序次序(即从根节点到叶子结点层),同一层中按照先左子树后右子树的次序遍历二叉树
算法: 1.初始化一个队列
2.将根节点的指针入队列
3.当队列非空时,循环以下步骤:
-> 取队头元素并访问
->若该节点的左子树非空,将该节点的左子树入队列
->若该节点的右子树非空,将该节点的右子树入队列
4.结束
// 层序遍历 void LevelOrder(PBTNode pRoot) { PBTNode pCur = NULL; SQueue q; if(pRoot == NULL) return; //1.初始化一个队列,让根节点进队列 SQueueInit(&q); //2.根节点指针入队列 SQueuePush(&q,pRoot); //3.队列非空,循环执行 while(!SQueueEmpty(&q)) { //a.取队头元素并访问 pCur = SQueueFrontData(&q); printf("%c ",pCur->_data); //b.左子树非空,将左子树入队列 if(pCur->_pLeft) SQueuePush(&q,pCur->_pLeft); //c.右子树非空,将右子树入队列 if(pCur->_pRight) SQueuePush(&q,pCur->_pRight); SQueuePop(&q); } }