深入了解:二叉树(最详细,约10000字)

目录

1. 树概念及结构

1.1树概念

1.2树的表示

2. 二叉树概念及结构

2.1概念

2.2数据结构中的二叉树

2.3特殊的二叉树

2.4二叉树的存储结构

2.4.1顺序存储

2.4.2链式存储

2.5二叉树的性质

3. 二叉树顺序结构及概念

3.1二叉树的顺序结构

3.2堆的概念及结构

3.3堆的实现

4. 二叉树链式结构及实现

4.1二叉树链式结构的遍历

4.2二叉树的链式实现


1. 树概念及结构

1.1树概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

节点的度:一个节点含有的子树的个数称为该节点的度; 如二叉树的图:100节点的度为2
叶节点:度为0的节点称为叶节点; 如二叉树的图:2,7,3,25,1节点
非终端节点或分支节点:度不为0的节点; 如二叉树的图:100,19,36,17节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如二叉树的图:100是19的父节点

(我解释为什么有的地方叫双亲节点:因为在实际世界中,父亲节点,有人会说它是男权,为了避免纠纷,就叫双亲节点,但是我们一定要注意双亲节点不是指两个节点,而是指一个。我们一般说父亲节点,会便于我们理解)
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如二叉树的图:19是100的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:19、36是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如多叉树图:树的度为5
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:深度相同,父亲不同。如二叉树的图,17的堂兄弟节点包括25,1(不包括3)
节点的祖先:从根到该节点所经分支上的所有节点;(包括父亲节点)
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如二叉树图:所有节点都是100的子孙
森林:由m棵互不相交的树的集合称为森林;

1.2树的表示

多叉树的表示:

typedef int DataType;
typedef struct TreeNode
{
     struct Node* firstChild1; //第一个孩子
     struct Node* pNextBrother; //兄弟节点
     DataType data; 
}TNode;

 

因为不知道节点的度是多少,这种结构巧妙的解决结构体定义几个指针的问题 。

让4这个节点的brother指针指向它的兄弟5节点,这样可以找到5节点,然后5节点的bother指针指向6节点....依次下去,可以把一层的节点都链接上

二叉树的表示:

typedef int BTDataType
typedef struct BinaryTreeNode
{
    BTDataType data;//存储数据
    struct BinaryTreeNode* left;//指向它的左孩子
    struct BinaryTreeNode* right;//指向它的右孩子
}BTNode;
     


2. 二叉树概念及结构


2.1概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。

2.2数据结构中的二叉树

包括五种树形结构:1.空树   2.只有一个根节点   3.只有左树   4.只有右树   5.左右子树都存在


2.3特殊的二叉树

1.完全二叉树:除了最后一层之外的其他层都是满的,最后一层有数据的节点要从左到右连续的。当出现空节点之后,右边不能有其他右数据的节点。

2.满二叉树: 除了叶子节点,其他节点的度都为2,没有度为1的节点每一层都是满的)


2.4二叉树的存储结构


2.4.1顺序存储(连续的)

用数组的形式来存储:

会产生以下特性:

leftchild=parent*2+1(左孩子=存储父亲节点的下标*2+1); 

rightchild=leftchild+1=parent*2+2;(右孩子=存储父亲节点的下标*2+2)

parent=(child+1)/2     ( 不管左右孩子,都是这一条公式,你可以自己看看存储在数组中的下标,右孩子都是存储偶数的下标,左孩子都是存储在奇数的下标。+1再/2不会改变整数的部分。)


2.4.2链式存储(不连续的)

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。

一般分为二叉链和三叉链:

 二叉链:两个指针,指向它的左右孩子。

三叉链:不仅有两个指针指向它的孩子,还多一个指针,用来指向它的父亲节点。

   // 二叉链
   struct BinaryTreeNode
   {
    struct BinTreeNode* Left; // 指向当前节点左孩子
    struct BinTreeNode* Right; // 指向当前节点右孩子
    BTDataType data; // 当前节点存储的值
   }
   // 三叉链
   struct BinaryTreeNode
   {
    struct BinTreeNode* Parent; // 指向当前节点的双亲
    struct BinTreeNode* Left; // 指向当前节点左孩子
    struct BinTreeNode* Right; // 指向当前节点右孩子
    BTDataType data; // 当前节点存储的值
   };


