话说二叉树和二叉排序树是这样子的...学习吧~

           二叉树是树形结构的一个重要类型。许多实际问题抽象出来的数据结构都是二叉树的形式,而且二叉树的存储结构及其算法都比较为简单,因此二叉树使用极为广泛。

           二叉树是n(n>=0)个节点的有限集合,它可以是空集(n=0),也可以是由一个根节点及两棵互不相交、分别称作这个根的左子树和右子树的二叉树组成。

就会出现像这样的面试题:(牛人就可以忽略,但初学者要是耐心的看完,肯定可以学到东西的...)

           用C++实现一个二叉排序树,完成创建节点、插入节点、删除节点、查找节点等功能。

解析:二叉排序树又称二叉查找数。其定义为:二叉排序树是空树,或者是满足如下性质的二叉树。

(1)若它的左子树非空,则左子树上所有的节点的值均小于根节点的值。

(2)若它的右子树非空,则右子树上所有的节点的值均大于根节点的值。

(3)左、右子树本身又各是一棵二叉排序树。

一个简单的二叉排序树如下图:


          对于二叉排序树模型首先需要定义节点类以及二叉排序树类。对于节点类来说,每个节点有向下的两个指针,而且每个节点都只有一个父节点;对于二叉排序树类来说,只需要有树的根节点,就可以进行操作了,因此它们的数据结构如下:

#include <iostream>
#include <stack>
#include <queue>
using namespace std;

//节点类定义
class Node
{
public:
	int data;		//节点数据
	Node *parent;	//父节点
	Node *left;		//左节点
	Node *right;	//有节点
	int tag;		//为后序遍历所用
public:
	Node() : data(-1), parent(NULL), left(NULL), right(NULL), tag(0){};
	Node(int num) : data(num), parent(NULL), left(NULL), right(NULL), tag(0){};
};

//二叉排序树类定义----------------------------------------
class Tree
{
public:
	Tree(int num[], int len);		//插入num数组的前len个数据
	void insertNode(int data);		//插入节点,递归方法
	void insertNode1(int data);		//插入节点,非递归方法
	Node *searchNode(int data);		//查找节点
	void deleteNode(int data);		//删除节点及其子树
	void InOrderTree();				//中序遍历,递归
	void InOrderTreeUnRec();		//中序遍历,非递归
	void PreOrderTree();			//先序遍历,递归
	void PreOrderTreeUnRec();		//先序遍历,非递归
	void PostOrderTree();			//后序遍历,递归
	void PostOrderTreeUnRec();		//后序遍历,非递归
	void LevelOrderTree();			//层次遍历, 队列实现
	bool IsSortedTree(Tree tree);	//判断是否为二叉排序树

private:
	void insertNode(Node *current, int data);	//递归插入方法
	Node *searchNode(Node *current, int data);	//递归查找方法
	void deleteNode(Node *current);				//递归删除方法
	void InOrderTree(Node *current);			//中序遍历,递归
	void PreOrderTree(Node *current);			//先序遍历,递归
	void PostOrderTree(Node *current);			//后序遍历,递归

private:
	Node *root;						//二叉排序树的根节点
};
接下来具体说明各个方法的实现。

           构造函数中创建二叉排序树的方法。这里首先生成根节点,然后循环调用插入节点对二叉树进行插入操作。

//插入num数组的前len个数据--------------------------------------
Tree::Tree(int num[], int len)
{
	root = new Node(num[0]);
	for(int i=1; i<len; i++)
	{
		//insertNode(num[i]);
		insertNode1(num[i]);
	}
}
          插入节点操作,通常在二叉树的遍历中可以选择递归与非递归方法。insertNode1()使用非递归方法插入节点,其代码如下:

//插入数据为参数data的节点,非递归方法------------------
void Tree::insertNode1(int data)
{
	Node *p, *par;
	Node *newNode = new Node(data);	//创建节点

	p = par = root;
	while(NULL != p)			//查找插入到哪个节点下面
	{	
		par = p;				//保存节点
		if(data > p->data)		//如果data大于当前节点的data
		{						//下一步到左子节点,否则进行到右子节点
			p = p->right;
		}
		else if(data < p->data)
		{
			p = p->left;
		}
		else if(data == p->data)	//不能插入重复节点
		{
			delete newNode;
			return;
		}
	}
	newNode->parent = par;			//保存父节点
	if(par->data > newNode->data)	//把新节点插入在目标节点的正确位置
	{
		par->left = newNode;
	}
	else
	{
		par->right = newNode;
	}
}
下面insertNode(int data)使用递归方法插入节点,它的内部调用了private成员函数isnertNode(Node *current, int data),代码如下:

