数据结构之树
来源:互联网 发布:国外刻录软件 编辑:程序博客网 时间:2024/04/29 18:37
一、概念:
树:是一种重要的非线性数据结构,它是数据元素(在树中称为结点)按分支关系组织起来的结构。
度:节点的子树个数;
树的度:树中任意节点的度的最大值;
兄弟:两节点的parent相同;
层:根在第一层,以此类推;
高度:叶子节点的高度为1,根节点高度最高;
有序树:树中各个节点是有次序的;
森林:多个树组成;
二叉树:二叉树是每个节点最多有两个子树的有序树。
树和二叉树的2个主要差别:
- 树中结点的最大度数没有限制,而二叉树结点的最大度数为2;
- 树的结点无左、右之分,而二叉树的结点有左、右之分。
满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。
完全二叉树:若一棵二叉树至多只有最下面的两层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
二叉搜索树:又称为二叉排序树。Binary Search Tree,Binary Sort Tree,简写为BST。
它或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
三、树的表示
1.双亲表示法:每个节点存储:数据、parent在数组中的下标;
2.孩子表示法:全部节点组成一个数组,每个数组指向一个单链表,存放其孩子;如下图:
3.双亲孩子表示法
4.孩子兄弟表示法
此种方法的好处在于一个多叉树能够转换成一颗二叉树,是树转换成二叉树的好办法;
三、二叉树的遍历
(1)前序遍历:先双亲、再左孩子、最后右孩子;
(2)中序遍历:先左孩子、再双亲、最后右孩子;
(3)后序遍历:先左孩子、再右孩子、最后双亲;
(4)层次遍历:一层一层,从左到右、从上到下遍历;
注意:
(1)已知前序、后序遍历结果,不能推导出一棵确定的树;
(2)已知前序、中序遍历结果,能够推导出后序遍历结果;
(3)已知后序、中序遍历结果,能够推导出前序遍历结果;
四、二叉树的存储结构
(1)顺序存储:只适用于完全二叉树;
(2)链式存储:最通用的存储方法;
缺点:
这样很浪费空间,因为会有很多空指针(如果有n个节点,则有2n个left、right指针,但是用到的只有n-1个指针)
改进:
线索二叉树:将空指针链接到前驱或后继节点;(此处前驱和后继是按照中序遍历上讲的)
节点数据结构如下图:
比如:
五、二叉树的性质
六、二叉树的建立
二叉树的每一个节点最多有两个子孩子,在建立二叉树的时候需要注意的是对于每一个子孩子为空的二叉树,在用键盘输入的时候分别要给输入相应的空符号,例如现在要建立一个二叉树,如下图所示:
在建立如上图的二叉树的时候,一定要注意输入NULL节点,这一点对于初学者很容易造成疑惑,因为只有八个节点,为什么我输入八个节点之后,显示出来的二叉树不是我想要的二叉树,原因就是NULL节点的输入以及输入的顺序,如本例的图所示,在此假设当输入的数是-1的时候代表的是NULL节点,那么应该输入的顺序就应该是:1,2,4,-1,-1,5,-1,-1,3,6,-1,-1,7,-1,8,-1,-1。
七、二叉树的遍历
1、前序遍历
(1)递归的前序遍历
前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。
//前序遍历,递归实现void D_PreOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { cout << pRoot->data << " "; D_PreOrder(pRoot->left); D_PreOrder(pRoot->right); }}
(2)非递归的前序遍历
根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。
因此其处理过程如下:
对于任一结点P:
1) 访问结点P,并将结点P入栈;
2) 判断结点P的左孩子是否为空若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3) 直到P为NULL并且栈为空,则遍历结束。
//前序遍历,非递归实现,用栈实现void ND_PreOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; while (pRoot || !bntStack.empty()) { if (pRoot) { cout << pRoot->data << " "; bntStack.push(pRoot); pRoot = pRoot->left; } else { pRoot = bntStack.top(); bntStack.pop(); //已经输出之后的值直接弹出删除 pRoot = pRoot->right; } } }}
2、中序遍历
(1)递归的中序遍历
中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。
//中序遍历,递归实现void D_MidOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { D_MidOrder(pRoot->left); cout << pRoot->data << " "; D_MidOrder(pRoot->right); }}
(2)非递归的中序遍历
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束
//中序遍历,非递归实现,用栈实现void ND_MidOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; while (pRoot || !bntStack.empty()) { if (pRoot) { bntStack.push(pRoot); pRoot = pRoot->left; } else { pRoot = bntStack.top(); cout << pRoot->data << " "; bntStack.pop();//已经输出之后的值直接弹出删除 pRoot = pRoot->right; } } }}
3、后序遍历
(1)递归的后序遍历
后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。
//后序遍历,递归实现void D_PostOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { D_PostOrder(pRoot->left); D_PostOrder(pRoot->right); cout << pRoot->data << " "; }}
(2)非递归的后序遍历
后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。
情况1:当前节点的左孩子以及右孩子同时为空的时候,才会将当前节点值输出
情况2:当前一个访问的是当前节点的左孩子或者右孩子的时候,那么此时也会将当前节点的值输出
具体的思路就是上述所表述的,具体的步骤如下:
(1)初始化,将根节点入栈,将前一个访问的置为NULL;
(2)如果栈不为空,取栈顶元素作为当前节点的值,判断当前节点是否满足两种输出情况,如果满足输出,出栈,将当前节点的值赋给pre,即前一个访问节点,进行下次循环;如果不满足进行步骤3
(3)如果当前节点的左孩子以及右孩子都不为空,现将右孩子入栈,再入栈左孩子;如果一个为空,那么入栈不为空的
(4)循环(2)到(3),直至栈为空的时候截止。
//后序遍历,非递归实现,用栈实现void ND_PostOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; bntStack.push(pRoot); BinaryTreeNode *PreNode = NULL;//前一次访问的节点 while (!bntStack.empty()) { pRoot = bntStack.top(); //叶子节点或左右子节点都已经访问过的节点, if ((!pRoot->left&&!pRoot->right) || PreNode && ((PreNode == pRoot->left) || (PreNode == pRoot->right))) //之所以有PreNode &&是为了防止根节点只有一个分支 { cout << pRoot->data << " "; PreNode = pRoot; bntStack.pop(); } else { if (pRoot->right) //空节点不入栈 bntStack.push(pRoot->right);//将后访问的节点先入栈 if (pRoot->left) bntStack.push(pRoot->left); } } }}
4、二叉树遍历的完整代码:
#include <iostream>#include <stack>using namespace std;//二叉树结点数据结构struct BinaryTreeNode{ int data; BinaryTreeNode *left; BinaryTreeNode *right;};//创建二叉树结点BinaryTreeNode *CreatBinaryTreeNode(int val){ BinaryTreeNode *pNode = new BinaryTreeNode; pNode->data = val; pNode->left = NULL; pNode->right = NULL; return pNode;}//链接二叉树节点void ConnectBinaryTreeNode(BinaryTreeNode *pParent, BinaryTreeNode *pLeft, BinaryTreeNode *pRight){ if (pParent != NULL) { pParent->left = pLeft; pParent->right = pRight; }}//销毁二叉树void DestoryBinaryTree(BinaryTreeNode *pRoot){ if (pRoot != NULL) { BinaryTreeNode *pLeft = pRoot->left; BinaryTreeNode *pRight = pRoot->right; delete pRoot; DestoryBinaryTree(pLeft);//递归删除 DestoryBinaryTree(pRight); }}//前序遍历,递归实现void D_PreOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { cout << pRoot->data << " "; D_PreOrder(pRoot->left); D_PreOrder(pRoot->right); }}//前序遍历,非递归实现,用栈实现void ND_PreOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; while (pRoot || !bntStack.empty()) { if (pRoot) { cout << pRoot->data << " "; bntStack.push(pRoot); pRoot = pRoot->left; } else { pRoot = bntStack.top(); bntStack.pop(); //已经输出之后的值直接弹出删除 pRoot = pRoot->right; } } }}//中序遍历,递归实现void D_MidOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { D_MidOrder(pRoot->left); cout << pRoot->data << " "; D_MidOrder(pRoot->right); }}//中序遍历,非递归实现,用栈实现void ND_MidOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; while (pRoot || !bntStack.empty()) { if (pRoot) { bntStack.push(pRoot); pRoot = pRoot->left; } else { pRoot = bntStack.top(); cout << pRoot->data << " "; bntStack.pop();//已经输出之后的值直接弹出删除 pRoot = pRoot->right; } } }}//后序遍历,递归实现void D_PostOrder(BinaryTreeNode *pRoot){ if (pRoot != NULL) { D_PostOrder(pRoot->left); D_PostOrder(pRoot->right); cout << pRoot->data << " "; }}//后序遍历,非递归实现,用栈实现void ND_PostOrder(BinaryTreeNode *pRoot){ if (pRoot) { stack<BinaryTreeNode *> bntStack; bntStack.push(pRoot); BinaryTreeNode *PreNode = NULL;//前一次访问的节点 while (!bntStack.empty()) { pRoot = bntStack.top(); //叶子节点或左右子节点都已经访问过的节点, if ((!pRoot->left&&!pRoot->right) || PreNode && ((PreNode == pRoot->left) || (PreNode == pRoot->right))) //之所以有PreNode &&是为了防止根节点只有一个分支 { cout << pRoot->data << " "; PreNode = pRoot; bntStack.pop(); } else { if (pRoot->right) //空节点不入栈 bntStack.push(pRoot->right);//将后访问的节点先入栈 if (pRoot->left) bntStack.push(pRoot->left); } } }}int main(){ BinaryTreeNode* pNode1 = CreatBinaryTreeNode(10); BinaryTreeNode* pNode2 = CreatBinaryTreeNode(6); BinaryTreeNode* pNode3 = CreatBinaryTreeNode(14); BinaryTreeNode* pNode4 = CreatBinaryTreeNode(4); BinaryTreeNode* pNode5 = CreatBinaryTreeNode(8); BinaryTreeNode* pNode6 = CreatBinaryTreeNode(12); BinaryTreeNode* pNode7 = CreatBinaryTreeNode(16); ConnectBinaryTreeNode(pNode1, pNode2, pNode3); ConnectBinaryTreeNode(pNode2, pNode4, pNode5); ConnectBinaryTreeNode(pNode3, pNode6, pNode7); cout << "递归的前序遍历:"; D_PreOrder(pNode1); cout << endl; cout << "非递归的前序遍历:"; ND_PreOrder(pNode1); cout << endl; cout << "递归的中序遍历:"; D_MidOrder(pNode1); cout << endl; cout << "非递归的中序遍历:"; ND_MidOrder(pNode1); cout << endl; cout << "递归的后序遍历:"; D_PostOrder(pNode1); cout << endl; cout << "非递归的后序遍历:"; ND_PostOrder(pNode1); cout << endl; DestoryBinaryTree(pNode1); system("pause"); return 0;}
- 数据结构之树
- JAVA数据结构之树
- 数据结构之Trie树
- 数据结构之trie树
- 数据结构之【trie树】
- 数据结构之败者树
- 数据结构之Trie树
- 数据结构之二叉树
- 数据结构复习之【树】
- 数据结构之AVL树
- 数据结构之Trie树
- 数据结构之二叉树
- 数据结构之字典树
- 数据结构之Trie树
- 数据结构之Trie树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- <C/C++算法> 图论基础算法小结(邻接矩阵实现)
- Java中List<>的用法
- poipdfsolr学习相关网址
- JDBC相关机制与JDBC连接数据库步骤
- IOS工具篇
- 数据结构之树
- C++中头文件(.h)和源文件(.cpp)都应该写些什么
- Linux下的一些I/O统计工具
- 洛谷1220关路灯
- 题目:复制带随机指针的链表
- Java Log4j配置说明
- 学习笔记02
- *LeetCode-Combination Sum
- 解决axis2处理java.util.Date类型对象时丢弃时间部分的问题