[数据结构笔记]二叉树初阶

1基本知识

1.1树

-节点的度:一个节点含有的子树的个数称为该节点的度;
-叶节点或终端节点:度为0的节点称为叶节点;
-非终端节点或分支节点:度不为0的节点;
-父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
-子节点:一个节点含有的子树的根节点称为该节点的子节点;
-兄弟节点:具有相同父节点的节点互称为兄弟节点;
-树的度:一棵树中,最大的节点的度称为树的度;
-节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
-树的高度或深度:树中节点的最大层次;
-森林:由m(m>0)棵互不相交的多颗树的集合称为森林;

树的高度默认从1开始,是为了便于将空树高度以0表示。与之相对的,数组下标从0开始是为了方便数组名代表首元素,且能直接使用数组名加减数字找到目标元素位置。

最优一般树结构(孩子兄弟表示法):
只用两个指针表示任意多个子节点

typedef int DataType;
struct Node{
   
   
	struct Node* firstChild;//指向当前节点的第一个孩子
	struct Node* nextBrother;//指向当前节点“右边”的兄弟
	DataType data;
};

1.2二叉树

在这里插入图片描述
0.一棵二叉树是结点的一个有限集合,该集合由一个根节点加上两棵别称为左子树和右子树的二叉树组成。空树也是一种子树。
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。

1.2.1满二叉树与完全二叉树

在这里插入图片描述

满二叉树:
-每一个层的结点数都达到最大值的二叉树。
-层数为k且结点总数为(2^k) -1的二叉树是满二叉树。

完全二叉树:
-对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一 一对应时称之为完全二叉树。
-完全二叉树的叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
(也就是说除最后一层外都是满的。最后一层的叶节点从左到右连续存在,空缺只能存在于最右侧)
-满二叉树是特殊的完全二叉树。

1.2.2二叉树的性质

1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2^h)-1.
3.对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0=n2+1
4.若规定根节点的层数为1,则具有n个结点的满二叉树的深度为:
在这里插入图片描述
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
(1)若i>0,i位置节点的双亲序号:(i-1)/2;若i=0,i为根节点编号,无双亲节点
(2)若2i+1<n,左孩子序号:2i+1,2i+1>=n,否则无左子节点
(3)若2i+2<n,右孩子序号:2i+2,2i+2>=n,否则无右子节点

1.2.3二叉树的存储结构

1.顺序存储
-使用数组来存储。一般使用数组只适合表示完全二叉树,因为若不是完全二叉树会有空间浪费。
-实际应用中只有堆(是堆结构,不是内存堆区)才会使用数组来存储。
-顺序存储的二叉树在物理上是一个数组,在逻辑上是一颗二叉树。

2.链式存储
-用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
-通常的实现方式是链表中每个结点由三个域组成,数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
-链式结构又分为二叉链和三叉链,暂且只讨论二叉链。

1.2.4二叉树遍历思路

已知:二叉树每个节点可以看作一个根,每个根有两个子树。空子树也算子树,只不过空树没有子树。
在这里插入图片描述

递归实现:
1.前序遍历/先根遍历:根、左子树、右子树
在这里插入图片描述
在这里插入图片描述

2.中序遍历/中根遍历:左子树、根、右子树
在这里插入图片描述
3.后序遍历/后根遍历:左子树、右子树、根
在这里插入图片描述

非递归实现:
4.层序遍历:用队列实现,从第一层开始,每层从左到右,逐层遍历
在这里插入图片描述

1.3堆结构(又称优先级队列)

堆结构是一种用数组实现的完全二叉树。也可以用链式结构实现,但远不如数组方便。

大堆(大根堆):树中所有亲节点的值都大于等于其子节点的值。
小堆(小根堆):树中所有亲节点的值都小于等于其子节点的值。

任意数组都可以看作是完全二叉树,但未必能看作是堆结构。

根据1.2.3,堆结构使用数组以实现顺序存储。
根据1.2.2-5,关于各节点在数组中的下标,有:

