有序二叉树
1)树:
具有一对多的特点
2)二叉树特点:
1.每个节点最多有两个子节点
2.单根性:每个子节点有且仅有一个父节点,整棵树只有一个根节点
左子树:根节点左边的子树
右子树:根节点右边的子树
3.一般用递归函数来处理
3)有序二叉树(二叉的核心)
概念:一般来说,当左子树不为空时,左子树的元素值小于根节点
当右子树不为空时,右子树的元素值大于根节点
一句话:不管站在哪个节点去看,左边总是小于右边
三种遍历方式:
先序遍历:处理节点本身->处理左节点->处理右节点
中序遍历:处理左节点->处理节点本身->处理右节点
后序遍历:处理左节点->处理右节点->处理节点本身
中序遍历读取的有序二叉树是有序的
4)有序二叉树涉及的两个结构体
描述节点属性的结构体
struct node {
int data; //节点数据
struct node *left; //左节点首地址
struct node *right; //右节点首地址
};
struct tree {
struct node *root; //保存根节点的首地址
int cnt; //保存节点的个数
};
参见:有序二叉树的内存示意图!
删除节点:9个字,三字经:找节点,找新爹,提一级
vim tree.h
//tree.h:声明
#ifndef __TREE_H
#define __TREE_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述节点属性的结构体
typedef struct node {
int data; //数据
struct node *left; //保存左节点的首地址
struct node *right;//保存右节点的首地址
}node_t;
//描述二叉树的结构体
typedef struct tree {
struct node *root; //保存根节点的首地址
int cnt; //保存节点个数
}tree_t;
//声明操作函数
extern void tree_travel(tree_t *tree);//遍历
extern void tree_insert(tree_t *tree, int data);//插入新节点
extern void tree_clear(tree_t *tree);//清空所有节点
extern void tree_del(tree_t *tree, int data);//删除data所在的节点
extern void tree_modify(tree_t *tree, int old_data, int new_data);//修改节点数据
#endif
vim tree.c
//tree.c:定义
#include "tree.h"
//travel(50)
// travel(50的左边20)
// travel(20的左边10)
// travel(10的左边NULL)
// return
// printf:10
// travel(10的右边NULL)
// return
// return
// printf:20
// travel(20的右边40)
// travel(40的左边30)
// travel(30的左边NULL)
// return
// printf:30
// travel(30的右边NULL)
// return
// return
// printf:40
// travel(40的右边NULL)
// return
// return
// return
// printf:50
// travel(50的右边70)
// ...
//定义遍历的递归函数
static void travel(node_t *proot) {
#ifdef A
//中序遍历
if(proot != NULL) {
travel(proot->left); //打印左节点数据
printf("%d ", proot->data); //打印节点数据
travel(proot->right); //打印右节点数据
return;
}
#endif
#ifdef B
//先序遍历
if(proot != NULL) {
printf("%d ", proot->data); //打印节点数据
travel(proot->left); //打印左节点数据
travel(proot->right); //打印右节点数据
return;
}
#endif
#ifdef C
//后序遍历
if(proot != NULL) {
travel(proot->left); //打印左节点数据
travel(proot->right); //打印右节点数据
printf("%d ", proot->data); //打印节点数据
return;
}
#endif
return;
}
//定义遍历函数
void tree_travel(tree_t *tree) {
//调用递归函数从根节点开始遍历
travel(tree->root);
printf("\n");
}
//clear(50)
// clear(50的左边20)
// clear(20的左边10)
// clear(10的左边NULL)
// return
// clear(10的右边NULL)
// return
// free(10)
// 20的L->NULL
// return
// clear(20的右边40)
// clear(40的左边30)
// clear(30的左边NULL)
// return
// clear(30的右边NULL)
// return
// free(30)
// 40的L->NULL
// return
// clear(40的右边NULL)
// return
// free(40)
// 20的R->NULL
// return
// free(20)
// 50的L->NULL
// return
// clear(50的右边70)
// ...
//定义清空所有节点的递归函数
static void clear(node_t **pproot) {
if(*pproot != NULL) {
clear(&(*pproot)->left);//释放左节点
clear(&(*pproot)->right);//释放右节点
free(*pproot); //释放节点内存
*pproot = NULL; //让父节点的L和R指向NULL
return;
}
return;
}
//定义清空所有节点的函数
void tree_clear(tree_t *tree) {
//调用递归函数从根节点开始释放所有节点
clear(&tree->root);
tree->cnt = 0; //节点个数清0
}
//定义创建新节点内存函数
static node_t *create_node(int data) {
node_t *pnew = (node_t *)malloc(sizeof(node_t));
pnew->data = data;
pnew->left = NULL;
pnew->right = NULL;
return pnew; //返回新节点首地址
}
//insert(NULL, 50)
// root->50新节点
// return
//cnt++;
//insert(50, 20)
// insert(50的左边NULL, 20)
// 50的L->20
// return
// return
//cnt++;
//insert(50, 70)
// insert(50的右边NULL, 70)
// 50的R->70
// retrun
// return
//cnt++
//insert(50, 10)
// insert(50的左边20, 10)
// insert(20的左边NULL, 10)
// 20的L->10
// return
// return
// return
//cnt++
//定义插入新节点的递归函数
static void insert(node_t **pproot, node_t *pnew) {
if(*pproot == NULL) {
*pproot = pnew; //插入新节点
return;
}
if((*pproot)->data > pnew->data) { //插入到左子树
insert(&(*pproot)->left, pnew);
return;
} else {
insert(&(*pproot)->right, pnew); //插入到右子树
return;
}
return;
}
//定义插入新节点的函数
void tree_insert(tree_t *tree, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.调用递归函数插入新节点
insert(&tree->root, pnew);
//3.更新计数
tree->cnt++;
}
//find(50, 5)
// find(50的左边20, 5)
// find(20的左边10, 5)
// find(10的左边NULL, 5)
// return NULL
// return NULL
// return NULL
// return NULL
//没有找到
//find(50, 80)
// find(50的右边70, 80)
// find(70的右边90, 80)
// find(90的左边80, 80)
// return 80
// return 80
// return 80
// return 80
//找到了
//定义查找节点的递归函数
static node_t **find(node_t **pproot, int data) {
//1.停止查找
if(*pproot == NULL)
return pproot; //没有找到
//2.比较
if((*pproot)->data == data)
return pproot; //找到了
else if((*pproot)->data > data)
return find(&(*pproot)->left, data); //左边找
else
return find(&(*pproot)->right, data); //右边找
}
//定义查找节点的函数
static node_t **find_node(tree_t *tree, int data) {
//调用递归函数从根节点开始查找并且返回对应的二级指针
return find(&tree->root, data);
}
//定义删除指定数字所在的节点函数
void tree_del(tree_t *tree, int data) {
//1.找节点
node_t **ppnode = find_node(tree, data);
if(*ppnode == NULL) {
printf("没有找到节点.\n");
return;
}
//2.找新爹:将删除的节点的左子树插入到右子树上
if((*ppnode)->left != NULL)
insert(&(*ppnode)->right, (*ppnode)->left);
//3.提一级:将指向删除节点的父节点的左子树指向要删除的节点的右子树上
//例如:50的L->40
//(*ppnode):表示要删除的节点,也就是50的L
//(*ppnode)->right:右节点,也就是40
node_t *ptmp = *ppnode; //临时备份要删除的节点,用于将来free
*ppnode = (*ppnode)->right;//50的L->40
free(ptmp); //释放要删除的节点
tree->cnt--; //更新计数
}
//修改节点值的函数
void tree_modify(tree_t *tree, int old_data, int new_data) {
//1.先删除节点
tree_del(tree, old_data);
//2.添加新节点
tree_insert(tree, new_data);
}
vim main.c
#include "tree.h"
int main(void) {
tree_t tree;
tree.root = NULL;
tree.cnt = 0;
tree_insert(&tree, 50);
tree_insert(&tree, 70);
tree_insert(&tree, 60);
tree_insert(&tree, 20);
tree_insert(&tree, 40);
tree_insert(&tree, 30);
tree_insert(&tree, 10);
tree_insert(&tree, 90);
tree_insert(&tree, 80);
tree_travel(&tree);
tree_del(&tree, 40);
tree_del(&tree, 40);
tree_travel(&tree);
tree_modify(&tree, 10, 520);
tree_travel(&tree);
tree_clear(&tree);
tree_travel(&tree);
return 0;
}
vim Makefile
#Makefile
BIN=tree
OBJ=main.o tree.o
CC=gcc
#make CFLAGS=A或者B或者C 可以动态修改CFLAGS变量值,从而选择先序后序还是中序
CFLAGS=A
$(BIN):$(OBJ)
$(CC) -o $(BIN) $(OBJ)
%.o:%.c
$(CC) -D$(CFLAGS) -c -o $@ $<
clean:
rm $(BIN) $(OBJ)