【无标题】

二叉树的概念

二叉搜索树又叫二叉排序树,它的结构满足三个性质:
1、若树的左子树非空,则左子树上的节点都小于根节点。
2、若树的右子树非空,则右子树上的节点都大于根节点。
3、左右子树本身又是一颗二叉搜索树。
上述性质简称二叉排序树性质(BST性质),所以二叉排序树实际上是满足BST性质的二叉树。(注意:二叉搜索树中没有相同的节点!!!
这是一棵二叉排序树

二叉树节点类的定义

二叉树的节点的成员包括数据、指向左右节点的指针、指向父节点的指针(为了更好的操作)、标记节点是左子节点还是右子节点的枚举类。成员函数是比较运算符的重载,重载运算符有利于大小的比较。

enum CHILD//标记节点是左孩子还是右孩子
{
	LEFT = 0,
	RIGHT =1
};

class Node	//节点类
{
public:
	Node(int data);//构造函数
	int data;
	Node* parent;	//父节点
	Node* left;		//左子节点
	Node* right;	//右子节点
	CHILD child;
	bool tag;		//后序遍历的标记

	bool operator<(const Node& other);//重载 “<”运算符
	bool operator>(const Node& other);//重载 “>”运算符
	bool operator==(const Node& other);//重载 “=”运算符
	bool operator!=(const Node& other);//重载 “!=”运算符
};

二叉树类的定义

二叉树类的成员是一个私有的根节点,外部接口又插入、删除、查找、判空、以及打印

class Tree
{
public:
	Tree();	//构造函数

public:
	bool insertNode(Node* node);//插入节点
	bool deleteNode(int data);//删除节点
	Node* findNode(int data);	//查找节点
	bool isEmpty();				//判断树是否为空
	void print();				//打印树
	void _print();				//前序遍历
	void print_();				//后序遍历
	void lever_print();			//层序遍历

private:
	bool deleteNode(Node* node);//删除不是全子树的节点

private:
	Node* root;//根节点

};

二叉树的插入

二叉树插入节点后要保持原有的结构不变,插入节点后要保证这棵树是一颗满足BST性质的二叉树。遵循二叉树的性质(大的放右边,小的放左边)进行插入。基本步骤为:
1、创建节点
2、通过不断比较节点的大小找到插入节点的位置,并且判断这个位置在左子树还是右子树
3、插入节点

bool Tree::insertNode(Node* node)
{
	//判断node是一个有效的节点
	if (node == NULL)
	{
		cout << "节点错误" << endl;
		return false;
	}

	if (root == NULL)//如果根节点为空,新的节点成为根节点
	{
		root = node;
		return true;
	}
	
	Node* p = root;
	Node* parentNode = NULL;//保存插入位置的父节点
	bool leftOrRight = false;//标记节点要插入左子树还是右子树,在左子树为true,右子树为false
	//循环查找节点插入位置
	while (p != NULL && *p != *node)
	{
		parentNode = p;//循环结束时p为空,所以要保存p作为插入位置的父节点
		if (*node < *p)//要插入的节点比根节点小
		{
			p = p->left;
			leftOrRight = true;
		}else if (*node > *p)//要插入的节点比根节点大
		{
			p = p->right;
			leftOrRight = false;
		}
	}

	if (p!=NULL && *node == *p)//循环结束是因为遇到相同的节点
	{
		cout << "节点:" << node->data << "已经存在,不能插入相同节点!" << endl;
		return false;
	}

	if (p == NULL)//找到了插入位置
	{
		if (leftOrRight == true)//插入的位置在左子树
		{
			parentNode->left = node;
			node->child = LEFT;//标记该节点为左子节点
		}
		else//插入的位置在右子树
		{
			parentNode->right = node;
			node->child = RIGHT;//标记节点为右子节点
		}
		node->parent = parentNode;//设置新节点的父节点
		return true;
	}
}

二叉树的删除

删除二叉树的节点同样也要保持原有的结构,删除一个节点的同时也要保证剩下的节点要构成一颗完整的二叉搜索树。删除节点时,有三种不同的情况:

1、删除的是叶子节点

也就是说这个节点没有任何子树,删除它不会对树的结构有影响。找到叶子结点后,判断这个节点是左子节点还是右子节点,将父节点指向该节点的指针置空,然后再delete该节点。
删除的节点是叶子节点

if (node->child == LEFT)//node是父节点的左子节点
{
	node->parent->left = NULL;//父节点的做孩子置空
	delete node;
}else if(node->child == RIGHT)//node是父节点的右子节点
{
	node->parent->left = NULL;//父节点的右孩子置空
	delete node;
}

2、删除的节点有左子树或者右子树

这种情况下,删除节点后会对树的结构产生影响,要将这个节点的子树连接到节点的父节点才能保证树的完整性。同样的还是先查找节点,找到节点后,先判断该节点在左子树中还是右子树中,而且还要判断该节点的子树是在左边还是右边,然后将节点的子树连接该节点的父节点在delete。
要删除的有一边子树

if (node->child == LEFT)//node是父节点的左子节点
{
	if (node->left != NULL && node->right == NULL)//node只存在左子树
	{
		node->parent->left = node->left;//将node的左子树连接到父节点
		node->left->parent = node->parent;
	}	
	else if (node->right != NULL && node->left == NULL)//node只存在右子树
	{
		node->parent->left = node->right;//将node的右子树连接到父节点
		node->right->parent = node->parent;
	}
}
else if(node->child == RIGHT)//node是父节点的右子节点
{
	if (node->left != NULL && node->right == NULL)//node只存在左子树
	{
		node->parent->right = node->left;//将node的左子树连接到父节点
		node->left->parent = node->parent;
	}
	else if (node->right != NULL && node->left == NULL)//node只存在右子树
	{
		node->parent->right = node->right;//将node的右子树连接到父节点
		node->right->parent = node->parent;
	}
}
delete node;//释放内存

其实可以发现,第一种情况和第二种情况很类似,可以将删除叶子节点看成删除有一边子树的节点的一种特例,即子树为空(left = right = NULL),所以一二种情况可以合并起来

//删除非全子树的节点
bool Tree::deleteNode(Node* node)
{
	if (node->child == LEFT)//node是父节点的左子节点
	{
		if (node->left != NULL)
		{
			node->parent->left = node->left;
			node->left->parent = node->parent;
		}	
		else if (node->right != NULL)
		{
			node->parent->left = node->right;
			node->right->parent = node->parent;
		}
		else node->parent->left = NULL;
		return true;
	}
	else if(node->child == RIGHT)//node是父节点的右子节点
	{
		if (node->left != NULL)
		{
			node->parent->right = node->left;
			node->left->parent = node->parent;
		}
		else if (node->right != NULL)
		{
			node->parent->right = node->right;
			node->right->parent = node->parent;
		}else node->parent->right = NULL;
		return true;
	}
}

3、删除的节点有左右子树

这种情况比较复杂。拿根节点来说,删除根节点会造成群龙无首,所以要从下面的节点中重新选举一个根节点来替代原来的根节点,根据二叉搜索树的性质,可以发现根节点比左子树所有的节点大,比右子树所有的节点小,所以新的根节点要么是左子树中最大的节点,要么是右子树中最小的节点。
删除根节点

//要删除的节点有左右子树
if ((pnode->left != NULL) && (pnode->right != NULL))
{
	//查找左子树中最大的节点
	Node* node = pnode->left;//定位到左子树
	while (node->right != NULL)//循环查找最大的节点
	{
		node = node->right;
	}
	//替换节点数据,删除node
	pnode->data = node->data;//将根节点的数据替换
	deleteNode(node);//删除左子树中最大的节点
	delete node;//释放内存
	return true;
}

在找左子树最大的节点时要注意一个问题,即左子树最大的节点就是左子树的根节点。

二叉树的遍历

二叉树的遍历有多重方法,每一种方法得到的结果也不一样。根据访问根节点的顺序分为:前序遍历、中序遍历、后序遍历。层序遍历是一层一层往下遍历。

中序遍历

中序遍历的算法实现需要用到一个栈(先进后出的特性)来临时的储存节点。具体的方法:
1、将根节点入栈。
2、依次将根节点的左节点入栈,直到节点为空。
3、获取到栈顶元素,并由此定位到其右子树
4、栈顶元素出栈的同时将其右子节点入栈,再次从第一步执行,直到所有节点全部遍历。

void Tree::print()
{
	stack<Node*> s;
	Node* p = root;
	Node* pnode = NULL;//临时存储栈顶指针

	while (p != NULL || !s.empty())
	{
		while (p)//将根节点及左节点入栈
		{
			s.push(p);
			p = p->left;
		}
		if (s.empty() == false)
		{
			pnode = s.top();//获取栈顶元素
			s.pop();//出栈
			cout << pnode->data << "\t";//访问节点数据
			p = pnode->right;//定位到右节点
		}
	}
}

中序遍历的结果就是二叉树的排序结果

前序遍历

前序遍历的算法实现同样需要用到栈来临时储存节点,具体的实现:
1、访问根节点,并将其入栈。
2、将左节点依次访问并入栈,直到节点为空。
3、获取栈顶元素后出栈,同时将它的右节点入栈,并以此为子树的根节点执行第1步,直到所有节点全部遍历。

void Tree::_print()
{
	stack<Node*> s;
	Node* p = root;
	if (isEmpty()) return;

	while (p != NULL || !s.empty())
	{
		while (p != NULL)
		{
			cout << p->data << "\t";//先访问节点数据
			s.push(p);//将节点入栈
			p = p->left;//定位到其左节点
		}
		if (!s.empty())
		{
			p = s.top();//获取栈顶元素
			s.pop();//出栈
			p = p->right;//将p指向其右子节点
		}
	}
}

后序遍历

后序遍历要求在访问完左右子树后再访问根节点,需要判断根节点的左右子树是否均被遍历。在节点类里添加一个标记bool变量tag作为现场保护,表示该节点可以被访问,并在构造时将其初始化为false。具体的实现:
1、将根节点以及下面的左节点依次入栈,直到节点为空
2、获取栈顶元素,并将它的标记置为true,同时扫描指针指向它的右子节点,以此为根重复执行1、2,直到叶子节点为止,停止入栈。
3、此时的栈顶元素一定是一个左子节点,并且它的标记为true,可以访问。
4、访问完之后,将栈顶元素出栈,同时将扫描指针置空,在一次执行循环执行2、3、4,直到所有节点都被访问完。

//后序遍历
void Tree::print_()
{
	stack<Node*> s;
	Node* p = root;
	if (isEmpty()) return;
	while (p != NULL || !s.empty())
	{
		//从根节点出发,以此将左节点入栈
		while (p != NULL)
		{
			s.push(p);
			p = p->left;
		}
		if (!s.empty())
		{
			p = s.top();//获取栈顶元素
			if (p->tag == true)//表示该节点可以被访问
			{
				cout << p->data << "\t";
				s.pop();//访问后出栈
				p = NULL;//将指针置空,表示不进行扫描树,而是再次访问栈顶元素
			}
			else
			{
				p->tag = true;//将栈顶元素标记置为true
				p = p->right;//将右子节点入栈
			}
		}
	}

}

层序遍历

层序遍历是从根节点开始往下一层一层遍历,如下图的树:

第一层:A
第二层:B、C
第三层:D、E、F、G
第四层:H
层次遍历的算法可以借助队列实现(先进先出的特性)。以上面的树为例子:
1、将根节点A入队
2、A出队,同时将其子节点B、C入队
3、B出队,同时将其子节点D、E入队
4、C出队,同时将其子节点F、G入队
5、由于D、E、F没有子节点,可以直接出队
6、G出队,同时将其子节点H入队
7、H出队,队列为空,所有节点访问完成。

//层序遍历
void _print_()
{
	queue<Node*> q;
	Node* p = root;
	q.push(p);//根节点入队
	whiel(!q.empty())//直到队列空
	{
		p = q.front();//获取队首元素
		cout << p->data << "\t";
		q.pop();//出队
		if(p->left != NULL)//如果有左子节点,将其入队
			q.push(p->left);
		if(p->right != NULL)//如果有右子节点,将其入队
			q.push(p->right);
	}
}

完整代码

节点类 Node.h

#pragma once

enum CHILD//标记节点是左孩子还是右孩子
{
	LEFT = 0,
	RIGHT =1
};

class Node	//节点类
{
public:
	Node(int data);//构造函数
	int data;
	Node* parent;	//父节点
	Node* left;		//左子节点
	Node* right;	//右子节点
	CHILD child;
	int tag;

	bool operator<(const Node& other);//重载 “<”运算符
	bool operator>(const Node& other);//重载 “>”运算符
	bool operator==(const Node& other);//重载 “=”运算符
	bool operator!=(const Node& other);//重载 “!=”运算符
};
/// <summary>
/// 构造函数
/// </summary>
/// <param name="data"></param>
Node::Node(int data):tag(0)
{
	this->data = data;
	parent = this;
	left = NULL;
	right = NULL;
}

/// <summary>
/// 重载小于运算符,实现类的小于比较
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
bool Node::operator<(const Node& other)
{
	if (data < other.data)
		return true;
	else return false;
}


/// <summary>
/// 重载大于运算符,实现类的大于比较
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
bool Node::operator>(const Node& other)
{
	if (data > other.data)
		return true;
	else return false;
}


/// <summary>
/// 重载等号运算符,实现类的等于比较
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
bool Node::operator==(const Node& other)
{
	if (data == other.data)
		return true;
	else return false;
}


/// <summary>
/// 重载不等于运算符,实现类的不等于比较
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
bool Node::operator!=(const Node& other)
{
	if (data != other.data)
		return true;
	else return false;
}

树类 Tree.h

#include <iostream>
#include <stack>
#include <queue>
#include "Node.h"
using namespace std;


class Tree
{
public:
	Tree();	//构造函数

public:
	bool insertNode(Node* node);//插入节点
	bool deleteNode(int data);//删除节点
	Node* findNode(int data);	//查找节点
	bool isEmpty();				//判断树是否为空
	void print();				//打印树
	void _print();				//前序遍历
	void print_();				//后序遍历
	void _print_();				//层序遍历

private:
	bool deleteNode(Node* node);//删除不是全子树的节点

private:
	Node* root;//根节点

};



Tree::Tree():root(NULL)
{

}

/// <summary>
/// 插入节点
/// </summary>
/// <param name="node">要插入的节点指针</param>
/// <returns></returns>
bool Tree::insertNode(Node* node)
{
	if (node == NULL)
	{
		cout << "节点错误" << endl;
		return false;
	}

	if (root == NULL)//如果根节点为空,插入的节点成为根节点
	{
		root = node;
		return true;
	}
	
	Node* p = root;
	Node* parentNode = NULL;
	bool leftOrRight = false;//标记节点要插入左节点还是右节点

	while (p != NULL && *p != *node)
	{
		parentNode = p;
		if (*node < *p)//要插入的节点比根节点小
		{
			p = p->left;
			leftOrRight = true;
		}
		else if (*node > *p)//要插入的节点比根节点大
		{
			p = p->right;
			leftOrRight = false;
		}
	}

	if (p!=NULL && *node == *p)//循环结束是因为遇到相同的节点
	{
		cout << "节点:" << node->data << "已经存在,不能插入相同节点!" << endl;
		return false;
	}

	if (p == NULL)//找到了插入位置
	{
		if (leftOrRight == true)//要插入左子节点
		{
			parentNode->left = node;
			node->child = LEFT;
		}
		else
		{
			parentNode->right = node;
			node->child = RIGHT;
		}
		node->parent = parentNode;
		return true;
	}
}

/// <summary>
/// 判断树是否为空,为空返回true
/// </summary>
/// <returns></returns>
bool Tree::isEmpty()
{
	if (root == NULL)
	{
		cout << "不存在根节点,树为空" << endl;
		return true;
	}
	else if ((root->left == NULL) && (root->right == NULL))
	{
		cout << "根节点下没有任何子树" << endl;
		return true;
	}
	else return false;
}


/// <summary>
/// 查找节点
/// </summary>
/// <param name="data">要查找的节点数据</param>
/// <returns>返回节点的指针</returns>
Node* Tree::findNode(int data)
{
	if (isEmpty() == true) return NULL;

	Node* p = root;
	while (p != NULL)
	{
		if (data < p->data)//比根节点小,往左子树中找
		{
			p = p->left;
		}
		else if (data > p->data)//比根节点大,往右子树中找
		{
			p = p->right;
		}
		else return p;
	}
	if (p == NULL)
	{
		cout << "树中没有节点:" << data << "!" << endl;
		return NULL;
	}
}


/// <summary>
/// 打印整个树,中序遍历
/// </summary>
void Tree::print()
{
	stack<Node*> s;
	Node* p = root;
	Node* pnode = NULL;

	while (p != NULL || !s.empty())
	{
		while (p)
		{
			s.push(p);
			p = p->left;
		}
		if (s.empty() == false)
		{
			pnode = s.top();
			s.pop();
			cout << pnode->data << "\t";
			p = pnode->right;
		}
	}
}

/// <summary>
/// 前序遍历
/// </summary>
void Tree::_print()
{
	stack<Node*> s;
	Node* p = root;
	if (isEmpty()) return;

	while (p != NULL || !s.empty())
	{
		while (p != NULL)
		{
			cout << p->data << "\t";
			s.push(p);
			p = p->left;
		}
		if (!s.empty())
		{
			p = s.top();
			s.pop();
			p = p->right;
		}
	}
}


/// <summary>
/// 后序遍历
/// </summary>
void Tree::print_()
{
	stack<Node*> s;
	Node* p = root;
	if (isEmpty()) return;
	while (p != NULL || !s.empty())
	{
		while (p != NULL)
		{
			s.push(p);
			p = p->left;
		}
		if (!s.empty())
		{
			p = s.top();
			if (p->tag == 1)
			{
				cout << p->data << "\t";
				s.pop();
				p = NULL;
			}
			else
			{
				p->tag = 1;
				p = p->right;
			}
		}
	}

}



/// <summary>
/// 删除指定的节点
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
bool Tree::deleteNode(int data)
{
	//判断树是否空
	if (isEmpty() == true) return false;

	//查找节点
	Node* pnode = findNode(data);
	if (pnode == NULL) return false;

	//定位当前节点是在父节点的左子树中还是右子树中
	//bool LOR = false;//该节点在那边子树中
	//bool lor = false;//有左子树还是右子树
	//if (pnode->parent->left == pnode)
	//	LOR = true;
	//else if (pnode->parent->right == pnode)
	//	LOR = false;

	/*
	* 1、要删除的节点是叶子节点,删除节点即可
	* 2、要删除的节点有一边子树,删除当前节点后,将节点下的子树连接到父节点
	* 3、要删除的节点有两边子树,找到左子树中最大的节点或右子树中最小的节点代替该节点后,删除子树节点
	 */

	//有左右子树的处理
	if ((pnode->left != NULL) && (pnode->right != NULL))
	{
		//查找左子树中最大的节点
		Node* node = pnode->left;
		/*if (node->right == NULL) LOR = true;*///最大的节点是左子树的根节点
		while (node->right != NULL)
		{
			node = node->right;
		}
		//替换节点数据,删除node
		pnode->data = node->data;
		deleteNode(node);
		delete node;
		return true;
	}
	else
	{
		deleteNode(pnode);
		delete pnode;
		return true;
	}
}


//删除非全子树的节点
bool Tree::deleteNode(Node* node)
{
	if (node->child == LEFT)//node是父节点的左子节点
	{
		if (node->left != NULL)
		{
			node->parent->left = node->left;
			node->left->parent = node->parent;
		}	
		else if (node->right != NULL)
		{
			node->parent->left = node->right;
			node->right->parent = node->parent;
		}
		else node->parent->left = NULL;
		

		return true;
	}
	else if(node->child == RIGHT)//node是父节点的右子节点
	{
		if (node->left != NULL)
		{
			node->parent->right = node->left;
			node->left->parent = node->parent;
		}
		else if (node->right != NULL)
		{
			node->parent->right = node->right;
			node->right->parent = node->parent;
		}
		return true;
	}
}

//层序遍历
void Tree::_print_()
{
	queue<Node*> q;
	Node* p = root;
	q.push(p);
	while (!q.empty())
	{
		p = q.front();
		q.pop();//出队
		cout << p->data << "\t";
		if (p->left != NULL)
			q.push(p->left);
		if (p->right != NULL)
			q.push(p->right);
	}
	cout << endl;
}

main函数

#include "tree.h"

int main()
{
	Tree tree;
	int dest = 0;
	int number[] = { 20,24,26,18,13,45,10,30,39,3 };
	for (auto n : number)
	{
		Node* node = new Node(n);
		if (node == NULL) return -1;
		if (tree.insertNode(node) == false)//插入失败,释放节点空间
		{
			delete node;
		}
	}

	tree.print();
	cout << "\n请输入要查找的节点:";
	cin >> dest;
	cout << "查找结果:" << tree.findNode(dest)->data << endl;

	cout << "\n请输入要删除的节点的数据:";
	cin >> dest;
	if (tree.deleteNode(dest) == false) return -1;
	else tree.print();

	cout << "\n前序遍历:\n";
	tree._print();
	cout << "\n后序遍历\n";
	tree.print_();
	cout << "\n层序遍历\n";
	tree._print_();

	system("pause");
	return 0;
}

运行结果

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值