//插入数据为参数data的节点,调用递归插入方法----------------
void Tree::insertNode(int data)
{
	if(NULL != root)
	{
		insertNode(root, data);	//调用私有的递归插入方法
	}
}
//递归插入方法
void Tree::insertNode(Node *current, int data)
{
	//如果data小于当前节点数据,在当前节点的左子树插入
	if(data < current->data)	
	{
		if(NULL == current->left)	//如果左节点不存在,则插入到左子树
		{
			current->left = new Node(data);
			current->left->parent = current;
		}
		else
		{
			insertNode(current->left, data); //对左子树进行递归调用
		}
	}
	else if(data > current->data)	//如果data大于当前节点数据,在当前节点的右子树插入
	{
		if(NULL == current->right)	//如果有节点不存在,则插入到有节点
		{
			current->right = new Node(data);
			current->right->parent = current;
		}
		else
		{
			insertNode(current->right, data);	//对右子树进行递归调用
		}
	}

	return;			//data等于当前节点数据时,不插入
}
查找节点也使用了递归,代码如下:

//查找节点--------------------------------------
Node * Tree::searchNode(int data)
{
	Node *current = NULL;

	if(NULL != root)
	{				//调用私有递归方法
		current = searchNode(root, data);
	}
	return current;
}
//递归查找方法
Node *Tree::searchNode(Node *current, int data)
{
	if(data < current->data)	//左子树
	{
		if(NULL == current->left)	//不存在,返回NULL
			return NULL;
		return searchNode(current->left, data);	//递归左子树
	}
	else if(data > current->data)	//右子树
	{
		if(NULL == current->right)
			return NULL;
		return searchNode(current->right, data); //递归右子树
	}
	
	return current;		//如果相等返回current
}
删除节点的操作,代码如下:

//删除数据为data的节点及其子树----------------------------
void Tree::deleteNode(int data)
{
	Node *current = NULL;

	current = searchNode(data);	//查找节点
	if(NULL != current)
	{	
		deleteNode(current);	//调用私有删除节点及其子树
	}
}
//删除current节点及其子树的所有节点
void Tree::deleteNode(Node *current)
{
	if(NULL != current->left)	//删除左子树
	{
		deleteNode(current->left);
	}
	else if(NULL != current->right)	//删除右子树
	{
		deleteNode(current->right);
	}

	//如果current是根节点,把root置空
	if(NULL == current->parent)
	{
		delete current;
		root = NULL;
		return;
	}
	
	//将current父亲节点的相应指针置空
	if(current->parent->data  >  current->data)
	{		
		//current为其父节点的左子节点
		current->parent->left = NULL;
	}
	else	//current为parNode的右子节点
	{
		current->parent->right = NULL;
	}

	delete current;		//最后删除此节点
}
        public成员函数deleteNode()删除数据为data的节点及其子树,它首先调用了searchNode()查找数据等于data的节点,如果找到节点,则调用private成员函数deleteNode()函数删除节点及其子树。

private成员函数也是使用递归方法进行删除操作的,它的步骤如下:

(1)如果current左子树存在,则递归删除current左子树。

(2)如果current右子树存在,则递归删除current右子树。

(3)最后删除current节点,次数如果current是根节点,需要把root置空,否则把其父节点相应的指针置空。


接下来是实现二叉树的中序遍历,分别使用递归和非递归的方法。

解析:中序遍历的递归算法定义为,若二叉树非空,则一次执行如下操作。

(1)遍历左子树

(2)访问根节点

(3)遍历右子树

中序遍历的递归算法程序代码如下:

//递归,中序遍历------------------------------
void Tree::InOrderTree()
{
	if(NULL == root)
	{
		return ;
	}
	InOrderTree(root);	//调用私有函数
}
void Tree::InOrderTree(Node *current)
{
	if(NULL != current)
	{
		InOrderTree(current->left);	//遍历左子树
		cout<<current->data<<" ";	//打印节点数据
		InOrderTree(current->right);//遍历右子树
	}
}
            对于中序遍历非递归算法,可以采用栈(stack)来临时存储节点。方法如下:

