数据结构中关于二叉树的使用

来源:互联网 发布:平舆在线软件下载 编辑:程序博客网 时间:2024/06/18 02:38

文章参考自http://www.cnblogs.com/xpjiang/p/4569591.html

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[n] 中查找关键字为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的两个不相交的二叉树组成

特殊二叉树

满二叉树(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 是否为空
  • 遍历,按某顺序访问每个结点
常用的遍历方法:
  • 先序——根、左子树、右子树
  • 中序——左子树、根、右子树
  • 后序——左子树、右子树、根
  • 层次遍历——从上到下、从左到右

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,有

  1. 访问node,将node入栈
  2. 判断node的左孩子节点是否为空。若为空,则将node出栈,并将node设为node的右孩子节点,重复第一步;若不为空 ,则将node设为node的左孩子节点。
  3. 直到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,有

  1. node的左孩子节点不为空,则将其入栈,且将node设为node的左孩子节点。
  2. 左孩子节点为空时,将栈顶元素出栈,并访问它,且将node设为node的右孩子节点。
  3. 直到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分情况讨论

  1. 将node入栈
  2. 如果node没有左右孩子节点或者左右孩子节点都已经被访问过,可以直接访问node
  3. 如果不满足第二步,则将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>

下面是二叉树的层序遍历

层序遍历顾名思义是将二叉树的节点一层一层的读出来。层序遍历的处理过程如下:

  1. 建立一个队列,将根节点加入队列。
  2. 如果队列不空时,将队列第一个元素出队列,访问它。如果出队列的节点的左右孩子不为空,则依次加入队列。
  3. 直到队列为空,遍历结束。

<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));}



0 0
原创粉丝点击