数据结构----结构--非线性结构--树

数据结构----结构–非线性结构–树

一.树(Tree)

1.树的结构

树是一对多的结构

2.关于树的知识点

1.根节点:树最上面的节点

2.中间节点:树中间的节点

3.叶子节点:树最下面的节点

如下图

数据结构----结构--非线性结构--树_第1张图片

4.边:在树中连接节点与节点之间的箭头

5.路径:从根节点到叶子节点(如上图就有4条路径)

6.路径长度:根节点到叶子节点边的个数

7.深度和高度:树的深度和高度就是它的层数,(注意 一个节点的深度是从上往下看,高度是从下往上看)

8.度:一个节点有几个孩子,那它的度就是几(如上图A点它的度就是3)

二.二叉树(BinaryTree)

1.什么是二叉树

二叉树:在树的基础上多了一个限制条件 ,二叉树的每一个节点最多允许有两个孩子

2.关于二叉树的知识点

1.二叉树的每个节点两个孩子分别叫左孩子和右孩子

2.二叉树以每个节点左孩子为根节点的树叫左子树,以右孩子为根节点的树叫右子树

3.关于二叉树的性质

1.k层的二叉树,总的节点个数最多有2的k次方-1

2.k层的二叉树,总的叶子节点有2的k-1次方个

3.任意一颗二叉树中,度为0的节点总比度为2的节点多一个

3.以二叉树为基础的特殊的树

1.满二叉树
1.定义:

树中每一层都是满的

2.完全二叉树(可以用顺序存储来存)(CBT)
1.定义:

只允许树中最后一层有空缺,并且其空缺方式从右向左连续空缺

2.关于完全二叉树的性质

1.完全二叉树度为1的节点要么为0要么为1

2…完全二叉树高度和深度为log2的n次方向下取整+1

3.将一颗完全二叉树按照从上到下从左至右从0开始编号(0~n-1)

编号为i的节点如果满足2*i+1<=n-1,则其有左孩子,否则没有

如果满足2*i+2<=n-1,则其有右孩子,否则没有

父亲节点的编号是0~2分之n-1

4.将一颗完全二叉树按照从上到下从左至右从1(这里是将0作为特殊位置留下了)开始编号(1~n)

编号为i的节点如果满足2*i<=n,则其有左孩子,否则没有

如果满足2*i+1<=n,则其有右孩子,否则没有

基于此性质的一道题

一个完全二叉树度为0的节点有127个,那么这颗完全二叉树最多有多少个节点

此题用到了二叉树的第三条性质和完全二叉树的第一条性质

所以可以得出答案为 127+126+1 =254

3.二叉排序树 (也叫二叉搜索树)(BST)
1.定义:

树中任意一个父亲节点均满足值大于整颗左子树,小于整颗右子树

二叉排序树搜索一个数据最坏的情况下搜索的次数就是层数

4.平衡搜索树 (在二叉排序树上做的优化使树上的各个节点分配均匀)
1.二叉平衡搜索树(AVL树)
1.定义:

树中任意节点左右子树高度超不超过1

2.红黑树(RBT)
1.定义:

1.红黑树的每个节点不是黑色就是红色

2.红黑树的根节点一定是红色

3.空节点被认为是黑色的(黑哨兵)

4.不允许两个红色的节点是父子关系

5.从任意节点向下出发,到其所能到达的各个终端节点的各条路径上,黑节点的数目都必须是完全相同的

2.结论

RBT上从一个相同节点向下出发到其所能到达的终端节点的各个路径上这些路径中不会有一条路径的长度超过其他路径长度的2倍

3.在RBT上进行一些操作所需要的时间

RBT上增加数据、删除数据、搜索数据所需要的时间都为 O(log2的n次方)

这里给出一张红黑树的图

数据结构----结构--非线性结构--树_第2张图片

3.多路平衡搜索树(B、B+树)
1.B树

M阶B树就是M叉B树

定义:

​ 1.根节点可以是叶子节点

​ 2.根节点内记录个数>=1

​ 3.每个节点内记录个数<=m-1(几叉树m就是几)

​ 4.除根节点以外,其余节点内记录个数>=ceil(m/2)-1

​ 5.每个节点内的记录按照索引值从左至右有序

​ 6.左指针指向的节点值均小于当前记录,右指针指向的节点值均大于当前记录

2.B+树

M阶B+树就是M叉B+树

定义:

​ 1.内部节点(也叫索引节点,包含索引的节点)+叶子节点(包含记录的节点)

​ 2.根节点既可以是索引节点也可以是叶子节点

​ 3.每个节点内的记录/索引个数<=m-1

​ 4.根节点内的记录/索引个数>=1

​ 5.非根节点内记录/索引个数>=ceil(m/2)-1

​ 6.每个节点内的记录/索引按照从左到右有序

​ 7.每个记录/索引的左子树的值均小于当前记录/索引,右子树的值均大于当前记录/索引

​ 8.相邻的叶子节点之间从左至右有指针指向

3.B树和B+树的区别

1.结构的区别:

(1)B+树有两种节点,一种是内部节点(索引节点),一种是叶子节点(包含记录),B树中只有包含记录的节点

(2)B+树中相邻的叶子节点之间从左至右右指针指向,B树没有

2.增删的区别:

增加的区别:

增加时会涉及到裂变,裂变时两者有差别

删除的区别:

B+树删除时既删除索引节点的索引也删除记录节点的记录

3.查找的区别:

(1)单个数据搜索

​ B+树的时间复杂度是O(logm的n次方)

​ B树的时间复杂度是O(1~logm的n次方) (与所查数据所在树中的深度有关)

(2) 范围搜索

​ B树需要进行跨层访问

​ 而B+树因为相邻的叶子节点之间有从左至右的指针指向,所以只需要找到两个端点的记录,然后通过指针指向即可,不需要跨层访问

4.空间消耗的区别:

B+树比B树多一种索引节点,当一个节点中的记录个数多余该节点的记录个数时,那就会发生裂变,在此过程中B+树会多复制一份该节点的中间的那个记录到父亲层(B+树种除了叶子节点其他节点都是索引节点)

5.字典树(TrieTree)
1.定义

1.字典树不能为空树

2.字典树中不真的包含字符

2.功能

字符串的搜索、计数、排序

注意:

哈夫曼树可以解决第三大字符串问题(也就是在多个串中找某个串/某些串的问题)

6.哈夫曼树(HuffmanTree)
1.在认识哈夫曼树之前先了解一些新的知识

看下图进行了解

数据结构----结构--非线性结构--树_第3张图片

另外还有两个知识

1.带权路径长度(二叉树的每条边都有权值,每一条路径的长度是权值的相加,所以叫带权路径长度)为W*L

2.整棵树的带权路径长度为WPL=W1 * L1 + W2 * L2 + W3 * L3 + … + Wn * Ln

2.哈夫曼树
1.什么树是哈夫曼树

满足下面四条条件即为哈夫曼树(也叫最优二叉树)

1.真正的带权节点作为叶子节点

2.每个辅助节点都为父亲节点

3.没有度为1的节点

4.整棵树的带权路径长度最小

注意:

一般在哈夫曼树中权值代表对应字符/数据所出现的频率

3.哈夫曼树的功能

为了哈夫曼编码存在的

1.哈夫曼编码的功能

无损压缩/恢复

我们这里拿文本文档的压缩来进行一下说明 假如’a’的字符出现的频率为10000次,假设’b‘字符出现的频率为10次,那这时我们将每个’a’所占的位数变小,每个’b’字符所占的位数变大,那么就会节省出空间,也就完成了空间的压缩

注意:编码一般的功能是传输,加密,压缩

5.关于哈夫曼树的一道小题

已知一颗哈夫曼树的叶子节点有2m个,求这颗哈夫曼树总的节点有多少个

此题用到了二叉树的第三条性质和是哈夫曼树的第三个条件

所以可以得出答案为 2m + 2m - 1 = 4m

三.关于二叉树和完全二叉树的操作

1.二叉树的简单创建

要创建的二叉树如下图

数据结构----结构--非线性结构--树_第4张图片

代码如下
//此代码是用c写的
#include 
#include

typedef struct Node{
	int n_value;

	struct Node* p_left;
	struct Node* p_right;

}BinaryTree;

BinaryTree* creatTree() {
	//根节点
	BinaryTree* head= (BinaryTree*)malloc(sizeof(BinaryTree));
	head->n_value = 1;
	head->p_left = NULL;
	head->p_right = NULL;

	//根的左
	head->p_left = (BinaryTree*)malloc(sizeof(BinaryTree));
	head->p_left->n_value = 2;
	head->p_left->p_left = NULL;
	head->p_left->p_right = NULL;

	//左的左
	head->p_left->p_left = (BinaryTree*)malloc(sizeof(BinaryTree));
	head->p_left->p_left->n_value = 4;
	head->p_left->p_left->p_left = NULL;
	head->p_left->p_left->p_right = NULL;

	//左的右
	head->p_left->p_right = (BinaryTree*)malloc(sizeof(BinaryTree));
	head->p_left->p_right->n_value = 5;
	head->p_left->p_right->p_left = NULL;
	head->p_left->p_right->p_right = NULL;

	//根的右
	head->p_right= (BinaryTree*)malloc(sizeof(BinaryTree));
	head->p_right->n_value = 3;
	head->p_right->p_left = NULL;
	head->p_right->p_right = NULL;

	return head;
}

