数据结构中关于二叉树的使用
2016-09-22 18:57
344 查看
文章参考自http://www.cnblogs.com/xpjiang/p/4569591.html
http://echo.vars.me/c/er-cha-shu/
静态查找:集合中记录是固定的,没有插入和删除操作,只有查找
动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除
静态查找——方法一:顺序查找(时间复杂度O(n))
静态查找——方法二:二分查找(时间复杂度O(logn))
二分查找的启示?
二分查找判定树:
判定树上每个节点需要查找的次数刚好为该节点所在的层数
查找成功时查找次数不会超过判定树的深度
n个节点的判定树的深度为⌊log2n⌋+1
一颗N个节点的树有N-1条边
树的一些基本术语:
结点的度(Degree):结点的子树个数
树的度:树的所有结点中最大的度数
叶节点(Leaf):度为0的结点
父节点(Parent):有子树的结点是其子树的根结点的父结点
子节点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点
兄弟节点(Sibling):具有同一父结点的各结点彼此是兄弟结点
路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2,...,nk,ni是ni+1的父结点,路径所包含的边的个数为路径的长度
祖先节点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点
子孙节点(Descendant):某一结点的子树中的所有结点都是这个结点子孙
节点的层次(Level):规定根结点在1层,其它任一结点的层数时其父节点的层数加1
树的深度(Depth):树中所有结点中的最大层次是这棵树的深度
二叉树T:一个有穷的结点集合
这个集合可以为空
若不为空,则它是由根结点和称为其左子树TL和右子树TR的两个不相交的二叉树组成
满二叉树(Full
Binary Tree):
除叶子结点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上。
完全二叉树(Complete
Binary Tree):
若设二叉树的深度为h,除第
h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。其实满二叉树是完全二叉树的特例,因为满二叉树已经满了,而完全并不代表满。
霍夫曼树:
每个节点要吗没有子节点,要么有两个子节点。
一个二叉树第i 层的最大结点数为:2i-1,i
≥ 1
深度为k
的二叉树有最大结点总数为:2k-1,k ≥ 1
对任何非空二叉树T,若n0 表示叶结点的个数,n2 是度为2
的非叶结点个数,那么两者满足关系n0 = n2 +
1
重要操作:
创建一个二叉树
判别BT 是否为空
遍历,按某顺序访问每个结点
常用的遍历方法:
先序——根、左子树、右子树
中序——左子树、根、右子树
后序——左子树、右子树、根
层次遍历——从上到下、从左到右
下面的代码是一个二叉树的层序遍历创建、递归版本的先中后序遍历、树的深度、左右子树的互换的过程
下面是前序遍历的非递归实现
可以将每一个节点都看做是根节点,前序遍历的非递归处理过程如下: 对每个节点node,有
访问node,将node入栈
判断node的左孩子节点是否为空。若为空,则将node出栈,并将node设为node的右孩子节点,重复第一步;若不为空 ,则将node设为node的左孩子节点。
直到node为空,且栈为空。
下面是中序遍历的非递归实现
对每个节点,先访问左孩子节点,将左孩子节点看成根节点,继续访问,直到左孩子节点为空才开始访问该节点,再访问根节点,然后对于右孩子节点以相同方法访问。中序遍历的非递归处理过程如下: 对每个节点node,有
node的左孩子节点不为空,则将其入栈,且将node设为node的左孩子节点。
左孩子节点为空时,将栈顶元素出栈,并访问它,且将node设为node的右孩子节点。
直到node为空,且栈为空。
下面是后序遍历的非递归实现
后序遍历的非递归相较于前序和中序,稍显复杂。因为其必须保证在访问根节点前,其左右孩子节点都已经被访问,且左孩子节点必须在右孩子节点前访问。后序遍历的非递归处理过程:
对每个节点node分情况讨论
将node入栈
如果node没有左右孩子节点或者左右孩子节点都已经被访问过,可以直接访问node
如果不满足第二步,则将node的右孩子节点和左孩子节点依次入栈。这样就可以保证访问的顺序是左孩子节点,右孩子节点,根节点。
下面是二叉树的层序遍历
层序遍历顾名思义是将二叉树的节点一层一层的读出来。层序遍历的处理过程如下:
建立一个队列,将根节点加入队列。
如果队列不空时,将队列第一个元素出队列,访问它。如果出队列的节点的左右孩子不为空,则依次加入队列。
直到队列为空,遍历结束。
下面的代码是由前序遍历和中序遍历构建二叉树
根据二叉树的前序遍历中,第一个元素必是根节点;中序遍历中根节点的左侧部分是根节点的左子树,右侧部分是根节点的右子树。由此,可以使用递归的方式构建二叉树了。
下面的代码是由中序遍历和后序遍历构建二叉树
与由前序遍历和中序遍历构建二叉树的方法类似,同样使用递归来建立二叉树。主要的不同是,后序遍历的特点是最后一个元素是根节点。
下面的代码为求二叉树中节点的个数(递归)、二叉树的深度(递归)、打印二叉树中根到叶子节点的路径(递归)、判断二叉树是否结构相同(递归)
http://echo.vars.me/c/er-cha-shu/
1、树与树的表示
数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录)。一个自然的问题就是,如何实现有效率的查找?静态查找:集合中记录是固定的,没有插入和删除操作,只有查找
动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除
静态查找——方法一:顺序查找(时间复杂度O(n))
<span style="font-size:18px;">int SequentialSearch(StaticTable * Tbl, ElementType K) { // 在表Tbl[1]~Tbl 中查找关键字为K的数据元素 int i; Tabl->Element[0] = K; // 建立哨兵 for(i = Tbl->Length; Tbl->Element[i] != K; i--) ; return i; // 查找成功返回所在单元下标;不成功返回0 }</span>
静态查找——方法二:二分查找(时间复杂度O(logn))
二分查找的启示?
二分查找判定树:
判定树上每个节点需要查找的次数刚好为该节点所在的层数
查找成功时查找次数不会超过判定树的深度
n个节点的判定树的深度为⌊log2n⌋+1
树的一些概念
一颗N个节点的树有N-1条边树的一些基本术语:
结点的度(Degree):结点的子树个数
树的度:树的所有结点中最大的度数
叶节点(Leaf):度为0的结点
父节点(Parent):有子树的结点是其子树的根结点的父结点
子节点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点
兄弟节点(Sibling):具有同一父结点的各结点彼此是兄弟结点
路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2,...,nk,ni是ni+1的父结点,路径所包含的边的个数为路径的长度
祖先节点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点
子孙节点(Descendant):某一结点的子树中的所有结点都是这个结点子孙
节点的层次(Level):规定根结点在1层,其它任一结点的层数时其父节点的层数加1
树的深度(Depth):树中所有结点中的最大层次是这棵树的深度
2.二叉树及存储结构
二叉树的定义
二叉树T:一个有穷的结点集合这个集合可以为空
若不为空,则它是由根结点和称为其左子树TL和右子树TR的两个不相交的二叉树组成
特殊二叉树
满二叉树(FullBinary Tree):
除叶子结点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上。
完全二叉树(Complete
Binary Tree):
若设二叉树的深度为h,除第
h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。其实满二叉树是完全二叉树的特例,因为满二叉树已经满了,而完全并不代表满。
霍夫曼树:
每个节点要吗没有子节点,要么有两个子节点。
二叉树几个重要性质
一个二叉树第i 层的最大结点数为:2i-1,i≥ 1
深度为k
的二叉树有最大结点总数为:2k-1,k ≥ 1
对任何非空二叉树T,若n0 表示叶结点的个数,n2 是度为2
的非叶结点个数,那么两者满足关系n0 = n2 +
1
二叉树的抽象数据类型
重要操作:创建一个二叉树
判别BT 是否为空
遍历,按某顺序访问每个结点
常用的遍历方法:
先序——根、左子树、右子树
中序——左子树、根、右子树
后序——左子树、右子树、根
层次遍历——从上到下、从左到右
3.二叉树的构建
二叉树本来就是递归定义的下面的代码是一个二叉树的层序遍历创建、递归版本的先中后序遍历、树的深度、左右子树的互换的过程
#include<stdio.h> #include<string.h> struct treeNode { int data; treeNode *left; treeNode *right; }; //层次遍历创建一颗二叉树 treeNode* creatBinaryTree(int a[], int pos, int length) { treeNode *node = NULL; //边界条件 if ( pos > length || a[pos] == '0') return NULL; node = new treeNode; node->data = a[pos]; //递归创建 node->left = creatBinaryTree(a, 2 * pos, length); node->right = creatBinaryTree(a, 2 * pos + 1, length); return node; } //前序遍历二叉树 void preOrderScanBinary(treeNode *node) { if (node) { //打印节点 printf("%d", node->data); //递归打印左右子树 preOrderScanBinary(node->left); preOrderScanBinary(node->right); } } //中序遍历 void inOrderScanBinary(treeNode* node) { if (node) { inOrderScanBinary(node->left); printf("%d", node->data); inOrderScanBinary(node->right); } } //后序遍历 void postOrderScanBinary(treeNode* node) { if (node) { postOrderScanBinary(node->left); postOrderScanBinary(node->right); printf("%d", node->data); } } //求二叉树的深度 int binaryDepth(treeNode* node) { if (!node) return 0; int nLeft = binaryDepth(node->left);//左子树深度 int nRight = binaryDepth(node->right);//右子树深度 return nLeft > nRight ? (nLeft + 1) : (nRight + 1); //树的深度=max(左子树深度,右子树深度)+1 } //左右子树的交换 void exchangeSubTree(treeNode* node) { treeNode* tempSubTree;//找一个临时节点 if (!node) { tempSubTree = node->left; node->left = node->right; node->right = tempSubTree; exchangeSubTree(node->left); exchangeSubTree(node->right); } } int main() { int length; printf("请输入数组的长度:\n"); scanf("%d", &length); int *a = new int[length + 10]; printf("请输入数组元素:\n"); fflush(stdin); for (int i = 1; i <= length; i++) { scanf("%d", &a[i]); //fflush(stdin); } //创建一颗二叉树 treeNode* head = creatBinaryTree(a, 1, length); //先序遍历 printf("先序遍历:"); preOrderScanBinary(head); //中序遍历 printf("\n中序遍历:"); inOrderScanBinary(head); //后序遍历 printf("\n后序遍历:"); postOrderScanBinary(head); //树的深度 printf("\n树的深度:%d\n", binaryDepth(head)); return 0; }下面的代码是一个二叉树先中后序遍历的迭代版本,以及层次遍历二叉树
#include<stack> #include<iostream> #include<stdio.h> #include<queue> using namespace std; typedef struct BinaryTreeNode { char value; struct BinaryTreeNode* lchild; struct BinaryTreeNode* rchild; }Node,*BTree; //层次遍历创建一颗二叉树 BTree creatBinaryTree(char a[], int pos, int length) { BTree node = NULL; //边界条件 if (pos > length || a[pos] == '0') return NULL; node = new Node; node->value = a[pos]; //递归创建 node->lchild = creatBinaryTree(a, 2 * pos, length); node->rchild = creatBinaryTree(a, 2 * pos + 1, length); return node; }
下面是前序遍历的非递归实现
可以将每一个节点都看做是根节点,前序遍历的非递归处理过程如下: 对每个节点node,有
访问node,将node入栈
判断node的左孩子节点是否为空。若为空,则将node出栈,并将node设为node的右孩子节点,重复第一步;若不为空 ,则将node设为node的左孩子节点。
直到node为空,且栈为空。
void preOrder_2(BTree T) { stack<BTree> mStack; BTree root = T; while (root != NULL || !mStack.empty()) { while (root != NULL) { printf("%c", root->value); mStack.push(root); root = root->lchild; } if (!mStack.empty()) { root = mStack.top(); mStack.pop(); root = root->rchild; } } }
下面是中序遍历的非递归实现
对每个节点,先访问左孩子节点,将左孩子节点看成根节点,继续访问,直到左孩子节点为空才开始访问该节点,再访问根节点,然后对于右孩子节点以相同方法访问。中序遍历的非递归处理过程如下: 对每个节点node,有
node的左孩子节点不为空,则将其入栈,且将node设为node的左孩子节点。
左孩子节点为空时,将栈顶元素出栈,并访问它,且将node设为node的右孩子节点。
直到node为空,且栈为空。
<span style="font-size:18px;">void inOrder_2(BTree T) { stack<BTree> mStack; BTree root = T; while (root != NULL || !mStack.empty()) { while (root != NULL) { mStack.push(root); root = root->lchild; } if (!mStack.empty()) { root = mStack.top(); printf("%c", root->value); mStack.pop(); root = root->rchild; } } }</span>
下面是后序遍历的非递归实现
后序遍历的非递归相较于前序和中序,稍显复杂。因为其必须保证在访问根节点前,其左右孩子节点都已经被访问,且左孩子节点必须在右孩子节点前访问。后序遍历的非递归处理过程:
对每个节点node分情况讨论
将node入栈
如果node没有左右孩子节点或者左右孩子节点都已经被访问过,可以直接访问node
如果不满足第二步,则将node的右孩子节点和左孩子节点依次入栈。这样就可以保证访问的顺序是左孩子节点,右孩子节点,根节点。
<span style="font-size:18px;">void postOrder_2(BTree T) { stack<BTree> mStack; BTree curT=NULL; //当前访问的节点 BTree preT=NULL;//上一次访问的节点 mStack.push(T); while (!mStack.empty()) { curT = mStack.top(); if ((curT->lchild == NULL && curT->rchild == NULL) || (preT != NULL && (preT == curT->lchild || preT == curT->rchild))) { mStack.pop(); printf("%c", curT->value); preT = curT; } else { if (curT->rchild != NULL) mStack.push(curT->rchild); if (curT->lchild != NULL) mStack.push(curT->lchild); } } } </span>
下面是二叉树的层序遍历
层序遍历顾名思义是将二叉树的节点一层一层的读出来。层序遍历的处理过程如下:
建立一个队列,将根节点加入队列。
如果队列不空时,将队列第一个元素出队列,访问它。如果出队列的节点的左右孩子不为空,则依次加入队列。
直到队列为空,遍历结束。
<span style="font-size:18px;">void levelOrder(BTree T) { queue<BTree> mQueue; BTree root; if (T == NULL) return; mQueue.push(T); while (!mQueue.empty()) { root = mQueue.front(); mQueue.pop(); printf("%c", root->value); if (root->lchild != NULL) mQueue.push(root->lchild); if (root->rchild != NULL) mQueue.push(root->rchild); } } int main() { int length; printf("请输入数组的长度:"); scanf("%d", &length); char *a = new char[length + 10]; fflush(stdin); printf("请输入数组的数据:"); for (int i = 1; i <= length; i++) scanf("%c", &a[i]); BTree head = creatBinaryTree(a, 1, length); //先序遍历 printf("先序遍历:"); preOrder_2(head); //中序遍历 printf("\n中序遍历:"); inOrder_2(head); //后序遍历 printf("\n后序遍历:"); postOrder_2(head); //层序遍历 printf("\n层序遍历:"); levelOrder(head); return 0; }</span>
下面的代码是由前序遍历和中序遍历构建二叉树
根据二叉树的前序遍历中,第一个元素必是根节点;中序遍历中根节点的左侧部分是根节点的左子树,右侧部分是根节点的右子树。由此,可以使用递归的方式构建二叉树了。
<span style="font-size:18px;">BTree rebuildTreeByPreIn(string mPreOrder, string mInOrder, int pLeft, int pRight, int mLeft, int mRight) { BTree root = new Node; root->value = mPreOrder[pLeft];//前序遍历中第一个节点是根节点 root->lchild = NULL; root->rchild = NULL; int rootIndex = mLeft; while (mPreOrder[pLeft] != mInOrder[rootIndex])//查找两个数组中的相同元素 { rootIndex++; } int leftChild = rootIndex - mLeft; if (rootIndex > mLeft) //如果有左子树 { root->lchild = rebuildTreeByPreIn(mPreOrder, mInOrder, pLeft + 1, pLeft + leftChild, mLeft, rootIndex - 1); } if (rootIndex < mRight)//如果有右子树 { root->rchild = rebuildTreeByPreIn(mPreOrder, mInOrder, pLeft + leftChild + 1, pRight, rootIndex + 1, mRight); } return root; }</span>
下面的代码是由中序遍历和后序遍历构建二叉树
与由前序遍历和中序遍历构建二叉树的方法类似,同样使用递归来建立二叉树。主要的不同是,后序遍历的特点是最后一个元素是根节点。
<span style="font-size:18px;">BTree rebuildTreeByInPost(string mInOrder, string mPostOrder, int mLeft, int mRight, int pLeft, int pRight) { BTree root = new Node; root->value = mPostOrder[pRight];//后续遍历中最后一个节点是根节点 root->lchild = NULL; root->rchild = NULL; int rootIndex = mLeft; while (mPostOrder[pRight] != mInOrder[rootIndex]) { rootIndex++; } int leftChild = rootIndex - mLeft; if (rootIndex > mLeft) //如果有左子树 { root->lchild = rebuildTreeByInPost(mInOrder, mPostOrder, mLeft, rootIndex - 1, pLeft, pLeft + leftChild - 1); } if (rootIndex < mRight) { root->rchild = rebuildTreeByInPost(mInOrder, mPostOrder, rootIndex + 1, mRight, pLeft + leftChild, pRight - 1); } return root; }</span>
下面的代码为求二叉树中节点的个数(递归)、二叉树的深度(递归)、打印二叉树中根到叶子节点的路径(递归)、判断二叉树是否结构相同(递归)
typedef struct Bin_Tree { int value; BinTree* right; BinTree* left; } BinTree; //二叉树中节点的个数 int NumOfTree(BinTree* root) { if (root == NULL) return 0; return (NumOfTree(root->right) + NumOfTree(root->left)) + 1;//左子树结点个数+右子树节点个数+1 } //二叉树的深度 int Depth(BinTree* root) { if (root == NULL) return 0; int left = Depth(root->left); int right = Depth(root->right); return (left > right ? left : right) + 1;//左子树深度和右子树深度的最大值加上1 } //二叉树中根到叶子节点的路径 void Routh(BinTree* root, vector<BinTree*>& vec) //vec为路径的记录 { if (root == NULL) return; vec.push_back(root); if (root->left == NULL && root->right == NULL) { vector<BinTree*>::iterator itr = vec.begin(); for (; itr != vec.end(); itr++) { cout << (*itr)->value << endl; } cout << "----" << endl; } Routh(root->left, vec); Routh(root->right, vec); vec.pop_back(); } //判断两颗二叉树是否结构相同 bool JudgeSame(BinTree* first, BinTree* second) { if (first == NULL && second == NULL) return true; if ((first == NULL && second != NULL) || (first != NULL && second == NULL)) return false; if (first->value != second->value) return false; return (JudgeSame(first->left, second->left) && JudgeSame(first->right, second->right)); }
相关文章推荐
- 关于java数据结构 在什么情况下使用什么样的数据结构的总结
- 11.11数据结构 ----关于二叉树小结
- 这是我参考网上编写的一道数据结构关于二叉树求其子树是否指针或者线索,及其对应得值,但是我运行结果却是有点问题,希望高手帮指教下!
- 关于matlab的table数据结构的使用
- 关于Java的数据结构HashMap,ArrayList的使用总结及使用场景和原理分析
- 关于数据结构中栈的若干使用
- 关于二叉树非递归算法使用的栈问题
- 关于数据结构中一些二叉树的定义
- 数据结构 学习笔记之:关于顺序栈中给结构体类型指针分配内存时,使用malloc和不使用malloc的疑惑之解惑!
- 数据结构——关于二叉树
- 数据结构:关于重建二叉树的三种思路
- 数据结构 二叉树的创建 关于二级指针的问题
- 数据结构关于二叉树的操作
- 关于数据结构的学习经验分享 (二叉树相关的内容)
- 关于数据结构中二叉树的遍历
- 关于java二叉树数据结构的实现
- 关于数据结构--二叉树
- 数据结构:关于重建二叉树的三种思路
- 关于RowSet的使用(1)
- 关于在asp.net中使用数据库的临时表.