------------------siwuxie095
树
这里介绍 树,那么什么是树呢?
这个问题太简单了,这就是一棵树,如下:
数据结构中的树,与生活中的树有几分类似,但不完全相同
树的定义:树是节点的有限集合
上面的概念看上去还是云遮雾罩的,那实际的树是什么样的呢?如下:
左边是一棵正着的树,它有一个节点 A,它向下分出一些分节点,
分节点又向下分出更多的分节点
对于这棵树来说,第一:它是有限的集合,第二:每一个元素都
是一个节点
如果把这棵树倒过来,就是右边的那棵树的样子,A 节点看成树
根,它上面的节点就是一个一个的叶子
树的诸多概念:
作为一棵树来说,最顶端的节点,称之为 根节点
显然,对于根节点 A 来说,它肯定是双亲,作为双亲来说,
它下面就有孩子,分别是 B、C、D
而对于 B 结点来说,它的孩子就是 E 和 F,对于 D 结点来
说,它的孩子就是 G 和 H,对于 C 结点来说,它没有孩子
注意:双亲不是两个节点,而是一个结点。叫法上可能感觉
有些奇异,也可以把 双亲 叫做 父节点
度 这个名词在树中是用的非常多的,什么是度呢?
如下:
A 节点的度为 3,因为它有三个孩子,分别是 B、C、D
B 节点的度是 2,因为它有两个孩子,分别是 E 和 F
C 节点的度为 0,因为它没有孩子
D 节点的度为 2,因为它有两个孩子,分别是 G 和 H
E、F、G、H 这 4 个节点的度都为 0,因为它们都没有孩子
可见:度 就是当前节点下直接的孩子数
叶子:对于一棵树来说,终端节点就是叶子,如:E、F、G、H、C
根:根是相对于叶子来说的,非终端节点就是根,如:A、B、D
「相对于整棵树来说,会有子树的概念,所以,根 也是相对而言的,
但一般情况下,还是称总的根节点为 根节点,其他根节点为 父节点」
有序树 和 无序树 是一个相对的概念,如下:
对于由 B、E、F 构成的子树而言:
B 节点有两个孩子,分别是 E 和 F,如果 E 和 F 不可以换顺序,
那么它就是一棵有序的树,如果 E 和 F 可以随便换顺序,而不
影响逻辑,那么它就是一颗无序的树
祖先 和 子孙:
对于祖先来说,如下:
对于 E 节点来说,它的祖先就是 B 节点 和 A 节点,
对于 G 节点来说,它的祖先就是 D 节点 和 A 节点
可见:祖先,即 指定当前节点之后,它向上一直到总的根节点,
所路过的所有节点,都是它的祖先
对于子孙来说,如下:
对于 A 节点来说,下面所有的节点都是它的子孙,
对于 D 节点来说,G 节点 和 H 节点是它的子孙
可见:子孙,即 指定当前节点之后,只要是从这个节点开始,向
下分出的节点,以及分出节点的子节点,都是它的子孙
除了上述概念之外,还需要介绍如下概念:
依然是上面那棵树,它分为三层:第一层、第二层、第三层
深度 分为 节点深度 和 树的深度
对于 节点深度 来说,它和它所在的层是统一的
如下:
在第一层的节点,它的深度就为 1,
在第二层的节点,它的深度就为 2,
在第三层的节点,它的深度就为 3
而 树的深度 是指当前这棵树中,节点所具有的最大深度
对于这棵树来说,它最多只有三层,那么它的深度就为 3
森林
对于树来说,是一棵独立的树,而多棵独立的树放到一起,就是 森林
也可以这样理解:
对于同一棵树来说,可以把它看成两棵不同的子树,如果看成两棵不同
的子树,那么它也组成了一个森林,这有点像孤木成林的感觉
二叉树
所有节点的度都小于等于 2
如下:
左边这颗树的根节点,它的度为 3,致使这一整颗树不能称之为 二叉树,
而右边这颗树的所有节点的度要么为 2、要么为 1、要么为 0,所以它是
一棵二叉树
二叉树的遍历
二叉树的遍历分为:前序遍历、中序遍历、后序遍历
前、中、后 是相对于二叉树的根来说的,如下:
如果先访问根,再访问左节点,最后访问右节点,就称之为 前序遍历,
如果先访问左节点,再访问根,最后访问右节点,就称之为 中序遍历,
如果先访问左节点,再访问右节点,最后访问根,就称之为 后序遍历
所以,可以简记为:
前序遍历:根 - 左 - 右,
中序遍历:左 - 根 - 右,
后续遍历:左 - 右 - 根
树的用途
树的用途很多,如:大家非常熟悉的压缩软件,就可以通过
赫夫曼树 做 赫夫曼编码 来实现
而树的一个更让人心驰神往的作用就是能够实现人机对战
大家都听说过 李世石 和 AlphaGo 这个机器之间的对战,也都玩过
一些人机对战的象棋游戏,肯定都能够领略到人机对战的乐趣
其实人机对战的过程就是不断地做树的搜索,找到最佳的对战方法,
从而机器做出最佳的当前选择
程序 1:用数组实现二叉树
Tree.h:
#ifndef TREE_H #define TREE_H
//用数组实现的二叉树 class Tree { public: Tree(int size,int *pRoot); //创建树 ~Tree(); //销毁树 //根据索引寻找节点 int *SearchNode(int nodeIndex); //添加节点 bool AddNode(int nodeIndex, int direction, int *pNode); //删除节点 bool DeleteNode(int nodeIndex, int *pNode); //遍历节点 void TreeTraverse(); private: int *m_pTree; //指向二叉树的指针 int m_iSize; //表示二叉树的数组的大小 };
#endif |
Tree.cpp:
#include "Tree.h" #include "stdlib.h" #include <iostream> using namespace std;
Tree::Tree(int size, int *pRoot) { m_iSize = size; m_pTree = new int[m_iSize]; //将数组中每个元素都初始化为0 for (int i = 0; i < m_iSize; i++) { m_pTree[i] = 0; } //再初始化根节点 m_pTree[0] = *pRoot; }
Tree::~Tree() { delete[]m_pTree; m_pTree = NULL; }
//根据索引,也就是数组下标进行搜索 int *Tree::SearchNode(int nodeIndex) { //判断nodeIndex是否合法 if (nodeIndex < 0 || nodeIndex >= m_iSize) { return NULL; } //如果传进来的索引对应的值为0 //说明当前结点本身也没有意义 if (m_pTree[nodeIndex] == 0) { return NULL; } return &m_pTree[nodeIndex]; }
bool Tree::AddNode(int nodeIndex, int direction, int *pNode) { if (nodeIndex < 0 || nodeIndex >= m_iSize) { return false; } if (m_pTree[nodeIndex] == 0) { return false; }
//定义direction: //值为0,则插入nodeIndex处结点的左孩子处 //值为1,则插入nodeIndex处结点的右孩子处 if (direction == 0) { //如果超出范围,返回false if (nodeIndex * 2 + 1 >= m_iSize) { return false; } //如果已经有值存在,返回false if (m_pTree[nodeIndex * 2 + 1] != 0) { return false; } m_pTree[nodeIndex * 2 + 1] = *pNode; } if (direction == 1) { //如果超出范围,返回false if (nodeIndex * 2 + 2 >= m_iSize) { return false; } //如果已经有值存在,返回false if (m_pTree[nodeIndex * 2 + 2] != 0) { return false; } m_pTree[nodeIndex * 2 + 2] = *pNode; } return true; }
bool Tree::DeleteNode(int nodeIndex, int *pNode) { if (nodeIndex < 0 || nodeIndex >= m_iSize) { return false; } if (m_pTree[nodeIndex] == 0) { return false; } //将对应nodeIndex的值取出来 *pNode = m_pTree[nodeIndex]; //删除节点,赋值0即可 m_pTree[nodeIndex] = 0; return true; }
void Tree::TreeTraverse() { for (int i = 0; i < m_iSize; i++) { cout << m_pTree[i] << " "; } } |
main.cpp:
#include "Tree.h" #include "stdlib.h" #include <iostream> using namespace std;
/**********************************************/ /* 二叉树(数组表示):使用数组的方式来表达二叉树
关于数组与树之间的算法转换:
int tree[n] 3 5 8 2 6 9 7
----------------3(0) --------5(1)------------8(2) ----2(3)----6(4)----9(5)----7(6)
把数组看成二叉树: 3 的左右孩子是 5 和 8, 5 的左右孩子是 2 和 6, 8 的左右孩子是 9 和 7
父亲节点下标*2+1,即该节点左孩子 父亲节点下标*2+2,即该节点右孩子
一个节点,如果它只有右孩子而没有左孩子,就在左孩子处填0, 同理,如果它只有左孩子而没有右孩子,就在右孩子处填0
即用 0 来表达当前位置上不存在节点的情况 */ /**********************************************/ int main(void) { int root = 3; Tree *p = new Tree(10, &root);
int n1 = 5; int n2 = 8; p->AddNode(0, 0, &n1); p->AddNode(0, 1, &n2);
int n3 = 2; int n4 = 6; p->AddNode(1, 0, &n3); p->AddNode(1, 1, &n4);
int n5 = 9; int n6 = 7; p->AddNode(2, 0, &n5); p->AddNode(2, 1, &n6);
p->TreeTraverse();
int node = 0; p->DeleteNode(6, &node); cout << endl << "delNode:" << node << endl;
p->TreeTraverse();
int *temp = p->SearchNode(2); cout << endl << "searchNode:" << *temp << endl;
delete p; p = NULL;
system("pause"); return 0; } |
运行一览:
程序 2:用链表实现二叉树
Node.h:
#ifndef NODE_H #define NODE_H
//节点五要素:索引、数据、左孩子指针、右孩子指针、父节点指针 class Node { public: Node(); Node *SearchNode(int nodeIndex); void DeleteNode(); void PreorderTraversal(); void InorderTraversal(); void PostorderTraversal(); int index; int data; Node *pLChild; Node *pRChild; Node *pParent; };
#endif |
Node.cpp:
#include "Node.h" #include "stdlib.h" #include <iostream> using namespace std;
Node::Node() { index = 0; data = 0; pLChild = NULL; pRChild = NULL; pParent = NULL; }
Node *Node::SearchNode(int nodeIndex) { //先要看看当前节点是不是要找的节点 if (this->index == nodeIndex) { return this; }
Node *temp = NULL;
//如果不是,就要看当前节点的左右孩子 if (this->pLChild != NULL) { if (this->pLChild->index == nodeIndex) { return this->pLChild; } else { temp = this->pLChild->SearchNode(nodeIndex); if (temp != NULL) { //temp不为空才返回 return temp; } } }
if (this->pRChild != NULL) { if (this->pRChild->index == nodeIndex) { return this->pRChild; } else { temp = this->pRChild->SearchNode(nodeIndex); //到了下面,不管temp是否为空,都返回 return temp; } } //树中没有这个节点,返回NULL return NULL; }
void Node::DeleteNode() { //将当前节点的左右孩子先干掉 if (this->pLChild != NULL) { //如果不为空,继续执行DeleteNode(),迭代递归 this->pLChild->DeleteNode(); } if (this->pRChild != NULL) { //如果不为空,继续执行DeleteNode(),迭代递归 this->pRChild->DeleteNode(); } //找到当前节点的父节点,前提是父节点不为NULL if (this->pParent != NULL) { //比较父节点的左孩子是不是与当前节点相同 //如果相同,当前节点是父节点的左孩子 if (this->pParent->pLChild == this) { //将自己父节点的左孩子指针置为NULL this->pParent->pLChild = NULL; } //同理 if (this->pParent->pRChild == this) { this->pParent->pRChild = NULL; } }
//删掉当前节点: //子节点删除完毕,父节点指向当前节点的指针赋空, //最后当前节点自杀 delete this; }
//前序遍历:根左右 void Node::PreorderTraversal() { //先访问自己,再访问自己的左孩子,再访问自己的右孩子 cout << this->index << " " << this->data << endl; if (this->pLChild != NULL) { this->pLChild->PreorderTraversal(); } if (this->pRChild != NULL) { this->pRChild->PreorderTraversal(); } }
//中序遍历:左根右 void Node::InorderTraversal() { if (this->pLChild != NULL) { this->pLChild->InorderTraversal(); }
cout << this->index << " " << this->data << endl;
if (this->pRChild != NULL) { this->pRChild->InorderTraversal(); } }
//后序遍历:左右根 void Node::PostorderTraversal() { if (this->pLChild != NULL) { this->pLChild->PostorderTraversal(); } if (this->pRChild != NULL) { this->pRChild->PostorderTraversal(); } cout << this->index << " " << this->data << endl; } |
Tree.h:
#ifndef TREE_H #define TREE_H
#include "Node.h"
//用链表实现的二叉树 class Tree { public: Tree(); //创建树(1) ~Tree(); //销毁树(5) //根据索引寻找节点(2) Node *SearchNode(int nodeIndex); //添加节点(3) bool AddNode(int nodeIndex, int direction, Node *pNode); //删除节点(4) bool DeleteNode(int nodeIndex, Node *pNode); void PreorderTraversal(); //前序遍历(6) void InorderTraversal(); //中序遍历(7) void PostorderTraversal(); //后序遍历(8) private: Node *m_pRoot; };
//树中很多函数的实现都转化为了节点函数的实现 //即将树级的实现转化为节点级,更容易理解 #endif |
Tree.cpp:
#include "Tree.h" #include "stdlib.h"
//(1) Tree::Tree() { m_pRoot = new Node(); }
//(5) Tree::~Tree() { //传入根节点索引0,同时不再取出数据 DeleteNode(0, NULL);
//或直接使用根节点删除,均可 //m_pRoot->DeleteNode(); //如果Tree中除了m_pRoot之外,还有其他数据成员, //并且是指针,而且在构造函数中还申请了内存 // //上面的两种调用就不够了,还需要释放其他的内存 }
//(2) Node *Tree::SearchNode(int nodeIndex) { return m_pRoot->SearchNode(nodeIndex); }
//(3) bool Tree::AddNode(int nodeIndex, int direction, Node *pNode) {
//先找到要挂载的目标节点 Node *temp = SearchNode(nodeIndex); if (NULL == temp) { return false; }
//将传入参数pNode里的数据拷贝出来,因为如果直接将 //pNode挂载到树的下边,pNode作为外边传进来的数据, //它如果被外边的函数或其它的语句修改,那它的完整 //性就不复存在了,对于树的影响也是非常大的,往往 //会造成一些致命错误 Node *node = new Node(); if (NULL == node) { return false; } node->index = pNode->index; node->data = pNode->data; node->pParent = temp;
//定义direction为0,表示挂载到左边,为1,表示挂载到右边 if (direction == 0) { temp->pLChild = node; } if (direction == 1) { temp->pRChild = node; }
return true; }
//(4) //删除指定节点,但在树中要删除指定节点并不是那么容易, //除了要删除指定节点之外,还有与其相连的子节点,以及 //子节点的子节点...也必须都要删除掉 bool Tree::DeleteNode(int nodeIndex, Node *pNode) { //先找到要删除的目标节点 Node *temp = SearchNode(nodeIndex); if (NULL == temp) { return false; }
//如果pNode为NULL,就不取temp中的数据了 //只作删除操作即可 if (pNode != NULL) { //主要取data pNode->data = temp->data; } //删除temp及其子节点 temp->DeleteNode(); return true; }
//(6) void Tree::PreorderTraversal() { m_pRoot->PreorderTraversal(); }
//(7) void Tree::InorderTraversal() { m_pRoot->InorderTraversal(); }
//(8) void Tree::PostorderTraversal() { m_pRoot->PostorderTraversal(); } |
main.cpp:
#include "Tree.h" #include "stdlib.h"
/*********************************************/ /* 二叉树:链表实现 节点要素:索引、数据、左孩子指针、右孩子指针、父节点指针 (数据即数据域,左孩子指针、右孩子指针、父节点指针即指针域)
-----------------(0) --------5(1)------------8(2) ----2(3)----6(4)----9(5)----7(6)
索引的遍历,如下: 前序遍历:0 1 3 4 2 5 6 中序遍历:3 1 4 0 5 2 6 后序遍历:3 4 1 5 6 2 0
创建一棵树时,只需要将第一个根节点创建出来即可, 根节点的数据域可以没有任何意义,但它的指针域一 定要先指定左右两边,初始状态下为 NULL
当根节点挂载相应的左孩子或右孩子时,再为它的左 右指针分别赋相应的值 */ /*********************************************/ int main(void) { Tree *tree = new Tree();
Node node1; node1.index = 1; node1.data = 5;
Node node2; node2.index = 2; node2.data = 8;
Node node3; node3.index = 3; node3.data = 2;
Node node4; node4.index = 4; node4.data = 6;
Node node5; node5.index = 5; node5.data = 9;
Node node6; node6.index = 6; node6.data = 7;
tree->AddNode(0, 0, &node1); tree->AddNode(0, 1, &node2); tree->AddNode(1, 0, &node3); tree->AddNode(1, 1, &node4); tree->AddNode(2, 0, &node5); tree->AddNode(2, 1, &node6);
tree->DeleteNode(6, NULL);
//tree->PreorderTraversal(); //tree->InorderTraversal(); tree->PostorderTraversal();
delete tree; tree = NULL;
system("pause"); return 0; } |
运行一览:
【made by siwuxie095】