int main(){
    //创建二叉树
	BinaryTree* headtree = creatTree();
}

2.二叉树的遍历

二叉树的遍历有三种

1.前序遍历:根节点 左子树 右子树

2.中序遍历:左子树 根节点 右子树

3.后序遍历:左子树 右子树 根节点

1.二叉树的三种遍历(递归)
1.前序遍历(递归)
//此代码是用c写的
//遍历上面的树
void preorderTraversal(Tree* node) {
	if (node == NULL) {
		return;
	}
	//根
	printf("%d\n", node->n_value);
	//左子树
	preorderTraversal(node->p_left);
	//右子树
	preorderTraversal(node->p_right);
}
2.中序遍历(递归)
//此代码是用c写的
//遍历上面的树
void inorderTraversal(Tree* node) {
	if (node == NULL) {
		return;
	}	
	//左子树
	inorderTraversal(node->p_left);
	//根
	printf("%d\n", node->n_value);
	//右子树
	inorderTraversal(node->p_right);
}
3.后序遍历(递归)
//此代码是用c写的
//遍历上面的树
void lastorderTraversal(Tree* node) {
	if (node == NULL) {
		return;
	}
	//左子树
	lastorderTraversal(node->p_left);
	
	//右子树
	lastorderTraversal(node->p_right);

	//根
	printf("%d\n", node->n_value);

}

3.二叉树的创建—使用前序的方法(可输入数据)

//此代码是用c写的
#include 
#include 
typedef struct Node {
	int n_value;

	struct Node* p_left;
	struct Node* p_right;

}BinaryTree;


void CreateTree(BinaryTree** p_node) {
	int num;
	scanf_s("%d", &num);
	//进行检测
	if (num == 0) {
		return;
	}
	//申请节点
	*p_node = (BinaryTree*)malloc(sizeof(BinaryTree));
	(*p_node)->n_value = num;
	(*p_node)->p_left = NULL;
	(*p_node)->p_right = NULL;

	//左子树
	CreateTree(&((*p_node)->p_left));

	//右子树
	CreateTree(&((*p_node)->p_right));
}

int main(){
    //创建二叉树
	BinaryTree* Tree;
	CreateTree(&Tree);//用二级指针接一级指针的地址以达成用形参改实参中值的目的(这里是使一级指针指向所创建树的根节点的地址)
}
补充一个知识点:

由一维数组(自己输入的一些或者单个的数据也可以看成是一维数组)变为二叉树叫做二叉树的序列化

由一维数组(自己输入的一些单个的数据也可以看成是一维数组)变为线性结构叫做二叉树的反序列化

4完全二叉树的创建

1.完全二叉树创建的步骤

1.创建一个节点数组

2.给每个节点进行赋值

3.将每个节点进行左右关联

2.代码如下
BinaryTree* CreateCBT(int arr[],int length) {
	if (arr == NULL || length == 0) return;//如果没有数组或数组中没有元素,则结束创建
	
    //节点数组
	BinaryTree* PTree = NULL;
	PTree = (BinaryTree*)malloc(sizeof(BinaryTree) * length);
    
    //赋值
	for (int i = 0; i < length; i++) {
		PTree[i].n_value = arr[i];
		PTree[i].p_left = NULL;
		PTree[i].p_right = NULL;
	}

    //进行左右关联
	for (int i = 0; i < length / 2; i++) {
		if (2 * i + 1 < length - 1) {
			PTree[i].p_left = &arr[2 * i + 1];
		}
		if (2 * i + 2 < length - 1) {
			PTree[i].p_right = &arr[2 * i + 2];
		}
	}
	return PTree;
}
int main() {
	int a[] = { 1,2,3,4,5,6 };
	BinaryTree*  TreeCBT= CreateCBT(a, 6);
	return 0;
}

5.二叉树的三种遍历(非递归)

1.前序遍历(非递归)
前序遍历的步骤

1.创建一个栈(用来存节点,便于之后走到叶子节点后能找到之前节点的右子树,否则右子树的数据就没法遍历到了)

2.判断当前节点是否为空节点

(1)如果是空节点 获取栈顶元素(获得是该节点的父节点)并将栈顶元素删除 然后对获取的栈顶元素继续步骤2的操作

(2)如果不是空节点 打印输出当前节点的值,将当前节点保存到栈中,然后到此节点的左子树

3.看此节点的左子树是否为空

如果不为空继续步骤2的(2)操作

如果为空则获取栈顶元素并将栈顶元素删除,然后到此节点的右子树继续步骤2

终止条件:直到栈中没有元素了结束操作(稍后在代码中要判断好写的位置)

代码如下
void preorderTraversal(Tree* pTree) {
	stack st;
	while (1) {
		while (pTree) {//当没有左节点时结束
			cout << pTree->n_value << " ";//打印 先打印的是根节点
			st.push(pTree);//入栈 
			pTree = pTree->	p_left;
		}
		if (st.empty())break;//如果压栈之后栈仍空 则说明没有节点需要处理 结束循环


		pTree = st.top();//找到父节点 从而找到右节点
		st.pop();//将父节点弹出
		pTree = pTree->p_right;//对右节点进行处理 方式与之前相同
	}
}
2.中序遍历(非递归)
中序遍历的步骤

中序遍历的步骤与前序遍历的步骤就差了打印的位置不同,这里就不加以说明了

代码如下
void inorderTraversal(Tree* pTree) {
	stack st;
	while (1) {
		while (pTree) {//当没有左节点时结束
			st.push(pTree);//入栈 
			pTree = pTree->p_left;
		}
		if (st.empty())break;//如果压栈之后栈仍空 则说明没有节点需要处理 结束循环


		pTree = st.top();//找到父节点 从而找到右节点
		cout << pTree->n_value << " ";//打印 先打印的是左节点
		st.pop();//将父节点弹出
		pTree = pTree->p_right;//对右节点进行处理 方式与之前相同
	}
}
3.后序遍历(非递归)
后序遍历的步骤

1.创建一个栈(用来存节点,便于之后走到叶子节点后能找到之前节点的右子树,否则右子树的数据就没法遍历到了)和一个标记点(用来看一个节点的右子树是否已经遍历完了)

2.判断当前节点是否为空节点

(1)如果是空节点 获取栈顶元素(获得是该节点的父节点)并将栈顶元素删除 然后对获取的栈顶元素继续步骤2的操作

(2)如果不是空节点 将当前节点保存到栈中,然后到此节点的左子树

3.看此节点的左子树是否为空

如果不为空继续步骤2的(2)操作

如果为空则获取栈顶元素并将栈顶元素删除,然后到此节点的右子树

4.如果右子树为空,或者右子树是标记点那么就获取栈顶元素然后打印该元素中的值,最后删除该栈顶元素

否则就继续步骤2的(2)操作

终止条件:直到栈中没有元素了结束操作(稍后在代码中要判断好写的位置)

代码如下
void lastorderTraversal(Tree* pTree) {
	stack st;
	Tree* flag = NULL;
	while (1) {
		while (pTree) {//当没有左节点时结束
			st.push(pTree);
			pTree=pTree->p_left;
		}

		if (st.empty()) break;
		
		if (st.top()->p_right == NULL || st.top()->p_right== flag) {
			flag = st.top();
			st.pop();
			cout << flag->n_value<<"  ";
			
		}
		else {
			pTree = st.top()->p_right;
		}
	}
}

6.层序遍历

层序遍历的步骤

1.创建一个队列,将根节点入队

2.将当前队首元素(节点)弹出,打印,并将队首元素(节点)的非空左右子树入队

终止条件:直到队中没有元素了结束遍历

代码如下
void levelTraversal(Tree* tree) {
	queue qe;
	qe.push(tree);//根元素入栈
	while (1) {
		if (qe.empty()) break;//如果队列为空,循环退出
		//获得队首元素
		Tree* Temp = qe.front();
		//打印队首元素的值
		cout << Temp->n_value << "   ";
		//删除队首元素
		qe.pop();
		//如果左右节点不为空,入队
		if (Temp->p_left != NULL) {
			qe.push(Temp->p_left);
		}
		if (Temp->p_right != NULL) {
			qe.push(Temp->p_right);
		}
	}
}
关于层序遍历的一道题
题目:

将一颗二叉树按层打印

解决方法
1.补空

​ 创建一个队列(此队列存的是节点的地址),然后将根节点的地址入队,然后将当前队首元素(节点)弹出,打印,并将队首元素(节点)的左右子树的地址入队,如果是空的,那么就用一个特殊标记标记上,然后打印的时候不打印,(每一行元素的的打印是按照1,2,4,8个元素…这样打印的,当然如果是特殊标记那就不打印,但也占一个打印元素的数量)

这里注意一下:如果弹出的是特殊标记(也就是空节点)那么在队尾就会添加它的左右子树,两个特殊标记(空节点)

2.计算下一层个数

