【数据结构与算法】树和二叉树
文章目录
- 【数据结构与算法】树和二叉树
- 前言
- 一、树的基本概念
- 二、二叉树的基本概念
- 三、二叉树的递归遍历
- 四、二叉树的编程
- 五、二叉树的非递归遍历
- 总结
前言
本篇文章将讲到树的基本概念,二叉树的基本概念,二叉树的递归遍历,二叉树的编程,二叉树的非递归遍历
一、树的基本概念
树的定义:
由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称作这个根的子树 。
树的结构特点:
- 非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)
树的定义具有递归性,树中还有树。
树可以为空,即节点个数为0。
若干术语:
- 根 即根结点(没有前驱)
叶子 即终端结点(没有后继)
森林 指m棵不相交的树的集合(例如删除A后的子树个数)
有序树 结点各子树从左至右有序,不能互换(左为第一)
无序树 结点各子树可互换位置。
双亲 即上层的那个结点(直接前驱) parent
孩子 即下层结点的子树 (直接后继) child
兄弟 同一双亲下的同层结点(孩子之间互称兄弟)sibling
堂兄弟 即双亲位于同一层的结点(但并非同一双亲)cousin
祖先 即从根到该结点所经分支的所有结点
子孙 即该结点下层子树中的任一结点
结点 即树的数据元素
结点的度 结点挂接的子树数(有几个直接后继就是几度)
结点的层次 从根到该结点的层数(根结点算第一层)
终端结点 即度为0的结点,即叶子
分支结点 除树根以外的结点(也称为内部结点)
树的度 所有结点度中的最大值(Max{各结点的度})
树的深度(或高度) 指所有结点中最大的层数(Max{各结点的层次})
上图中的结点数= 13,树的度= 3,树的深度= 4
二、二叉树的基本概念
定义:
n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成 。
逻辑结构:
一对二(1:2)
基本特征:
- 每个结点最多只有两棵子树(不存在度大于2的结点);
左子树和右子树次序不能颠倒(有序树)。
基本形态:
二叉树性质
- 性质1: 在二叉树的第i层上至多有 2^(i-1) 个结点(i>0)
性质2: 深度为k的二叉树至多有 2^k-1 个结点(k>0)
性质3: 对于任何一棵二叉树,若度为2的结点数有n2个,则叶子数(n0)必定为n2+1 (即n0=n2+1)
性质4: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)
概念解释:
-
满二叉树
一棵深度为k 且有2k -1个结点的二叉树。
特点:每层都“充满”了结点
-
完全二叉树
除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。
理解:k-1层与满二叉树完全相同,第k层结点尽力靠左
三、二叉树的递归遍历
遍历方法:
牢记一种约定,对每个结点的查看都是“先左后右” 。
限定先左后右,树的遍历有三种实现方案:
DLR LDR LRD
先 (根)序遍历 中 (根)序遍历 后(根)序遍历
DLR — 先序遍历,即先根再左再右
LDR — 中序遍历,即先左再根再右
LRD — 后序遍历,即先左再右再根
注:“先、中、后”的意思是指访问的结点D是先于子树出现还是后于子树出现。
从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问结点的时机不同。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>struct BinaryNode
{//数据域char ch;//指针域struct BinaryNode* lChild;struct BinaryNode* rChild;};void recursion(struct BinaryNode* root)
{if (root == NULL){return;}//先序遍历printf("%c ", root->ch);recursion(root->lChild);recursion(root->rChild);}void test01()
{struct BinaryNode nodeA = { 'A', NULL, NULL };struct BinaryNode nodeB = { 'B', NULL, NULL };struct BinaryNode nodeC = { 'C', NULL, NULL };struct BinaryNode nodeD = { 'D', NULL, NULL };struct BinaryNode nodeE = { 'E', NULL, NULL };struct BinaryNode nodeF = { 'F', NULL, NULL };struct BinaryNode nodeG = { 'G', NULL, NULL };struct BinaryNode nodeH = { 'H', NULL, NULL };//建立关系nodeA.lChild = &nodeB;nodeA.rChild = &nodeF;nodeB.rChild = &nodeC;nodeC.lChild = &nodeD;nodeC.rChild = &nodeE;nodeF.rChild = &nodeG;nodeG.lChild = &nodeH;//递归遍历recursion(&nodeA);
}int main()
{test01();system("pause");return EXIT_SUCCESS;
}
四、二叉树的编程
- 计算二叉树叶子数量
- 获取树的高度
- 拷贝二叉树
- 递归遍历
- 释放二叉树
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>struct BinaryNode
{//数据域char ch;//指针域struct BinaryNode* lChild;struct BinaryNode* rChild;
};//计算二叉树叶子数量
void calculateLeafNum(struct BinaryNode* root, int* p)
{if (root == NULL){return;}//如果节点 左子树 与右子树 同时为空 称为叶子if (root->lChild == NULL && root->rChild == NULL){(*p)++;}calculateLeafNum(root->lChild, p);calculateLeafNum(root->rChild, p);}//获取树的高度
int getTreeHeight(struct BinaryNode* root)
{if (root == NULL){return 0;}//获取左子树高度int lHeight = getTreeHeight(root->lChild);//获取右子树高度int rHeight = getTreeHeight(root->rChild);//从左子树和右子树中取大的值+1int height = lHeight > rHeight ? lHeight + 1 : rHeight + 1;return height;
}//拷贝二叉树
struct BinaryNode* copyTree(struct BinaryNode* root)
{if (root == NULL){return NULL;}//先拷贝左子树struct BinaryNode* lChild = copyTree(root->lChild);//再拷贝右子树struct BinaryNode* rChild = copyTree(root->rChild);struct BinaryNode* newNode = malloc(sizeof(struct BinaryNode));newNode->ch = root->ch;newNode->lChild = lChild;newNode->rChild = rChild;return newNode;
}void recursion(struct BinaryNode* root)
{if (root == NULL){return;}printf("%c ", root->ch);recursion(root->lChild);recursion(root->rChild);}void freeTree(struct BinaryNode* root)
{if (root == NULL){return;}//先释放左子树freeTree(root->lChild);//再释放右子树freeTree(root->rChild);//释放根printf("%c被释放了\n", root->ch);free(root);
}void test01()
{struct BinaryNode nodeA = { 'A', NULL, NULL };struct BinaryNode nodeB = { 'B', NULL, NULL };struct BinaryNode nodeC = { 'C', NULL, NULL };struct BinaryNode nodeD = { 'D', NULL, NULL };struct BinaryNode nodeE = { 'E', NULL, NULL };struct BinaryNode nodeF = { 'F', NULL, NULL };struct BinaryNode nodeG = { 'G', NULL, NULL };struct BinaryNode nodeH = { 'H', NULL, NULL };//建立关系nodeA.lChild = &nodeB;nodeA.rChild = &nodeF;nodeB.rChild = &nodeC;nodeC.lChild = &nodeD;nodeC.rChild = &nodeE;nodeF.rChild = &nodeG;nodeG.lChild = &nodeH;//1、求二叉树 叶子数量int num = 0;calculateLeafNum(&nodeA, &num);printf("树的叶子数量为:%d\n", num);//2、 求树的高度/深度int height = getTreeHeight(&nodeA);printf("树的高度为:%d\n", height);//3、 拷贝二叉树struct BinaryNode* newTree = copyTree(&nodeA);//递归遍历recursion(newTree);printf("\n");//4、 释放二叉树freeTree(newTree);}int main() {test01();system("pause");return EXIT_SUCCESS;
}
五、二叉树的非递归遍历
利用栈容器可以实现二叉树的非递归遍历
首先将每个节点都设置一个标志,默认标志为假,根据节点的的状态进行如下流程。
执行上述流程,可以得到先序遍历的结果,如果想得到其他二叉树遍历结果,修改2.4步骤即可。
seqStack.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>#define MAX 1024//struct SStack
//{
// void * data[MAX]; //栈的数组
//
// int m_Size; //栈大小
//};typedef void * SeqStack;//初始化栈
SeqStack init_SeqStack();//入栈
void push_SeqStack(SeqStack stack, void * data);//出栈
void pop_SeqStack(SeqStack stack);//返回栈顶
void * top_SeqStack(SeqStack stack);//返回栈大小
int size_SeqStack(SeqStack stack);//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack);//销毁栈
void destroy_SeqStack(SeqStack stack);
seqStack.c
#include "seqStack.h"struct SStack
{void * data[MAX]; //栈的数组int m_Size; //栈大小
};//初始化栈
SeqStack init_SeqStack()
{struct SStack * myStack = malloc(sizeof(struct SStack));if (myStack == NULL){return NULL;}//初始化数组memset(myStack->data, 0, sizeof(void *)* MAX);//初始化栈大小myStack->m_Size = 0;return myStack;
}
//入栈
void push_SeqStack(SeqStack stack, void * data)
{//入栈本质 --- 数组尾插if (stack == NULL){return;}if (data == NULL){return;}struct SStack * mystack = stack;if (mystack->m_Size == MAX){return;}mystack->data[mystack->m_Size] = data;mystack->m_Size++;
}
//出栈
void pop_SeqStack(SeqStack stack)
{//出栈本质 --- 数组尾删if (stack == NULL){return;}struct SStack * mystack = stack;if (mystack->m_Size == 0){return;}mystack->data[mystack->m_Size - 1] = NULL;mystack->m_Size--;}
//返回栈顶
void * top_SeqStack(SeqStack stack)
{if (stack == NULL){return NULL;}struct SStack * mystack = stack;if (mystack->m_Size == 0){return NULL;}return mystack->data[mystack->m_Size - 1];
}
//返回栈大小
int size_SeqStack(SeqStack stack)
{if (stack == NULL){return -1;}struct SStack * mystack = stack;return mystack->m_Size;}
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack)
{if (stack == NULL){return -1;//返回-1代表真 空栈}struct SStack * mystack = stack;if (mystack->m_Size == 0){return 1;}return 0; //返回0 代表 不是空栈}
//销毁栈
void destroy_SeqStack(SeqStack stack)
{if (stack == NULL){return;}free(stack);stack = NULL;
}
这里是引用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "seqStack.h"struct BinaryNode
{//数据域char ch;//指针域struct BinaryNode* lChild;struct BinaryNode* rChild;//标志int flag;
};/*
1、将根节点 入栈
2、只要栈中元素个数大于 0 执行循环获取栈顶元素出栈如果标志位真 直接输出 并且执行下一次循环如果为假 将标志改为真将右子树 左子树 根 入栈执行下一次循环
*/void nonRecursion(struct BinaryNode* root)
{//初始化栈SeqStack myStack = init_SeqStack();push_SeqStack(myStack, root);while (size_SeqStack(myStack) > 0){//获取栈顶元素struct BinaryNode* pTop = top_SeqStack(myStack);//出栈pop_SeqStack(myStack);//如果标志位真 直接输出 并且执行下一次循环if (pTop->flag == 1){printf("%c ", pTop->ch);continue;}//如果为假 将标志改为真pTop->flag = 1;//将右子树 左子树 根 入栈if (pTop->rChild != NULL){push_SeqStack(myStack, pTop->rChild);}if (pTop->lChild != NULL){push_SeqStack(myStack, pTop->lChild);}push_SeqStack(myStack, pTop);}//销毁栈destroy_SeqStack(myStack);}void test01()
{struct BinaryNode nodeA = { 'A', NULL, NULL,0 };struct BinaryNode nodeB = { 'B', NULL, NULL,0 };struct BinaryNode nodeC = { 'C', NULL, NULL,0 };struct BinaryNode nodeD = { 'D', NULL, NULL,0 };struct BinaryNode nodeE = { 'E', NULL, NULL,0 };struct BinaryNode nodeF = { 'F', NULL, NULL,0 };struct BinaryNode nodeG = { 'G', NULL, NULL,0 };struct BinaryNode nodeH = { 'H', NULL, NULL,0 };//建立关系nodeA.lChild = &nodeB;nodeA.rChild = &nodeF;nodeB.rChild = &nodeC;nodeC.lChild = &nodeD;nodeC.rChild = &nodeE;nodeF.rChild = &nodeG;nodeG.lChild = &nodeH;//非递归遍历nonRecursion(&nodeA);}int main() {test01();system("pause");return EXIT_SUCCESS;
}
总结
到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔!