树结构是一类重要的非线性数据结构。直观来看,树是以分支关系定义的层次结构。
1.二叉树的定义
二叉树是每个节点最多只有两个分支的树结构。通常分支被称作“左子树”或“右子树”。二叉树的左右次序不能随意颠倒。
2.二叉树的表示
typedef struct BTNode {
char element;
BTNode* left;
BTNode* right;
}BTNode,*BTNodePtr;
二叉树是非线性结构,二叉树的指针域有两个分别指向两个不相等和在整个树结构中不相交的空间的指针。根据定义和习惯,左子树的指针命名为left,右子树的指针命名为right。
3.二叉树存储的实现
3.1使用队列
二叉树存储实现的方式有很多,这里采用的是层次存储,使用到了队列,因此,还需要定义一个队列。
typedef struct BTNodePtrQueue {
BTNodePtr* nodePtr;
int front;
int rear;
}BTNodePtrQueue,*QueuePtr;
指针nodePtr指向二叉树的节点,整形front和rear则表示该队列队首和队尾的下标。
3.2队列的初始化
#define QUEUE_SIZE 5
QueuePtr initQueue()
{
QueuePtr resultQueuePtr = (QueuePtr)malloc(sizeof(BTNodePtrQueue));
resultQueuePtr->nodePtr = (BTNodePtr*)malloc(QUEUE_SIZE * sizeof(BTNode));
resultQueuePtr->front = 0;
resultQueuePtr->rear = 1;
return resultQueuePtr;
}//off initQueue
定义了一个队列,nodePtr可以存储5个树结构的指针,队首为0,队尾为1。
顺便定义一个判断队列是否为空队的函数,空队返回1,否则返回0。
bool isQueueEmpty(QueuePtr paraQueuePtr)
{
if ((paraQueuePtr->front + 1) % QUEUE_SIZE == paraQueuePtr->rear)
{
return true;
}//off if
return false;
}//off isQueueEmpty
3.3入队和出队
void enqueue(QueuePtr paraQueuePtr, BTNodePtr paraBTNodePtr)
{
printf("front is %d, rear is %d\r\n", paraQueuePtr->front, paraQueuePtr->rear);
if ((paraQueuePtr->rear + 1) % QUEUE_SIZE == paraQueuePtr->front % QUEUE_SIZE)
{
printf("Error,queue full\r\n");
return;
}//off if
paraQueuePtr->nodePtr[paraQueuePtr->rear] = paraBTNodePtr;
paraQueuePtr->rear = (paraQueuePtr->rear + 1) % QUEUE_SIZE;
printf("enqueue %c\r\n", paraBTNodePtr->element);
}//off enqueue
BTNodePtr dequeue(QueuePtr paraQueuePtr)
{
if (isQueueEmpty(paraQueuePtr))
{
printf("Error, queue empty\r\n");
return NULL;
}//off if
paraQueuePtr->front = (paraQueuePtr->front + 1) % QUEUE_SIZE;
printf("dequeue %c\r\n", paraQueuePtr->nodePtr[paraQueuePtr->front]->element);
return paraQueuePtr->nodePtr[paraQueuePtr->front];
}//off dequeue
3.4通过队列实现二叉树的层次存储
要实现二叉树的层次存储,要使用到队列。具体思路是:先在队列中存入一个节点的指针,再连续存入两个节点的指针,第一个节点的指针表示根节点存储“1”,即第一层,后续两个指针按顺序表示为左子树存储“2”和右子树存储“3”,为第二层。若后续存入再存入两个节点“4”、“5”的指针,那这两个指针就为存入“2”的指针的左子树和右子树,此时为第三层,若再存入两个节点“6”、“7”,那么他们就是“3”的左子树和右子树。若队列的长度无限且不考虑入队出队,树的层次存储在队列中的表现就如下图所示
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
节点 | “1” | “2” | “3” | “4” | “5” | “6” | “7” | ||
层次 | 第一层(根节点,不是左右子树) | 第二层(“1”的左子树) | 第二层(“1”的右子树) | 第三层(“2”的左子树) | 第三层(“2”的右子树) | 第三层(“3”的左子树) | 四三层(“3”的右子树) |
因此我们可以进行二叉树层次存储的代码实现
BTNodePtr constructBTNode(char paraChar)
{
BTNodePtr resultPtr = (BTNodePtr)malloc(sizeof(BTNode));
resultPtr->element = paraChar;
resultPtr->left = NULL;
resultPtr->right = NULL;
return resultPtr;
}//off constructBTNode
BTNodePtr stringToBTree(const char* paraString)
{
int i;
char ch;
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);
i++;
ch = paraString[i];
if (ch == '#')
{
tempParent->left = NULL;
}//off if
else
{
tempLeftChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempLeftChild);
tempParent->left = tempLeftChild;
}//off else
i++;
ch = paraString[i];
if (ch == '#')
{
tempParent->right = NULL;
}//off if
else
{
tempRightChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempRightChild);
tempParent->right = tempRightChild;
}//off else
}//off while
return resultHeader;
}//off stringToBTree
constructBTNode函数很简单,就是定义一个新节点,存储新的数据,使左右子树指向空,再返回该节点。stringToBTree函数比较复杂,是前面提到的函数的结合体,面对复杂的问题,要分布解决,因此我们将该函数拆分成三部分。
3.4.1第一部分:函数开头到while循环
这一部分主要是对各种变量的定义和处理,比如定义了一个队列tempQueuePtr;还定义了四个树结构resultHeader(根节点),tempParent(父节点),tempLeftChild(左子树)和tempRightChild(右子树)。让字符ch承接字符串paraString,让根节点resultHeader存入ch,并将resultHeader放入队列tempQueuePtr的队尾。
3.4.2第二部分:while循环到第一个if else语句结束
在添加新节点前要判断队列tenpQueuePtr是否为空队,若是空队则代表没有根节点,直接返回没有根节点的空树resultHeader。while循环中两个if else语句分别表示左子树的添加和右子树的添加,两个语句除了对象都一样,所以只说左子树的添加。上文中提及了队列tempQueuePtr的队首是下标0,根节点在下标1,所以在树添加新节点前要先进行出队操作,这样队首就是下标1的根节点,同时根节点在这次的循环中被赋值给父节点tempParent,ch承接字符串paraString的第二个元素,若ch是节点结束符号“#”,则tempParent的左子树赋值空,否则tempLeftChild存储ch,让tempLeftChild入队,tempParent->left赋值tempLeftChild,完成二叉树的添加左子树分支。
3.4.3第三部分:返回树根节点
代码实现的是树的存储,队列只是实现层次存储的工具,所以返回的依然根节点。
4.二叉树的前序,中序,后序和层次遍历
二叉树是非线性结构,无法像线性结构链表的遍历一样用一个for循环就能实现,这里使用的方法是递归。
4.1前序遍历
void preorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
printf("%c", tempPtr->element);
preorder(tempPtr->left);
preorder(tempPtr->right);
}//off preoeder
前序遍历指的是每到一个节点先打印该节点的数据,再往下递归,如果我们用树结构层次存储1234567,通过前序遍历最后会得到1245367。(下图中箭头数字指向为打印的数据,且箭头顺序为打印顺序)
4.2中序遍历
void inorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
inorder(tempPtr->left);
printf("%c", tempPtr->element);
inorder(tempPtr->right);
}//off inorder
中序遍历时,先进行左子树的递归,直到根节点的左子树走到尽头(为空),然后打印该节点的数据,再进行右子树的递归。层次存储1234567,得到4251637。
4.3后序遍历
void postorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
postorder(tempPtr->left);
postorder(tempPtr->right);
printf("%c", tempPtr->element);
}//off postorder
后序遍历为先递归左子树,再递归右子树,最后打印数据。层次存储1234567,后序遍历得到4526731。
4.4层次遍历
void levelwise(BTNodePtr paraTreePtr)
{
char tempString[100] = { 0 };
int i = 0;
QueuePtr tempQueuePtr = initQueue();
BTNodePtr tempNodePtr;
enqueue(tempQueuePtr, paraTreePtr);
while (!isQueueEmpty(tempQueuePtr))
{
tempNodePtr = dequeue(tempQueuePtr);
tempString[i] = tempNodePtr->element;
i++;
if (tempNodePtr->left != NULL)
{
enqueue(tempQueuePtr, tempNodePtr->left);
}//off if
if (tempNodePtr->right != NULL)
{
enqueue(tempQueuePtr, tempNodePtr->right);
}//off if
}//off while
tempString[i] = '\0';
printf("levelwise: %s\r\n", tempString);
}//off levelwise
二叉树的层次遍历需要用到队列,因此代码比前序、中序、后序遍历要复杂。首先定义一个字符串和一个队列,将二叉树根节点入队。while循环依然是根据队列是否为空(二叉树层次遍历是否结束),新定义的树节点tenmpNodePtr承接出队后的新队首paraTreePtr,字符串存储该节点的数据,若左子树不为空,该节点的左子树入队,若右子树不为空,右子树入队。重复循环直到二叉树层次遍历结束,最后打印字符串完成层次遍历。
5完整代码和测试结果
#include<stdio.h>
#include<stdlib.h>
#define QUEUE_SIZE 5
typedef struct BTNode {
char element;
BTNode* left;
BTNode* right;
}BTNode,*BTNodePtr;
typedef struct BTNodePtrQueue {
BTNodePtr* nodePtr;
int front;
int rear;
}BTNodePtrQueue,*QueuePtr;
QueuePtr initQueue()
{
QueuePtr resultQueuePtr = (QueuePtr)malloc(sizeof(BTNodePtrQueue));
resultQueuePtr->nodePtr = (BTNodePtr*)malloc(QUEUE_SIZE * sizeof(BTNode));
resultQueuePtr->front = 0;
resultQueuePtr->rear = 1;
return resultQueuePtr;
}//off initQueue
bool isQueueEmpty(QueuePtr paraQueuePtr)
{
if ((paraQueuePtr->front + 1) % QUEUE_SIZE == paraQueuePtr->rear)
{
return true;
}//off if
return false;
}//off isQueueEmpty
void enqueue(QueuePtr paraQueuePtr, BTNodePtr paraBTNodePtr)
{
printf("front is %d, rear is %d\r\n", paraQueuePtr->front, paraQueuePtr->rear);
if ((paraQueuePtr->rear + 1) % QUEUE_SIZE == paraQueuePtr->front % QUEUE_SIZE)
{
printf("Error,queue full\r\n");
return;
}//off if
paraQueuePtr->nodePtr[paraQueuePtr->rear] = paraBTNodePtr;
paraQueuePtr->rear = (paraQueuePtr->rear + 1) % QUEUE_SIZE;
printf("enqueue %c\r\n", paraBTNodePtr->element);
}//off enqueue
BTNodePtr dequeue(QueuePtr paraQueuePtr)
{
if (isQueueEmpty(paraQueuePtr))
{
printf("Error, queue empty\r\n");
return NULL;
}//off if
paraQueuePtr->front = (paraQueuePtr->front + 1) % QUEUE_SIZE;
printf("dequeue %c\r\n", paraQueuePtr->nodePtr[paraQueuePtr->front]->element);
return paraQueuePtr->nodePtr[paraQueuePtr->front];
}//off dequeue
BTNodePtr constructBTNode(char paraChar)
{
BTNodePtr resultPtr = (BTNodePtr)malloc(sizeof(BTNode));
resultPtr->element = paraChar;
resultPtr->left = NULL;
resultPtr->right = NULL;
return resultPtr;
}//off constructBTNode
BTNodePtr stringToBTree(const char* paraString)
{
int i;
char ch;
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);
i++;
ch = paraString[i];
if (ch == '#')
{
tempParent->left = NULL;
}//off if
else
{
tempLeftChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempLeftChild);
tempParent->left = tempLeftChild;
}//off else
i++;
ch = paraString[i];
if (ch == '#')
{
tempParent->right = NULL;
}//off if
else
{
tempRightChild = constructBTNode(ch);
enqueue(tempQueuePtr, tempRightChild);
tempParent->right = tempRightChild;
}//off else
}//off while
return resultHeader;
}//off stringToBTree
void levelwise(BTNodePtr paraTreePtr)
{
char tempString[100] = { 0 };
int i = 0;
QueuePtr tempQueuePtr = initQueue();
BTNodePtr tempNodePtr;
enqueue(tempQueuePtr, paraTreePtr);
while (!isQueueEmpty(tempQueuePtr))
{
tempNodePtr = dequeue(tempQueuePtr);
tempString[i] = tempNodePtr->element;
i++;
if (tempNodePtr->left != NULL)
{
enqueue(tempQueuePtr, tempNodePtr->left);
}//off if
if (tempNodePtr->right != NULL)
{
enqueue(tempQueuePtr, tempNodePtr->right);
}//off if
}//off while
tempString[i] = '\0';
printf("levelwise: %s\r\n", tempString);
}//off levelwise
void preorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
printf("%c", tempPtr->element);
preorder(tempPtr->left);
preorder(tempPtr->right);
}//off preoeder
void inorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
inorder(tempPtr->left);
printf("%c", tempPtr->element);
inorder(tempPtr->right);
}//off inorder
void postorder(BTNodePtr tempPtr)
{
if (tempPtr == NULL)
{
return;
}//off if
postorder(tempPtr->left);
postorder(tempPtr->right);
printf("%c", tempPtr->element);
}//off postorder
int main()
{
BTNodePtr tempHeader;
tempHeader = constructBTNode('c');
printf("there is only one node. preorder visit:");
preorder(tempHeader);
printf("\r\n\n");
const char* tempString = "1234567########";
tempHeader = stringToBTree(tempString);
printf("\npreorder: ");
preorder(tempHeader);
printf("\r\n\n");
printf("inorder: ");
inorder(tempHeader);
printf("\r\n\n");
printf("postorder: ");
postorder(tempHeader);
printf("\r\n\n");
printf("levelwise: ");
levelwise(tempHeader);
printf("\r\n\n");
return 0;
}