​ 第一种方法

​ 1.定义一个count变量,用来记录下一层元素的个数

​ 2.创建一个队列(此队列存的是节点的地址),将根节点入队,用count变量记录第一层元素的个数(也就是当前队列中有多少元素,用size()函数进行获取)

​ 3.将当前队首元素(节点)弹出,打印,count变量的大小减1,并将队首元素(节点)的非空左右子树的地址入队

​ 4.当变量的大小减为0时,输出换行,然后变量重新获得下一层的元素个数(也就是用变量记录当前队列中有多少元素,用size()函数进行获取)

​ 第二种方法

​ 1.创建一个队列(此队列存的是节点的地址),将根节点的地址入队

​ 2.定义两个变量,一个记录当前层元素个数,一个记录下一层元素的个数

​ 3.将队首元素(节点)弹出,打印(打印的是元素指向的节点中的值),记录当前层元素个数的变量大小减1,队首元素(节点)的非空左右子树的地址入队,

​ 记录下一层元素个数的变量大小加上入队的元素的个数

​ 4.当记录当前层元素个数的变量大小减为0时,进行换行。然后将记录当前层元素个数的变量的值与记录下一层元素个数的变量值进行互换(当前层元素的个数变为了下一层元素的个数,然后将下一层元素的个数置为空),继续重复操作,直到记录当前层元素个数的变量和记录下一层元素个数的变量的大小同时为0

3.双队列

​ 1.创建两个队列(两个队列存的都是节点的地址)

​ 2.将根节点的地址存入到第一个队列中

​ 3.将第一个队列的中元素依次弹出,打印(打印的是元素指向的节点中的值),然后将第一个队列的中元素的非空左右子树入到第二个队列中,当第一个队列为空时进行换行

​ 4.将第二个队列的中元素依次弹出,打印(打印的是元素指向的节点中的值),然后将第二个队列的中元素的非空左右子树入到第一个队列中,当第二个队列为空时进行换行

​ 5.重复步骤3和步骤4的操作直到两个队列同时为空操作结束

4.map

​ map的key值是地址,value值存的是层数

​ 1.创建一个队列(此队列存的元素是map类型的),将根节点的地址作为key值,0作为value值,然后将此作为一个map类型的变量存入队列

​ 2.将队首元素弹出,打印(打印的是key值指向的节点中的值),队首元素的key值的非空左右子树作为新的key值,队首元素的value值进行加1操作,然后将此作为一个map类型的变量存入队列

​ 3.如果前一个弹出的元素的value值不等于下一个弹出的元素的value值,那么进行换行操作

​ 4.重复步骤2和步骤3操作直到队列中没有元素了结束操作

5.标记

​ 第一种方法(直接放入标记节点)

​ 1.创建一个队列(此队列存的是节点的地址)

​ 2.将根节点的地址存入到第一个队列中,并在其后面添加一个标记节点

​ 3.如果遇到的不是标记节点,将队首元素(节点)弹出,打印(打印的是元素指向的节点中的值),记录当前层元素个数的变量大小减1,队首元素(节点)的非空左右子树的地址入队

​ 如果遇到是标记节点,将此节点弹出,进行换行,再次将此节点入队

​ 第二种方法(单标记)

​ 1.创建一个队列(此队列存的是节点的地址)

​ 2.将根节点的地址存入到第一个队列中,设置一个标记变量用来存当前层的末尾的节点地址(此时存的就是根节点的地址)

​ 3.将队首元素(节点)弹出,打印(打印的是元素指向的节点中的值),队首元素(节点)的非空左右子树的地址入队

​ 4.判断是否要进行换行

​ 基于步骤三的判断如果弹出的元素是标记变量所存的地址,那么进行换行,然后将标记变量所存的地址更新成最后存进去的那个节点的地址(也就是下一层末尾的节点的地址)

​ 5.重复继续步骤3和步骤4的操作,直到队列中没有元素时,结束操作

​ 第三种方法(双标记)

​ 1.创建一个队列(此队列存的是节点的地址)

​ 2.将根节点的地址存入到第一个队列中,设置两个标记变量第一个用来存当前层的末尾的节点地址(此时存的就是根节点的地址)第二个用来存下一层末尾的节点地址

​ 3.将队首元素(节点)弹出,打印(打印的是元素指向的节点中的值),

​ 如果弹出的元素是当前层的末尾的节点地址,进行换行,然后第一个标记变量的值更新为第二个标记变量的值(此时第一个标记变量存下一层的末尾的节点地址了),第二个标记变量清空

​ 看队首元素(节点)的左子树,如果不为空那么第二个标记变量会存入这个节点的地址,然后将此节点入队

​ 看队首元素(节点)的右子树,如果不为空那么第二个标记变量会存入这个节点的地址,然后将此节点入队

​ 4.重复继续步骤3的操作,直到队列中没有元素时,结束操作

四.关于BST的操作

1.BST的创建(添加)

操作步骤:

看是否有树,如果没有树则添加的节点为根节点

如果有树,则新节点与树上节点进行比较

如果新节点小于当前树上节点,则与此节点的左子树进行比较,如果此时没有左子树,那么新节点就变成树的左子树,如果有左子树,则重复比较的这个操作

如果新节点大于当前树上节点,则与此节点的右子树进行比较,如果此时没有右子树,那么新节点就变成树的右子树,如果有右子树,则重复比较的这个操作

如果新节点等于当前树上节点,则不进行添加

代码如下
BSTTree* CreateBST(BSTTree** BSTTree1) {
	BSTTree* createBST=NULL;
	int num;
	printf("请输入要添加多少数据\n");
	scanf_s("%d", &num);
	while (num--) {
		printf("请输入要添加的数据\n");
		int data;
		scanf_s("%d", &data);
		if (*BSTTree1 == NULL) {//如果是空树
			BSTTree* Root = (BSTTree*)malloc(sizeof(BSTTree));
			Root->value = data;
			Root->p_left = NULL;
			Root->p_right = NULL;
			createBST = Root;
			*BSTTree1 = Root;
		}
		else {//如果是非空树
			BSTTree* Note = (BSTTree*)malloc(sizeof(BSTTree));
			Note->value = data;
			Note->p_left = NULL;
			Note->p_right = NULL;
			BSTTree* Temp = *BSTTree1;
			while (1) {
				if (Temp->value < Note->value) {//如果新节点中的值大于树上节点中的值
					BSTTree* Temp2= Temp->p_right;//看此树节点的右子树是否为空
					if (Temp2 == NULL) {//为空,那此新节点成为此树节点的右子树
						Temp->p_right = Note;
						break;
					}
					Temp = Temp->p_right;//不为空,就继续进行比较
				}
				else if (Temp->value > Note->value) {//如果新节点中的值小于树上节点中的值
					BSTTree* Temp2 = Temp->p_left;//看此树节点的左子树是否为空
					if (Temp2 == NULL) {//为空,那此新节点成为此树节点的左子树
						Temp->p_left = Note;
						break;
					}
					Temp = Temp->p_left;//不为空,就继续进行比较
				}
				
				else if (Temp->value == Note->value) {//如果新节点中的值等于树上节点中的值,那么程序退出
					printf("错误\n");
					exit(1);
				}
			}
		}
	}
	return createBST;
}

2.BST中节点的删除

操作步骤:

1.查找(被删除的节点,还有被删除的节点的父亲)

2.当前被删除节点孩子情况的分析

​ (1)如果是叶子节点(没有孩子节点),那么直接删除叶子节点,然后将该叶子节点的父节点的指向赋空

​ (2)如果是只有一个孩子的节点,那么让该节点的父亲节点指向这个节点所指向的地址(相当于是爷孙相连)然后删除该节点

​ (3)如果有两个孩子节点,找当前节点左子树中值最大的节点或者当前节点右子树中值最小的节点,然后用找到的这个节点的值把要删除的节点的值覆盖,最后删除这个找到的节点即可(删除的时候还是要对删除的节点进行其孩子的分析其实就是再来一次步骤2中(1)或(2)的操作)

代码如下
void Search(BSTTree* pTree, BSTTree** Fa, BSTTree** Del ,int x) {
	
	while (pTree) {
		if (pTree->value == x) {//如果相等
			*Del = pTree;
			return;
		}
		else if (pTree->value>x) {//如果大于要找的数
			//左子树
			*Fa = pTree;//保留一下父节点
			pTree = pTree->p_left;
		}
		else if (pTree->value < x) {//如果小于要找的数
			*Fa = pTree;//保留一下父节点
			pTree = pTree->p_right;
		}
	}
	*Fa= NULL; //没找到 父节点为NULL

}


