您的位置:首页 > 理论基础 > 数据结构算法

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

2016-09-22 18:57 344 查看
文章参考自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
中查找关键字为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,有
访问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));

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: