参考文献:
1. 数据结构与算法分析——c语言描述
2. 百度百科 http://baike.baidu.com/view/88806.htm http://baike.baidu.com/view/127820.htm
定义:二叉树是一棵树,其中每个节点都不能有多于两个儿子节点
性质:
1、二叉树的平均深度要比N小,为O(N^(1/2));二叉查找树,其深度的平均值为O(logN)
2、二叉树分为满二叉树和完全二叉树。
满二叉树:所有非叶子节点都达到最大的二叉树
完美二叉树:叶子节点仅在层次最大的两层出现,对于任意节点,左子树的高度等于右子数的高度加1
3、第i层的节点总数不超过2^(i-1)
4、深度为h的二叉树,最多有2^h-1个节点(h>=1),最少有h个节点
5、对于任意二叉树,如果其叶子节点数为N0,而度数为2的节点总数为N2,则有N0=N2+1
6、具有n个节点的完全二叉树,其深度为int(log2n)+1
7、有N个节点的完全二叉树,如果采用顺序方式存储,则节点所在顺序表中的位置有如下关系:
若节点i不为根节点,则其父节点编号为i/2;
若2*i<=N,则其左儿子的编号为i*2,若2*i>N,则无左儿子;
若2*i+1<=N,则其右儿子的编号为2*i+1;若2*i>N,则无右儿子。
8、给定N个节点,能构成h(N)中不同的二叉树。
h(N)为卡特兰树德第N项, h(N) = C(n, 2n)/(n+1)
类型:
1、二叉查找树:对于树中的每个节点x,其左子树的所有关键字的值小于关键字x的值,右子树中所有关键字的值,大于x
下面是二叉查找树的定义及常用方法的C实现:
- struct TreeNode;
- typedef struct TreeNode *Position;
- typedef struct TreeNode *SearchTree;
- typedef int ElementType;
- SearchTree MakeEmpty(SearchTree T);
- Position Find(ElementType x, SearchTree T);
- SearchTree Insert(ElementType X, SearchTree T);
- SearchTree Delete(ElementType X, SearchTree T);
- Position findMin(SearchTree T);
- Position findMax(SearchTree T);
- struct TreeNode
- {
- ElementType Element;
- SearchTree left;
- SearchTree Right;
- };
- //建立空树
- SearchTree MakeEmpty(SearchTree T)
- {
- if(T != NULL)
- {
- MakeEmpty(T->Left);
- MakeEmpty(T->Right);
- free(T);
- }
- return NULL;
- }
- Position Find(ElementType X, SearchTree T)
- {
- if(T == NULL)
- return NULL;
- if(x < T->Element)
- return Find(X, T->Left);
- else if(X > T->Element)
- return Find(X, T->Right);
- else
- return T;
- }
- Position FindMin(SearchTree T)
- {
- if(T == NULL)
- return NULL;
- else if(T->Left == NULL)
- return T;
- else return FindMin(T->Left);
- }
- Position FindMax(SearchTree T)
- {
- if(T != NULL)
- {
- while(T->Right != NULL)
- T = T->Right;
- }
- return T;
- }
- SearchTree Insert(ElementType X, SearchTree T)
- {
- if(T == NULL)
- {
- //Create and return an one-node tree
- T = (SearchTree)malloc(sizeof(struct TreeNode));
- if(T == NULL)
- Error("out of space!\n");
- else
- {
- T->Element = X;
- T->Left = T->Right = NULL;
- }
- }
- else if(X < T->Element)
- T->Left = Insert(X, T->Left);
- else if(X > T->Element)
- T->Right = Insert(X, T->Right);
- return T;
- }
关于二叉查找树的删除,存在三种情况:
1、叶子节点,直接删除即可;
2、如果只有一个儿子节点,将其父节点指向其儿子节点,然后free掉该节点,即可;
3、最复杂的情况,有两个节点,一般的删除策略是用其右子树的最小节点的数据,代替该节点的数据,并递归删除那个节点(即右子数最小节点)
代码如下:
- SearchTree Delete(ElementType X, SearchTree T)
- {
- Position TmpCell;
- if(T == NULL)
- Error("Element not found");
- else if(x < T->Element)
- T->left = Delete(X, T->Left);
- else if(X > T->Element)
- T->Right = Delete(X, T->Right);
- else if(T->Left&&T->Right)
- {
- TmpCell = FindMin(T->Right);
- T->Element = TmpCell->Element;
- T->Right = Delete(T->Element, T->Right);
- }
- else
- {
- TmpCell = T;
- if(T->Left == NULL)
- T = T->Right;
- else if(T->Right == NULL)
- T = T->Left;
- free(TmpCell);
- }
- return T;
- }
- void PreOrderTraverse(Tree T)
- {
- if(T != NULL)
- {
- PrintElement(T->Element);
- PreOrderTraverse(T->Left);
- PreOrderTraverse(T->Right);
- }
- }
中序和后序的递归遍历和上面相似,就是在中间或最后打印当前节点。下面分析树的非递归遍历,二叉树前序非递归遍历:
- void PreOrderTraverseNew(Tree T)
- {
- InitStack(S);
- Position p = T;
- while(p || !StackEmpty(S))
- {
- if(p)
- {
- PrintElement(p->Element);
- Push(S, p);
- p = p->lchild;
- }
- else
- {
- Pop(S, p);
- p = p->rchild;
- }
- }
- }
- void InOrderTraverseNew(Tree T)
- {
- InitStack(S);
- Position p = T;
- while(p || !StackEmpty(S))
- {
- if(p)
- {
- Push(S, p);
- p = p->lchild;
- }
- else
- {
- Pop(S, p);
- PrintElement(p->Element);
- p = p->rchild;
- }
- }
- }
- void PostOrderTraverse(Tree T)
- {
- InitStack(S);
- Position p = T;
- while(p || !StackEmpty)
- {
- while(p)
- {
- Push(S, p, 0);
- p = p->lchild;
- }
- while(!StackEmpty(S) && GetTopTag(S) == 1)
- {
- Pop(S, p);
- PrintElement(p->Element);
- }
- if(!StackEmpty(S))
- {
- setTop(S, 1);
- p = GetTop(S);
- p = p->rchild;
- }
- else
- break;
- }
- }
- void BreadthSearch(Tree T)
- {
- Queue Q;
- InitQueue(Q);
- Position p = T;
- EnQueue(Q, p);
- while(!QueueEmpty(Q))
- {
- DeQueue(Q, p);
- PrintElement(p->Element);
- if(p->lchild)
- {
- EnQueue(Q, p->lchild);
- }
- else if(p->rchild)
- {
- EnQueue(Q, p->rchild);
- }
- }
- }
树的高度
- int Height(Tree T)
- {
- if(T == NULL)
- return 0;
- else
- {
- return Max(Height(T->Left), Height(T->Right)) + 1;
- }
- }
二叉树的遍历:
D:访问根结点,L:遍历根结点的左子树,R:遍历根结点的右子树。
给定一棵二叉树的前序遍历序列和中序遍历序列可以惟一确定一棵二叉树(后序遍历可以举出很多不唯一的反例)。
二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列。
深度优先遍历二叉树。
1. 中序遍历(LDR)的递归算法:
若二叉树为空,则算法结束;否则:
中序遍历根结点的左子树;
访问根结点;
中序遍历根结点的右子树。
2. 前序遍历(DLR)的递归算法:
若二叉树为空,则算法结束,否则:
访问根结点;
前序遍历根结点的左子树;
前序遍历根结点的右子树。
3. 后序遍历(LRD)的递归算法:
若二叉树为空,则算法结束,否则:
后序遍历根结点的左子树;
后序遍历根结点的右子树;
访问根结点。
广度优先遍历二叉树。
广度优先周游二叉树(层序遍历)是用队列来实现的,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。
按照从根结点至叶结点、从左子树至右子树的次序访问二叉树的结点。算法:
1初始化一个队列,并把根结点入列队;
2当队列为非空时,循环执行步骤3到步骤5,否则执行6;
3出队列取得一个结点,访问该结点;
4若该结点的左子树为非空,则将该结点的左子树入队列;
5若该结点的右子树为非空,则将该结点的右子树入队列;
6结束。
非递归深度优先遍历二叉树。
栈是实现递归的最常用的结构,利用一个栈来记下尚待遍历的结点或子树,以备以后访问,可以将递归的深度优先遍历改为非递归的算法。
1. 非递归前序遍历:遇到一个结点,就访问该结点,并把此结点推入栈中,然后下降去遍历它的左子树。遍历完它的左子树后,从栈顶托出这个结点,并按照它的右链接指示的地址再去遍历该结点的右子树结构。
2. 非递归中序遍历:遇到一个结点,就把它推入栈中,并去遍历它的左子树。遍历完左子树后,从栈顶托出这个结点并访问之,然后按照它的右链接指示的地址再去遍历该结点的右子树。
3. 非递归后序遍历:遇到一个结点,把它推入栈中,遍历它的左子树。遍历结束后,还不能马上访问处于栈顶的该结点,而是要再按照它的右链接结构指示的地址去遍历该结点的右子树。遍历遍右子树后才能从栈顶托出该结点并访问之。另外,需要给栈中的每个元素加上一个特征位,以便当从栈顶托出一个结点时区别是从栈顶元素左边回来的(则要继续遍历右子树),还是从右边回来的(该结点的左、右子树均已周游)。特征为Left表示已进入该结点的左子树,将从左边回来;特征为Right表示已进入该结点的右子树,将从右边回来。
4. 简洁的非递归前序遍历:遇到一个结点,就访问该结点,并把此结点的非空左结点推入栈中,然后下降去遍历它的左子树。遍历完左子树后,从栈顶托出一个结点,并按照它的右链接指示的地址再去遍历该结点的右子树结构。
----------------------------------------------------------------------
图的深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w,从w出发按同样的方法向前遍历,直到图中所有顶点都被访问。
图的广度优先搜索是树的按层次遍历的推广,它的基本思想是:首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2, …, vi t,并均标记已访问过,然后再按照vi1,vi2, …, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点vi有路径相通的顶点都被访问过为止。