void BSTdelete(BSTTree** BSTTree1, int x) {
	//查找,返回的是找到的那个节点(这里传入的参数是找到的那个节点的父节点)
	BSTTree* Fa = NULL;
	BSTTree* Del = NULL;
	Search(*BSTTree1, &Fa, &Del, x);
	if (Del == NULL) return;//如果没找到要删除的元素结束删除操作
	BSTTree* pMark = NULL;
	//分析孩子的个数

	//两个孩子
	if (Del->p_left != NULL && Del->p_right != NULL) {
		pMark = Del;

		//左子树最右
		Fa = Del;
		Del = Del->p_left;
		while (Del->p_right != NULL) {
			Fa = Del;
			Del = Del->p_right;
		}

		//值覆盖
		pMark->value = Del->value;
	}
	//一个或0个孩子
	//根
	if (Fa == NULL) {
		*BSTTree1 = Del->p_left ? Del->p_left : Del->p_right;
		free(Del);
		Del=NULL;
		return;
	}

	//非根
	if (Del == Fa->p_left) {
		Fa->p_left = Del->p_left ? Del->p_left : Del->p_right;
	}
	else {
		Fa->p_right = Del->p_left ? Del->p_left : Del->p_right;
	}
	free(Del);
	Del = NULL;
	return;
}

3.将一颗BST变为有序的双向链表

操作步骤:

1.因为是要有序的双向,所以BST需要以中序遍历

2.我们将树中每个节点的left指针变为链表中的pre指针,right指针变为链表中的next指针

3.用中序遍历遍历一遍BST,遍历到的节点原来的right指针就会指向中序遍历的下一个节点,left指针会指向中序遍历的上一个节点

4.所以我们只需要在中序遍历的代码上稍作需改即可

代码如下
void BSTToList(BSTTree* pTree, BSTTree** Head, BSTTree** Tail) {
	stack st;
	BSTTree* Temp = NULL;
	while (1) { 
		while (pTree) {//当没有左节点时结束
			st.push(pTree);//打印 入栈 
			pTree = pTree->p_left;
		}
		if (st.empty())break;//如果压栈之后栈仍空 则说明没有节点需要处理 结束循环


		pTree = st.top();//找到父节点 从而找到右节点
		//改造成有序的双向链表
		if (*Head == NULL && *Tail == NULL) {
			*Head = *Tail = pTree;
			Temp = *Head;
		}
		else {
			*Tail = pTree;
			Temp->p_right = (*Tail);
			(*Tail)->p_left = Temp;
			Temp = Temp->p_right;
		}

		st.pop();//将父节点弹出
		pTree = pTree->p_right;//对右节点进行处理 方式与之前相同0
	}
}

int main() {
	BSTTree* Head=NULL;
	BSTTree* Tail=NULL;
	midOrder(Tree, &Head, &Tail);
    //输出链表
	while (Head != NULL) {
		cout << Head->value << endl;
		Head = Head->p_right;
	}
	return 0;
}

4.BST的旋转

1.为什么要进行BST的旋转操作

为了让BST中的数据平衡,以此去达到BST的理想的增删查的所需要的时间( O(log2的n次方) )

2.BST的右旋

如图,为了使BST中的数据平衡,这里对图中左边的BST进行了右旋操作,然后BST变成了图中右边的样子

数据结构----结构--非线性结构--树_第5张图片

注意:

1.这里的BST中的每一个节点是可以找到父亲节点的,所以我们需要对之前的BST进行一下修改

2.当进行右旋修改树中每个节点的父亲孩子时是要先调树的下面的节点再调树的上面的节点

操作步骤:
1.改树
2.看哪个节点左子树和右子树不平衡了进行旋转(这里参照上面的图来进行操作。是对A节点右旋)

​ 注意:在进行旋转节点操作时X节点和E节点可能是不存在的,所以在写代码时还要注意判断节点是否存在(上图中X节点和E节点都是存在的)

(1)节点的孩子的改变

​ A节点的左孩子变为E节点

​ B节点的右孩子变为A节点

​ X节点的孩子变为B节点(写代码时要具体判断时X节点的哪个孩子是C节点,如果没有X节点要进行换根操作)

(2)节点的父亲的改变

​ E节点的父亲变成A节点 (写代码时要具体判断是否存在D节点)

​ A节点的父亲变成B节点

​ B节点的父亲变成X节点

代码如下
#include 
#include

typedef struct Node {
	int n_value;

	struct Node* p_left;
	struct Node* p_right;
	struct Node* p_fa;//在节点的数据类型中加一个可以指向父亲节点的指针
}Tree;
//对BST的创建过程进行修改
Tree* creatTree() {
	//根节点
	Tree* head = (Tree*)malloc(sizeof(Tree));
	head->n_value = 1;
	head->p_left = NULL;
	head->p_right = NULL;
	head->p_fa = NULL;//此处是新修改的

	//左
	head->p_left = (Tree*)malloc(sizeof(Tree));
	head->p_left->n_value = 2;
	head->p_left->p_left = NULL;
	head->p_left->p_right = NULL;
	head->p_left->p_fa = head;//此处是新修改的

	//左左
	head->p_left->p_left = (Tree*)malloc(sizeof(Tree));
	head->p_left->p_left->n_value = 4;
	head->p_left->p_left->p_left = NULL;
	head->p_left->p_left->p_right = NULL;
	head->p_left->p_left->p_fa = head->p_left;//此处是新修改的

	//左右
	head->p_left->p_right = (Tree*)malloc(sizeof(Tree));
	head->p_left->p_right->n_value = 5;
	head->p_left->p_right->p_left = NULL;
	head->p_left->p_right->p_right = NULL;
	head->p_left->p_right->p_fa = head->p_left;//此处是新修改的

	//右
	head->p_right = (Tree*)malloc(sizeof(Tree));
	head->p_right->n_value = 3;
	head->p_right->p_left = NULL;
	head->p_right->p_right = NULL;
	head->p_right->p_fa = head;//此处是新修改的
	return head;
}

Tree* pRoot = NULL;//全局的根节点,方便进行换根操作
void RightRotate(Tree* pTree) {
	if (pTree == NULL || pTree->p_left == NULL) {
		return;
	}

	Tree* p_mark = pTree->p_left;

	//三个孩子

	pTree->p_left = p_mark->p_right;

	p_mark->p_right = pTree;

	if (pTree->p_fa != NULL) {//如果旋转的节点不是根节点
		if (pTree->p_fa->p_right == pTree) {
			pTree->p_fa->p_right = p_mark;
		}
		if (pTree->p_fa->p_left == pTree) {
			pTree->p_fa->p_left = p_mark;
		}
	}
	else {//如果旋转的节点是根节点,那进行旋转之后需要进行换根操作
		pRoot = p_mark;
	}


	//三个父亲
	if (pTree->p_left != NULL) {
		pTree->p_left->p_fa = pTree;
	}

	p_mark->p_fa = pTree->p_fa;
	
	pTree->p_fa = p_mark;
}
3.BST的左旋

如图,为了使BST中的数据平衡,这里对图中左边的BST进行了左旋操作,然后BST变成了图中右边的样子

数据结构----结构--非线性结构--树_第6张图片

操作步骤:
1.改树(使每个节点有父节点)
2.看哪个节点左子树和右子树不平衡了进行旋转(这里参照上面的图来进行操作。是对A节点左旋)

​ 注意:在进行旋转节点操作时X节点和E节点可能是不存在的,所以在写代码时还要注意判断节点是否存在(上图中X节点和E节点都是存在的)

(1)节点的孩子的改变

​ A节点的右孩子变为D节点

​ C节点的左孩子变为A节点

​ X节点的孩子变为C节点(写代码时要具体判断时X节点的哪个孩子是C节点,如果没有X节点要进行换根操作)

(2)节点的父亲的改变

​ D节点的父亲变为A节点(写代码时要具体判断是否存在D节点)

​ C节点的父亲变为X节点

​ A节点的父亲变为C节点

代码如下
#include 
#include

typedef struct Node {
	int n_value;

	struct Node* p_left;
	struct Node* p_right;
	struct Node* p_fa;//在节点的数据类型中加一个可以指向父亲节点的指针
}Tree;
//对BST的创建过程进行修改
Tree* creatTree() {
	//根节点
	Tree* head = (Tree*)malloc(sizeof(Tree));
	head->n_value = 1;
	head->p_left = NULL;
	head->p_right = NULL;
	head->p_fa = NULL;//此处是新修改的

	//左
	head->p_left = (Tree*)malloc(sizeof(Tree));
	head->p_left->n_value = 2;
	head->p_left->p_left = NULL;
	head->p_left->p_right = NULL;
	head->p_left->p_fa = head;//此处是新修改的

	//左左
	head->p_left->p_left = (Tree*)malloc(sizeof(Tree));
	head->p_left->p_left->n_value = 4;
	head->p_left->p_left->p_left = NULL;
	head->p_left->p_left->p_right = NULL;
	head->p_left->p_left->p_fa = head->p_left;//此处是新修改的

	//左右
	head->p_left->p_right = (Tree*)malloc(sizeof(Tree));
	head->p_left->p_right->n_value = 5;
	head->p_left->p_right->p_left = NULL;
	head->p_left->p_right->p_right = NULL;
	head->p_left->p_right->p_fa = head->p_left;//此处是新修改的

	//右
	head->p_right = (Tree*)malloc(sizeof(Tree));
	head->p_right->n_value = 3;
	head->p_right->p_left = NULL;
	head->p_right->p_right = NULL;
	head->p_right->p_fa = head;//此处是新修改的
	return head;
}

