树的概念
树是一种非线性的结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。把他叫做树是因为它看起来像一个倒挂的树,它的根是朝上的,但是它的叶是朝下的。
它具有以下特点:每个节点有零个或多个子结点;没有父结点的称为根节点;每一个非根节点有且只有一个父结点;除了根节点外,每个子节点可以分为多个不相交的子树。
关于树,还有一些相关知识,我们需要了解。
节点的度:一个节点含有的子树的个数称为该节点的度。
双亲节点或父节点:若一个节点含有子节点,那么这个节点就是该子节点的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点
节点的层次:从根节点开始定义,根为第一次,根的子节点为第二层,以此类推
树的高度或深度:树中节点的最大层次
二叉树的概念
一棵二叉树是节点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵被称为左子树和右子树的二叉树组成
二叉树的特点
1、每个节点最多有两棵子树,即二叉树不存在度大于2的节点。
2、二叉树的子树有左右之分,其子树的次序不能颠倒。
二叉树的链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个节点由三个域组成,数据域和左右指针域,左右指针分别用来给出该节点左孩子和右孩子所在的链节点的存储地址。
二叉树的代码实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
//定义一个二叉树节点
typedef struct Node{
struct Node *left;
struct Node *right;
char value;
} Node;
树的枝干是向外不断地延伸出去的,那么二叉树的节点链接也是。有三种方式可以遍历二叉树,也就是我们常说的前序遍历,中序遍历,后序遍历。
以前的我对于二叉树的遍历也不是很懂,但是学完之后,我就记住了这一个方法。我个人觉得这样记忆是最好的。就是经过节点的次数。
如果是前序遍历,当第一次经过该节点时就将该节点记录。中序遍历,当第二次经过该节点时就将该节点记录。后序遍历,当第三次经过该节点时就将该节点记录。但是最后的每个左子树或者右子树也要遍历是否是空节点。这样才能全部遍历生成。
//前序遍历
void Preorder(Node *root){
if(root != NULL){
printf("%c",root->value);
Preorder(root->left);
Preorder(root-right);
}
}
//中序遍历
void Inorder(Node *root){
if(root != NULL){
Inorder(root->left);
printf("%c",root->value);
Inorder(root->right);
}
}
//后序遍历
void Postorder(Node *root){
if(root != NULL){
Postorder(root->left);
Postorder(root->right);
printf("%c",root->value);
}
}
代码看起来是很简单,主要需要记住的就是前中后序的特点。
前序—根,左子树,右子树
中序—左子树,根,右子树
后序—左子树,右子树,根
但是递归的特点就是代码简单,所需要的就是找到终止条件,返回上一层。
还是代码学习知识!!!
//创建二叉树结点
void CreateNode(char v){
Node *node = (Node*)malloc(sizeof(Node));
node->value = v;
node->left = node->right = NULL;
return node;
}
//返回该二叉树的总节点数
int GetSize(Node *root){
if(root == NULL){
return 0;
}else{
int left = GetSize(root->left);
int right = GetSize(root->right);
}
return left + right + 1;//不要忘了根节点
}
//返回叶子节点的数量
int GetLeafSize(Node *root){
if(root == NULL){
return 0;
}else if(root->left == NULL && root->right == NULL){
return 1;//如果没有左右子树,直接记录该子树的父结点
}else{
return GetLeafSize(root->left) + GetLeafSize(root->right);//叶子节点递归查询
}
}
递归中有一个返回值之后将跳到上一层,在上一层之后将寻找层中合适的出口在返回上上一层。
//返回二叉树的层数
int GetHeight(Node *root){
if(root == NULL){
return 0;
}else{
int left = GetHeight(root->left) + 1;
int right = GetHeight(root->right) + 1;
return (left > right ? left : right);
}
}
//遍历根节点的左右两个节点,谁返回的数值大,那么这个数就是层数
//返回K层中的节点个数
int GetKLevelSize(Node *root, int k){
if(root == NULL){
return 0;
}else if(k == 1){
return 1;
}else{
int left = GetKLevelSize(root->left, k-1);
int right = GetKLevelSize(root->right, k-1);
return left + right;
}
}
二叉树的查找
// 普通二叉树的查找
// 如果找到了,返回 v 所在结点的地址
// 如果没找到,返回 NULL
// 先去根里找
// 如果没找到,再去左子树找
// 如果还没找到,再去右子树找
// 在空树里找的结果,一定找不到,return NULL
Node *Find(Node *root, char v){
}
判断是否是相同的二叉树
bool isSame(Node *p, Node *q){
if(p == NULL && q ==NULL){
return ture;
}
if(p == NULL || q == NULL){
return false;
}
return q->value == p->value && isSame(p->left, q->left) && isSame(p->right, q->right);
}
另一颗树的子树
bool preorder(Node *root, Node *t){
if(root != NULL){
if(isSame(root, t)){
return true;
}//如果t就是root的一个子树,直接返回true
if(preorder(root->left, t) == true){//如果在左子树中找到t子树,直接返回true,否则直接去右子树中查找
return true;
}
return preorder(root->right, t);
}
return flase;//如果都没找到直接返回false
}
bool isSubtree(Node *s, Node *t){
return preorder(s,t);
}
二叉树的构建及遍历
Node *CreatTree(char preorder[], int size, int *used){
//如下图所示,数组保存的是前序遍历的一棵二叉树,#代表的是NULL
if(size == 0){
*used = 0;
return NULL;
}
if(preorder[0] == '#'){//#也占了一个数组的*used,所以+1
*used = 1;
return NULL;
}
Node *root = (Node*)malloc(sizeof(Node));
//先定义一个父节点
int leftUesd;
root->left = CreatTree(preorder + 1, size - 1, &leftUsed);
//根的左子数为:递归中数组中下一个的数,数组的容量-1,将左子树用的值返回
int rightUsed;
root->right = CreatTree(preorder + 1 + leftUsed, size - 1 - leftUsed, &rightUsed);
*used = 1 + leftUsed + rightUsed;
return root;
}
在二叉树的递归中,不用特别明确的想出具体的递归路线。当为root->left
那么此时一直都走的是左子树,反之一直都走的右子树。关键还是在于终止条件的寻找,只要找到了终止条件,那么递归才可一层一层的向上返回。
前中后序组合找出二叉树。怎么组合可以找出?
前序 找根最方便,preorder[0]
中序 分割左右子树最方便
后序 找根最方便,preorder[size - 1]
答案一目了然,只有前序+中序和后序+中序。前序+后序的组合,都是找根,那么左右子树的划分就不方便了。这样遍历起来明显有问题
就比如生活中,工作应该配合做才能更快,如果是两个做相同事情的人,那么工作总有一部分完成不了
void Find(char array[], size, v){
for(int i = 0; i < size; i++){
if(array[i] == v){
return i;
}
}
return -1;
}
//利用中序和后序进行创建二叉树
Node *buildtree(char inorder[], char postorder[], int size){
if(size == 0){
return NULL;
}
char rootValue = postorder[size - 1];//记录根节点的值,对于前后序不是第一个就是最后一个
int leftsize = Find(inorder,size,rootValue);//查找的是中序数组中左子树的个数
Node *root = (Node*)malloc(sizeof(Node));
root->value = rootValue;
//左子树不断的向下个递归创建。
root->left = buildtree(inorder, postorder, leftsize);
root->right = buildtree(inorder + leftsize + 1,postorder + leftsize,size - 1 - leftsize);
return root;
}
那么可以试下写前序和中序组合的方式
//比如上图这个组合
Node *buildtree(char postorder[], char inorder[], int size){
if(size == 0){
return NULL;
}
char treeValue = postorder[0];
int leftused = Find(inorder, size, treeValue);
Node *root = (Node*)malloc(sizeof(Node));
root->value = treeValue;
buildtree(postorder + 1, inorder, leftused);
buildtree(postorder + leftused + 1,inorder + 1 + leftsize, size - 1 - leftsize);
return root;
}
二叉堆
堆是一种逻辑上的完全二叉树,利用顺序存储,存在数组里。
二叉堆用的是顺序存储的方式,更多用在完全二叉树。
任取一个节点,要求根的值 >= 左右孩子的值(大堆)
任取一个节点,要求根的值 <= 左右孩子的值(小堆)
作用:找最大值,永远出现在二叉树的根
操作:向下调整,为了调整成小堆,不断的向下将大的节点往下放(这个可以认为是寻找最小值)
void AdjustDown(char array[], int size, int rootIdx){
int leftIdx = rootIdx * 2 + 1;
int rightIdx = rootIdx * 2 + 1;
if(leftIdx > size){
return NULL;
}
int min = leftIdx;
if(rightIdx < size && rightIdx < leftIdx){
min = rightIdx;
}
if(array[min] >= array[rootIdx]){
return;
}
int tmp = array[min];
array[min] = array[rootIdx];
array[rootIdx] = tmp;
AdjustDown(array, size, min);
}
以上的都是怎么调整堆,那么怎么将一个完全无序的随机分布的数组变成满足堆的性质?
void CreateHeap(int tree[], int size){
//最后一个节点的下标是 size - 1
//最后一个非子叶节点就是最后一个节点的双亲
//parent = (child - 1) / 2;
//带入之后得 (size - 2) / 2
for(int i = (size - 2) / 2;i >= 0;i++){
AdjustDown(tree, size, i);
}
}
这些只能算是初步了解了二叉树的逻辑和算法,毕竟二叉树不仅仅需要递归,非递归也是可以完成的。而且关于二叉堆只是仅仅懂了方法,还是不太知道具体的使用位置。学习还是得不断努力呀!