1.二叉树的定义及概念
定义: 树(Tree)是n(n>=0)个结点的有限集,它或为空树(n = 0),或为非空树。对于非空树T:
(1) 有且仅有一个特定的称为根(root)的结点;
(2)除去根节点外,其余结点可分为m(m>0)个互不相交的有限集T1,T2,......,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree);
树的基本术语我这里就不再赘述,各位请自行百度。
2.二叉树的构建
二叉树的节点的构建
typedef struct BTNode{
char element;//存放节点中的元素
BTNode* left;//左子树的地址
BTNode* right;//右子树的地址
}BTNode, *BTNodePtr;
BTNodePtr constructBTNode(char paraChar){
BTNodePtr resultPtr = (BTNodePtr)malloc(sizeof(BTNode));
resultPtr->element = paraChar;//填入元素
resultPtr->left = NULL;
resultPtr->right = NULL;
//左右均不链接
return resultPtr;
}//Of constructBTNode
我们这里用一个队列来管理这个二叉树的每个节点的指针
当我们需要创造一个有意义的节点时(即其中有元素),将这个节点的地址存入队列中,当需要向下衍生更多的子树的时候,我们就将队列此时的首个地址弹出,这个节点的左右此时是没有子树的地址的,实际操作如下。
typedef struct BTNodePtrQueue{
BTNodePtr* nodePtrs;//队列的存储空间
int front;//首个元素
int rear;//末尾元素
}BTNodePtrQueue, *QueuePtr;
队列的初始化
QueuePtr initQueue(){
QueuePtr resultQueuePtr = (QueuePtr)malloc(sizeof(struct BTNodePtrQueue));
//申请队列的头节点
resultQueuePtr->nodePtrs = (BTNodePtr*)malloc(QUEUE_SIZE * sizeof(BTNodePtr));
//动态申请一片我们开始就预定好的空间
resultQueuePtr->front = 0;
resultQueuePtr->rear = 1;
//均为指针,用于队列的增删
return resultQueuePtr;
}//Of initQueue
队列的入出(这里仅展示代码,不再注释)
当然也要对整个队列空间进行检查,防止出现溢出。
void enqueue(QueuePtr paraQueuePtr, BTNodePtr paraBTNodePtr){
printf("front = %d, rear = %d.\r\n", paraQueuePtr->front, paraQueuePtr->rear);
if ((paraQueuePtr->rear + 1) % QUEUE_SIZE == paraQueuePtr->front % QUEUE_SIZE) {
printf("Error, trying to enqueue %c. queue full\r\n", paraBTNodePtr->element);
return;
}//Of if
paraQueuePtr->nodePtrs[paraQueuePtr->rear] = paraBTNodePtr;
paraQueuePtr->rear = (paraQueuePtr->rear + 1) % QUEUE_SIZE;
printf("enqueue %c ends.\r\n", paraBTNodePtr->element);
}//Of enqueue
BTNodePtr dequeue(QueuePtr paraQueuePtr){
if (isQueueEmpty(paraQueuePtr)) {
printf("Error, empty queue\r\n");
return NULL;
}//Of if
paraQueuePtr->front = (paraQueuePtr->front + 1) % QUEUE_SIZE;
//BTNodePtr tempPtr = paraQueuePtr->nodePtrs[paraQueuePtr->front + 1];
printf("dequeue %c ends\r\n", paraQueuePtr->nodePtrs[paraQueuePtr->front]->element);
return paraQueuePtr->nodePtrs[paraQueuePtr->front];
}//Of dequeue
接下来,是整个程序的关键
我会配合注释和一部分图片一步一步地讲解这部分,因为这是构建树的关键
BTNodePtr stringToBTree(char* paraString){
int i;。//用于输入的字符串的移动
char ch;//用于队列的出入
//Use a queue to manage the pointers
QueuePtr tempQueuePtr = initQueue();
//看到上面这句话了没?
用队列来管理这个树中的每个节点的指针
BTNodePtr resultHeader;//头指针
BTNodePtr tempParent, tempLeftChild, tempRightChild;
//存储树中主要元素的指针
i = 0;
ch = paraString[i];
resultHeader = constructBTNode(ch);
enqueue(tempQueuePtr, resultHeader);//这几句自行理解,不多赘述
//从这里开始就是如何链接各个节点了
while(!isQueueEmpty(tempQueuePtr)) {
tempParent = dequeue(tempQueuePtr);
//先从队列中出一个节点,作为根,之后的循环中,我们不断地去出,作为新的子树的根。
//The left child
i ++;
ch = paraString[i];
if (ch == '#') {
tempParent->left = NULL;
} else {
tempLeftChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempLeftChild);//将读入的字符所创建的节点进行进队列
tempParent->left = tempLeftChild;
}//Of if
//以下同理
//The right child
i ++;
ch = paraString[i];
if (ch == '#') {
tempParent->right = NULL;
} else {
tempRightChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempRightChild);
tempParent->right = tempRightChild;
}//Of if
}//Of while
return resultHeader;
}//Of stringToBTree
可能只看注释会有些生涩难懂,下面我会用图例加以注释
首先建立根
此时队列的情况
而后我们要构成树,就要将a从队列中弹出,作为下两个节点的父节点。
而后读入c,d 注意:此时若输出 enqueue 某个元素 ends,已经成了树的一部分,只有当这个节点需要作为其他节点的父节点的时候,我们就将它从队列中弹出。如图
因此,下面的字符的处理方式就不再赘述
两个问题:
1.当整个树构建完成,队列中还有元素吗?
2.树的图像又是什么样子的呢?
解答:
1.为空,因为所有意义的节点都是父节点,都从队列中弹出了
2.如下图
注:这里的e b f 都是有子节点的,但都为空,并未画出来。
4.遍历的方法
在课堂上我们讲了先序,中序,后序的遍历函数。
但本质上,它们并无差异,遍历元素的顺序都是一样的,只是打印的时机不同。
这里附上这三段代码
请读者自行思考
/**
* Preorder
*/
void preorder(BTNodePtr tempPtr){
if (tempPtr == NULL){
return;
}//Of if
printf("%c", tempPtr->element);
preorder(tempPtr->left);
preorder(tempPtr->right);
}//Of preorder
/**
* Inorder
*/
void inorder(BTNodePtr tempPtr){
if (tempPtr == NULL) {
return;
}//Of if
inorder(tempPtr->left);
printf("%c", tempPtr->element);
inorder(tempPtr->right);
}//Of inorder
/**
* Post order
*/
void postorder(BTNodePtr tempPtr){
if (tempPtr == NULL) {
return;
}//Of if
postorder(tempPtr->left);
postorder(tempPtr->right);
printf("%c", tempPtr->element);
}//Of postorder
而层次遍历,则是真的不同的遍历方式
我大概简述一下思路,后文会附上代码。
思路:既然是层次遍历,我们就要一层一层的遍历。
我们先将整个树的根入队列,然后将子树的有意义的元素进队列。
这样一层一层地将有意义的元素压入队列中,同时呢,因为队列先进后出的特点,我们本来就是一层一层地从左到右压入,在循环的过程中,作为父节点的元素就会弹出并输出。就是这么巧妙的原理。
代码如下
void levelwise(BTNodePtr paraTreePtr){
//Use a queue to manage the pointers
char tempString[100];
int i = 0;
QueuePtr tempQueuePtr = initQueue();
BTNodePtr tempNodePtr;
enqueue(tempQueuePtr, paraTreePtr);
while(!isQueueEmpty(tempQueuePtr)) {
tempNodePtr = dequeue(tempQueuePtr);
//For output.
tempString[i] = tempNodePtr->element;
i ++;
if (tempNodePtr->left != NULL){
enqueue(tempQueuePtr, tempNodePtr->left);
}//Of if
if (tempNodePtr->right != NULL){
enqueue(tempQueuePtr, tempNodePtr->right);
}//Of if
}//Of while
tempString[i] = '\0';
printf("Levelwise: %s\r\n", tempString);
}//Of levelwise
5.测试程序及结果
int main(){
BTNodePtr tempHeader;
tempHeader = constructBTNode('c');
printf("There is only one node. Preorder visit: ");
preorder(tempHeader);
printf("\r\n");
char* tempString = "acde#bf######";
tempHeader = stringToBTree(tempString);
printf("Preorder: ");
preorder(tempHeader);
printf("\r\n");
printf("Inorder: ");
inorder(tempHeader);
printf("\r\n");
printf("Postorder: ");
postorder(tempHeader);
printf("\r\n");
printf("Levelwise: ");
levelwise(tempHeader);
printf("\r\n");
return 1;
}//Of main