Tree* pRoot = NULL;//全局的根节点,方便进行换根操作
void LeftRatote(Tree* pTree) {
	if (pTree == NULL || pTree->p_right == NULL)return;

	Tree* pMark = pTree->p_right;
	//三个孩子
	pTree->p_right = pMark->p_left;
	pMark->p_left = pTree;
	if (pTree->p_fa != NULL) {//如果旋转的节点不是根节点
		if (pTree == pTree->p_fa->p_left) {
			pTree->p_fa->p_left = pMark;
		}
		else {
			pTree->p_fa->p_right = pMark;
		}
	}
	else {//如果旋转的节点是根节点,那进行旋转之后需要进行换根操作
		pRoot = pMark;
	}

	//三个父亲
	if (pTree->p_right != NULL) {
		pTree->p_right->p_fa = pTree;
	}
	pMark->p_fa = pTree->p_fa;
	pTree->p_fa = pMark;
}
4.BST的双旋

在看双旋之前我们先来看看一下左旋和右旋

1.左旋
1.左旋可以解决的问题

左旋是解决由一个节点左的左导致的不平衡

可以用左旋解决的不平衡的BST的简略图

数据结构----结构--非线性结构--树_第7张图片

这里是以A节点右旋去解决不平衡问题的

2.右旋
1.左旋可以解决的问题

右旋是解决由一个节点右的右导致的不平衡

可以用右旋解决的不平衡的BST的简略图

数据结构----结构--非线性结构--树_第8张图片

这里是以A节点左旋去解决不平衡问题的

双旋:
1.什么是双旋

为了处理BST不平衡的问题同时采用左旋和右旋就是双旋

2.双旋的两种情况

1.先左旋再右旋

此方法解决的是由一个节点左的右导致的不平衡

我们看一下可以这种方法解决的不平衡的BST的简略图

数据结构----结构--非线性结构--树_第9张图片

这里是先对B点进行左旋,再对A点进行右旋

2.先右旋再左旋

此方法解决的是由一个节点右的左导致的不平衡

我们看一下可以这种方法解决的不平衡的BST的简略图

数据结构----结构--非线性结构--树_第10张图片

这里是先对B点进行右旋,再对A点进行左旋

五.关于红黑树的操作

1.红黑树的创建(添加)

注意:

1.红黑树的节点比BST中的节点多一个颜色变量

2.红黑树红色的节点要么有一个孩子要么有两个孩子

操作步骤:

1.判断红黑树是否存在

如果不存在,那新添加的节点颜色变为黑色成为根节点

如果存在,则找到要添加的位置,然后看其父亲节点的颜色(执行步骤2)

2.查找被添加元素的父亲节点是谁

3.如果父亲节点是黑色,那直接在该父亲节点的左子树或者右子树添加该节点即可(在哪棵树上进行添加需要在写代码时进行判断)

如果父亲节点是红色,先在该父亲节点的左子树或者右子树添加该节点(在哪棵树上进行添加需要在写代码时进行判断),然后看其叔叔节点的颜色(执行步骤3)

4.如果叔叔节点是红色,那么将父亲节点和叔叔节点变为黑色,爷爷节点变为红色,爷爷变为新的要处理节点(从步骤2重新开始)

如果叔叔节点是黑色,那么要进行旋转(执行步骤4)

5.如果父亲是在爷爷的左子树上

​ (1)如果要处理的节点在父亲节点的右子树上

​ 那么父亲成为新的要处理的节点,以要处理的的节点为旋转点进行左旋

​ (2)如果要处理的节点在父亲节点的左子树上

​ 那么父亲节点变为黑色,爷爷节点变为红色,以爷爷节点为旋转点右旋,结束

​ 如果父亲是在爷爷的右子树上

​ (1)如果要处理的节点在父亲节点的左子树上

​ 那么父亲成为新的要处理的节点,以要处理的的节点为旋转点进行右旋

​ (2)如果要处理的节点在父亲节点的右子树上

​ 那么父亲节点变为黑色,爷爷节点变为红色,以爷爷节点为旋转点左旋,结束

代码如下:
#include
#include
using namespace std;

typedef struct Tree {
	int val;
	int color;//1表示红,0表示黑
	struct Tree* p_left;
	struct Tree* p_right;
	struct Tree* p_fa;
}Tree;



Tree* pRoot = NULL;
void RightRatote(Tree* pTree) {
	if (pTree == NULL || pTree->p_left == NULL) {
		return;
	}

	Tree* p_mark = pTree->p_left;
	//孩子

	pTree->p_left = p_mark->p_right;



	p_mark->p_right = pTree;

	if (pTree->p_fa != NULL) {
		if (pTree->p_fa->p_right == pTree) {
			pTree->p_fa->p_right = p_mark;
		}
		if (pTree->p_fa->p_left == pTree) {
			pTree->p_fa->p_left = p_mark;
		}
	}
	else {
		pRoot = p_mark;
	}
	//父亲
	if (pTree->p_left != NULL) {
		pTree->p_left->p_fa = pTree;
	}


	p_mark->p_fa = pTree->p_fa;


	pTree->p_fa = p_mark;
}



void LeftRatote(Tree* pTree) {
	if (pTree == NULL || pTree->p_right == NULL)return;

	Tree* pMark = pTree->p_right;
	//三个孩子
	pTree->p_right = pMark->p_left;
	pMark->p_left = pTree;
	if (pTree->p_fa != NULL) {
		if (pTree == pTree->p_fa->p_left) {
			pTree->p_fa->p_left = pMark;
		}
		else {
			pTree->p_fa->p_right = pMark;
		}
	}
	else {//换根
		pRoot = pMark;
	}

	//三个父亲
	if (pTree->p_right != NULL) {
		pTree->p_right->p_fa = pTree;
	}
	pMark->p_fa = pTree->p_fa;
	pTree->p_fa = pMark;
}

void CreatNode(Tree** Node,int data) {
	(*Node) = new(Tree);
	(*Node)->color = 1;
	(*Node)->p_fa = NULL;
	(*Node)->p_left = NULL;
	(*Node)->p_right = NULL;
	(*Node)->val = data;
}

void Search(Tree* root, Tree* Node,Tree** Fa) {
	if (root->val < Node->val) {//如果原有的树节点小于新节点
		if (root->p_right == NULL) {
			*Fa = root;
		}
		else {
			root = root->p_right;
			Search(root, Node, Fa);
		}
	}
	if (root->val > Node->val) {//如果原有的树节点大于新节点
		if (root->p_left == NULL) {
			*Fa = root;
		}
		else {
			root = root->p_left;
			Search(root, Node, Fa);
		}
	}
	if (root->val == Node->val) {
		printf("失败");
		exit(1);
	}

}
void CreatRBT(Tree* root,int data) {
	Tree* Node = NULL;
	CreatNode(&Node,data);
	if (root == NULL) {//如果不存在红黑树
		Node->color = 0;//将节点颜色变为黑色
		pRoot = Node;
		return;
	}
	else {//如果存在红黑树,找到添加节点的位置
		Tree* Fa = NULL;
		Search(root, Node, &Fa);
		//进行连接
		Node->p_fa = Fa;
		if (Fa->val < Node->val) {
			Fa->p_right = Node;
		}
		else {
			Fa->p_left = Node;
		}
		while (1) {

			//看其父亲节点的颜色
			if (Fa->color == 0) {//如果父亲节点是黑色
				break;
			}
			else if (Fa->color == 1) {//如果父亲节点是红色
				//1.找到父亲节点和叔叔节点
				//2.看其叔叔节点的颜色
				
				//1.找到父亲节点在爷爷节点的哪一侧,以此来找到叔叔节点
				if (Fa->p_fa->p_left == Fa) {//爷爷节点的左子树是父亲节点,所以可知叔叔节点是在爷爷节点的右侧
				//2.看其叔叔节点的颜色
					if (Fa->p_fa->p_right == NULL || Fa->p_fa->p_right->color == 0) {//,叔叔节点是黑色(叔叔节点为空节点当成黑色处理)
						//父亲节点是在爷爷的左子树上,要处理的节点在父亲的哪颗树上
							if (Fa->val < Node->val) {//待处理的数据是在父亲的右子树上
								Node = Fa;
								LeftRatote(Node);
								Fa = Node->p_fa;
							}

							if (Fa->val > Node->val) {//待处理的数据是在父亲的左子树上
								Fa->color = 0;
								Fa->p_fa->color = 1;
								RightRatote(Fa->p_fa);
								break;
							}
			
					}
					if (Fa->p_fa->p_right != NULL&&Fa->p_fa->p_right->color == 1) {//叔叔节点是红色
						Fa->color = 0;
						Fa->p_fa->p_right->color = 0;
						Fa->p_fa->color = 1;
						Node = Fa->p_fa;//爷爷变为新的要处理节点
						Fa = Node->p_fa;
					}
					if (Fa == NULL) {
						root->color = 0;
						break;
					}
				}

				//1.找到父亲节点在爷爷节点的哪一侧,以此来找到叔叔节点
				if (Fa->p_fa->p_right == Fa) {//爷爷节点的右子树是父亲节点,所以可知叔叔节点是在爷爷节点的左侧
				//2.看其叔叔节点的颜色
					if (Fa->p_fa->p_left == NULL || Fa->p_fa->p_left->color == 0) {//叔叔节点为空节点当成黑色处理
						//看父亲节点是在爷爷的那颗树上,要处理的节点在父亲的哪颗树上

							if (Fa->val > Node->val) {//待处理的数据是在父亲的左子树上
								Node = Fa;
								RightRatote(Node);
								Fa = Node->p_fa;
							}

							if (Fa->val < Node->val) {//待处理的数据是在父亲的右子树上
								Fa->color = 0;
								Fa->p_fa->color = 1;
								LeftRatote(Fa->p_fa);
								break;
							}
					}
					if (Fa->p_fa->p_left != NULL && Fa->p_fa->p_left->color == 1) {//叔叔节点是红色
						Fa->color = 0;
						Fa->p_fa->p_left->color = 0;
						Fa->p_fa->color = 1;
						Node = Fa->p_fa;//爷爷变为新的要处理节点
						Fa = Node->p_fa;
					}
					if (Fa == NULL) {
						root->color = 0;
						break;
					}
				}
			}
		}
		
	}

}

