数据结构-二叉树

一、定义

二叉树 是每个节点最多拥有两个子节点 的树形结构,子节点区分左孩子、右孩子,顺序不能互换。

节点结构通用形式(C 语言):

复制代码
typedef struct BiTNode {
    int data;                // 数据域
    struct BiTNode *lchild;  // 左孩子指针
    struct BiTNode *rchild;  // 右孩子指针
} BiTNode, *BiTree;

二、基础概念

  1. 根节点:最顶层,无父节点
  2. 叶子节点:左、右孩子都为空
  3. :节点拥有的子节点数量;二叉树节点度 ≤ 2
  4. 深度 / 层数:根一般记第 1 层
  5. 高度:从当前节点向下到最远叶子的层数

三、特殊二叉树

满二叉树 所有层节点全部填满;叶子都在最后一层。 高度 h,结点总数:(2^n-1)

完全二叉树 按层序依次填充,空缺只能出现在最下层左侧。

二叉树的数据访问方式:

二叉树的完整创建和数据存储都需要依靠拓展序列进行,普通二叉树有些结点缺少左孩子或右孩子。拓展二叉树:把原二叉树所有空缺的左、右子树全部补上空结点,一般用 #表示。

练习:

根据前序和中序的序列,可以区分哪些节点是位于根节点的左侧,哪些节点位于根节点的右侧。主要看前序的第一个,视作为根节点,而在中序访问中,根节点的位置位于正中间,以此即可区分左右部分的子节点。对比前序和后续中,不同节点的位置,可以看出,在子节点中,谁是谁的根,便可以推算出树的结构,这主要得益于前序和中序的访问顺序差异,从而进行相互映照。

二叉树算法:

二叉树的创建:

拓展前序序列递归创建二叉树,# 代表空分支。 序列规则:拓展前序遍历序列,顺序:根 → 左子树 → 右子树。

复制代码
typedef int data_t;
typedef struct btnode
{
   data_t data;
   struct btnode *pl; 
   struct btnode *pr;
}btree_t; //二叉树节点类型


char tree_seq[] = "ABDG##H###CE#I##F##";
//char tree_seq[] = "AB#CD##E##F#GH###";
int idx = 0;

btree_t *create_btree(void)
{
	char data = tree_seq[idx++];//从数组中取得数据
	if (data == '#')            //判断是否为空节点
	{
		return NULL; //表示结束 
	}

	btree_t *new = malloc(sizeof(btree_t));    //申请堆区空间创建新节点
	if (new == NULL)
	{
		printf("malloc fail!\n");
		return NULL;
	}
	new->data = data;            //新节点写入数据
	new->pl = create_btree();    //前序创建,先进行做分支递归创建
	new->pr = create_btree();    //左分支递归完成后进行右分支递归创建

	return new;
}

二叉树的深度访问:

前序访问:

接收二叉树根节点指针作为参数,首先判断节点是否为空,若为空直接返回终止当前递归;若节点有效,则先输出当前节点数据,接着递归遍历其左子树,左子树全部遍历完成后再递归遍历右子树,遵循根、左、右的访问顺序完成整棵树前序遍历。

复制代码
int pre_order_traverse(btree_t *t) //传根节点的地址 
{
//   结束条件 t == NULL
  if (t == NULL)
	  return 0;
  printf("%c ",t->data); //根 
  pre_order_traverse(t->pl); //左子树 //左 
  pre_order_traverse(t->pr); //右子树 //右 

  return 0;
}

中序访问:

接收二叉树根节点指针,先判断节点是否为空,为空则结束本次递归;若节点有效,先递归遍历左子树,左子树遍历完毕后输出当前节点数据,最后递归遍历右子树,按照左、根、右的顺序完成整棵树的中序遍历。

复制代码
int in_order_traverse(btree_t *t) //传根节点的地址 
{
//   结束条件 t == NULL
  if (t == NULL)
	  return 0;
  in_order_traverse(t->pl); //左子树 //左 
  printf("%c ",t->data); //根 
  in_order_traverse(t->pr); //右子树 //右 

  return 0;
}

后序访问:

接收二叉树根节点指针,先判断节点是否为空,为空则终止本次递归;若节点有效,先递归遍历左子树,再递归遍历右子树,左右子树遍历完成后输出当前节点数据,按照左、右、根的顺序完成整棵树的后序遍历。

复制代码
int post_order_traverse(btree_t *t) //传根节点的地址 
{
//   结束条件 t == NULL
  if (t == NULL)
	  return 0;
  post_order_traverse(t->pl); //左子树 //左 
  post_order_traverse(t->pr); //右子树 //右 
  printf("%c ",t->data); //根 

  return 0;
}

销毁二叉树:

销毁二叉树必须使用后序遍历方式:销毁节点时,要先释放当前节点的左、右子树空间,再释放自身节点。如果先释放根节点,会直接丢失左右孩子指针,造成子树内存无法回收、内存泄漏。而后序遍历顺序为左→右→根,正好匹配先销毁左右子节点,最后销毁当前节点的执行逻辑。