parent = (child - 1) / 2

1.3.1堆结构与堆排序的实现

头文件Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<assert.h>

typedef int HPDataType;

typedef struct Heap{
   
   
	HPDataType* arr;//保存元素的数组
	int size;//数组所存元素的数量
	int capacity;//可用容量
}HP;

void HeapPrint(HP* php);//打印数组
void Swap(HPDataType* p1,HPDataType* p2);//交换数组中的元素
void AdjustUp(HPDataType* arr, int child);//向上调整子节点,这里用大堆逻辑
void AdjustDown(HPDataType* arr,int size,int parent);//向下调整亲节点,这里用大堆逻辑

void HeapInit(HP* php);//初始化堆
void HeapDestroy(HP* php);//销毁堆
void HeapPush(HP* php,HPDataType x);//元素入堆
void HeapPop(HP* php);//根元素出堆
void HeapCreate(HP* php, HPDataType* a, int size);//根据数组建堆

HPDataType HeapTop(HP* php);//取顶
int HeapSize(HP* php);//查大小
bool HeapEmpty(HP* php);//判空

void HeapSort(int* arr, int size);//堆排序
接口实现Heap.c
#include"Heap.h"

//打印数组
void HeapPrint(HP* php) {
   
   
	assert(php);
	for (int i = 0; i < php->size; i++) {
   
   
		printf("%d ", php->arr[i]);
	}
	printf("\n");
}

//交换数组中的元素
void Swap(HPDataType* p1, HPDataType* p2) {
   
   
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向上调整子节点,这里用大堆逻辑
void AdjustUp(HPDataType* arr, int child) {
   
   
	int parent = (child - 1) / 2;//按公式求出初始亲节点下标
	while (child > 0){
   
   //从下至上(从叶至根)逐个调整,遇根则停止循环 
		if (arr[child] > arr[parent]) {
   
   //这里的>换成<即为小堆逻辑
			Swap(&arr[child], &arr[parent]);//符合交换的条件则交换亲子节点(的数据)
			child = parent;//在交换后保持该下标指向待调整的节点(数据)
			parent = (child - 1) / 2;//按公式求出下一个要比较的亲节点的下标
		}
		else{
   
   
			break;
		}
	}
	//这里的节点不是链表节点,而是逻辑上的二叉树的节点
}

//向下调整亲节点,这里用大堆逻辑
void AdjustDown(HPDataType* arr, int size, int parent) {
   
   
	int child = parent * 2 + 1;//先默认左子节点较大
	while (child<size){
   
   //完全二叉树,若左子节点不存在则右子节点必不存在
		//检查两子节点大小关系
		if (child + 1 < size && arr[child + 1] > arr[child]) {
   
   //这里的>换成<即为小堆逻辑
			//if右子节点存在且比左子节点大(若不检查存在则可能越界)
			child++;//将child指向右子节点
		}
		//检查子节点是否大于亲节点 
		if (arr[child] > arr[parent]) {
   
   //这里的>换成<即为小堆逻辑
			Swap(&arr[child], &arr[parent]);//符合交换的条件则交换亲子节点(的数据)
			parent = child;//在交换后保持该下标指向待调整的节点(数据)
			child = parent * 2 + 1;//按公式求出下一个要比较的子节点的下标
		}
		else{
   
   
			break;//子小于等于亲,则跳出
		}
	}
}

//初始化堆
void HeapInit(HP* php) {
   
   
	assert(php);
	php->arr = NULL;//初始不开空间,故arr初始指向空
	php->size = php->capacity = 0;//初始大小与容量为0
}

//销毁堆
void HeapDestroy(HP* php) {
   
   
	assert(php);
	free(php->arr);//释放arr
	php->arr = NULL;//置空arr
	php->size = php->capacity = 0;//大小与容量归零
}

//元素入堆
void HeapPush(HP* php, HPDataType x) {
   
   
	
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值