void preorderTraversal(Tree* pTree) {
	stack st;
	while (1) {
		while (pTree) {//当没有左节点时结束
			cout << "val =  " << pTree->val <<"\t" << "color =  " << pTree->color << endl; //打印 先打印的是根节点
			//cout << pTree->color<<"     "<< pTree->val << endl;//打印 先打印的是根节点
			st.push(pTree);//入栈 
			pTree = pTree->p_left;
		}
		if (st.empty())break;//如果压栈之后栈仍空 则说明没有节点需要处理 结束循环


		pTree = st.top();//找到父节点 从而找到右节点
		st.pop();//将父节点弹出
		pTree = pTree->p_right;//对右节点进行处理 方式与之前相同
	}
}
int main() {
	cout << "请输入要输入几个数据" << endl;
	int num;
	cin >> num;
	while (num--) {
		cout << "请输入要输入的数据" << endl;
		int data;
		cin >> data;
		CreatRBT(pRoot,data);
	}
	preorderTraversal(pRoot);
}

2.红黑树的删除

注意:

1.如果一个节点只有一个孩子,那这个节点一定是黑色的

2.在进行替换删除的时候只进行值域的覆盖

操作步骤:
1.在红黑树中找到要删除的节点
2.分析删除节点孩子个数

​ 如果要删除的节点有两个孩子,那么找到其左子树的最大值或其右子树的最小值然后进行复制删除,删除的节点变为一个孩子或0个孩子,如果是一个孩子或者零个孩子则不需要进行此操作

3.如果删除的是根节点

​ (1)如果根节点没有孩子那么直接删除根节点,结束

​ (2)如果根节点只有一个孩子,那么删除根节点使根节点的孩子变为根节点(注意根节点的颜色是黑色),结束

3.如果要删除的不是根节点,讨论该节点的颜色

​ 1.如果该节点是红色的那么使其父亲节点指向其节点的左孩子或右孩子,删除该节点,结束

​ 如果是黑色的,那么就看此节点是否有孩子,执行操作2

​ 2.如果有孩子 那么红孩子变黑与爷爷相连,删除该节点,结束

​ 如果没有孩子那么删除该节点然后看兄弟颜色,执行操作3

​ 3.如果兄弟是红色的那么父亲变红,兄弟变黑,以父亲为旋转点旋转,重新执行步骤3操作

​ 如果兄弟是黑色的,那么看侄子颜色,执行操作4

​ 4.如果两个侄子全黑,那么看父亲颜色,执行步骤5操作

​ 如果左侄子红,右侄子黑,那么看兄弟方向,执行步骤6操作

​ 如果右侄子红,那么看兄弟方向,执行步骤7操作

​ 5.如果父亲是红色的,那么兄弟变红,父亲变黑,结束

​ 如果父亲是黑色的,那么兄弟变红,父亲变为新的讨论节点重新讨论,执行步骤3

​ 6.如果兄弟在讨论节点的右边,那么兄弟变红,左侄子变黑,以兄弟为旋转点右旋

​ 如果兄弟在讨论节点的左边,那么父亲颜色给兄弟,父亲变黑,左侄子变黑,以父亲为旋转点右旋,结束

​ 7.如果兄弟在讨论节点的左边,那么兄弟变红,右侄子变黑,以兄弟为旋转点左旋

​ 如果兄弟在讨论节点的右边,那么父亲颜色给兄弟,父亲变黑,右侄子变黑,以父亲为旋转点左旋,结束

代码如下:
#include
#include
using namespace std;

typedef struct Tree {
	int val;
	int color;//1表示红,0表示黑
	struct Tree* p_left;
	struct Tree* p_right;
	struct Tree* p_fa;
}Tree;



Tree* pRoot = NULL;
void RightRatote(Tree* pTree) {
	if (pTree == NULL || pTree->p_left == NULL) {
		return;
	}

	Tree* p_mark = pTree->p_left;
	//孩子

	pTree->p_left = p_mark->p_right;



	p_mark->p_right = pTree;

	if (pTree->p_fa != NULL) {
		if (pTree->p_fa->p_right == pTree) {
			pTree->p_fa->p_right = p_mark;
		}
		if (pTree->p_fa->p_left == pTree) {
			pTree->p_fa->p_left = p_mark;
		}
	}
	else {
		pRoot = p_mark;
	}
	//父亲
	if (pTree->p_left != NULL) {
		pTree->p_left->p_fa = pTree;
	}


	p_mark->p_fa = pTree->p_fa;


	pTree->p_fa = p_mark;
}



void LeftRatote(Tree* pTree) {
	if (pTree == NULL || pTree->p_right == NULL)return;

	Tree* pMark = pTree->p_right;
	//三个孩子
	pTree->p_right = pMark->p_left;
	pMark->p_left = pTree;
	if (pTree->p_fa != NULL) {
		if (pTree == pTree->p_fa->p_left) {
			pTree->p_fa->p_left = pMark;
		}
		else {
			pTree->p_fa->p_right = pMark;
		}
	}
	else {//换根
		pRoot = pMark;
	}

	//三个父亲
	if (pTree->p_right != NULL) {
		pTree->p_right->p_fa = pTree;
	}
	pMark->p_fa = pTree->p_fa;
	pTree->p_fa = pMark;
}

void CreatNode(Tree** Node, int data) {
	(*Node) = new(Tree);
	(*Node)->color = 1;
	(*Node)->p_fa = NULL;
	(*Node)->p_left = NULL;
	(*Node)->p_right = NULL;
	(*Node)->val = data;
}

void Search(Tree* root, Tree* Node, Tree** Fa) {
	if (root->val < Node->val) {//如果原有的树节点小于新节点
		if (root->p_right == NULL) {
			*Fa = root;
		}
		else {
			root = root->p_right;
			Search(root, Node, Fa);
		}
	}
	if (root->val > Node->val) {//如果原有的树节点大于新节点
		if (root->p_left == NULL) {
			*Fa = root;
		}
		else {
			root = root->p_left;
			Search(root, Node, Fa);
		}
	}
	if (root->val == Node->val) {
		printf("失败");
		exit(1);
	}

}
void CreatRBT(Tree* root, int data) {
	Tree* Node = NULL;
	CreatNode(&Node, data);
	if (root == NULL) {//如果不存在红黑树
		Node->color = 0;//将节点颜色变为黑色
		pRoot = Node;
		return;
	}
	else {//找到添加节点的位置

		Tree* Fa = NULL;
		Search(root, Node, &Fa);
		//进行连接
		Node->p_fa = Fa;
		if (Fa->val < Node->val) {
			Fa->p_right = Node;
		}
		else {
			Fa->p_left = Node;
		}
		while (1) {

			//看其父亲节点的颜色
			if (Fa->color == 0) {//如果父亲节点是黑色
				/*if (Fa->val > Node->val) {
					Fa->p_left = Node;
					Node->p_fa = Fa;
				}
				else {
					Fa->p_right = Node;
					Node->p_fa = Fa;
				}*/
				break;
			}
			else if (Fa->color == 1) {//如果父亲节点是红色
				//1.找到父亲节点和叔叔节点
				//2.看其叔叔节点的颜色

				//1.找到父亲节点在爷爷节点的哪一侧,以此来找到叔叔节点
				if (Fa->p_fa->p_left == Fa) {//爷爷节点的左子树是父亲节点,所以可知叔叔节点是在爷爷节点的右侧
					//2.看其叔叔节点的颜色
					if (Fa->p_fa->p_right == NULL || Fa->p_fa->p_right->color == 0) {//叔叔节点为空节点当成黑色处理
						//父亲节点是在爷爷的左子树上,要处理的节点在父亲的哪颗树上
						if (Fa->val < Node->val) {//待处理的数据是在父亲的右子树上
							Tree* Temp = Fa;
							Fa = Fa->p_fa;
							LeftRatote(Temp);
						}

						if (Fa->val > Node->val) {//待处理的数据是在父亲的左子树上
							Fa->color = 0;
							Fa->p_fa->color = 1;
							RightRatote(Fa->p_fa);
							break;
						}

					}
					if (Fa->p_fa->p_right->color == 1) {//叔叔节点是红色
						Fa->color = 0;
						Fa->p_fa->p_right->color = 0;
						Fa->p_fa->color = 1;
						Fa = Fa->p_fa->p_fa;//爷爷变为新的要处理节点
					}
					if (Fa == NULL) {
						root->color = 0;
						break;
					}
				}

				//1.找到父亲节点在爷爷节点的哪一侧,以此来找到叔叔节点
				if (Fa->p_fa->p_right == Fa) {//爷爷节点的右子树是父亲节点,所以可知叔叔节点是在爷爷节点的左侧
					//2.看其叔叔节点的颜色
					if (Fa->p_fa->p_left == NULL || Fa->p_fa->p_left->color == 0) {//叔叔节点为空节点当成黑色处理
						//看父亲节点是在爷爷的那颗树上,要处理的节点在父亲的哪颗树上

						if (Fa->val > Node->val) {//待处理的数据是在父亲的左子树上
							Tree* Temp = Fa;
							Fa = Fa->p_fa;
							RightRatote(Temp);
						}

						if (Fa->val < Node->val) {//待处理的数据是在父亲的右子树上
							Fa->color = 0;
							Fa->p_fa->color = 1;
							LeftRatote(Fa->p_fa);
							break;
						}
					}
					if (Fa->p_fa->p_left->color == 1) {//叔叔节点是红色
						Fa->color = 0;
						Fa->p_fa->p_left->color = 0;
						Fa->p_fa->color = 1;
						Fa = Fa->p_fa->p_fa;//爷爷变为新的要处理节点
					}
					if (Fa == NULL) {
						root->color = 0;
						break;
					}
				}
			}
		}

	}

}