2.5二叉树的性质

 2.5.1  性质一:

度为0的节点个数一定比度为2的节点个数多1(在一些选择题会用到) 

如题:

 设Ni表示度为i的节点个数,则节点总数 N = N0 + N1 + N2

节点个数于节点边的关系: N个节点的树有N-1个边

边与度的关系:N - 1 = N1 + 2 * N2

故:N0 + N1 + N2 - 1 = N1 + 2 * N2

因此,得:N0 = N2 + 1

回到原题,N0 = 3,N1 = 8,可得N2 = 2。

因此答案是 3 + 8 + 2 = 13。

 2.5.2  性质二:

若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log2(N+1) (解析: 是log以2 为底,n+1为对数)

2.5.3  性质三:

若规定根节点的层数为1,则一棵非空二叉树的第 i 层上最多有:  2^(i-1)个   结点.

2.5.4  性质四:

若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 :n=(2^h )-1个

2.5.5   性质五:

 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 n0= n2+1(注:n0=n2+1的意思就是在所有二叉树中,度为0的节点个数永远比度为2的节点个数多一个。)


3. 二叉树顺序结构及概念


3.1二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。 (同名字,但是性质完全不同)


3.2堆的概念,结构,实现

里面包括堆的概念和用堆来实现的排序。 

http://t.csdnimg.cn/n0qur



4. 二叉树链式结构

对于二叉树这个章节,我们不学二叉树的增删查改 (因为二叉树不是用来单纯的存储数据,而是用来查找的)

我们需要学二叉树的查找相关的知识

我们要学二叉树的前序遍历,中序遍历,后序遍历,层序遍历,个数,高度,第k层的节点个数,查找x所在节点的指针,查找二叉树中有没有x,二叉树的销毁,判断二叉树是否是完全二叉树。

1.前序遍历: 先遍历根节点,再左节点,最后右节点

2.中序遍历 :先遍历左节点,再根节点,最后右节点

3.后序遍历: 先遍历左节点,再右节点,最后根节点

遍历:我们需要用到递归,先去到最深层,然后遍历,return到上一层,重复下去。

A->B->D->H->NULL->NULL->D->I->NULL->NULL->D->B->E->J->NULL->NULL->C->F->K->NULL->NULL->G->NULL->NULL, 然后回到第一层出去。

中序,后序同理。

我们需要想象出NULL节点,才能便于理解。

if(root==NULL)
{
        return;

}//递归的返回条件

在这里我只找到了后序遍历的动态图。

前中后序都大同小异。 

三种遍历的代码:

//前序遍历
void PreOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;//在viod类型的函数,进行递归,想要返回上一层,可以用  return; 用来终止函数的执行
	}
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

//中序遍历
void InOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}

//后序遍历
void PostOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

查找二叉树的节点个数:

递归左边,返回左边的个数,+递归右边,返回右边的个数+1(1表示当前节点)

int TreeSize(BT* root)
{
	if (root == NULL)
	{
		return 0;//如果是NULL,返回0,递归的返回条件
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

二叉树的高度:

int TreeHight(BT* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftsize = TreeHight(root->left);
	int rightsize = TreeHight(root->right);
	return leftsize > rightsize ? leftsize + 1 : rightsize + 1;
}

 第k层的节点个数:

 遍历的过程k--,当k==1时说明到达那一层,如果有节点就返回1,如果出现NULL,就返回0了。

int TreeKsize(BT* root,int k)
{
	assert(k > 0); 
	if (root == NULL)	return 0;
	if (k == 1)	return 1;
	int tleft = TreeKsize(root->left, k - 1);
	int tright = TreeKsize(root->right, k - 1);
	return tleft+tright;
}

查找x所在的位置,并且返回节点的地址 :

因为返回的时候是返回到上一层,所以需要用变量保存指针,判断返回出来的是否为空,如果不为空,就会一直回到上一层。直到出去。

//查找x所在节点的指针
BT* TreeNodeFind(BT* root, int x)
{
	if (root == NULL)	return NULL;
	if (root->val == x)
	{
		return root;
	}
	BT* ret1=TreeNodeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BT* ret2=TreeNodeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}

查找二叉树中有没有x:

 如果找到了就返回true,然后因为是或||,一个为真就返回true

//查找二叉树中有没有x
bool TreeSearch(BT* root, int x)
{
    if(root==NULL) return fasle;
    if(root->val==x) return true;
    return TreeSearch(root->left,x)||TreeSearch(root->right,x);
}

 判断二叉树是否是完全二叉树:

插入到队列中,完全二叉树的特性是:前面都是有数据的节点,后面都是NULL。

不满足的话就不是完全二叉树。

所以写代码的时候可以利用这一点特性,把数据存储到队列中,利用层序遍历来判断。

bool BinaryTreeComplete(BT* root)
{
    // 层序遍历
	QueueNode pq;
	QueueInit(&pq);
	if (root)//不是空树的时候
	{
		QueuePush(&pq, root);//插入当前节点的值到队列中
	}
	while (!QueueEmpty(&pq))
	{
		BT* tmp = QueueFront(&pq);
		QueuePop(&pq);
		if(tmp == NULL)	break;//一层一层的出和进,所以不可能遇到空的时候,非空还没有进
		QueuePush(&pq, tmp->left);
		QueuePush(&pq, tmp->right);
	}
	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) == NULL)
		{
			QueuePop(&pq);
		}
		if (QueueFront(&pq))
		{
			QueueDestry(&pq);
			return false;
		}
	}
	return true;
	QueueDestry(&pq);
}

二叉树链式结构全部的代码:

Binary_tree.h(二叉树的头文件)


#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<math.h>
typedef struct Binary_tree
{
	struct Binary_tree* left;
	struct Binary_tree* right;
	int val;
}BT;

BT* BTCreate(int val);

//前序,中序,后序,个数,高度,第k层的节点个数
void PreOrder(BT* root);
void InOrder(BT* root);
void PostOrder(BT* root);
int TreeSize(BT* root);
int TreeHight(BT* root);
int TreeKsize(BT* root,int k);

//查找x所在节点的指针
BT* TreeNodeFind(BT* root, int x);

//查找二叉树中有没有x
bool TreeSearch(BT* root, int x);

//二叉树的销毁
void BTDestory(BT* root);

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BT* root);

// 层序遍历
void BinaryTreeLevelOrder(BT* root);

 Binary_tree.c(二叉树的源文件)

#define _CRT_SECURE_NO_WARNINGS 1
#include"Binary_tree.h"
#include"Queue.h"
BT* BTCreate(int val)
{
	BT* NewNode = (BT*)malloc(sizeof(BT));
	if (NewNode == NULL)
	{
		perror("malloc error");
		exit(-1);
	}
	NewNode->left = NULL;
	NewNode->right = NULL;
	NewNode->val = val;
	return NewNode;
}
 
void PreOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;                          //在viod类型的函数,进行递归,想要返回上一层,可以用  return; 用来终止函数的执行
	}
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}
void InOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}
void PostOrder(BT* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}
int TreeSize(BT* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}
int TreeHight(BT* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftsize = TreeHight(root->left);
	int rightsize = TreeHight(root->right);
	return leftsize > rightsize ? leftsize + 1 : rightsize + 1;
}
 
int TreeKsize(BT* root,int k)
{
	assert(k > 0); 
	if (root == NULL)	return 0;
	if (k == 1)	return 1;
	int tleft = TreeKsize(root->left, k - 1);
	int tright = TreeKsize(root->right, k - 1);
	return tleft+tright;
}
 