复制代码
int btree_destroy(btree_t *t) // 销毁二叉树
{
    if (t == NULL)
		return -1;
	//递归 --- 后序方式
    //左右根
    btree_destroy(t->pl);//左
    btree_destroy(t->pr);//右边
    free(t);
	return 0;
}

二叉树的层序遍历:

按照从上到下、同一层从左往右依次访问节点,必须借助队列实现。 队列作用:保存当前层节点,取出节点访问时,依次把它的左孩子、右孩子入队。

  • 根节点不为空,先将根节点入队列;

  • 循环判断队列是否非空:

    • 队首节点出队并访问;
    • 如果存在左孩子,左孩子入队;
    • 如果存在右孩子,右孩子入队;
  • 队列为空,遍历结束。

    typedef int data_t;
    typedef struct btnode
    {
    data_t data;
    struct btnode *pl;
    struct btnode *pr;
    }btree_t; //二叉树节点类型

    /定义队列里的数据类型为二叉树节点的指针类型/
    typedef btree_t* pdata_t;

    typedef struct Node
    {
    pdata_t d; //数据域
    struct Node *pnext; //指针域
    }node_t; //节点

    int layer_order_traverse(btree_t *t)
    {
    if (t == NULL)
    return -1;
    //s1.创建队列
    node_t *pq = linkqueue_create();

    复制代码
      enqueue(pq,t);
      while (is_empty(pq) != 1)
      {
      	btree_t *data = NULL;
      	dequeue(pq,&data);
    
      	printf("%c ",data->data);
          
    
          if (data->pl != NULL)
      		enqueue(pq,data->pl);
          if (data->pr != NULL)
      		enqueue(pq,data->pr);
    
      }
    
      linkqueue_destroy(&pq);
      return 0;

    }

    //创建链表队列
    node_t * linkqueue_create(void)
    {
    node_t *p = malloc(sizeof(node_t));
    if (p == NULL)
    {
    printf("malloc fail");
    return NULL;
    }

    复制代码
      //表示空链表的头节点 
      p->pnext = NULL; 
    
      return p;

    }

    //入列
    int enqueue(node_t *head, pdata_t data)
    {
    if (head==NULL)
    {
    return -1;
    }
    node_t *p = head;
    //1.找到尾节点
    while (p->pnext)
    {
    p = p->pnext;
    }
    //2.创建新节点
    node_t *pNew = malloc(sizeof(node_t));
    if (pNew == NULL)
    {
    printf("malloc fail");
    return -1;
    }
    pNew->d = data;
    pNew->pnext = NULL;

    复制代码
      //3.连接 
      p->pnext = pNew;
    
      return 0;

    }

    //出队
    int dequeue(node_t *head,pdata_t *data)
    {
    if (NULL==head || is_empty(head) == 1)
    {
    return -1;
    }

    复制代码
       node_t *p = head->pnext;
       *data = p->d; //拿到出队的数据 
       head->pnext = p->pnext;
       free(p);
       return 0;

    }

    //销毁
    int linkqueue_destroy(node_t **head)
    {
    if (NULL==head )
    {
    return -1;
    }
    if(is_empty(*head) == 1)
    {
    return -1;
    }
    node_t *p = (*head)->pnext;
    while (p != NULL)
    {
    node_t *cur = p->pnext;
    free(p);
    p = cur;
    }

    复制代码
      free(*head);
      *head = NULL;
      return 0;

    }

主要函数layer_order_traverse 层序遍历

该函数实现二叉树层序遍历,首先判断根节点是否为空,为空直接返回;创建带头结点的链表队列并将根节点入队;循环判断队列非空时执行出队操作,取出二叉树节点并打印数据;若该节点存在左孩子、右孩子,则依次将左右孩子入队,持续循环直至队列为空;遍历完成后释放队列内存,实现从上到下、同层从左至右依次访问二叉树所有节点。

相关推荐
爱跳舞的烤冷面2 小时前
自学嵌入式第20天(数据结构篇——队列)
数据结构
旖旎夜光3 小时前
LeetCode 202:快乐数(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
cpp_25014 小时前
P1113 [USACO02FEB] 杂务
数据结构·c++·算法·动态规划·图论·拓扑排序·洛谷题解
Doraemomo4 小时前
数据结构-哈希表
数据结构·算法·散列表
君君思密达4 小时前
Leetcode Hot 100 题目详解-哈希表
数据结构·算法·leetcode
Doraemomo5 小时前
数据结构-双向链表与多文件工程整合之makefile基础
数据结构·链表
爱跳舞的烤冷面6 小时前
自学嵌入式第21天(数据结构——树)
数据结构·算法·链表
神明不懂浪漫6 小时前
【第五章】队列
开发语言·数据结构·经验分享·笔记·算法
Doraemomo7 小时前
数据结构-内存调试工具
数据结构