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

文章参考自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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值