今天和大家讨论的是二叉树,在讨论二叉树之前,我们在之前也接触了一维的线性存储结构,比如数组,双端链表,以及对双端的链表的封装形成栈和队列,之后又将数组和双端链表的结合形成hash表,在存储和查找方面解决了很多问题,但是一维线性存储结构仍然存在问题,比如一维的线性数据结构在查找时的效率只能达到o(n)的时间复杂度,所以为了解决上述问题,我们引入树型数据结构。
最常见的树型结构为二叉树,二叉树有左右两个孩子,在处理二叉树时基本都采用了递归的思想,在本文章中也会给大家分享一些非递归的操作,在非递归操作时就需要借助于栈和队列。那么首先对于栈和队列进行封装,本文章中的栈和队列都是在双端链表的基础上进行封装,对于双端链表的实现在之前的博客中已经详细的进行了介绍,今天则主要说明栈、队列、二叉树的接口实现:
我们先来说明栈的实现:
stack.h:
#ifndef _STACK_H_ #define _STACK_H_ #include "dlist.h" //用通用双端链表实现通用栈 // //栈的功能是为了保存和记忆 typedef struct Stack{ Dlist *dlist; //使用双端链表封装栈 }Stack; //栈的接口 Stack *init_stack(void) ; //栈的初始化 void destroy_stack(Stack **stack) ; //栈的销毁 Boolean get_top(Stack *stack, void **value); //得到栈顶元素 Boolean pop(Stack *stack) ; //出栈 void push(Stack *stack, void *value) ; //入栈 Boolean is_stack_empty(Stack *stack) ; //判断栈是否为空 int get_stack_count(Stack *stack) ; //得到栈元素 #endif
#include <stdio.h> #include <stdlib.h> #include "stack.h" Stack *init_stack(void) //栈的初始化 { Stack *stack = (Stack *)Malloc(sizeof(Stack)); stack->dlist = init_dlist(); return stack; } void destroy_stack(Stack **stack) //栈的销毁 { if(stack == NULL || *stack == NULL){ return ; } //1.先销毁双端链表,再销毁stack控制信息 destroy_dlist(&((*stack)->dlist)); free(*stack); *stack = NULL; } Boolean get_top(Stack *stack, void **value) //得到栈顶元素 { if(stack == NULL || is_stack_empty(stack)){ return FALSE; } if(value != NULL){ get_front(stack->dlist, value); return TRUE; } } Boolean pop(Stack *stack) //出栈 { if(stack == NULL || is_stack_empty(stack)){ return FALSE; } pop_front(stack->dlist); return TRUE; } void push(Stack *stack, void *value) //入栈 { if(stack == NULL || value == NULL){ return ; } push_front(stack->dlist, value); } Boolean is_stack_empty(Stack *stack) //判断栈是否为空 { return get_stack_count(stack) == ZERO; } int get_stack_count(Stack *stack) //得到栈元素 { if(stack == NULL){ return -1; } return get_dlist_count(stack->dlist); }上述则是对栈的描述,下面我们来看对于队列(queue)的实现:
首先我们来看queue.h:
#ifndef _QUEUE_H_ #define _QUEUE_H_ #include "dlist.h" typedef struct Queue{ Dlist *dlist; }Queue; //队列的接口 Queue *init_queue(void) ; //队列的初始化 void destory_queue(Queue **queue) ; //队列的销毁 Boolean get_queue_front(Queue *queue, void **value); //得到对首元素 Boolean is_queue_empty(Queue *queue) ; //判断队列是否为空 void in(Queue *queue, void *value) ; //入队 Boolean out(Queue *queue) ; //出队 int get_queue_count(Queue *queue) ; //得到队列元素个数 #endif
#include <stdio.h> #include <stdlib.h> #include "queue.h" Queue *init_queue(void) //队列的初始化 { Queue * queue = NULL; queue = (Queue *)Malloc(sizeof(Queue)); queue -> dlist = init_dlist(); return queue; } void destory_queue(Queue **queue) //队列的销毁 { if(queue == NULL || *queue == NULL){ return ; } destroy_dlist (&((*queue) -> dlist)); free(*queue); *queue = NULL; } Boolean get_queue_front(Queue *queue, void **value) //得到对首元素 { if(queue == NULL || is_queue_empty(queue)){ return FALSE; } if(value != NULL){ return get_tail(queue -> dlist , value); } return FALSE; } Boolean is_queue_empty(Queue *queue) //判断队列是否为空 { return get_queue_count(queue) == ZERO; } void in(Queue *queue, void *value) //入队 { if(queue == NULL || value == NULL){ return ; } push_front(queue->dlist,value); } Boolean out(Queue *queue) //出队 { if(queue == NULL || is_queue_empty(queue)){ return FALSE; } return pop_back(queue->dlist); } int get_queue_count(Queue *queue) //得到队列元素个数 { if(queue == NULL){ return -1; } return get_dlist_count(queue->dlist); }至此,栈(stack)和队列(queue)的封装已实现。。。
下面我们来看二叉树的实现:
对于二叉树的节点,我们不仅要记录其数据域的值,还要记录其左孩子和右孩子的值。对于binary_tree.h的定义如下:
#ifndef _BINARY_TREE_H_ #define _BINARY_TREE_H_ #include "tools.h" typedef struct Tree_node{ char data; //数据区域 struct Tree_node *left_child ; //左孩子 struct Tree_node *right_child; //右孩子 }Tree_node; typedef Tree_node *Bin_tree; //二叉树根节点指针 //二叉树的接口 //二叉树的创建和销毁 Bin_tree create_tree(char **string); //创建二叉树 Bin_tree create_tree_by_pre_mid(const char *pre_str, const char *mid_str, int length); //通过先序和中序构建二叉树 Bin_tree create_tree_by_mid_last(const char *mid_str, const char *last_str, int length); //通过中序和后序构建二叉树 void destroy_tree(Bin_tree *root); //二叉树的销毁 void destroy_tree_nr(Bin_tree *root); //二叉树的非递归销毁 //二叉树的遍历(先序、中序、后序、层序) void pre_order_print(Bin_tree root); //先序遍历(递归) void mid_order_print(Bin_tree root); //中序遍历(递归) void last_order_print(Bin_tree root); //后序遍历(递归) void pre_order_print_nr(Bin_tree root); //先序遍历(非递归) void mid_order_print_nr(Bin_tree root); //中序遍历(非递归) void last_order_print_nr(Bin_tree root); //后序遍历(非递归) void level_order_print(Bin_tree root); //层序遍历 //二叉树的性质(高度、节点个数、叶子节点个数) int get_binarytree_height(Bin_tree root); //得到二叉树的高度 int get_binarytree_node_count(Bin_tree root); //得到二叉树的节点个数 int get_binarytree_leaf_count(Bin_tree root); //得到二叉树的叶子节点个数 int get_binarytree_level_count(Bin_tree root, int level); //得到二叉树指定层级节点个数 //判断是否是满二叉树、完全二叉树、平衡二叉树 Boolean is_full_binarytree(Bin_tree root); //是否是满二叉树 Boolean is_complete_binarytree(Bin_tree root); //是否是完全二叉树 Boolean is_balance_binarytree(Bin_tree root); //是否是平衡二叉树 //其他操作 Bin_tree copy_binarytree(Bin_tree root); //二叉树的拷贝 Boolean is_binarytree_equal(Bin_tree root1, Bin_tree root2); //判断两个二叉树是否相等 Tree_node *find_value(Bin_tree root, char value); //找到指定数值的节点 Tree_node *find_common_parent(Bin_tree root, Tree_node *node1, Tree_node *node2); //找到两个节点的最近双亲节点 Tree_node *find_parent(Bin_tree root, Tree_node *node); //找到指定节点的双亲节点 Boolean is_include_tree(Bin_tree root1, Bin_tree root2); //判断二叉树是否有包含关系 #endif
(1)二叉树的创建:
今天我们利用字符串创建二叉树,当遇到普通字符时我们直接创建二叉树节点,当遇到‘#’字符的时候我们认为该二叉树节点不存在,并且当二叉树的节点存在后,再遇到普通字符认为是其左孩子,当左孩子存在时再遇到普通字符我们认为是右孩子,下面我们举一个例子。
例:
ABC##DE##F##GH#I##J##
则其对应的二叉树如下图所示:
二叉树的创建代码如下:
Bin_tree create_tree(char **string) //创建二叉树 { //"ABC##DE##F##GH#I##J##" // // // A // / \ // B G // / \ / \ // C D H J // / \ \ // E F I // 先序:ABCDEFGHIJ // 中序:CBEDFAHIGJ // 后序:CEFDBIHJGA Bin_tree root = NULL; if(string != NULL && *string != NULL && **string != '#'){ root = create_tree_node(); root ->data = **string; (*string)++; root ->left_child = create_tree(string); (*string)++; root ->right_child = create_tree(string); } return root; }使用遍历结果创建二叉树:
先序:A B C D E F G H I J
中序:C B E D F A H I G J
后序:C E F D B I H J G A
我们可以通过先序和中序唯一确定二叉树,在先序的遍历中,在先序遍历中,我们可以很容易确定根节点,然后在中序中先确定出根节点的位置,然后左递归,右递归进行创建,其代码实现如下;
static int find_index(const char * pre_str,char ch) { char *find = NULL; return (find = strchr(pre_str,ch)) ? (find - pre_str) : -1; } Bin_tree create_tree_by_pre_mid(const char *pre_str, const char *mid_str, int length) //通过先序和中序构建二叉树 { // 先序:ABCDEFGHIJ // 中序:CBEDFAHIGJ Bin_tree root = NULL; int root_index = -1; char root_value = 0; if(pre_str == NULL || mid_str == NULL || length <= 0){ return root; } root_value = pre_str[0]; root = create_tree_node(); root ->data = root_value; root_index = find_index(mid_str,root_value); root ->left_child = create_tree_by_pre_mid(pre_str + 1,mid_str,root_index); root ->right_child = create_tree_by_pre_mid(pre_str + root_index + 1, mid_str + root_index + 1,length - root_index - 1); return root; }
Bin_tree create_tree_by_mid_last(const char *mid_str, const char *last_str, int length) //通过中序和后序构建二叉树 { // 中序:CBEDFAHIGJ // 后序:CEFDBIHJGA Bin_tree root = NULL; char root_value = 0; int root_index = -1; if(mid_str == NULL || last_str == NULL || length <= 0){ return root; } root_value = last_str[length - 1]; root = create_tree_node(); root ->data = root_value; root_index = find_index(mid_str,root_value); root ->left_child = create_tree_by_mid_last(mid_str,last_str,root_index); root ->right_child = create_tree_by_mid_last(mid_str + root_index + 1, last_str + root_index ,length - root_index - 1); return root; }
对于二叉树的销毁,我们应该采用递归的思想,先销毁其左孩子,再销毁其右孩子,如果直接销毁根节点,则会导致二叉树的丢失。我们先来看利用递归销毁二叉树,其代码如下:
void destroy_tree(Bin_tree *root) //二叉树的销毁 { if(root == NULL || *root == NULL){ return; } destroy(*root); *root = NULL; } static void destroy(Bin_tree root) { if(root != NULL){ destroy(root ->left_child); destroy(root ->right_child); free(root); } }
void destroy_tree_nr(Bin_tree *root) //二叉树的非递归销毁 { Queue *queue = NULL; Tree_node *node = NULL; if(root == NULL || *root == NULL){ return; } queue = init_queue(); in(queue,*root); while(!is_queue_empty(queue)){ get_queue_front(queue,(void **)&node); out(queue); *root = NULL; if(node ->left_child){ in(queue,node ->left_child); } if(node ->right_child){ in(queue,node ->right_child); } free(node); } destory_queue(&queue); }
(3)接下来我们讨论关于二叉树的遍历:
对于二叉树的遍历,我们可以分为两种,第一种采用递归方式进行遍历,第二种则采用非递归的方式进行遍历;我们首先实现递归方式的遍历:
//二叉树的遍历(先序、中序、后序、层序) void pre_order_print(Bin_tree root) //先序遍历(递归) { if(root){ printf("%c ",root->data); pre_order_print(root ->left_child); pre_order_print(root ->right_child); } } void mid_order_print(Bin_tree root) //中序遍历(递归) { if(root){ mid_order_print(root ->left_child); printf("%c ",root->data); mid_order_print(root ->right_child); } } void last_order_print(Bin_tree root) //后序遍历(递归) { if(root){ last_order_print(root ->left_child); last_order_print(root ->right_child); printf("%c ",root->data); } }
void pre_order_print_nr(Bin_tree root) //先序遍历(非递归) { // A // / \ // B G // / \ / \ // C D H J // / \ \ // E F I // 先序:ABCDEFGHIJ Stack *stack = NULL; Tree_node *node = NULL; if(root == NULL){ return; } stack = init_stack(); push(stack,root); while(!is_stack_empty(stack)){ get_top(stack,(void **)&node); pop(stack); printf("%c ",node ->data); if(node ->right_child){ push(stack,node ->right_child); } if(node ->left_child){ push(stack,node ->left_child); } } destroy_stack(&stack); }中序、后序的非递归遍历代码如下:
void mid_order_print_nr(Bin_tree root) //中序遍历(非递归) { Stack *stack = NULL; Tree_node *node = NULL; if(root == NULL){ return; } stack = init_stack(); node = root; while(!is_stack_empty(stack) || node != NULL){ while(node != NULL){ push(stack,node); node = node ->left_child; } get_top(stack,(void **)&node); printf("%c ",node ->data); pop(stack); node = node ->right_child; } destroy_stack(&stack); } void last_order_print_nr(Bin_tree root) //后序遍历(非递归) { Stack *stack = NULL; Tree_node *node = NULL; Tree_node *prev = NULL; if(root == NULL){ return; } stack = init_stack(); node = root; push(stack,node); while(!is_stack_empty(stack)){ get_top(stack,(void **)&node); if((node ->left_child == NULL && node ->right_child == NULL) || (prev != NULL && (node ->left_child == prev || node ->right_child == prev))){ printf("%c ",node ->data); pop(stack); prev = node; }else{ if(node ->left_child){ push(stack,node ->left_child); } if(node ->right_child){ push(stack,node ->right_child); } } } destroy_stack(&stack); }
void level_order_print(Bin_tree root) //层序遍历 { Queue *queue = NULL; Tree_node *node = NULL; if(root == NULL){ return; } queue = init_queue(); in(queue,root); while(!is_queue_empty(queue)){ get_queue_front(queue,(void **)&node); out(queue); printf("%c ",node ->data); if(node ->left_child){ in(queue,node ->left_child); } if(node ->right_child){ in(queue,node ->right_child); } } destory_queue(&queue); }(4)二叉树的性质:
得到二叉树的高度、叶子节点个数:
static int max(int a,int b) { return (a > b) ? a : b; } //二叉树的性质(高度、节点个数、叶子节点个数) int get_binarytree_height(Bin_tree root) //得到二叉树的高度 { if(root == NULL){ return 0; } return max(get_binarytree_height(root ->left_child), get_binarytree_height(root ->right_child)) + 1;<pre name="code" class="plain">int get_binarytree_leaf_count(Bin_tree root) //得到二叉树的叶子节点个数 { if(root == NULL){ return 0; }else if((!root ->left_child) && (!root ->right_child)){ return 1; }else{ return get_binarytree_leaf_count(root ->left_child) + get_binarytree_leaf_count(root ->right_child); } }
int get_binarytree_node_count(Bin_tree root) //得到二叉树的节点个数 { if(root == NULL){ return 0; } return 1 + get_binarytree_node_count(root ->left_child) + get_binarytree_node_count(root ->right_child); }(5)判断二叉树的状态:(两树是否相等、是否是满二叉树、完全二叉树、平衡二叉树)
判断两树是否相等,其代码如下:
Boolean is_binarytree_equal(Bin_tree root1, Bin_tree root2) //判断两个二叉树是否相等 { if(root1 == NULL || root2 == NULL){ return TRUE; }else if(root1 && root2 && root1 ->data == root2 ->data && is_binarytree_equal(root1 ->left_child,root2 ->left_child) && is_binarytree_equal(root1 ->right_child,root2 ->right_child)){ return TRUE; }else { return FALSE; } }判断是否是满二叉树,我们可以利用二叉树的性质进行判断,一个高度为h的二叉树,对于满二叉树其节点个数为2 ^ h - 1
Boolean is_full_binarytree(Bin_tree root) //是否是满二叉树 { int height = 0; int node_count = 0; if(root == NULL){ return FALSE; } height = get_binarytree_height(root); node_count = get_binarytree_node_count(root); return ((int)pow(2,height) - 1) == node_count; }
Boolean is_complete_binarytree(Bin_tree root) //是否是完全二叉树 { Boolean find_first = FALSE; Queue *queue = NULL; Tree_node *node = NULL; if(root == NULL){ return FALSE; } queue = init_queue(); in(queue,root); while(!is_queue_empty(queue)){ get_queue_front(queue,(void **)&node); out(queue); if(find_first == FALSE){ if(node ->left_child && node ->right_child){ in(queue,node ->left_child); in(queue,node ->right_child); }else if(node ->left_child == NULL && node ->right_child){ return FALSE; }else if(node ->left_child && node ->right_child == NULL){ find_first = TRUE; in(queue,node ->left_child); }else { find_first = TRUE; } }else{ if(node ->left_child != NULL || node ->right_child != NULL){ return FALSE; } } } destory_queue(&queue); return TRUE; }
Boolean is_balance_binarytree(Bin_tree root) //是否是平衡二叉树 { int height_left = 0; int height_right = 0; Boolean left = FALSE; Boolean right = FALSE; if(root == NULL){ return TRUE; } height_left = get_binarytree_height(root ->left_child); height_right = get_binarytree_height(root ->right_child); left = is_balance_binarytree(root ->left_child); right = is_balance_binarytree(root ->right_child); return (left && right && abs(height_left - height_right) <= 1) ? TRUE : FALSE; }
二叉树的拷贝,代码如下:
Bin_tree copy_binarytree(Bin_tree root) //二叉树的拷贝 { Bin_tree root1 = NULL; if(root != NULL){ root1 = create_tree_node(); root1 ->data = root ->data; root1 ->left_child = copy_binarytree(root ->left_child); root1 ->right_child = copy_binarytree(root ->right_child); } return root1; }
Tree_node *find_value(Bin_tree root, char value) //找到指定数值的节点 { Tree_node *node = NULL; if(root == NULL || root ->data == value){ return root; } node = find_value(root ->left_child,value); if(node == NULL){ node = find_value(root ->right_child,value); } return node; }找到指定节点的双亲节点:
Tree_node *find_parent(Bin_tree root, Tree_node *node) //找到指定节点的双亲节点 { Tree_node *p_node = NULL; if(root == NULL || root ->left_child == node || root ->right_child == node){ return root; } p_node = find_parent(root ->left_child,node); if(p_node == NULL){ p_node = find_parent(root ->right_child,node); } return p_node; }
找到两个节点的最近双亲节点:首先处理特殊情况,当一个节点在根节点的左边,并且另一个节点在根节点的右边,或者一个节点在根结点的右边,并且另一个节点在根结点的左边,则根是它们最近的双亲节点;当两个节点存在继承关系时,可以通过得到两个节点的高度,则可以判断出谁继承谁,从而可以找到最近的双亲的节点,当两个节点都在一边,并且没有继承关系时,可以进行递归调用,其代码如下:
static Tree_node *find_common(Bin_tree root,Tree_node *node1, Tree_node *node2) { if(find_value(root->left_child,node1 ->data)){ if(find_value(root ->right_child,node2 ->data)){ return root; }else{ return find_common(root ->left_child,node1,node2); } }else{ if(find_value(root ->left_child,node2 ->data)){ return root; }else{ return find_common(root ->right_child,node1,node2); } } } Tree_node *find_common_parent(Bin_tree root, Tree_node *node1, Tree_node *node2) //找到两个节点的最近双亲节点 { int height1 = 0; int height2 = 0; if(root == NULL || node1 == NULL || node2 == NULL){ return NULL; } height1 = get_binarytree_height(node1); height2 = get_binarytree_height(node2); if(height1 > height2){ if(find_value(node1,node2->data)){ return find_parent(root,node1); } }else if(height1 < height2){ if(find_value(node2,node1->data)){ return find_parent(root,node2); } }else{ return find_common(root,node1,node2); } }
static Boolean root1_has_root2(Bin_tree root1,Bin_tree root2) { if(root2 == NULL){ return TRUE; } if(root1 == NULL){ return FALSE; } if(root1->data != root2 ->data){ return FALSE; } return root1_has_root2(root1->left_child,root2 ->left_child) && root1_has_root2(root1->right_child,root2->right_child); } Boolean is_include_tree(Bin_tree root1, Bin_tree root2) //判断二叉树是否有包含关系 { Boolean ok = FALSE; if(root1 != NULL && root2 != NULL){ if(root1 ->data == root2 ->data){ ok = root1_has_root2(root1,root2); } if(ok == FALSE){ ok = is_include_tree(root1 ->right_child,root2 ->right_child); } if(ok == FALSE){ ok = is_include_tree(root1 ->left_child,root2 ->left_child); } } return ok;
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "binary_tree.h" int main(int argc, char **argv) { Bin_tree root = NULL; Bin_tree root1 = NULL; Bin_tree root2 = NULL; Bin_tree root3 = NULL; Tree_node *p_node = NULL; Tree_node *parent = NULL; Tree_node *node1 = NULL; Tree_node *node2 = NULL; char *string = "ABC##DE##F##GH#I##J##"; char *pre_str = "ABCDEFGHIJ"; char *mid_str = "CBEDFAHIGJ"; char *last_str = "CEFDBIHJGA"; int length = strlen(pre_str); root = create_tree(&string); #if 1 printf("pre order:\n"); pre_order_print(root); printf("\n"); printf("mid order:\n"); mid_order_print(root); printf("\n"); printf("last order:\n"); last_order_print(root); printf("\n"); printf("level order:\n"); level_order_print(root); printf("\n"); printf("the height of root:%d\n", get_binarytree_height(root)); printf("the node count of root:%d\n", get_binarytree_node_count(root)); root1 = create_tree_by_pre_mid(pre_str, mid_str, length); printf("root1 level order:\n"); level_order_print(root1); printf("\n"); root2 = create_tree_by_mid_last(mid_str, last_str, length); printf("root2 level order:\n"); level_order_print(root2); printf("\n"); if(is_complete_binarytree(root2)){ printf("root2 is complate tree!\n"); }else{ printf("root2 is not complate tree!\n"); } if(is_balance_binarytree(root2)){ printf("root2 is balance tree!\n"); }else{ printf("root2 not is balance tree!\n"); } root3 = copy_binarytree(root1); printf("root3 level order:\n"); level_order_print(root3); printf("\n"); if(is_binarytree_equal(root2, root3)){ printf("root2 equals root3!\n"); } if((p_node = find_value(root3, 'A'))){ printf("the value %c was found!\n", p_node->data); }else{ printf("the M is not found!\n"); } if((parent = find_parent(root3, p_node))){ printf("%c parent found:%c\n", p_node->data, parent->data); }else{ printf("%c parent not found\n",p_node ->data); } node1 = find_value(root3, 'B'); node2 = find_value(root3, 'I'); if((parent = find_common_parent(root3, node1, node2))){ printf("the common parent of %c and %c is:%c\n", node1->data, node2->data, parent->data); }else{ printf("%c and %c has no common parent:\n",node1 ->data,node2 ->data); } if(is_include_tree(root3, node1)){ printf("root3 has node1!\n"); }else{ printf("root3 has not node1!\n"); } printf("pre order:\n"); pre_order_print_nr(root); printf("\n"); #endif //destroy_tree(&root); destroy_tree_nr(&root); destroy_tree_nr(&root1); destroy_tree_nr(&root2); destroy_tree_nr(&root3); return 0; }其测试结果:
至此基本二叉树的操作已经完成,此后还会和大家分享AVL树、b树的一些基本操作,敬请期待!!!!!