(1)先将根节点入栈,遍历左子树。

(2)遍历完左子树返回时,栈顶元素应为根节点,此时出栈,并打印节点数据。

(3)再中序遍历右子树

代码如下:

//非递归,中序遍历---栈实现--------------------------------
void Tree::InOrderTreeUnRec()
{
	stack<Node *> s;	//定义栈
	Node *p = root;

	while(NULL != p || !s.empty())
	{
		while(NULL != p)	//遍历左子树
		{
			s.push(p);
			p = p->left;
		}
		if(!s.empty())
		{
			p = s.top();	//得到栈顶内容
			s.pop();
			cout<<p->data<<" ";	//打印
			p = p->right;		//指向右子节点,
		}						//下一次循环时就会有中序遍历右子树
	}
}
下面是先序遍历的实现。

解析:若二叉树非空,则先序遍历的递归算法,依次执行如下操作。

(1)访问根节点数据

(2)遍历左子树

(3)遍历右子树

先序遍历代码如下:

// 递归,先序遍历-------------------------------
void Tree::PreOrderTree()
{
	if(NULL == root)
	{
		return;
	}
	PreOrderTree(root);	//调用私有函数
}
void Tree::PreOrderTree(Node *current)
{

	if(NULL != current)
	{
		cout<<current->data<<" ";		//打印
		PreOrderTree(current->left);	//遍历左子树
		PreOrderTree(current->right);	//遍历右子树
	}
}
        对于先序遍历的非递归算法,使用栈来临时存储节点,方法如下:

(1)打印根节点数据

(2)把根节点的right入栈,遍历左子树

(3)遍历完左子树返回时,栈顶元素应为right,出栈,遍历以该指针为根的子树。

//先序遍历,非递归,栈实现-----------------------------
void Tree::PreOrderTreeUnRec()
{
	stack<Node *> s;
	Node *p = root;

	while(NULL != p || !s.empty())
	{
		while(NULL != p)	//遍历左子树
		{
			cout<<p->data<<" ";	//打印
			s.push(p);		//把遍历的节点全部压栈
			p = p->left;		
		}
		if(!s.empty())
		{
			p = s.top();	//得到栈顶内容
			s.pop();		//出栈
			p = p->right;	//指向右子节点,
		}					//下一次循环时就会先序遍历左子树
	}
}
接下来是后序遍历二叉树:

解析:若二叉树非空,则后序遍历的递归算法依次执行如下操作,

(1)遍历左子树

(2)遍历右子树

(3)访问根节点

后序遍历的程序代码如下:

//后序遍历,递归-------------------------------
void Tree::PostOrderTree()
{
	if(NULL == root)
	{
		return;
	}
	PostOrderTree(root);	//调用私有函数
}
void Tree::PostOrderTree(Node *current)
{
	if(NULL != current)
	{
		PostOrderTree(current->left);	//遍历左子树
		PostOrderTree(current->right);	//遍历右子树
		cout<<current->data<<" ";		//打印
	}
}
        后序遍历非递归算法,可采用标记法,节点入栈时,配一个标志tag一同入栈(tag为0表示遍历左子树前段现场保护,tag为1表示遍历右子树前段现场保护)。首先将tag为0入栈,遍历左子树;返回后,修改栈顶tag为1,遍历右子树;最后访问根节点。程序代码如下:

//后序遍历,非递归----栈实现---------------------------
void Tree::PostOrderTreeUnRec()
{
	stack<Node *> s;
	Node *p = root;

	while(NULL != p || !s.empty())
	{
		while(NULL != p)
		{
			s.push(p);		//压栈
			p = p->left;	//遍历左子树
		}
		if(!s.empty())
		{
			p = s.top();	//得到栈顶元素
			if(p->tag)		//tag为1时
			{
				cout<<p->data<<" ";	//打印
				s.pop();			//退栈
				p = NULL;		//第二次访问标志其右子树已经遍历
			}
			else
			{
				p->tag = 1;		//修改tag为1
				p = p->right;	//指向有节点,下次遍历其左子树
			}
		}
	}
}
接下来是层次遍历二叉树的算法:

解析:很难直接使用节点的指针(left、right、parent)来实现,但是借助队列就可以轻松的实现。下面是执行步骤:

(1)A入队。

(2)A出队,同时A的子节点B、C入队(此时队列有:B、C)。