void preorderTraversal(Tree* pTree) {
	stack st;
	while (1) {
		while (pTree) {//当没有左节点时结束
			cout << "val =  " << pTree->val << "\t" << "color =  " << pTree->color << endl; //打印 先打印的是根节点
			//cout << pTree->color<<"     "<< pTree->val << endl;//打印 先打印的是根节点
			st.push(pTree);//入栈 
			pTree = pTree->p_left;
		}
		if (st.empty())break;//如果压栈之后栈仍空 则说明没有节点需要处理 结束循环


		pTree = st.top();//找到父节点 从而找到右节点
		st.pop();//将父节点弹出
		pTree = pTree->p_right;//对右节点进行处理 方式与之前相同
	}
}

void DeleteSearch(Tree* root, Tree** Del, int x) {//找到要删除的数据和要呗删除数据的父亲
	while (1) {
		if (root->val == x) {
			*Del = root;
			return;
		}
		if (root->val < x) {//如果原有的树节点小于要删除的数据
			root = root->p_right;

		}
		if (root->val > x) {//如果原有的树节点大于要删除的数据
			root = root->p_left;
		}
		if (root == NULL) {
			break;
		}
	}
}
void DeleteRBT(Tree* pTree, int x) {
	Tree* Del = NULL;
	Tree* Mark = NULL;
	DeleteSearch(pTree, &Del, x);
	if (Del == NULL) {//如果没有找到要删除的数据
		return;
	}
	Tree* Fa = Del->p_fa;
	//分析删除节点孩子个数,如果是两个孩子的情况
	if (Del->p_left != NULL && Del->p_right != NULL) {
		//找到根节点左子树上的最大值
		Mark = Del->p_left;
		while (1) {
			if (Mark->p_right == NULL) {
				break;
			}
			Mark = Mark->p_right;
		}
		//进行复制
		Del->val = Mark->val;
		Del = Mark;//更换删除节点位置
		Fa = Del->p_fa;
	}


	//判断要删除的节点是不是根节点
	if (Fa == NULL) {//如果删除的节点是根节点
		//如果根节点没有孩子
		if (Del->p_left == NULL && Del->p_right == NULL) {
			delete Del;
			return;
			pRoot = NULL;
		}
		//如果根节点有孩子,分析孩子情况

		if (Del->p_left != NULL && Del->p_right == NULL) {//如果根节点只有一个孩子,左孩子
			delete Del;
			pRoot = Del->p_left;
			pRoot->color = 0;//颜色变黑
		}

		if (Del->p_right != NULL && Del->p_left == NULL) {//如果根节点只有一个孩子,右孩子
			delete Del;
			pRoot = Del->p_right;
			pRoot->color = 0;//颜色变黑
		}
	}

	//-------------------------------------------------------------------------------------------------------------


	//如果删除的节点不是根节点
	if (Fa != NULL) {

		//判断节点的颜色
		while (1) {
			//如果是红色的节点,那么直接删除
			if (Del->color == 1) {
				if (Del->p_left != NULL) {//如果节点的左子树存在
					if (Fa->p_right == Del) {//如果父亲的右子树是要删除的节点
						Fa->p_right = Del->p_left;
						Del->p_left->p_fa = Fa;
					}
					if (Fa->p_left == Del) {//如果父亲的左子树是要删除的节点
						Fa->p_left = Del->p_left;
						Del->p_left->p_fa = Fa;
					}
					delete Del;
				}
				if (Del->p_right != NULL) {//如果节点的右子树存在
					if (Fa->p_right == Del) {//如果父亲的右子树是要删除的节点
						Fa->p_right = Del->p_right;
						Del->p_right->p_fa = Fa;
					}
					if (Fa->p_left == Del) {//如果父亲的左子树是要删除的节点
						Fa->p_left = Del->p_right;
						Del->p_right->p_fa = Fa;
					}
					delete Del;
				}
				if (Del->p_left == NULL && Del->p_right == NULL) {//如果节点的左右子树都不存在
					if (Fa->p_right == Del) {//如果父亲的右子树是要删除的节点
						Fa->p_right = NULL;
					}
					if (Fa->p_left == Del) {//如果父亲的左子树是要删除的节点
						Fa->p_left = NULL;
					}
					delete Del;
				}
				break;//结束
			}
			//如果是黑色的节点


			if (Del->color == 0) {
				//判断其节点是否有孩子

				//如果有孩子
				if (Del->p_left != NULL || Del->p_right != NULL) {
					if (Del->p_left != NULL) {//节点的左孩子不为空
						Del->p_left->color = 0;
						Del->p_left->p_fa = Fa;

						if (Fa->p_left == Del) {//如果节点的左子树是要删除的节点
							Fa->p_left = Del->p_left;
						}
						if (Fa->p_right == Del) {//如果节点的右子树是要删除的节点
							Fa->p_right = Del->p_right;
						}

					}

					if (Del->p_right != NULL) {//节点的右孩子不为空
						Del->p_right->color = 0;
						Del->p_right->p_fa = Fa;

						if (Fa->p_left == Del) {//如果节点的左子树是要删除的节点
							Fa->p_left = Del->p_left;
						}
						if (Fa->p_right == Del) {//如果节点的右子树是要删除的节点
							Fa->p_right = Del->p_right;
						}

					}
					break;
				}

				int flag = 2;
				//如果没有孩子
				if (Del->p_left == NULL && Del->p_right == NULL) {//如果节点的左右子树都不存在
					if (Fa->p_right == Del) {//如果父亲的右子树是要删除的节点
						flag = 1;
						Fa->p_right = NULL;
					}
					if (Fa->p_left == Del) {//如果父亲的左子树是要删除的节点
						flag = 0;
						Fa->p_left = NULL;
					}
				}

				//看兄弟节点的颜色

				//获得兄弟节点
				Tree* bro = NULL;
				if (flag == 1) {
					bro = Fa->p_left;
				}
				if (flag == 0) {
					bro = Fa->p_right;
				}
				delete Del;
				while (1) {
					//如果兄弟节点为红色
					if (bro != NULL && bro->color == 1) {
						Fa->color = 1;
						bro->color = 0;
						if (Fa->p_right == bro) {
							LeftRatote(Fa);
						}
						if (Fa->p_left == bro) {
							RightRatote(Fa);
						}
						continue;
					}

					//如果兄弟节点为黑色
					if (bro == NULL || bro->color == 0) {
						if (bro->p_left->color == 0 && bro->p_right->color == 0) {//如果两个侄子全黑
							//判断父亲的颜色

							//如果父亲的颜色为红色
							if (Fa->color == 1) {
								bro->color = 1;
								Fa->color = 0;
								return;
							}
							//如果父亲的颜色为黑色
							if (Fa->color == 0) {
								bro->color = 1;
								Del = Fa;
								Fa = Del->p_fa;
								continue;
							}
						}
						if (bro->p_left->color == 1 && bro->p_right->color == 0) {//如果左侄子红右侄子黑
							//判断兄弟方向

							//如果父亲的右侧是兄弟节点
							if (Fa->p_right == bro) {
								bro->color = 1;
								bro->p_left->color = 0;
								RightRatote(bro);
							}
							//如果父亲的左侧是兄弟节点
							if (Fa->p_left == bro) {
								bro->color = Fa->color;
								Fa->color = 0;
								bro->p_left->color = 0;
								RightRatote(Fa);
							}
							return;
						}
						if (bro->p_right->color == 1) {//如果右侄子为红
							//判断兄弟方向

							//如果父亲的左侧是兄弟节点
							if (Fa->p_left == bro) {
								bro->color = 1;
								bro->p_right->color = 0;
								LeftRatote(bro);
							}

							//如果父亲的右侧是兄弟节点
							if (Fa->p_right == bro) {
								bro->color = Fa->color;
								Fa->color = 0;
								bro->p_right->color = 0;
								LeftRatote(Fa);
							}

							return;
						}
					}
				}

			}
		}

	}

}
int main() {
    //进行测试
	cout << "请输入要输入几个数据" << endl;
	int num;
	cin >> num;
	while (num--) {
		cout << "请输入要输入的数据" << endl;
		int data;
		cin >> data;
		CreatRBT(pRoot, data);
	}
	preorderTraversal(pRoot);
	int x;
	printf("请输入要删除的数据\n");
	cin >> x;
	DeleteRBT(pRoot, x);
	preorderTraversal(pRoot);
}

