树这一模块对于线性表模块而言就复杂一点,这种数据结构以现实中的树为模型来存储数据,而又以二叉树为研究和应用居多。二叉树是说在满足树也就是由根节点像叶子节点发散的基础上,又满足没个节点最多有两个子树这一条件,满足这样的条件的二叉树在逻辑思维上和具体实现方面都是较简单的。同样二叉树也有两种实现方式:链式表示的二叉树和线性表示的二叉树,其中线性表示的二叉树我们通常只用于表示完全二叉树,也就是堆。
一、链式二叉树
链式二叉树和链表一样的实现方式,每一个节点除了定义数据之外,在定义两个指针分别指向其左子树和右子树。定义代码如下:
//树的数据类型
typedef int TreeDataType;
//树节点类型
typedef struct TreeNode
{
TreeDataType data;
struct TreeNode* liftChild;
struct TreeNode* rightChild;
}TreeNode;
//树类型,包含一个指向根节点的指针
typedef struct Tree
{
TreeNode* root;
}Tree;
在把树的数据类型定义好之后,我们想一想树的创建和遍历问题,二叉树的实现方式决定了用递归思想去处理它的相关问题是非常便利的,在处理一棵树的时候,我们只需要对当前节点进行处理然后以同样的方式处理其左子树和右子树,直到节点为NULL,至于什么样的方式,如果你对递归有一定的认识,你会说我也不知道是什么样的方式,用递归思想创建二叉树的方法如下:
void TreeCreate(Tree * pt)
{
assert(pt != NULL);
pt->root = TreeCreate_before();
}
TreeNode * TreeCreate_before()
{
TNDataType ch = '0';
scanf("%c", &ch);
if (ch == '#')
return NULL;
else
{
//为此节点申请空间,并填入数据data
TreeNode * node = (TreeNode *)malloc(sizeof(TreeNode));
assert(node != NULL);
node->data = ch;
//建立左子树
node->leftChild = TreeCreate_before();
//建立右子树
node->rightChild = TreeCreate_before();
return node;
}
}
树的前序遍历是说先访问当前节点,再访问其左子树,最后访问右子树。
树的中序遍历是指访问完其左子树再访问当前节点,最后访问右子树。
树的后序遍历是指在访问完左子树和右子树之后才去访问当前节点
树的前中后序遍历和树的创建问题是大同小异的,中序遍历方法如下:
//中序遍历法
void TreeBetweenOrder(Tree * pt)
{
assert(pt != NULL);
printf("LVR:");
Order_between(pt->root);
printf("\n");
}
//中序递归
void Order_between(TreeNode * ptn)
{
if (ptn != NULL)
{
Order_between(ptn->leftChild); //遍历左子树
printf("%c ", ptn->data); //输出此节点
Order_between(ptn->rightChild); //遍历右子树
}
}
树还有一种遍历方法叫做层次遍历,就是从树根开始往叶子节点一层一层的遍历,这种遍历方法需要用队列作为辅助,先把根节点入队,然后进入循环,在队列不为空的情况下输出队首元素,出队并且将其左右孩子入队,直到队列为空时,层次遍历完成,当然前边所有入队操作都是在要入队的节点不为空的情况下进行的。代码如下:
//层次遍历
void TreeLevelOrder(Tree * pt)
{
assert(pt != NULL);
//构造并初始化队列
LinkQueue assistQueue ;
LinkQueueInit(&assistQueue);
//在树根不为空的情况下,入队
if (pt->root != NULL)
LinkQueuePush(&assistQueue, pt->root);
printf("Level:");
//遍历队列,先输出当前节点,并且其左右孩子不为空时入队,然后将此节点出队
while ((&assistQueue)->head != NULL)
{
printf("%c ", LinkQueueHead(&assistQueue)->data);
if (LinkQueueHead(&assistQueue)->leftChild != NULL)
LinkQueuePush(&assistQueue, LinkQueueHead(&assistQueue)->leftChild);
if (LinkQueueHead(&assistQueue)->rightChild != NULL)
LinkQueuePush(&assistQueue, LinkQueueHead(&assistQueue)->rightChild);
LinkQueuePop(&assistQueue);
}
printf("\n");
//摧毁队列
LinkQueueDestory(&assistQueue);
}
在链式二叉树这里还有一个点,那就是用前序和中序或者中序和后序还原二叉树。无论这两种那种还原办法,都有一个要点,那就是中序序列不能少,因为我们要靠中序序列来区分它的左子树和右子树。思想是前序就从前往后,后序就从后往前,第一个节点就是根节点,建立根节点,并且在中序中查找此节点的值,找到之后用此下标进行相应运算得到左子树和右子树序列继续用此方法还原其左子树和右子树。代码如下:
//前序和中序恢复二叉树
Tree* TreeRegain_Bf(char* before, char* between)
{
Tree* newTree = (Tree*)malloc(sizeof(Tree));
assert(newTree != NULL);
TreeInit(newTree);
int len = strlen(before);
newTree->root = recursion_Bf(before, between, len);
return newTree;
}
//递归
TreeNode* recursion_Bf(char* before, char*between, int len)
{
if (len == 0)
{
return NULL;
}
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
assert(node != NULL);
node->data = *before;
int index = 0;
while (between[index] != *before)
{
index++;
}
node->leftChild = recursion_Bf(before+1, between, index);
node->rightChild = recursion_Bf(before + index + 1, between + index + 1, len - index - 1);
return node;
}
//后序和中序恢复二叉树
Tree* TreeRegain_Bh(char* behind, char* between)
{
Tree* newTree = (Tree*)malloc(sizeof(Tree));
assert(newTree != NULL);
TreeInit(newTree);
int len = strlen(behind);
newTree->root = recursion_Bh(behind, between, len);
return newTree;
}
//递归
TreeNode* recursion_Bh(char* behind, char* between, int len)
{
if (len == 0)
{
return NULL;
}
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
assert(node != NULL);
node->data = *(behind + len -1);
int index = 0;
while (between[index] != *(behind + len - 1))
{
index++;
}
node->rightChild = recursion_Bh(behind + index, between + index + 1, len - index - 1);
node->leftChild = recursion_Bh(behind, between, index);
return node;
}
二、线性二叉树
在说这个地方的时候先要看一下什么是完全二叉树,而要想理解完全二叉树就要知道什么是满二叉树。
满二叉树的定义是一个n层的二叉树具有2^n - 1个元素。也就是说你这个树从上到下每一层都满满当当的。
再理解了满二叉树之后完全二叉树就很好理解了,完全二叉树就是在满二叉树的层次遍历基础上缺少只能缺少最后的元素,比如说没有倒数第二个元素,那必然没有倒数第一个元素。完全二叉树的特点是其层次遍历没有一个空缺位置,这也是线性二叉树只适合完全二叉树的原因,如果有空缺并且空缺太多会造成空间的浪费。
线性结构存储二叉树的原理是通过父节点与孩子节点在线性表上的对应关系来用父节点下标推出子节点下标或通过子节点下标推出父节点下标。一个节点的下标为x,则其子节点小标为2x+1和2x+2,其父节点为(x-1)/2,其原理可以画图理解。有了这一点,我们就有了线性表到二叉树的映射关系。堆的内容后边再说……