数据结构——二叉树
一、二叉树的递归定义
- 要么二叉树没有根节点,是一棵空树。
- 要么二叉树由根节点、左子树、右子树组成,且左子树和右子树都是二叉树。
二、二叉树的存储结构
一般来说,二叉树使用链表来定义。二叉树每个结点有两条出边,因此指针域有两个——分别指向左子树和右子树的根结点地址,因此右把这种链表叫做二叉链表。其定义方式如下:
1 #define typename int 2 // 二叉树结构定义 3 struct node { 4 typename data; // 数据域 5 node* lchild; // 指向左子树的根结点 6 node* rchild; // 指向右子树的根结点 7 };
如果需要新建结点,可以使用下面的函数:
1 // 生成一个新节点,v为数据 2 node* newNode(typename v) { 3 node* Node = (node*)malloc(sizeof(node)); 4 Node->data = v; 5 // 初始状态下没有左右子树 6 Node->lchild = Node->rchild = NULL; 7 return Node; 8 }
三、二叉树的基本操作
二叉树的常用操作有以下几个:二叉树的建立、二叉树结点的查找、修改、插入与删除。本节主要介绍查找、修改、插入、建树的通用思想。
1. 二叉树结点的查找、修改
查找操作是指在给定数据域的条件下,在二叉树中找到所有数据域为给定数据域的结点,并将它们的数据域修改为给定的数据域。
可以使用递归来完成查找修改操作。先判断当前结点是否是需要查找的结点:如果是,则对其进行修改操作;如果不是,则分别往该结点的左孩子和右孩子递归,直到当前结点为 NULL 为止。代码如下:
1 // 在根结点为root的二叉树查找所有数据域为x的结点并修改为newdata 2 void search(node* root, typename x, typename newdata) { 3 if(root == NULL) { 4 return; // 空树 5 } 6 if(root->data == x) { // 查到 x 7 root->data = newdata; // 修改 8 } 9 search(root->lchild, x, newdata); // 遍历左子树 10 search(root->rchild, x, newdata); // 遍历右子树 11 }
2. 二叉树结点的插入
二叉树结点的插入位置与二叉树本身的性质有关,下面代码以二叉排序树为例:
1 // 在以*root为根结点的二叉树插入一个新节点 2 void insert(node** root, typename x) { 3 if((*root) == NULL) { // 找到插入位置 4 (*root) = newNode(x); 5 return; 6 } 7 if(x < (*root)->data) { // 往左子树插入 8 insert(&((*root)->lchild), x); 9 } else { // 往右子树插入 10 insert(&((*root)->rchild), x); 11 } 12 }
在上述代码中,函数参数使用了二维指针 **。这么做的原因是,在 insert 函数中新建了结点,并把新结点的地址赋给了当层的 root。
那么,如何判断是否要加指针呢?一般来说,如果函数中需要新建结点,即对二叉树的结构做出修改,就需要加引用;如果只是修改当前已有结点的内容,或仅仅是遍历树,就不用加指针。
3. 二叉树的创建
二叉树的创建其实就是二叉树结点的插入过程,比较常用的写法是把需要插入的数据存储在数组中,然后再将它们使用 insert 函数一个个插入二叉树中,并最终返回根结点的指针 root。代码如下:
// 二叉树的建立 node* create(typename data[], int n) { node* root = NULL; // 新建空根结点 root int i; for(i=0; i<n; ++i) { // 将 data 中的数据插入二叉树 insert(&root, data[i]); } return root; // 返回根结点 }
完整 C 代码如下:
1 /* 2 二叉树 3 */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <math.h> 8 #include <stdlib.h> 9 #include <time.h> 10 #include <stdbool.h> 11 12 #define typename int 13 // 二叉树结构定义 14 typedef struct _node { 15 typename data; // 数据域 16 struct _node* lchild; // 指向左子树的根结点 17 struct _node* rchild; // 指向右子树的根结点 18 } node; 19 20 // 生成一个新节点,v为数据 21 node* newNode(typename v) { 22 node* Node = (node*)malloc(sizeof(node)); 23 Node->data = v; 24 // 初始状态下没有左右子树 25 Node->lchild = Node->rchild = NULL; 26 return Node; 27 } 28 29 // 在根结点为root的二叉树查找所有数据域为x的结点并修改为newdata 30 void search(node* root, typename x, typename newdata) { 31 if(root == NULL) { 32 return; // 空树 33 } 34 if(root->data == x) { // 查到 x 35 root->data = newdata; // 修改 36 } 37 search(root->lchild, x, newdata); // 遍历左子树 38 search(root->rchild, x, newdata); // 遍历右子树 39 } 40 41 // 在以*root为根结点的二叉树插入一个新节点 42 void insert(node** root, typename x) { 43 if((*root) == NULL) { // 找到插入位置 44 (*root) = newNode(x); 45 return; 46 } 47 if(x < (*root)->data) { // 往左子树插入 48 insert(&((*root)->lchild), x); 49 } else { // 往右子树插入 50 insert(&((*root)->rchild), x); 51 } 52 } 53 54 // 二叉树的建立 55 node* create(typename data[], int n) { 56 node* root = NULL; // 新建空根结点 root 57 int i; 58 for(i=0; i<n; ++i) { // 将 data 中的数据插入二叉树 59 insert(&root, data[i]); 60 } 61 return root; // 返回根结点 62 } 63 64 // 输出二叉树,先序 65 void print(node* root) { 66 if(root == NULL) { 67 return; 68 } 69 printf("%d ", root->data); 70 print(root->lchild); 71 print(root->rchild); 72 } 73 74 int main() { 75 int data[5] = {1, 2, 3, 4, 5}; 76 node* tree = create(data, 5); // 创建二叉树 77 print(tree); // 输出二叉树 78 return 0; 79 }