六.关于多路平衡树的操作

1.B树

1.B树的创建(添加)
操作步骤:

1.查找

2.添加到叶子上

3.讨论节点记录数目:

​ (1) <=m-1,结束

​ (2) >m-1,裂变,中间记录上移至父亲层,左侧节点成为其左子树,右侧节点成为其右子树

​ 讨论父亲层记录个数,重复3

2.B树的删除
操作步骤:

1.查找

2.替换(叶子节点内的记录对其进行替换)

3.讨论节点内记录个数

(1)>=ceil(m/2)-1,结束

(2)

​ 1> >ceil(m/2)-1,父亲节点下移一个记录至当前节点,兄弟节点上移一个记录至父亲层

​ 2> =ceil(m/2)-1,父亲节点下移一个记录至当前节点,与当前节点和兄弟节点合并成一个新节点。讨论父亲层记录个数,重复3

2.B+树

1.B+树的创建(添加)
注意:

B+树如果一个节点有父亲节点那么其父亲节点中的一定是索引,不是记录

操作步骤:

1.查找

2.放入叶子节点

3.讨论叶子节点内的记录个数

(1)<=m-1,结束

(2)>m-1,裂变,前m/2个记录成为左节点,剩余记录在右节点,叶子节点间从左向右有指针指向,第m/2+1个记录的关键字复制一份至父亲层,讨论父亲层索引个数

4.索引个数:

(1)<=m-1,结束

(2)>m-1,裂变,中间索引上移至父亲层,左侧节点成为其左子树,右侧节点成为其右子树,对父亲层进行讨论,重复4

2.B+树的删除
操作步骤:

1.查找

2.删除

3.叶子节点内记录个数讨论:

(1)>=ceil(m/2)-1,结束

(2)

​ 1> >ceil(m/2)-1,兄弟节点移动一个记录至当前节点,更新父亲索引

​ 2> =ceil(m/2)-1,删除父亲索引,当前节点与兄弟节点合并成一个新节点。讨论父亲层索引个数

4.节点内索引个数

(1)>=ceil(m/2)-1,结束

(2)

​ 1> >ceil(m/2)-1,父亲索引下移至当前节点,兄弟节点上移一个索引至父亲层,结束

​ 2> =ceil(m/2)-1,父亲索引下移,与当前节点,兄弟节点合并成一个新节点,讨论父亲层索引个数,重复4

七.关于字典树的操作

1.往字典树中存入字符串

注意:这里创建的字典树是26叉树(可以对’a’~'z’的字符做出响应),正常应该是256叉树(因为ASCII码中的字符有256个)

1.创建一个结构体类型

其中有一个指针数组(这个指针数组中有26个指针)和末尾标志(用来进行计数)

2.进行创建

​ (1)创建一个根节点(此根节点内的指针数组全部赋空值,末尾标志也为0)

​ (2)单词的添加

​ 1.遍历单词,看对应字符节点是否存在执行步骤2

​ 2.如果存在,处理下一个字符

​ 如果不存在,申请节点,处理下一个字符和末尾标志(如果此字符是单词的最后一个字符,那么末尾标志进行加1操作)

代码如下(下面代码中也有字典树的遍历和在字典树中查找字符串的代码)
#include
#include
#include

typedef struct Node {

	struct Node* arr[26];
	char* str;
	int flag;

}TrieTree;

void CreatTrieTree(TrieTree** Trie) {
	//创建根节点
	//给根节点赋初始值
	TrieTree* root = (TrieTree*)malloc(sizeof(TrieTree));
	*Trie = root;
	(*Trie)->str = NULL;
	memset((*Trie)->arr, 0, sizeof((*Trie)->arr));
	(*Trie)->flag = 0;
	return;
}

void AddWord(TrieTree* Trie, char* s) {
	int length = strlen(s);//获取字符串长度

	//遍历单词,看对应字符节点是否存在
	for (int i = 0; i < length; i++) {
		
		if (Trie->arr[s[i] - 'a'] == NULL) {//如果不存在
			//申请节点
			//给节点赋初始值
			TrieTree* Node = (TrieTree*)malloc(sizeof(TrieTree));
			memset(Node->arr, 0, sizeof(Node->arr));
			Node->flag = 0;

			//使当前节点的 指针数组中的 相应的指针指向创建的新节点
			Trie->arr[s[i] - 'a'] = Node;

			//进行节点的移动
			Trie = Node;

		}
		else {//存在,继续处理下一个字符
			Trie = Trie->arr[s[i] - 'a'];
		}

	}
	Trie->str = s;
	Trie->flag++;
}

void PreorderTraversal(TrieTree* Trie) {//进行遍历

	if (Trie == NULL)  return;
	//如果当前节点的末尾标志不为0时,输出当前节点中字符指针指向的字符串
	if (Trie->flag != 0) {
		printf("%s\n", Trie->str);
	}

	//遍历当前节点的孩子节点(这里是通过节点中的指针数组中各个指针的指向找到的孩子节点)
	for (int i = 0; i < 26; i++) {
		PreorderTraversal(Trie->arr[i]);
	}

}

void Search(TrieTree* pTree, char* str) {
	if (pTree == NULL || str == NULL) return;

	int i = 0;
	while (i < strlen(str)) {
		//检测对应字符位置
		if (pTree->arr[str[i] - 'a'] == NULL) {//如果字典树中相应的位置没有要找的相应字符串的字符
			printf("Failed one\n");
			return;
		}
		pTree = pTree->arr[str[i] - 'a'];
		i++;
	}

	//检测末尾标志
	if (pTree->flag != 0) {//字典树中有此字符串
		printf("success %s\n", pTree->str);
	}
	else {//有以这个字符串为开头的字符串,但没有这个字符串
		printf("Failed two\n");
	}

}
int main() {

	
	//进行测试
	TrieTree* Trie = NULL;
	CreatTrieTree(&Trie);

	char* s = "abc";
	char* s2 = "aaa";
	AddWord(Trie, s);
	AddWord(Trie, s2);
	PreorderTraversal(Trie);
	
    Search(Trie, "abc");
    Search(Trie, "aa");
	Search(Trie, "ad");
	return 0;
}

八.关于哈夫曼树的操作

1.哈夫曼树的创建

操作步骤

​ 1.先根据序列中出现的数据/字符频率进行从小到大的排序

​ 2.拿前两个出现频率最小的数据/字符放到哈夫曼树中(放入时如果是左小右大,那么其他数据放入哈夫曼树时也要遵循这个规则,如果是左大右小,那么其他数据放入哈夫曼树时也要遵循这个规则),然后构成一个新的节点(新数据/字符)为辅助节点(也是它们的父节点)

​ 3.把新节点(新数据/字符)放回到原来的序列里去,重复操作1,直到序列中的数据/字符只有一个,那么操作结束(此时这个数据/字符就是哈夫曼树的根节点)

注意:如果两个数据/字符出现的频率相同那么这两个数据/字符可以随便放,谁左谁右都可以,但要哈夫曼树会随着两个数据/字符放置的位置不同而发生改变,哈夫曼编码也会随着两个数据/字符放置的位置不同而发生改变,但是总码长是一样

2.进行哈夫曼编码

操作步骤

1.将编码放到哈夫曼树的边上,

每个节点的左边放0,每个节点的右边放1

或者每个节点的左边放1,每个节点的右边放0

2.从根往每个叶子节点沿边进行读数,就可以获得每个数据/字符的哈夫曼编码了

注意:这种哈夫曼编码的方式会使得每个数据/字符的编码都是没有前缀码的,避免解压时有歧义

九.将一颗非二叉树变为二叉树

操作方法

1.将所有亲兄弟节点横向连上

2.只保留每个父亲节点和最左边孩子之间的连接(指向关系),然后将每个父亲节点和其他孩子节点之间的连接(指向关系)断开

3.将每个节点的左子树变为它的孩子,右子树变为它的兄弟

进阶
将一个森林变为一颗二叉树

森林实际上是指有多颗二叉树,那么这些二叉树(根节点)之间就是兄弟关系

之后的操作方法和上面是一样的

你可能感兴趣的:(数据结构,数据结构)