二叉树重点多捋一捋,有时我也懵
内核链表是一个双向链表,但是与普通的双向链表又有所区别。内核链表中的链表元素不与特定类型相关,具有通用性。
很多开发内核的黑客们都觉得内核中链表的设计是他们引以自豪的一部分。内核链表的好主要体现为两点,1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加。将链表常见的操作都进行封装,使用者只关注接口,不需关注实现。分析内核中的链表我们可以做些什么呢?可以将其复用到用户态编程中,以后在用户态下编程就不需要写一些关于链表的代码了,直接将内核中list.h中的代码拷贝过来用也可以整理my_list.h,在以后的用户态编程中直接将其包含到C文件中,代码不仅简洁,而且更加高效。
这已经是最详细的注释了,为了你们,我也是操碎了心
malloc,inline,结构体不熟练,指针很难? 好好看
#include
#include
#include
#include
#include
struct student_info{
char name[4096];//姓名
short age;//年龄
float height;//身高
};
//大结构体
typedef struct list_node{
struct student_info data;
struct list_head list;//导入内核链表小结构体
}node_t;//链表节点类型
//申请空间
node_t *request_link_list_node(void)
{
node_t *new_node;
new_node = malloc(sizeof(node_t));
if(new_node == NULL)
{
perror("申请链表节点失败");
return NULL;
}
INIT_LIST_HEAD(&new_node->list);//小结构初始化
return new_node;
}
/*
函数功能:
将insert_node这个节点插入到refer_node这个节点之后的位置
参数:
refer_node:是链表中的任意一个节点
insert_node:需要插入的节点
返回值:
无
inline:内联函数关键字,适合函数内容不多,并且指针是固定的操作形式
*/
static inline void insert_node_link_list(node_t *refer_node, node_t *insert_node)
{
list_add_tail(&insert_node->list, &refer_node->list);//传入小结构体头 和 要插入节点
}
//显示链表数据
void display_link_node_data(node_t *list_head)
{
node_t *pos;//大结构体指针
printf("链表现有数据:\n");
//安全的遍历方式
list_for_each_entry(pos, &list_head->list, list)//传入大结构体指针 ,小结构体头,小结构体变量
{
printf("学生信息:名字=%s, 年龄=%hd, 身高=%.2f\n", pos->data.name, pos->data.age, pos->data.height);
}
printf("____________________________\n");
}
/*
函数功能:
删除refer_node的下一个节点出链表
参数:
refer_node:链表当中的任意一个节点
*/
int remove_list_node(node_t *rm_node)
{
list_del(&rm_node->list);//移除出原本所在的链表
free(rm_node);//释放内存
return 0;
}
//销毁链表
void destroy_link_list(node_t *list_head)//参数:大结构体头
{
node_t *pos, *n;
//安全的遍历方式,不会越界,不会出现野指针
list_for_each_entry_safe(pos, n, &list_head->list, list)
{
printf("free %s\n", pos->data.name);
free(pos);//释放
}
free(list_head);//头节点也要销毁
}
/*
函数功能:
寻找指定find_data这个数据值存放的节点
参数:
list_heasd:寻找的链表头节点
find_data:寻找的数据值为多少
返回值:
成功返回找到的节点内存地址,失败返回NULL
*/
node_t *find_link_node(node_t *list_head, const char *find_name)
{
node_t *pos;
//不安全的遍历方式
list_for_each_entry(pos, &list_head->list, list)//传入:大结构体指针,小结构体头,小结构体变量
{
if(strcmp(pos->data.name, find_name) == 0)
{
return pos;
}
}
return NULL;
}
//注册学生信息
int register_student_info(node_t *list_head)//传入大结构体头
{
node_t *new_node;
new_node = request_link_list_node();
if(new_node == NULL)
{
perror("申请内存出错");
return -1;
}
printf("开始注册学生信息:\n");
printf("请输入学生名字:\n");
scanf("%s", new_node->data.name);
printf("请输入学生年龄:\n");
scanf("%hd", &new_node->data.age);
printf("请输入学生身高:\n");
scanf("%f", &new_node->data.height);
insert_node_link_list(list_head, new_node);//传入大结构体头和新的节点
return 0;
}
//查找学生信息
node_t *find_student_info(node_t *list_head)//参数大结构体头
{
char name[16];
node_t *find_node;//大结构体 临时指针
printf("请输入需要搜索的学生名字\n");
scanf("%s", name);
find_node = find_link_node(list_head, name);//传入大结构体头 和 名字
if(find_node != NULL)
{
printf("寻找到的学生信息:名字=%s, 年龄=%hd, 身高=%.2f\n",
find_node->data.name, find_node->data.age, find_node->data.height);
}
else
{
printf("找不到该学生信息\n");
}
return find_node;//返回节点
}
//删除学生信息
int remove_student_info(node_t *list_head)//参数:大结构体头
{
char name[16];
node_t *find_node;
printf("请输入需要开除的学生名字\n");
scanf("%s", name);
find_node = find_link_node(list_head, name);//先找到,才能删除
if(find_node != NULL)
{
remove_list_node(find_node);//删除
}
else
{
fprintf(stderr, "找不到你需要删除的学生信息\n");//标准出错输出
return -1;
}
return 0;
}
int main(void)
{
int input_cmd;//值
node_t *list_head;//大结构体头
list_head = request_link_list_node();//申请开始
if(list_head == NULL)
goto request_list_err;//申请失败
while(1)
{
printf("欢迎进入学生信息系统:\n");
printf("输入1:录入学生信息\n");
printf("输入2:打印学生信息:\n");
printf("输入3:寻找指定学生信息\n");
printf("输入4:开除指定学生\n");
printf("输入5:退出整个系统\n");
scanf("%d", &input_cmd);
switch(input_cmd)
{
case 1:
register_student_info(list_head);//录入学生信息
break;
case 2:
display_link_node_data(list_head);//打印学生信息
break;
case 3:
find_student_info(list_head);//寻找学生信息
break;
case 4:
remove_student_info(list_head);//删除指定学生信息
break;
case 5:
goto system_exit;
default:
break;
}
}
system_exit:
destroy_link_list(list_head);//销毁链表
return 0;
request_list_err:
return -1;
}
申请、查找、删除、遍历
双向链表的使用思想:
它是将双向链表节点嵌套在其它的结构体中;在遍历链表的时候,根据双链表节点的指针获取"它所在结构体的指针",从而再获取数据。
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
/*
* @Author: your name
* @Date: 2021-08-31 10:53:58
* @LastEditTime: 2021-09-01 15:52:50
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \Desktop\data_struct\dc_list.c
*/
#include
#include
#include
#include
#include
typedef struct link_node{
int data;
struct link_node *prev, *next;
}node_t;
//不安全的遍历
#define list_for_each(head, pos) for(pos=head->next; pos!=head; pos=pos->next)
#define list_for_each_tail(head, pos) for(pos=hread->prev; pos!=head; pos=pos->prev)
//安全的遍历
#define list_for_each_safe(head, pos, n) for(pos=head->next, n=pos->next; pos!=head; pos=n, n=n->next)
#define list_for_each_safe_tail(head, pos, n) for(pos=head->prev, n=pos->prev; pos!=head; pos=n, n=n->prev)
/*
函数功能:
新建节点
参数:
无
返回值:
成功返回申请的节点内存,失败返回NULL
*/
node_t *request_list_node(void)
{
node_t *new_node;
new_node = malloc(sizeof(node_t));//申请内存
if(new_node == NULL)//判断内存是否申请失败
{
perror("申请节点失败");
return NULL;
}
new_node->prev = new_node;//让新申请的节点的上一个位置指向自己
new_node->next = new_node;//让新申请的节点的下一个位置指向自己
return new_node;
}
/*
函数功能:
将insert_node这个节点插入到refer_node这个节点之后的位置
参数:
refer_node:是链表中的任意一个节点
insert_node:需要插入的节点
返回值:
无
*/
void insert_node_list(node_t *refer_node, node_t *insert_node)
{
insert_node->prev = refer_node;
insert_node->next = refer_node->next;
refer_node->next->prev = insert_node;
refer_node->next = insert_node;
}
/*
函数功能:
将insert_node这个节点插入到refer_node这个节点之前的位置
参数:
refer_node:是链表中的任意一个节点
insert_node:需要插入的节点
返回值:
无
*/
static inline void insert_node_to_list_tail(node_t *refer_node, node_t *insert_node)
{
insert_node->prev = refer_node->prev;
insert_node->next = refer_node;
refer_node->prev->next = insert_node;
refer_node->prev = insert_node;
}
void display_list_node(node_t *list_head)
{
node_t *pos;
printf("表格已有数据:");
list_for_each(list_head, pos)
{
printf("%d ", pos->data);
}
printf("\n");
}
/*
函数功能:
将rm_node这个节点移除出所在的表格
参数:
rm_node:删除出表格的节点地址
返回值:
无
*/
static inline void remove_list_node(node_t *rm_node)
{
rm_node->prev->next = rm_node->next;
rm_node->next->prev = rm_node->prev;
}
/*
函数功能:
将rm_node这个节点移除出所在的表格,并且释放其节点内存
参数:
rm_node:删除出表格的节点地址
返回值:
无
*/
void destroy_list_node(node_t *rm_node)
{
remove_list_node(rm_node);
free(rm_node);
}
/*
函数功能:
判断head这个表格是否为空
返回值:
为空返回真,没有空的话返回假
*/
bool is_empty(node_t *head)
{
return head->next == head;
}
/*
函数功能:
搜索指定节点
*/
/*
函数功能:
销毁整条链表
参数:
list_head:需要销毁的链表头节点
返回值:
无
*/
void destroy_link_list(node_t *list_head)
{
node_t *pos;
node_t *n;
//安全的遍历方式,可以任意的pos节点
//for(pos=list_head, n=pos->next; pos!=list_head; pos = n, n=n->next)
list_for_each_safe(list_head, pos, n)
{
printf("free %d \n", pos->data);
free(pos);
}
free(list_head);
}
node *find_list_node(node_t *list_head, int find_data)
{
node_t *pos;
list_for_each(list_head, pos)
{
if(pos->data == find_data)
return pos;
}
return NULL;
}
int main(void)
{
int input_value;
node_t *list_head, *new_node;//申明两个链表节点的指针变量,其中list_head用来存放链表的头节点,new_node用来临时存放一下新申请的节点
node_t *find_node;
//新建链表头节点
list_head = request_list_node();
while(1)
{
scanf("%d", &input_value);
if(input_value > 0)
{
//新建节点
new_node = request_list_node();//新申请一个节点
new_node->data = input_value;//将数据存放进去这个节点当中
//将这个新的节点插入到list_head所对应的表格中
insert_node_to_list_tail(list_head, new_node);
}
else if(input_value < 0)
{
//删除指定节点
//搜索指定节点
find_node = find_list_node(list_head, -input_value);
if(find_node != NULL)
{
//将其销毁掉
destroy_list_node(find_node);
}
else
{
fprintf(stderr, "对不起,没有找到对应的节点\n");
}
}
else
break;
//遍历表格
display_list_node(list_head);
}
//销毁链表
destroy_link_list(list_head);//传入头节点
return 0;
}
这个递归函数你看懂了,下面的二叉树也难不倒你
递归就是一个函数直接或间接的自己调用自己,间接就是f0 -> f1 -> f0 ->f1这种,但也就是一个名词而已,并不神奇,它只是一个普普通通的函数调用,但由于自己调用自己这个特性,有一种自动化的力量,给它一个规则,它可以自动的持续运行下去,直到触发停止条件。一个递归函数就像是一个简单的计算机。
递归可以把复杂的问题变得简单,是一种处理问题的模型。比如汉诺塔,快速排序,二叉树遍历和查找,如果学会使用递归这种思维方式思考问题,很多问题会变得很简单,大事化小,小事化了,每一步都是很简单的操作。
/*
* @Author: your name
* @Date: 2021-09-02 17:20:51
* @LastEditTime: 2021-09-02 17:31:59
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \Desktop\dc_func.c
*/
#include
/*
1,递归实现的功能
*/
void my_print(int numb)
{
if(numb < 0)
return;
printf("numb=%d\n", numb);
my_print(numb-1);//10-0 相当于这个函数自己调用自己,调用了10次
}
int main(void)
{
my_print(10);
return 0;
}
不要被树上一个猴,地上骑个猴吓到,他并不难
二叉树的插入不是随意的,有一个简单的算法依据:最小的在最左边,左-中-右依次越来越大,具体看代码把
二叉树是一个结点的集合,其中每个结点最多与两个后继结点相关联,分别称为左侧子结点和右侧子结点。二叉树中的每个结点并不是全都有两个子结点,也可能只有一个结点或两个结点都可能被省略。在二叉树中,没有子结点的结点称为叶结点。
包含子结点的结点称为其子结点的父结点。对于一个定义为二叉树的非空的结点集合,每个结点必须至多有一个父结点,并且必须有一个结点是没有父结点的。这个没有父结点的结点称为二叉树的根结点。一个空的结点集合可以构成一个空的二叉树。
链表和二叉树有一些相似之处。二叉树的根对应于链表的头部,二叉树结点的子结点对应于链表中的后继结点,二叉树结点的父结点对应于链表中结点的前驱结点。当然,空链表的模拟是空的二叉树。
/*
* @Author: your name
* @Date: 2021-09-02 16:52:10
* @LastEditTime: 2021-09-02 17:34:05
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \Desktop\bst.c
*/
#include
#include
#include
#include
typedef struct tree_node{
int data;//储存数据
struct tree_node *lchild;//二叉树左孩子
struct tree_node *rchild;//二叉树右孩子
}node_t;
//申请二叉树节点
node_t *request_tree_node(void)
{
node_t *new_node;
new_node = malloc(sizeof(node_t));
if(new_node == NULL)
{
perror("申请二叉树节点失败");
return NULL;
}
//初始化以下,防止孩子乱跑
new_node->lchild = NULL;
new_node->rchild = NULL;
return new_node;//返回申请的空间回去
}
/*
函数功能:
插入insert_node节点到root这棵树当中
返回值:
如果传入的root为NULL,这返回insert_node的地址,返回这返回root原本的值
*/
node_t *insert_node_to_tree(node_t *root, node_t *insert_node)//参数:二叉树头,要插入的节点
{
if(root == NULL)//二叉树不像其他链表,头节点也要存储数据,所以从头就要判断否空
return insert_node;//空
if(insert_node->data < root->data)//如果掺入的节点的数据比root要小
{
//插入到你的左树【左孩子的递归函数】
root->lchild = insert_node_to_tree(root->lchild, insert_node);
}
else
{
//插入到你的右树【右孩子的递归函数】
root->rchild = insert_node_to_tree(root->rchild, insert_node);
}
return root;//传入的节点地址是什么就返回什么
}
//显示二叉树内容
void display_tree_node(node_t *root)
{
if(root == NULL)
return;
//显然,根据递归函数的规律,打印顺序左-中-右,相当于从大到小
display_tree_node(root->lchild);//左孩子遍历
printf("%d ", root->data);
display_tree_node(root->rchild);//右孩子的遍历
}
int main(void)
{
node_t *root = NULL;
node_t *new_node;
int input_value;
while(1)
{
scanf("%d", &input_value);
if(input_value > 0)
{
//新建节点
new_node = request_tree_node();
new_node->data = input_value;
//插入到树当中
root = insert_node_to_tree(root, new_node);
}
else
{
//删除指定的节点【考考大家,帮我补齐】
}
printf("树的数据有:");
//遍历一整棵树
display_tree_node(root);
printf("\n");
}
return 0;
}
#include
#include
#include
typedef struct tree_node{
int data;
int height;//节点的高度
struct tree_node *lchild;//左子树
struct tree_node *rchild;//右子树
}node_t;
#define MAX(a, b) ((a)>(b)?(a):(b))
//申请一个节点
node_t *request_tree_node(int data)
{
node_t *node;
node = malloc(sizeof(node_t));
if(node == NULL)
{
perror("申请新节点异常");
return NULL;
}
node->data = data;
node->height = 1;
node->lchild = NULL;
node->rchild = NULL;
return node;
}
//获取树的高度
int get_tree_height(node_t *root)
{
if(root == NULL)
return 0;
return MAX(get_tree_height(root->lchild), get_tree_height(root->rchild))+1;
}
//右旋转,为了直接解决左左不平衡
node_t *tree_right_rotate(node_t *root)
{
node_t *tmp;
tmp = root->lchild;
root->lchild = tmp->rchild;
tmp->rchild = root;
root->height = get_tree_height(root);
tmp->height = get_tree_height(tmp);
return tmp;
}
//左旋转,为了解决右右不平衡
node_t *tree_left_rotate(node_t *root)
{
node_t *tmp;
tmp = root->rchild;
root->rchild = tmp->lchild;
tmp->lchild = root;
root->height = get_tree_height(root);
tmp->height = get_tree_height(tmp);
return tmp;
}
//先左后右旋转,为了解决左右不平衡
node_t *tree_left_right_rotate(node_t *root)
{
root->lchild = tree_left_rotate(root->lchild);
return tree_right_rotate(root);
}
//先右后左旋转,为了解决右左不平衡
node_t *tree_right_left_rotate(node_t *root)
{
root->rchild = tree_right_rotate(root->rchild);
return tree_left_rotate(root);
}
//将节点插入到树中
node_t *insert_node_to_tree(node_t *root, node_t *node)
{
if(root == NULL)//如果这是一颗空树,那这个node新节点就是树头啦
return node;
if(node->data > root->data)
{
//插入到你的右树
root->rchild = insert_node_to_tree(root->rchild, node);
}
else
{
//插入到你的左树
root->lchild = insert_node_to_tree(root->lchild, node);
}
if(get_tree_height(root->lchild) - get_tree_height(root->rchild) == 2)//左不平衡
{
if(root->lchild->lchild != NULL)//左左不平衡
{
root = tree_right_rotate(root);//右转
}
else//左右不平衡
{
root = tree_left_right_rotate(root);//先左后右转
}
}
else if(get_tree_height(root->rchild) - get_tree_height(root->lchild) == 2)//右不平衡
{
if(root->rchild->rchild != NULL)//右右不平衡
{
root = tree_left_rotate(root);//左转
}
else//右左不平衡
{
root = tree_right_left_rotate(root);//先右后左转
}
}
root->height = get_tree_height(root);
return root;
}
//遍历树
void display_tree_node(node_t *root)
{
if(root == NULL)
return;
display_tree_node(root->lchild);
printf("%d:高度为%d\n", root->data, root->height);
display_tree_node(root->rchild);
}
//删除一个指定的节点
node_t *remove_tree_node(node_t *root, int data)
{
node_t *pos;
if(root == NULL)
return NULL;
if(root->data == data)
{
if(root->lchild == NULL && root->rchild == NULL)
{
free(root);
return NULL;
}
else if(root->lchild != NULL)//要删除的节点左树不为NULL
{
//找到这个左树当中的最右边的节点
for(pos=root->lchild; pos->rchild != NULL; pos=pos->rchild);
//将最右边的节点的数据覆盖到我们这个节点里面
root->data = pos->data;
//进入到左子树当中将里面的最右边的节点删除掉
root->lchild = remove_tree_node(root->lchild, pos->data);
}
else//要出的节点的右树不为NULL
{
//找到这个右树当中的最左边的节点
for(pos=root->rchild; pos->lchild != NULL; pos=pos->lchild);
//将最左边的节点的数据覆盖到我们这个节点里面
root->data = pos->data;
//进入到右子树当中将里面的最左边的节点删除掉
root->rchild = remove_tree_node(root->rchild, pos->data);
}
}
else if(data < root->data)//往左树里面去寻找节点删除
{
root->lchild = remove_tree_node(root->lchild, data);
}
else//往右树里面去删除
{
root->rchild = remove_tree_node(root->rchild, data);
}
if(get_tree_height(root->lchild) - get_tree_height(root->rchild) == 2)//左不平衡
{
if(root->lchild->lchild != NULL)//左左不平衡
{
root = tree_right_rotate(root);//右转
}
else//左右不平衡
{
root = tree_left_right_rotate(root);//先左后右转
}
}
else if(get_tree_height(root->rchild) - get_tree_height(root->lchild) == 2)//右不平衡
{
if(root->rchild->rchild != NULL)//右右不平衡
{
root = tree_left_rotate(root);//左转
}
else//右左不平衡
{
root = tree_right_left_rotate(root);//先右后左转
}
}
root->height = get_tree_height(root);
return root;
}
//随堂小练习
//查找指定节点
/*
函数功能:
寻找二叉树当中的指定节点
参数:
root:传入树的根
find_data:寻找的节点数据值
返回值:
找到返回该节点的地址,找不到则返回NULL
*/
node_t *find_tree_node(node_t *root, int find_data)
{
if(root == NULL)
return NULL;
if(root->data == find_data)
{
return root;
}
else if(find_data > root->data)
{
return find_tree_node(root->rchild, find_data);
}
else
{
return find_tree_node(root->lchild, find_data);
}
return NULL;
}
//销毁整个树
void destroy_tree(node_t *root)
{
if(root == NULL)
return;
destroy_tree(root->lchild);
destroy_tree(root->rchild);
printf("free %d\n", root->data);
free(root);
}
int main(void)
{
int input_data;
node_t *root = NULL;
node_t *new_node, *find_node;
while(1)
{
scanf("%d", &input_data);
if(input_data > 0)
{
new_node = request_tree_node(input_data);
//将数据插入到树当中
root = insert_node_to_tree(root, new_node);
}
else if(input_data < 0)
{
//删除树当中某个指定的节点
root = remove_tree_node(root, -input_data);
//find_node = find_tree_node(root, -input_data);
//if(find_node != NULL)
//printf("找到这个节点啦:%d\n", find_node->data);
//else
//printf("这个树里面找不到这个宝贝\n");
}
else
break;
printf("树里面有元素:\n");
display_tree_node(root);
printf("\n");
}
//销毁掉一整棵树
destroy_tree(root);
return 0;
}