(3)B出队,同时B的子节点D、E入队(此时队列有C、D、E)。

(4)C出队,同时C的子节点F、G入队(此时队列有D、E、F、G)。

显然这样就能使出队列的顺序满足层次遍历。代码如下:

//层次遍历-----队列实现------------------------------------
void Tree::LevelOrderTree()
{
	queue<Node *> q;	//定义队列q
	Node *ptr = root;

	q.push(root);		//根节点入队
	while(!q.empty())
	{
		ptr = q.front();	//得到队头节点
		q.pop();			//出队
		cout<<ptr->data<<" ";	//打印当前节点数据
		if(NULL != ptr->left)	//当前节点存在左节点,则有节点入队
		{
			q.push(ptr->left);
		}
		if(NULL != ptr->right)	//当前节点存在有节点,则有节点入队
		{
			q.push(ptr->right);
		}
	}
}

最后一个重要的问题是判断给定二叉树是否为二叉排序树。

解析:上面已经编写过中序遍历的算法,使用中序遍历的结果就是二叉排序树的排序树出,因此可以使用中序遍历来实现判定二叉树是否为二叉排序树。程序代码如下:

//判断是否为二叉排序树--利用中序遍历------------------------------
bool Tree::IsSortedTree(Tree tree)
{
	int lastvalue = 0;			//保存当前值
	stack<Node *> s;
	Node *p = tree.root;

	while(NULL != p || !s.empty())
	{
		while(NULL != p)
		{
			s.push(p);		//遍历左子树
			p = p->left;
		}
		if(!s.empty())
		{
			p = s.top();	//得到栈顶元素
			s.pop();
			if(0 == lastvalue || lastvalue < p->data)
			{
				//如果第一次弹出或者lastvalue小于当前节点值
				lastvalue = p->data;
			}
			else if(lastvalue >= p->data)
			{
				//如果lastvalue大雨当前节点值,说明不是二叉排序树
				return false;
			}
			p = p->right;	//指向右子节点,
		}					//下一次循环时就会中序遍历右子树
	}

	return true;	//到这里说明节点数据是升序排列,返回true
}
         下面给出主函数的测试:

int main()
{
	//中序遍历
	int num[] = {5, 3, 7, 2, 4, 6, 8, 1};
	Tree tree(num, 8);
	cout<<"InOrder: ";
	tree.InOrderTree();
	cout<<"\nInOrder: ";
	tree.InOrderTreeUnRec();
	cout<<endl<<endl;

	//先序遍历
	Tree tree1(num, 8);
	cout<<"PerOrder: ";
	tree1.PreOrderTree();
	cout<<"\nPreOrder: ";
	tree1.PreOrderTreeUnRec();
	cout<<endl<<endl;

	//后序遍历
	Tree tree2(num, 8);
	cout<<"PostOrder: ";
	tree2.PostOrderTree();
	cout<<"\nPostOrder: ";
	tree2.PostOrderTreeUnRec();
	cout<<endl<<endl;

	//层次遍历
	Tree tree3(num, 8);
	cout<<"LevelOrder: ";
	tree3.LevelOrderTree();
	cout<<endl<<endl;

	//判断是否为二叉排序树
	Tree tree4(num, 8);
	cout<<"InOrder: ";
	tree4.InOrderTreeUnRec();	//中序遍历非递归
	cout<<"\nIsSortedTree: "<<tree4.IsSortedTree(tree4)<<endl;
	
	Node *node = tree4.searchNode(4);
	node->data = 1;					//动手把节点数据改成1
	cout<<"InOrder: ";
	tree4.InOrderTreeUnRec();	//中序遍历非递归
	cout<<"\nIsSortedTree: "<<tree4.IsSortedTree(tree4)<<endl<<endl;

	return 0;
}
执行结果:

InOrder: 1 2 3 4 5 6 7 8
InOrder: 1 2 3 4 5 6 7 8

PerOrder: 5 3 2 1 4 7 6 8
PreOrder: 5 3 2 1 4 7 6 8

PostOrder: 1 2 4 3 6 8 7 5
PostOrder: 1 2 4 3 6 8 7 5

LevelOrder: 5 3 7 2 4 6 8 1

InOrder: 1 2 3 4 5 6 7 8
IsSortedTree: 1
InOrder: 1 2 3 1 5 6 7 8
IsSortedTree: 0

Press any key to continue










  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值