//查找x所在节点的指针
BT* TreeNodeFind(BT* root, int x)
{
	if (root == NULL)	return NULL;
	if (root->val == x)
	{
		return root;
	}
	BT* ret1=TreeNodeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BT* ret2=TreeNodeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}
 
//查找节点在不在
bool TreeSearch(BT* root, int x)
{
	if (root == NULL) return false;
	if (root->val == x) return true;
	return  TreeSearch(root->left, x) || TreeSearch(root->right, x);
}
 
void BTDestory(BT* root)
{
	if (root == NULL)
	{
		return;
	}
	BTDestory(root->left);
	BTDestory(root->right);
	free(root);
}
 
// 判断二叉树是否是完全二叉树(包括满二叉树+完全二叉树)
//完全二叉树特征:非空 空
//普通的二叉树:非空 空 非空...
bool BinaryTreeComplete(BT* root)
{
	// 层序遍历
	QueueNode pq;
	QueueInit(&pq);
	if (root)//不是空树的时候
	{
		QueuePush(&pq, root);//插入当前节点的值到队列中
	}
	while (!QueueEmpty(&pq))
	{
		BT* tmp = QueueFront(&pq);
		QueuePop(&pq);
		if(tmp == NULL)	break;//一层一层的出和进,所以不可能遇到空的时候,非空还没有进
		QueuePush(&pq, tmp->left);
		QueuePush(&pq, tmp->right);
	}
	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) == NULL)
		{
			QueuePop(&pq);
		}
		if (QueueFront(&pq))
		{
			QueueDestry(&pq);
			return false;
		}
	}
	return true;
	QueueDestry(&pq);
}
 
// 层序遍历
void BinaryTreeLevelOrder(BT* root)//循环
{
	QueueNode pq;
	QueueInit(&pq);
	if (root)
	{
		QueuePush(&pq, root);//插入当前节点的值到队列中
	}
	while (!QueueEmpty(&pq))
	{
		BT* tmp = QueueFront(&pq);
		QueuePop(&pq);
		printf("%d ",tmp->val);
		if (tmp->left != NULL)
		{
			QueuePush(&pq, tmp->left);
		}
		if (tmp->right != NULL)
		{
			QueuePush(&pq, tmp->right);
		}
	}
	printf("\n");
	QueueDestry(&pq);
 
}

 队列的头文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef struct Binary_tree* QDataType;//节点的指针
typedef struct QueueNode
{
	QDataType data;
	struct Queue* next;
}QueueNode;

typedef struct Queue
{
	QueueNode* head;
	QueueNode* tial;
}Queue;

//初始化和销毁
void QueueInit(Queue* pq);
void QueueDestry(Queue* pq);

//插入
void QueuePush(Queue* pq,QDataType x);

//删除
void QueuePop(Queue* pq);

//读取队头的数据
QDataType QueueFront(Queue* pq);

//读取队尾的数据
QDataType QueueBreak(Queue* pq);

//读取队列的数据个数
int QueueSize(Queue* pq);

//判断队列是否为空
bool QueueEmpty(Queue* pq);

队列的源文件: 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
//初始化和销毁
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tial = NULL;
}
void QueueDestry(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tial = NULL;
}

//插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* tmp=(QueueNode*)malloc(sizeof(QueueNode));
	if (tmp == NULL)
	{
		perror("malloc error");
		exit(-1);
	}
	tmp->data = x;
	tmp->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tial = tmp;
	}
	else
	{
		pq->tial->next = tmp;
		pq->tial = tmp;
	}
}

//删除
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	if (pq->head == pq->tial)
	{
		pq->head = pq->tial = NULL;
	}
	else
	{
		QueueNode* tmp = pq->head->next;
		free(pq->head);
		pq->head = tmp;
	}
}

//读取队头的数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}

//读取队尾的数据
QDataType QueueBreak(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->tial->data;
}

//读取队列的数据个数
int QueueSize(Queue* pq)
{
	assert(pq);
	int count = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		cur = cur->next;
		count++;
	}
	return count;
}

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

  • 47
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值