------------------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 =newint[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"); return0; } |
运行一览:
程序 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"); return0; } |
运行一览:
【made by siwuxie095】