1. 树的基本结构
1.1、树的基本概念
- 树的定义 树是n(n>=0)个结点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的结点。
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,...,Tm,其中每个集合本身又是一棵树,并且称为根的子树。
- 树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
- 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
- 树中所有结点可以有零个或多个后继,因此n个结点的树中有n-1条边。
1.2 树的结构体
c++
typedef struct BiTNode {
char data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
这个结构体它定义了一个叫做BiTNode
的结构体,以及一个指向该结构体的指针类型BiTree
。
具体来说,BiTNode
结构体包含以下三个成员:
data
:一个字符类型的数据成员。lchild
:一个指向BiTNode
类型的左子树的指针。rchild
:一个指向BiTNode
类型的右子树的指针。
lchild
和rchild
分别表示指向左子树和右子树的指针,而data
则存储每个节点的数据。通过这种方式,可以构建出一个具有左右子树结构的二叉树。
此外,该定义还使用了typedef关键字来为结构体BiTNode
定义了一个别名BiTree
,表示指向该结构体的指针类型。这样,我们就可以使用BiTree
来声明指向BiTNode
结构体的指针变量。
2. 树的创建和前序遍历
- 现在要创建一棵如下的树
ABD##E##C##
,#
表示空节点。
bash
A
B C
D E # #
# # # #
- 递归创建树
c++
void createtree(BiTree& t) { // 先序构造二叉树。
char ch;
// ABD##E##C##
ch = getchar(); // 输入字符。
if (ch == '#')
t = NULL;
else {
t = (BiTNode*)malloc(sizeof(BiTNode));
t->data = ch;
t->lchild = NULL;
t->rchild = NULL;
createtree(t->lchild);
createtree(t->rchild);
}
};
- 前序遍历打印树的节点
c++
void PreOrder(BiTree T) {
if (T != NULL) {
cout << ' ' << T->data;
PreOrder(T->lchild); // 递归遍历左子树
PreOrder(T->rchild); // 递归遍历右子树
}
};
3. 递归遍历二叉树
前中后序遍历二叉树的完整可执行代码
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct BiTNode {
char data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 递归边界,递归式
/*
BiTNode *p 和BiTree
p,在代码的角度上来讲是等价的,都是定义一个指针变量p,来指向二叉树的节点 BiTree
p:侧重于表示p指向的节点为根的一棵树 BiTNode *p 侧重表示一个树的节点
*/
// PreOrder 先序遍历 根左右
// InOder 中序低估遍历名称
// PostOrder 后序递归遍历
/**
A
B C
D E # #
# # # #
*/
void createtree(BiTree& t) { // 先序构造二叉树。
char ch;
// ABD##E##C##
ch = getchar(); // 输入字符。
if (ch == '#')
t = NULL;
else {
// t = new Node; // 创建一个新的 Node 对象,并将其地址赋值给根节点。
//也可以这样写 t = (BiTNode*)malloc(sizeof(BiTNode));
t = (BiTNode*)malloc(sizeof(BiTNode));
t->data = ch;
t->lchild = NULL;
t->rchild = NULL;
createtree(t->lchild);
createtree(t->rchild);
}
};
// 递归-前序遍历二叉树
void PreOrder(BiTree T) {
if (T != NULL) {
cout << " " << T->data;
PreOrder(T->lchild); // 递归遍历左子树
PreOrder(T->rchild); // 递归遍历右子树
}
};
// 递归-中序遍历二叉树
void InOder(BiTree T) {
if (T != NULL) {
InOder(T->lchild); // 递归遍历左子树
cout << " " << T->data;
InOder(T->rchild); // 递归遍历右子树
}
};
// 递归-后序遍历二叉树
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild); // 递归遍历左子树
cout << " " << T->data;
PostOrder(T->rchild); // 递归遍历右子树
}
};
int main() {
BiTree t;
// 输入创建ABD##E##C##
createtree(t);
cout << "递归-前序遍历二叉树:";
PreOrder(t);
cout << endl;
cout << "递归-中序遍历二叉树:";
InOder(t);
cout << endl;
cout << "递归-后序遍历二叉树:";
PostOrder(t);
cout << endl;
return 0;
}
4. 非递归遍历二叉树
树结构,代码结合图理解,非常重要!!!
tips:栈的基本使用
用栈来保存数据
在C++中,你可以使用STL库中的stack类来创建一个堆栈。除了stack.size()之外,stack类还提供了以下几种常见的操作:
stack.empty()
:检查堆栈是否为空。如果堆栈为空,则返回true;否则返回false。stack.top()
:返回堆栈顶部的元素,但不将其从堆栈中移除。stack.push(x)
:将元素x推入堆栈的顶部。stack.pop()
:移除并返回堆栈顶部的元素。stack.data()
:返回一个表示堆栈的容器。这个容器是一个后进先出(LIFO)的容器。
stack.size() != 0,栈不为空,结果就是 true;stack.size() == 0如果堆栈为空,那么结果就为 false。
4.1 前序遍历
c++
// 非递归-前序遍历
/*
思想:用栈来实现。首先访问根节点,然后将根节点入栈,接着访问当前节点的左节点,然后入栈,当左节点访问完后,
出栈,并依次访问右节点
*/
void un_preTraversal(Node* root) {
stack<Node*>
stack; // 名为 stack 的堆栈(stack),名为 stack 的堆栈(stack)
// 当前节点
Node* p = root;
// 因此,stack.size() != 0
// 的含义是检查堆栈中是否有元素,也就是检查堆栈是否为空。如果堆栈不为空,那么
// stack.size() != 0 的结果就为 true;如果堆栈为空,那么结果就为 false。
while (p != NULL || stack.size() != 0) {
if (p != NULL) {
visit(p); // 访问p之前一定要保证p不为空
stack.push(p); // 入栈
p = p->lchild;
} else {
p = stack.top();
stack.pop();
p = p->rchild;
}
}
}
4.2 中序遍历
c++
// 非递归-中序遍历
/*
思想:用栈来实现。从根节点开始依次遍历当前节点的左节点,并依次入栈,当左节点遍历完成后,获取
栈顶元素并出栈,然后访问该节点,并依次遍历其右节点
*/
void un_midTraversal(Node* root) {
stack<Node*> stack;
Node* p = root;
while (p != NULL || stack.size() != 0) {
if (p != NULL) {
stack.push(p);
p = p->lchild;
} else {
p = stack.top();
stack.pop();
visit(p);
p = p->rchild;
}
}
}
4.3. 后序遍历
c++
#include <bits\stdc++.h>
typedef struct treenode {
char data;
struct treenode *lchild, *rchild;
int tag;
} treenode, *tree;
void createtree(tree& t) { // 先序构造二叉树。
char ch;
// ABD##E##C##
ch = getchar(); // 输入字符。
if (ch == '#')
t = NULL;
else {
t = (treenode*)malloc(sizeof(treenode));
t->data = ch;
t->tag = 0;
t->lchild = NULL;
t->rchild = NULL;
createtree(t->lchild);
createtree(t->rchild);
}
}
void back(tree t) {
struct treenode* stack[100]; // 直接利用结构体建立一个栈。
int top = -1;
treenode* p = t; // 建立指针指向这棵树。
treenode* x; // 保存出栈的结点。
while (p || top != -1) // p和栈不能为空。
{
if (p) // p不为空时进栈。
{
top++;
stack[top] = p;
p = p->lchild;
} else // 为空的时候。
{
p = stack[top]; // 访问栈顶。
if (p->rchild && p->rchild->tag == 0) // 判断右孩子。
{ // 没有被访问,指向它的右孩子。
p = p->rchild; // p有值要进行栈
} else // 已经访问过了。
{
p = stack[top];
top--;
printf("%2c", p->data);
p->tag = 1;
p = NULL;//这里p为空之后,访问栈顶
}
}
}
}
int main() {
tree t;
createtree(t);
back(t);
return 0;
}
5. 树的层次遍历
5.1 从上至下,从左至右
tips:队列的基本使用
首先需要理解队列的使用:
- 在C++中,
queue<Node*> q;
是一个声明一个名为q
的队列(queue)的语句,其中Node*
是存储在队列中的元素的类型。在这种情况下,队列将存储指向Node
对象的指针。 - 在C++的STL库中,queue容器提供了以下几种操作:
push(x)
:将元素x插入队列的尾部。pop()
:删除队列头部的元素。front()
:返回指向队列头部的元素。back()
:返回指向队列尾部的元素。size()
:返回队列中元素的个数。empty()
:判断队列是否为空。
q.empty() == false,队列不为空
c++
// 树的层次遍历
// 思想:使用队列queue。先将根节点入队列,循环判断当前队列不为空时,将头元素出队列并访问头元素,然后在将它的左节点和右节点入队列
void levelTraversal(Node* root) {
queue<Node*> q;
Node* p = root;
q.push(p); // 将元素p插入队列的尾部
// 判断条件,false队列不为空
while (q.empty() == false) {
p = q.front(); // 返回指向队列头部的元素
q.pop(); // 删除队列头部的元素
visit(p);
if (p->lchild != NULL)
q.push(p->lchild);//左子树继续入队
if (p->rchild != NULL)
q.push(p->rchild);//右子树入队
}
}
5.2 逆置层次遍历,从下至上,从左至右
思路:将正常层次遍历的结果,存入栈中,再取出来。
c++
// 层次遍历
void leveltraversal(tree* root) {
stack<tree*> stack;
queue<tree*> q;
tree* p = root;
q.push(p);
while (q.empty() == false) {
p = q.front();
q.pop();
cout << p->data;
stack.push(p);
if (p->lchild != NULL) {
q.push(p->lchild);
};
if (p->rchild != NULL) {
q.push(p->rchild);
};
}
cout << endl;
cout << "层次遍历,逆序:";
// 不等于0说明栈不为空
while (stack.size() != 0) {
tree* p = stack.top();
stack.pop();
cout << p->data;
}
}
6. 找二叉树的公共祖先节点
找i,j的公共祖先节点
顺序存储,将二叉树转换,空的地方,赋值为-1
。
i
,j
节点均存在,treep[i]!=-1,tree[j]!=-1
- 如果不存在更新
i
和j
的值,如果i>j
,寻找i的父节点(i/2),更新i
。如果i<j
,寻找j
的父节点(j/2),更新j
; i=j
时,返回i
的值。
c++
#include <bits/stdc++.h>
// 找i,j的公共祖先节点
struct tree {
// 不存在的数值为-1
int data[12] = {-1, 1, 2, 3, -1, 4, -1, 5, -1, -1, -6, -1};
};
/*
i节点的父亲节点为2i,
左孩子是2i
右孩子是2i+1
*/
int comon(tree t, int i, int j) {
if (t.data[i] != -1 && t.data[j] != -1) // 说明存在值
{
while (i != j) // 更新i和j的值。
{
if (i > j)
i /= 2; // 寻找大的父节点。更新i的值
else
j /= 2;
}
return t.data[i]; // 相同时返回公共祖先。
}
return -1;
};
int main() {
tree t;
int a = comon(t, 7, 10); // 找7号和10号节点的公共祖先。
printf("%d", a);
return 0;
}
7. 判断是否为完全二叉树
不满足下面任何一条就不是完全二叉树
- 节点无左孩子,必无右孩子。
- 节点缺孩子,所有后继必无孩子。
第一个if判断情况
否则的情况
树结构
写法一:
c++
// 判断是否是完全二叉树
#include <bits/stdc++.h>
using namespace std;
#define Max 15
typedef struct treenode {
char data;
struct treenode *lchild, *rchild;
} treenode, *tree;
void buildtree(tree& t) {
char ch;
ch = getchar();
if (ch == '#')
t = NULL;
else {
t = (treenode*)malloc(sizeof(treenode));
t->data = ch;
t->lchild = NULL;
t->rchild = NULL;
buildtree(t->lchild);
buildtree(t->rchild);
}
}
struct squeue {
struct treenode* data[Max];
int f, r, tag;
};
bool isempty(squeue s) {
if (s.f == s.r && s.tag == 0)
return true;
return false;
}
bool isfull(squeue s) {
if (s.f == s.r && s.tag == 1)
return true;
return false;
}
bool enters(squeue& s, treenode* p) {
if (isfull(s))
return false;
s.data[s.r] = p;
s.r = (s.r + 1) % Max;
s.tag = 1;
return true;
}
bool outs(squeue& s, treenode*& p) {
if (s.f == s.r && s.tag == 0)
return false;
p = s.data[s.f];
s.f = (s.f + 1) % Max;
s.tag = 0;
return true;
}
bool isok(tree t) {
squeue s;
s.f = s.r = s.tag = 0;
// flag 为true标志都有左右孩子,ans结果标志
bool flag = true, ans = true;
if (t == NULL) // 判断是否为空树,空树也是完全二叉树。
ans = true;
if (!t->lchild && !t->rchild) // 存在节点,左右孩子为空,也是完全二叉树
ans = true;
enters(s, t); // 根节点入队
treenode* p;
while (!isempty(s)) { // 遍历队
outs(s, p); // 先出队
if (!p->lchild) { // 出队元素,没有左孩子
flag = false;
if (p->rchild) // 在缺孩子的情况下,还有右孩子,那么结果直接为false,不是完全二叉树。
ans = false;
} else // 有左孩子
{
if (flag) // 之前不存在缺孩子的节点
{
enters(s, p->lchild); // 左孩子进队
if (p->rchild) // 右孩子存在继续进队
enters(s, p->rchild);
else // 没有右孩子的情况下,就是缺孩子将flag设置成false;
flag = false;
} else // 之前存在缺孩子的节点,的后继又有孩子,结果变量直接为false,不是完全二叉树
ans = false; //
}
}
if (ans)
return true;
return false;
}
int main() {
tree t;
buildtree(t);
if (isok(t))
cout << "yes" << endl;
else
cout << "no" << endl;
return 0;
}
// ABD##E##CF##G## 完全
// ABD###CE##F## 非完全
写法二(推荐)
c++
#include <bits/stdc++.h>
using namespace std;
// ABD##E##CF##G## 完全
// ABD###CE##F## 非完全
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
void createTree(tree*& root) {
char a;
a = getchar();
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTree(root->lchild);
createTree(root->rchild);
}
}
void preOrder(tree* root) {
if (root != NULL) {
cout << root->data;
preOrder(root->lchild);
preOrder(root->rchild);
}
}
bool isok(tree* root) {
queue<tree*> q;
tree* p;
bool flag = true, ans = true;
if (root == NULL) {
ans = true;
}
if (!root->rchild && !root->rchild) {
ans = true;
}
q.push(root);
// 判断条件,false队列不为空
while (q.empty() == false) {
p = q.front();
q.pop();
if (!p->lchild) { // 判断出队元素没有左孩子
flag = false; // 设置标记
if (p->rchild) { // 在没有左孩子的情况下,还没有右孩子,直接不是完全二叉树,设置false。
ans = false;
}
} else { // 有左孩子
if (flag) {
q.push(p->lchild);
if (p->rchild) {
q.push(p->rchild);
} else {
flag = false;
}
} else {
ans = false;
}
}
}
if (ans)
return true;
return false;
}
int main() {
tree* p;
createTree(p);
// ABD##E##CF##G## 完全
// ABD###CE##F## 非完全
// AB#D##CE##F##
cout << "前序遍历二叉树:";
preOrder(p);
cout << endl;
cout << "是否是完全二叉树:";
if (isok(p))
cout << "yes" << endl;
else
cout << "no" << endl;
return 0;
}
8 .查找第k个节点的值
假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第k(1 <k<二叉树中结点个数)个结点的值。
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第k(1
<k<二叉树中结点个数)个结点的值
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
void preorder(tree* root, int k, char& temp, int& index) {
// cout << index;
if (root != NULL) {
index++;
if (index == k) {
temp = root->data;
};
cout << root->data << " ";
preorder(root->lchild, k, temp, index);
preorder(root->rchild, k, temp, index);
}
};
int main() {
tree* root;
createTre(root);
char temp;
int index = 0;
cout << "递归前序遍历二叉树:";
preorder(root, 5, temp, index);
cout << endl;
cout << "第4个节点的值:" << temp << endl;
return 0;
}
9. 指针指向树的某个节点
在一棵以二叉链表为存储结构的二叉树中,查找数据域值等于 key 的结 点是否存在,如果存在,则将指针 q 指向该结点,假设结点数据域为 int 型
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
在一棵以二叉链表为存储结构的二叉树中,查找数据域值等于 key 的结
点是否存在,如果存在,则将指针 q 指向该结点,假设结点数据域为 int 型。(二
叉树中结点值都不相同)
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
void disp(tree* root) {
if (root != NULL) {
cout << root->data;
disp(root->lchild);
disp(root->rchild);
}
}
// q为指向改节点为key的指针,key为节点的值
void search(tree* root, tree*& q, int key) {
if (root != NULL) {
if (root->data == key) {
q = root;
} else {
search(root->lchild, q, key);
search(root->rchild, q, key);
}
}
};
int main() {
tree* root;
createTre(root);
tree* q;
cout << "递归前序遍历二叉树:";
disp(root);
cout << endl;
// cout << "查找值为E的节点的值:";
search(root, q, 'E');
cout << "q个节点的值:" << q->data << endl;
return 0;
}
10. 利用递归计算二叉树中所有结点的个数。
法一:定义全局变量 n 用来计数。
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
在一棵以二叉链表为存储结构的二叉树中,查找数据域值等于 key 的结
点是否存在,如果存在,则将指针 q 指向该结点,假设结点数据域为 int 型。(二
叉树中结点值都不相同)
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
int n = 0;
void disp(tree* root) {
if (root != NULL) {
n++;
cout << root->data;
disp(root->lchild);
disp(root->rchild);
}
}
int main() {
tree* root;
createTre(root);
tree* q;
cout << "递归前序遍历二叉树:";
disp(root);
cout << endl;
cout << "树节点的个数:" << n << endl;
return 0;
}
法二:定义 n1 和 n2 分别用于接收左右子树结点个数。
C++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
在一棵以二叉链表为存储结构的二叉树中,查找数据域值等于 key 的结
点是否存在,如果存在,则将指针 q 指向该结点,假设结点数据域为 int 型。(二
叉树中结点值都不相同)
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
int n = 0;
void disp(tree* root) {
if (root != NULL) {
n++;
cout << root->data;
disp(root->lchild);
disp(root->rchild);
}
}
int count(tree* root) {
int n1, n2;
if (root == NULL) { // 节点为空的时候,返回0
return 0;
} else {
n1 = count(root->lchild);
n2 = count(root->rchild);
return n1 + n2 + 1; // 这里的+1是树本身的节点。重点注意
}
}
int main() {
tree* root;
createTre(root);
tree* q;
cout << "递归前序遍历二叉树:";
disp(root);
cout << endl;
cout << "树节点的个数:" << count(root) << endl;
return 0;
}
11. 计算二叉树所有子节点个数
1.递归
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
利用递归计算二叉树中所有叶子结点的个数。
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
int n = 0;
void disp(tree* root) {
if (root != NULL) {
if (root->lchild == NULL && root->rchild==NULL) {
n++;
};
cout << root->data;
disp(root->lchild);
disp(root->rchild);
}
}
int main() {
tree* root;
createTre(root);
tree* q;
cout << "递归前序遍历二叉树:";
disp(root);
cout << endl;
cout<<"子树个数:"<<n;
return 0;
}
12 .找双分支节点
c++
#include <bits/stdc++.h>
using namespace std;
typedef struct tree {
char data;
struct tree *lchild, *rchild;
} tree;
/*
利用递归计算二叉树中所有双分支结点个数
双分支节点:既有左子树又有右子树(如下图的:A1,B2)
ABD##E##C##
// A1
// B2 C
// D3 E4 F #
// # # # # # #
*/
// 创建树
void createTre(tree*& root) {
char a;
cin >> a;
if (a == '#') {
root = NULL;
} else {
root = new tree();
root->data = a;
root->lchild = NULL;
root->rchild = NULL;
createTre(root->lchild);
createTre(root->rchild);
}
};
int n = 0;
void disp(tree* root) {
if (root != NULL) {
if (root->lchild != NULL &&
root->rchild != NULL) { // 当节点左右孩子不为空时,节点个数++
n++;
};
cout << root->data;
disp(root->lchild);
disp(root->rchild);
}
}
int count3(tree* root) {
int n1, n2;
if (root == NULL) { // 节点为空的时候,返回0
return 0;
} else if (root->lchild && root->rchild) { // 双分支节点的情况
n1 = count3(root->lchild);
n2 = count3(root->rchild);
return n1 + n2 + 1; // 这里的+1是树本身的节点。重点注意
} else { // 单分支和叶子节点的情况
n1 = count3(root->lchild);
n2 = count3(root->rchild);
return n1 + n2;
}
};
int main() {
tree* root;
createTre(root);
tree* q;
cout << "递归前序遍历二叉树:";
disp(root);
cout << endl;
cout << "双分支节点个数:" << n;
return 0;
}