【学习笔记】数据结构(六 ①)

news2024/9/21 13:17:20

树和二叉树 (一)

文章目录

  • 树和二叉树 (一)
    • 6.1 树(Tree)的定义和基本术语
    • 6.2 二叉树
      • 6.2.1 二叉树的定义
        • 1、斜树
        • 2、满二叉树
        • 3、完全二叉树
        • 4、二叉排序树
        • 5、平衡二叉树(AVL树)
        • 6、红黑树
      • 6.2.2 二叉树的性质
      • 6.2.3 二叉树的存储结构
    • 6.3 遍历二叉树和线索二叉树
      • 6.3.1 遍历二叉树

6.1 树(Tree)的定义和基本术语

树(Tree)是n(n≥0)个结点的有限集。

在任意一棵非空树中:

​ (1) 有且仅有一个特定的称为**根(Root)**的结点;

​ (2) 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每一个集合本身又是一棵树,并且

​ 称为根的子树(SubTree)

在这里插入图片描述

树的特点

  1. 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
  2. 树中所有结点可以有零个或多个后继。
  3. 树中的结点数等于所有结点的度数加1.
    度为m的树中第i层上至多有mi-1个结点(i > = 1)
    高度为h 的m叉树至多有( mh − 1 ) / ( m − 1 )个结点。
    具有n个结点的m叉树的最小高度为[ logm ( n ( m − 1 ) + 1 ) ] 。

树的其他表示形式

​ ( a ) 是以嵌套集合(即是一些集合的集体,对于其中任何两个集合,或者不相交,或者一个包含另一个)的形式表示的;

​ ( b ) 是以广义表的形式表示的,根作为由子树森林组成的表的名字写在表的左边;

​ ( c ) 用的是凹人表示法(类似书的编目)。

在这里插入图片描述

👉 基本术语:

  • 结点的祖先是从根到该结点所经分支上的所有结点。根A到结点K的唯一路径上的任意结点,称为结点K的祖先—如结点B是结点K的祖先

  • 以某结点为根的子树中的任一结点都称为该结点的子孙。结点K是结点B的子孙

  • 结点的子树的根称为该 结点的孩子(Child), 相应地,该结点称为孩子的**双亲(Parent) ** 。最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。

  • 同一个双亲的孩子 之间互称兄弟 (Sibling) 。如结点K和结点L有相同的双亲E,即K和L为兄弟。

  • 其双亲在同一层的结点互为堂兄弟。如结点G与E,F,H,I,J互为堂兄弟。

  • 树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数(树中一个结点的孩子个数)称为结点的度 (Degree) 。如结点B的度为2,结点D的度为3

  • 树的度是树内各结点的度的最大值。树的度为3。

  • 度为0的结点 称为叶子 (Leaf) 或终端结点。

  • 度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点

  • 结点的层次 (Level) 从根开始定义起.根为第一层,根的孩子为第二层。

  • 树中结点的最大层次称为树的深度 (Depth) 或高度。图中树的高度为4。

    结点的深度是从根结点开始自顶向下逐层累加的。
    结点的高度是从叶结点开始自底向上逐层累加的。

  • 如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子。

  • 森林 (Forest) m(m≥0) 棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

    任何一棵树是一个二元组 Tree= (root, F), 其中: root 是数据元素、 称做树的根结点; m(m≥0) 棵树的森林, F=(T1, T2, …, Tm), 其中 Ti = (ri, Fi) 称做 root 的第i棵子树;当m≠0时,在树根和其子树森林之间存在下列关系:RF = { < root , 78ri > I i = 1 , 2, …, m ,m>0 }

6.2 二叉树

6.2.1 二叉树的定义

二叉树 (Binary Tree) 的特点是每个结点至多只有两棵子树 (即二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒。[二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成]

在这里插入图片描述

🌟特殊的二叉树

1、斜树
  • 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
2、满二叉树
  • 一棵高度为h,且含有2h−1个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点。除叶子结点之外的每个结点度数均为2。
  • 可以对满二叉树按层序编号:约定编号从根结点(根结点编号为1)起,自上而下,自左向右。这样,每个结点对应一个编号,对于编号为i的结点,若有双亲,则其双亲为i / 2, 若有左孩子,则左孩子为2i ; 若有右孩子,则右孩子为2i + 1。
3、完全二叉树
  • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边。

在这里插入图片描述

4、二叉排序树
  • 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
    • 若它的左子树不空,则左子树上所有结点的值均小于它根结点的值。
    • 若它的右子树不空,则右子树上所有结点的值均大于它根结点的值。
    • 它的左、右子树都满足为⼆叉排序树

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

#include<stdio.h>
#include<stdlib.h>

//二叉排序树节点存储方式
typedef int DataType;
typedef struct BTnode {
    DataType data;	//数据域
    struct BTnode* left;	//左指针 
    struct BTnode* right;	//右指针
}BTnode, * BTree;

//插入结点
void Insert_node(BTree* root, DataType data) {
	if (*root == NULL) {
		*root = (BTnode*)malloc(sizeof(BTnode));
		if (!*root) {
			printf("ERROR\n");
			exit(-1);
		}
		(*root)->data = data;
		(*root)->left = NULL;
		(*root)->right = NULL;
	}

	else if ((*root)->data <= data)
		Insert_node(&(*root)->right, data);
	else if ((*root)->data > data)
		Insert_node(&(*root)->left, data);
}

//创建排序二叉树
void Create_sortBtree(BTree* T, DataType* arr, int size) {
	if (!arr)
		return NULL;
	else {
		for (int i = 0; i < size; i++) {
			Insert_node(T, arr[i]);
		}
	}
}

//中序遍历排序二叉树
void mid_travel(BTree T)
{
	if (!T)
		return;
	mid_travel(T->left);
	printf("%d ", T->data);
	mid_travel(T->right);
}

//递归查找
BTnode* Btree_search(BTree root, DataType target) {
	if (!root)
		return NULL;
	if (target == root->data) {
		return root;
	}
	return target > root->data ? Btree_search(root->right, target) : Btree_search(root->left, target);
}

//非递归查找
BTnode* Btree_search_fa(BTree root, DataType target) {
	BTnode* p = root;
	while (p) {
		if (p->data == target)
		{
			return p;
		}
		p = target > p->data ? p->right : p->left;
	}
	return NULL;
}

//获取最大值
int Btree_max(BTree T) {
	BTnode* cur = T;
	while (cur->right) {
		cur = cur->right;
	}
	return cur->data;
}

//获取最小值
int Btree_min(BTree T) {
	BTnode* cur = T;
	while (cur->left) {
		cur = cur->left;
	}
	return cur->data;
}

//删除结点
void Del_Node(BTree* T, int val)
{
	if (T == NULL) return ;
	if ((*T)->data < val)
	{
		Del_Node(&((*T)->right), val);
	}
	else if ((*T)->data > val)
	{
		Del_Node(&((*T)->left), val);
	}
	else 
	{
		//叶子结点
		if ((*T)->left == NULL && (*T)->right == NULL)
		{
			*T = NULL;
			return;
		}
		//没有左孩子,只有右孩子
		if ((*T)->left == NULL && (*T)->right != NULL)
		{
			*T = (*T)->right;
			return ;
		}
		//没有右孩子,只有左孩子
		if ((*T)->left != NULL && (*T)->right == NULL)
		{
			*T = (*T)->left;
			return ;
		}
		//左右子树都有
		if ((*T)->left != NULL && (*T)->right != NULL)
		{
			BTree cur = (*T)->left;
			while (cur->right != NULL)
			{
				cur = cur->right;
			}
			cur->right = (*T)->right;
			*T = (*T)->left;
			return ;
		}
	}
}

int main()
{
	BTree* root = NULL;
	DataType w[9] = { 8, 3, 10, 1, 6, 14, 4, 7, 13 };
	Create_sortBtree(&root, w, 9);
	mid_travel(root);
	printf("\n");
	printf("递归查找结点:");
	BTnode* node1 = Btree_search(root, 6);
	printf("%d, 左子结点 %d, 右子节点 %d\n", node1->data, node1->left->data, node1->right->data);
	printf("非递归查找结点:");
	BTnode* node2 = Btree_search_fa(root, 6);
	printf("%d, 左子结点 %d, 右子节点 %d\n", node2->data, node2->left->data, node2->right->data);
	printf("该树最大值:%d\n", Btree_max(root));
	printf("该树最小值:%d\n", Btree_min(root));
	/*删除叶子结点 4 */
	Del_Node(&root, 4);
	mid_travel(root);
	printf("\n");
	/*删除只有右子树,没有左子树结点 10 */
	Del_Node(&root, 10);
	mid_travel(root);
	printf("\n");
	/*删除只有左子树,没有右子树结点 14 */
	Del_Node(&root, 14);
	mid_travel(root);
	printf("\n");
	/*删除有左子树和右子树结点 8 */
	Del_Node(&root, 8);
	mid_travel(root);
	printf("\n");
	return 0;
}
5、平衡二叉树(AVL树)

平衡二叉树也叫自平衡二叉搜索树(Self-Balancing Binary Search Tree),所以其本质也是一颗二叉搜索树,树上任一结点的左子树和右子树的深度/高度差不超过1。左右子树的高度差称之为平衡因子。

自平衡 - 旋转操作

触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树

  • 左旋

    在这里插入图片描述

  • 右旋

    在这里插入图片描述

  • 单旋转

    • LL - 左左 一次右旋

      当不平衡节点的左子树的左子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

    • RR - 右右 一次左旋

      当不平衡节点的右子树的右子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

  • 双旋转

    • LR - 左右 先局部左旋,再整体右旋

      当不平衡节点的左子树的右子树有节点插入,导致二叉树不平衡
      在这里插入图片描述

    • RL - 右左 先局部右旋,再整体左旋

      当不平衡节点的右子树的左子树有节点插入,导致二叉树不平衡

      在这里插入图片描述

# include <stdio.h>
# include <stdbool.h>
# include <stdlib.h>
# include <math.h>

typedef struct TreeNode { // 直接定义结构体类型
	int data; // 数据节点
	struct TreeNode* left; // 指向左子树,现在可以使用 TreeNode
	struct TreeNode* right; // 指向右子树,现在可以使用 TreeNode
} TreeNode, * PTreeNode; // 注意这里去掉了*号,因为TreeNode现在已经是结构体类型

// 记录平衡二叉树
bool BalanceTrue = false;
// 最小不平衡子树地址
TreeNode* rjt = NULL;

// 检查二叉树是否平衡,若不平衡 BalanceTrue 为 true
int checkTreeBalance(TreeNode* root) {
	if (NULL == root) { return 0; }
	int x = checkTreeBalance(root->left);
	int y = checkTreeBalance(root->right);

	// 若检测到最小二叉树后,不进行后面的检查
	if (BalanceTrue) return 0;
	int xx = abs(x - y);

	if (xx > 1) {
		// 左子树 和 右子树 相差大于1 , 二叉树不平衡
		BalanceTrue = true;
		rjt = root;
	}

	return (x > y ? x + 1 : y + 1);
}

// 返回二叉树树高
int treeHeight(TreeNode* root) {
	if (NULL == root) return 0;
	int ll = treeHeight(root->left);
	int rr = treeHeight(root->right);
	return (ll > rr ? ll + 1 : rr + 1);
}

// 父节点查询
TreeNode* queryTopData(TreeNode* root, int data) {
	// 空地址异常抛出
	if (NULL == root) return NULL;

	// top: 父节点 ,如果为Null, 该节点为父节点
	// tmp: 遍历查询节点 
	TreeNode* top = NULL;
	TreeNode* tmp = root;

	while (tmp != NULL) {
		if (data == tmp->data) {
			// 节点为 返回Null
			if (NULL == top) return NULL;
			return top;
		}

		top = tmp;
		if (data > tmp->data) {
			tmp = tmp->right;
		}
		else if (data < tmp->data) {
			tmp = tmp->left;
		}
	}
	return NULL;
}

// 左左旋转 - 右旋
bool turnLL(TreeNode** root, TreeNode* notBalanceRoot) {

	if ((*root) != notBalanceRoot) {
		printf("左左旋转,非根节点\n");
		// 非根节点
		TreeNode* lleft = notBalanceRoot->left;
		TreeNode* lright = lleft->right;

		// 查找父节点
		TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);
		if (NULL == fdata) return false;

		lleft->right = notBalanceRoot;
		notBalanceRoot->left = lright;

		if (notBalanceRoot == fdata->left) {
			fdata->left = lleft;
		}
		else if (notBalanceRoot == fdata->right) {
			fdata->right = lleft;
		}
		return true;

	}
	else {
		// 根节点
		printf("左左旋转,是根节点\n");
		TreeNode* lleft = notBalanceRoot->left;
		TreeNode* absroot = lleft;
		TreeNode* lright = lleft->right;


		lleft->right = notBalanceRoot;
		notBalanceRoot->left = lright;

		(*root) = absroot;
		return true;
	}

}

// 左右旋转
bool turnLR(TreeNode** root, TreeNode* notBalanceRoot) {
	if ((*root) != notBalanceRoot) {
		printf("左右旋转,非根节点");

		TreeNode* lleft = notBalanceRoot->left;
		TreeNode* leftRight = lleft->right;
		TreeNode* leftRightLeft = leftRight->left;

		// 第一次调整
		leftRight->left = lleft;
		lleft->right = leftRightLeft;
		notBalanceRoot->left = leftRight;


		// 查找父节点
		TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);
		//if (NULL != fdata) printf("fdata: %d\n",fdata->data);

		// 第二次调整
		lleft = notBalanceRoot->left;
		leftRight = lleft->right;

		lleft->right = notBalanceRoot;
		notBalanceRoot->left = leftRight;


		if (notBalanceRoot == fdata->left) {
			fdata->left = lleft;
		}
		else if (notBalanceRoot == fdata->right) {
			fdata->right = lleft;
		}
	}
	else {
		printf("左右旋转,是根节点\n");

		TreeNode* lleft = notBalanceRoot->left;
		TreeNode* leftRight = lleft->right;
		TreeNode* leftRightLeft = leftRight->left;

		// 第一次调整
		leftRight->left = lleft;
		lleft->right = leftRightLeft;
		notBalanceRoot->left = leftRight;

		// 第二次调整
		lleft = notBalanceRoot->left;
		leftRight = lleft->right;

		lleft->right = notBalanceRoot;
		notBalanceRoot->left = leftRight;

		(*root) = lleft;
	}
}

// 右左旋转
bool turnRL(TreeNode** root, TreeNode* notBalanceRoot) {
	TreeNode* rright = notBalanceRoot->right;
	TreeNode* rightLeft = rright->left;
	TreeNode* rightLeftRight = rightLeft->right;

	// 第一次调整
	rightLeft->right = rright;
	rright->left = rightLeftRight;
	notBalanceRoot->right = rightLeft;

	// 查找父节点
	TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);
	//if (NULL != fdata) printf("fdata: %d\n",fdata->data);

	// 第二次调整
	rright = notBalanceRoot->right;
	rightLeft = rright->left;

	rright->left = notBalanceRoot;
	notBalanceRoot->right = rightLeft;

	if ((*root) != notBalanceRoot) {
		printf("右左旋转,非根节点\n");
		if (notBalanceRoot == fdata->left) {
			fdata->left = rright;
		}
		else if (notBalanceRoot == fdata->right) {
			fdata->right = rright;
		}

	}
	else {
		printf("右左旋转,是根节点\n");
		(*root) = rright;
	}
}

// 右右旋转
bool turnRR(TreeNode** root, TreeNode* notBalanceRoot) {
	if ((*root) != notBalanceRoot) {
		printf("右右旋转,非根节点");
		TreeNode* rright = notBalanceRoot->right;
		TreeNode* rleft = rright->left;

		// 查找父节点
		TreeNode* fdata = queryTopData((*root), notBalanceRoot->data);
		if (NULL != fdata) printf("fdata: %d\n", fdata->data);

		rright->left = notBalanceRoot;
		notBalanceRoot->right = rleft;

		if (notBalanceRoot == fdata->left) {
			fdata->left = rright;
		}
		else if (notBalanceRoot == fdata->right) {
			fdata->right = rright;
		}

	}
	else {
		// 右右旋转,是根节点
		printf("右右旋转,是根节点\n");
		TreeNode* rright = notBalanceRoot->right;
		TreeNode* absroot = rright;
		TreeNode* rleft = rright->left;

		rright->left = notBalanceRoot;
		notBalanceRoot->right = rleft;

		(*root) = absroot;

	}
}

// 二叉树前序遍历
void Print1(TreeNode* root) {
	if (NULL == root) return;
	printf("%d\t", root->data);
	Print1(root->left);
	Print1(root->right);
}

// 二叉树中序遍历
void Print2(TreeNode* root) {
	if (NULL == root) return;
	Print2(root->left);
	printf("%d\t", root->data);
	Print2(root->right);
}

// 二叉树后续遍历
void Print3(TreeNode* root) {
	if (NULL == root) return;
	Print3(root->left);
	Print3(root->right);
	printf("%d\t", root->data);
}

// 插入二叉树节点
TreeNode* addNode(TreeNode* root, int data) {
	if (NULL == root) {
		// 头节点插入
		TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));
		if (NULL == Node) {
			printf("新节点申请内存失败\n");
			return NULL;
		}
		Node->data = data;
		Node->left = NULL;
		Node->right = NULL;
		return Node;
	}

	TreeNode* tmp = root;
	TreeNode* top = NULL;

	// 找到合适的最尾巴节点
	while (NULL != tmp) {
		top = tmp;
		if (tmp->data == data) {
			printf("已经存在该节点,节点地址: %p\n", tmp);
			return root;
		}
		if (tmp->data < data) {
			tmp = tmp->right;
		}
		else if (tmp->data > data) {
			tmp = tmp->left;
		}
	}

	TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));
	if (NULL == Node) {
		printf("申请新节点内存失败\n");
		return root;
	}
	Node->data = data;
	Node->left = NULL;
	Node->right = NULL;
	// 链接节点
	if (data > top->data) {
		top->right = Node;
	}
	else if (data < top->data) {
		top->left = Node;
	}

	return root;
}


// 删除二叉排序树节点
bool DeleteTreeNode(TreeNode** TreeRoot, int data) {
	if (NULL == (*TreeRoot)) return false;

	printf("删除节点: %d\n", data);

	TreeNode* tmp = (*TreeRoot);
	TreeNode* top = NULL;

	while (tmp != NULL) {
		if (tmp->data == data) {
			// 叶子节点
			if ((NULL == tmp->left) && (NULL == tmp->right)) {
				// 叶子节点
				if (NULL == top) {
					// 仅有根节点的叶子节点
					free(tmp);
					return true;
				}
				else {
					// 其他的叶子节点
					TreeNode* lastNode = top;
					if (tmp == lastNode->left) {
						lastNode->left = NULL;
					}
					else if (tmp == lastNode->right) {
						lastNode->right = NULL;
					}
					free(tmp);
					return true;
				}
			}
			else {
				// 非叶子节点
				// 算法为: 
				// 默认算法为: 1.  当删除该节点时,获取该树右子树最左子节点
				//             2.  当右子树为空时,此时应该获取左子树最右端子节点

				if (NULL != tmp->right) {
					// 方案 1
					TreeNode* tmp2 = tmp->right;
					TreeNode* top2 = NULL;

					// 找到最后一个节点
					while (tmp2->left != NULL) {
						top2 = tmp2;
						tmp2 = tmp2->left;
					}

					// 删除老的节点
					tmp->data = tmp2->data;
					// 只有右子树节点 没有左子树节点
					if (NULL == top2) {
						tmp->right = NULL;

					}
					else {
						top2->left = NULL;
					}
					free(tmp2);
				}
				else {
					// 方案 2
					TreeNode* tmp2 = tmp->left;
					TreeNode* top2 = NULL;

					// 找到最后一个节点
					while (tmp2->right != NULL) {
						tmp2 = tmp2->right;
					}

					// 删除老的节点
					tmp->data = tmp2->data;
					if (NULL == top2) {
						tmp->left = NULL;
					}
					else {
						top2->right = NULL;
					}
					free(tmp2);
				}

			}
		}
		else {
			top = tmp;
			if (data > tmp->data) {
				tmp = tmp->right;
			}
			else {
				tmp = tmp->left;
			}
		}
	}
	return false;
}

// 二叉树平衡调整
bool treeBalance(TreeNode** root) {
	checkTreeBalance((*root));
	while (BalanceTrue) {
		printf("二叉树不平衡,最小不平衡子树数据结点: %d\n", rjt->data);
		TreeNode* tmp;

		if (1 < treeHeight(rjt->left) - treeHeight(rjt->right)) {
			// 对于不平衡二叉树而言,左子树比右子树高
			//
			//printf("左\n");
			if (rjt->left != NULL) {
				tmp = rjt->left;
				int ll = treeHeight(tmp->left);
				int rr = treeHeight(tmp->right);

				if (ll > rr) {
					// 对于不平衡子树 左子树 而言, 左子树比右子树高
					// 左左旋转

					turnLL(root, rjt);

				}
				else {
					// 对于不平衡子树 左子树 而言, 右子树比左子树高
					// 左右旋转
					//
					turnLR(root, rjt);
				}
			}
		}
		else if (1 < treeHeight(rjt->right) - treeHeight(rjt->left)) {
			// 对于不平衡二叉树而言,右子树比左子树高
			//
			//printf("右\n");
			if (rjt->right != NULL) {
				tmp = rjt->right;
				int ll = treeHeight(tmp->left);
				int rr = treeHeight(tmp->right);

				if (ll > rr) {
					//右左旋转
					turnRL(root, rjt);
				}
				else {
					//右右旋转
					turnRR(root, rjt);
				}
			}
		}

		BalanceTrue = false;
		checkTreeBalance((*root));
		printf("二叉树调整平衡后数据结点:\n");
		printf("前序遍历:");
		Print1(*root);
		printf("\n");
		printf("中序遍历:");
		Print2(*root);
		printf("\n");
		printf("\n");
	}

}

int main() {
	TreeNode* root = NULL;

	printf("平衡二叉树插入测试\n");
	int nums[] = { 65,60,70,55,40,63,69,66,68,77 };
	int i;
	for (i = 0; i < sizeof(nums) / sizeof(int); i++) {
		printf("插入数据: %d\n", nums[i]);

		root = addNode(root, nums[i]);
		if (NULL == root) {
			printf("首节点申请失败");
			return -1;
		}

		treeBalance(&root);

	}
	printf("\n当前二叉树遍历\n");
	printf("前序遍历:");
	Print1(root);
	printf("\n");
	printf("中序遍历:");
	Print2(root);
	printf("\n");
	//return 0;

	printf("\n\n平衡二叉树删除测试\n");

	for (i = 2; i < 5; i++) {
		DeleteTreeNode(&root, nums[i]);

		treeBalance(&root);
	}

	printf("\n当前二叉树遍历\n");
	printf("前序遍历:");
	Print1(root);
	printf("\n");
	printf("中序遍历:");
	Print2(root);
	printf("\n");

	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

删除节点

  • 如果节点是叶子节点
    • 如果叶子节点是根节点,则直接释放该节点的内存。
    • 如果叶子节点不是根节点,则需要在父节点中删除该节点,并释放该节点的内存。
  • 如果节点不是叶子节点
    • 如果节点有右子树,则找到右子树中最左边的节点(即右子树的最小值节点),用这个节点的值替换当前节点的值,然后删除那个最小值节点。通过替代节点的父节点将替代节点设置为NULL,并释放替代节点的内存。
    • 如果节点没有右子树(即只有左子树),则找到左子树中最右边的节点(即左子树的最大值节点),用这个节点的值替换当前节点的值,然后删除那个最大值节点。通过替代节点的父节点将替代节点设置为NULL,并释放替代节点的内存。
6、红黑树

红黑树也是一种自平衡的二叉查找树,以前也叫做平衡二叉B树(Symmetric Binary B-tree)。

  • 它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色每一个节点可以是红或者黑;

  • 红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的

  • 红黑树增删改查的性能都很好

平衡二叉树红黑树
区别高度平衡
当左右子树高度差超过1时,通过旋转保持平衡
不是高度平衡的
条件: 特有的红黑规则

红黑规则

  • 每一个节点或是红色的,或者是黑色的 【节点是**RED** 或者**BLACK**】
  • 根节点必须是黑色 【根节点必须是**BLACK**】
  • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶子节点,每个叶子节点(Nil)是黑色的【叶子节点(外部节点,空节点)都是**BLACK**】
  • 如果某一个节点是红色,那么它的父节点和子节点必须是黑色(不能出现两个红色节点相连的情况)【RED 节点的父节点和子节点都是**BLACK**】
  • 对每一个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

在这里插入图片描述

红黑树添加节点的规则

  • 默认颜色: 添加节点默认是红色的(效率高)

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

#define RED 0
#define BLACK 1

typedef struct RBNode {
    int color; // 节点颜色
    int data;  // 数据
    struct RBNode* left;
    struct RBNode* right;
    struct RBNode* parent;
} RBNode, * RBTree;

// 函数声明
RBNode* createNode(int data);
void insertFixup(RBTree* tree, RBNode* node);
RBNode* insertNode(RBTree* tree, int data);
void leftRotate(RBNode** root, RBNode* x);
void rightRotate(RBNode** root, RBNode* y);
void Print(RBNode* root);

// 创建新节点
RBNode* createNode(int data) {
    RBNode* node = (RBNode*)malloc(sizeof(RBNode));
    if (node)
    {

        node->data = data;
        node->color = RED;
        node->left = NULL;
        node->right = NULL;
        node->parent = NULL;
        return node;
    }
    return;
}

// 插入新节点并修复红黑树
RBNode* insertNode(RBTree* tree, int data) {
    RBNode* node = createNode(data);
    if (*tree == NULL) {
        *tree = node;
        node->color = BLACK;
    }
    else {
        RBNode* current = *tree;
        RBNode* parent = NULL;
        while (current != NULL) {
            parent = current;
            if (node->data < current->data) {
                current = current->left;
            }
            else {
                current = current->right;
            }
        }
        node->parent = parent;
        if (node->data < parent->data) {
            parent->left = node;
        }
        else {
            parent->right = node;
        }
        insertFixup(tree, node);
    }
    return node;
}

// 插入修复
void insertFixup(RBTree* tree, RBNode* node) {
    //父亲节点
    RBNode* parentOfNode = NULL;
    //祖父节点
    RBNode* grandparentOfNode = NULL;
    //只有父亲是红色的才需要操作,父亲是黑色的无需任何操作
    while (node != *tree && node->parent->color == RED) {
        //父亲节点
        parentOfNode = node->parent;
        //祖父节点
        grandparentOfNode = parentOfNode->parent;
        //父亲节点是祖父的左子节点
        if (parentOfNode == grandparentOfNode->left) {
            //叔叔节点
            RBNode* uncleOfNode = grandparentOfNode->right;
            //叔叔节点存在并是红色
            if (uncleOfNode && uncleOfNode->color == RED) {
                //父亲黑色,叔叔黑色,祖父红色
                grandparentOfNode->color = RED;
                parentOfNode->color = BLACK;
                uncleOfNode->color = BLACK;
                //祖父作为当前节点
                node = grandparentOfNode;
            }
            else {
                //叔叔是黑色,并且当前节点是父的右孩子 - 左右LR旋转
                if (node == parentOfNode->right) {
                    //把父为当前节点
                    node = parentOfNode;
                    //左旋
                    leftRotate(tree, node);
                }
                //叔叔是黑色,并且当前节点是父的左孩子 - 左左LL旋转 
                //即使叔叔是黑色,并且当前节点是父的右孩子,把父为当前节点进行左旋之后,也会变成叔叔是黑色,并且当前节点是父的左孩子的情况
                //父亲黑色,祖父红色
                parentOfNode->color = BLACK;
                grandparentOfNode->color = RED;
                //以祖父为支点右旋
                rightRotate(tree, grandparentOfNode);                
            }
        }
        else {
            //父亲节点是祖父的右子节点
            RBNode* uncleOfNode = grandparentOfNode->left;
            if (uncleOfNode && uncleOfNode->color == RED) {
                grandparentOfNode->color = RED;
                parentOfNode->color = BLACK;
                uncleOfNode->color = BLACK;
                node = grandparentOfNode;
            }
            else {
                //叔叔是黑色,并且当前节点是父的左孩子 - 右左RL旋转
                if (node == parentOfNode->left) {
                    node = parentOfNode;
                    rightRotate(tree, node);
                }
                //叔叔是黑色,并且当前节点是父的右孩子 - 右右LL旋转
                //即使叔叔是黑色,并且当前节点是父的左孩子,把父为当前节点进行右旋之后,也会变成叔叔是黑色,并且当前节点是父的右孩子的情况
                parentOfNode->color = BLACK;
                grandparentOfNode->color = RED;
                leftRotate(tree, grandparentOfNode);
            }
        }
    }
    //根只能是黑色的
    (*tree)->color = BLACK;
}

// 左旋
void leftRotate(RBNode** root, RBNode* x) {
    RBNode* y = x->right;

    //建立x、y->left的关系
    x->right = y->left;
    if (y->left != NULL) {
        y->left->parent = x;
    }

    //建立x->parent和y的关系
    y->parent = x->parent;
    if (x->parent == NULL) {
        *root = y;
    }
    else if (x == x->parent->left) {
        x->parent->left = y;
    }
    else {
        x->parent->right = y;
    }

    //建立x、y的关系
    y->left = x;
    x->parent = y;
}

// 右旋
void rightRotate(RBNode** root, RBNode* y) {
    RBNode* x = y->left;

    //建立y、x->right的关系
    y->left = x->right;
    if (x->right != NULL) {
        x->right->parent = y;
    }

    //建立y->parent和x的关系
    x->parent = y->parent;
    if (y->parent == NULL) {
        *root = x;
    }
    else if (y == y->parent->left) {
        y->parent->left = x;
    }
    else {
        y->parent->right = x;
    }

    //建立y,x关系
    x->right = y;
    y->parent = x;
}


// 二叉树中序遍历
void Print(RBNode* root) {
    if (NULL == root) return;
    Print(root->left);
    printf("%d - %d\t", root->data, root->color);
    Print(root->right);
}


// 主函数或其他函数可以调用这些函数来操作红黑树
int main() {
    RBTree tree = NULL;
    int nums[9] = { 20,18,23,22,17,24,19,15,14 };
    int i;
    for (i = 0; i < 9; i++)
    {
        insertNode(&tree, nums[i]);
    }
    Print(tree);
    return 0;
}

6.2.2 二叉树的性质

  1. 任意一棵树,若结点数量为n, 则边的数量为n − 1。
  2. 非空二叉树上第i层上至多有2i-1个结点
  3. 高度为h的二叉树至多有2h-1个结点
  4. 非空二叉树上的叶子结点数等于度为2的结点数加1,即n0= n2+ 1
  5. 具有 n个结点的完全二叉树的深度为 ⌊ log ⁡ 2 n ⌋ + 1 \left \lfloor \log_2 n \right \rfloor + 1 log2n+1
  6. 对完全二叉树按从上到下、从左到右的顺序依次编号1 , 2… ∗ , n,则有以下关系:
    • 当 i = 1 时, 则结点 i 是二叉树的根-无双亲; 当 i > 1 时, 结点 i 的双亲的编号为 i / 2 ,即当 i 为偶数时,它是双亲的左孩子;当 i 为奇数时,它是双亲的右孩子。
    • 当 2i ≤ n 时,结点 i 的左孩子编号为 2i , 否则无左孩子。
    • 当 2i+1 ≤ n 时,结点 i 的右孩子编号为2i + 1,否则无右孩子。

在这里插入图片描述

6.2.3 二叉树的存储结构

  • 顺序存储结构: 一种极端的情况,一棵深度为k的右斜树,只有k个结点,却需要分配2k-1个存储单元,造成明显的浪费,所以顺序存储结构一般只用于完全二叉树

    #define MAX_TREE_SIZE 100 //二义树的最大结点数
    typedef char TElementType;
    typedef TElementType SqBiTree[MAX_TREE_SIZE];
    SqBiTree bt;
    

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS 1
    
    #include<stdio.h>
    #include<math.h>
    #define MAX_TREE_SIZE 100
    #define CHAR 1
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    
    #if CHAR
    typedef char TElemType;
    TElemType Nil = ' '; /* 设字符型以空格符为空 */
    #else
    typedef int TElemType;
    TElemType Nil = 0; /* 设整型以0为空 */
    #endif
    
    #define MAX_TREE_SIZE 100 /* 二叉树的最大结点数 */
    typedef TElemType SqBiTree[MAX_TREE_SIZE]; /* 0号单元存储根结点 */
    
    Status InitBiTree(SqBiTree T); /* 构造空二叉树T */
    Status CreateBiTree(SqBiTree T); /* 按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T */
    Status BiTreeEmpty(SqBiTree T); /* 判空 */
    int BiTreeDepth(SqBiTree T); /*求深度*/
    Status Root(SqBiTree T, TElemType* e); /* 求根节点 */
    void Print_BiTree(SqBiTree T); /* 打印 */
    
    int main()
    {
        Status i;
        TElemType e;
        SqBiTree T;
        InitBiTree(T);
        CreateBiTree(T);
        printf("打印二叉树的数据\n");
        Print_BiTree(T);
        printf("建立二叉树后,树空否?%d(1:是 0:否) 树的深度=%d\n", BiTreeEmpty(T), BiTreeDepth(T));
        i = Root(T, &e);
        if (i)
    #if CHAR
            printf("二叉树的根为:%c\n", e);
    #else
            printf("二叉树的根为:%d\n", e);
    #endif
        else
            printf("树空,无根\n");
    	return 0;
    }
    
    
    Status InitBiTree(SqBiTree T)
    { /* 构造空二叉树T。因为T是固定数组,不会改变,故不需要& */
        int i;
        for (i = 0; i < MAX_TREE_SIZE; i++)
            T[i] = Nil; /* 初值为空 */
        return OK;
    }
    
    Status CreateBiTree(SqBiTree T)
    { /* 按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T */
        int i = 0;
    #if CHAR
        int l;
        char s[MAX_TREE_SIZE];
        printf("请按层序输入结点的值(字符),空格表示空结点,结点数≤%d:\n", MAX_TREE_SIZE);
        gets(s); /* 输入字符串 */
        l = strlen(s); /* 求字符串的长度 */
        for (; i < l; i++) /* 将字符串赋值给T */
        {
            T[i] = s[i];
            if (i != 0 && T[(i + 1) / 2 - 1] == Nil && T[i] != Nil) /* 此结点(不空)无双亲且不是根 */
            {
                printf("出现无双亲的非根结点%c\n", T[i]);
                exit(ERROR);
            }
        }
        for (i = l; i < MAX_TREE_SIZE; i++) /* 将空赋值给T的后面的结点 */
            T[i] = Nil;
    #else
        printf("请按层序输入结点的值(整型),0表示空结点,输999结束。结点数≤%d:\n", MAX_TREE_SIZE);
        while (1)
        {
            scanf("%d", &T[i]);
            if (T[i] == 999)
                break;
            if (i != 0 && T[(i + 1) / 2 - 1] == Nil && T[i] != Nil) /* 此结点(不空)无双亲且不是根 */
            {
                printf("出现无双亲的非根结点%d\n", T[i]);
                exit(ERROR);
            }
            i++;
        }
        while (i < MAX_TREE_SIZE)
        {
            T[i] = Nil; /* 将空赋值给T的后面的结点 */
            i++;
        }
    #endif
        return OK;
    }
    
    Status BiTreeEmpty(SqBiTree T)
    { /* 初始条件: 二叉树T存在 */
      /* 操作结果: 若T为空二叉树,则返回TRUE,否则FALSE */
        if (T[0] == Nil) /* 根结点为空,则树空 */
            return TRUE;
        else
            return FALSE;
    }
    
    int BiTreeDepth(SqBiTree T)
    //高度为h的二叉树至多有2^h-1个结点 - n<=pow(2,h)-1
    { /* 初始条件: 二叉树T存在。操作结果: 返回T的深度 */
        int i, j = -1;
        for (i = MAX_TREE_SIZE - 1; i >= 0; i--) /* 找到最后一个结点 */
            if (T[i] != Nil)
                break;
    
        i++; /* 为了便于计算 */
        do
            j++;
        while (i >= pow(2, j));//计算2的J次幂,返回double值。因为i<=pow(2,j)-1,因此i<pow(2,j).
        return j;//返回二叉树的深度
    }
    
    Status Root(SqBiTree T, TElemType* e)
    { /* 初始条件: 二叉树T存在 */
      /* 操作结果:  当T不空,用e返回T的根,返回OK;否则返回ERROR,e无定义 */
        if (BiTreeEmpty(T)) /* T空 */
            return ERROR;
        else
        {
            *e = T[0];//返回T的根,也就是第一个节点
            return OK;
        }
    }
    
    void Print_BiTree(SqBiTree T)
    {
        int i;
    
        for (i = 0; i < 100; ++i)
        {
            if (T[i] != '\0')
    #if CHAR
                printf("%c", T[i]);
    #else
                printf("%d", T[i]);
    #endif
    
        }
        printf("\n");
    }
    
  • 链式存储结构

    二叉树的结点 由一个数据元素和分别指向其 左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包含3个域:数据域和左、右指针域。有时,为了便于找到结点的双亲,则还可在结点结构中增加一个指向其双亲结点的指针域。利用这两种结点结构所得二叉树的存储结构分别称之为二叉链表三叉链表

    在这里插入图片描述

    在这里插入图片描述

    #define _CRT_SECURE_NO_WARNINGS 1
    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX_TREE_SIZE 100
    
    typedef char TElementType;
    typedef struct BiTNode {
    	TElementType data;
    	struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
    }BiTNode, * BiTree;
    
    //先序
    BiTree CreateBiTree() {
        char ch;
        if (scanf(" %c", &ch) != 1) { // 检查输入是否成功
            // 输入失败,可能是文件结束符或错误输入
            exit(EXIT_FAILURE);
        }
        if (ch != '#') {
            BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存
            if (node == NULL) {
                // 内存分配失败
                perror("内存分配失败");
                exit(EXIT_FAILURE);
            }
            node->data = ch;
            node->lchild = CreateBiTree(); // 递归创建左子树
            node->rchild = CreateBiTree(); // 递归创建右子树
            return node;
        }
        else {
            return NULL;
        }
    }
    
    //中序
    void Print_Tree(BiTree T)
    {
    	if (T)
    	{		
    		Print_Tree(T->lchild);
            printf("%c  ", T->data);
    		Print_Tree(T->rchild);
    	}
    }
    
    int main()
    {
    	BiTree T = NULL;
    	T = CreateBiTree(); // abc##de#g##f###
    	Print_Tree(T); // c  b  e  g  d  f  a
    	return 0;
    }
    

6.3 遍历二叉树和线索二叉树

6.3.1 遍历二叉树

  • 先(根)序遍历

    (1) 访问根结点; (2) 先序遍历左子树; (3) 先序遍历右子树

  • 中(根)序遍历

    (1) 中序遍历左子树; (2) 访问根结点; (3) 中序遍历右子树

  • 后(根)序遍历

    (1) 后序遍历左子树; (2) 后序遍历右子树; (3) 访问根结点

  • 层序遍历

    层序遍历以树的根节点开始,按照从上到下、从左到右的顺序逐层遍历树中的节点。

    过程:

    1. 创建一个队列,并将根节点入队。
    2. 当队列不为空时,执行以下步骤:
      1. 从队列中取出一个节点,访问该节点。
      2. 将该节点的所有子节点(如果存在)依次入队。
    3. 如果队列为空,则表示遍历结束。
/*  递归  */
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

#define MAX_TREE_SIZE 100
#define MAXQSIZE 10

#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define ERROR -2
#define TRUE 1
#define FALSE 0
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */

typedef char TElementType;
typedef struct BiTNode {
	TElementType data;
	struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
}BiTNode, * BiTree;

typedef struct {
    BiTNode** base; // 队列存储的是TreeNode*的指针
    int front;
    int rear;
} SqQueue;

Status InitQueue(SqQueue* Q) {
    Q->base = (BiTNode**)malloc(MAXQSIZE * sizeof(BiTNode*));
    if (!Q->base) exit(1); // 使用exit(1)表示错误退出
    Q->front = Q->rear = 0;
    return OK;
}

// 判断队列是否为空
int IsEmpty(SqQueue* q) {
    return q->rear <= q->front;
}

Status EnQueue(SqQueue* Q, BiTNode* node) {
    if ((Q->rear + 1) % MAXQSIZE == Q->front) {
        return OVERFLOW; // 队列已满,返回OVERFLOW
    }
    Q->base[Q->rear] = node;
    Q->rear = (Q->rear + 1) % MAXQSIZE;
    return OK;
}

BiTNode* DeQueue(SqQueue* Q) {
    if (Q->front == Q->rear) {
        return ERROR; // 队列为空,返回ERROR
    }
    BiTNode* dequeuedNode = Q->base[Q->front]; // 使用引用来修改e的值
    Q->front = (Q->front + 1) % MAXQSIZE;
    return dequeuedNode;
}

void DestroyQueue(SqQueue* Q) {
    free(Q->base);
    Q->base = NULL;
    Q->front = Q->rear = 0;
}

//先序
BiTree CreateBiTree() {
    char ch;
    if (scanf(" %c", &ch) != 1) { // 检查输入是否成功
        // 输入失败,可能是文件结束符或错误输入
        exit(EXIT_FAILURE);
    }
    if (ch != '#') {
        BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存
        if (node == NULL) {
            // 内存分配失败
            perror("内存分配失败");
            exit(EXIT_FAILURE);
        }
        node->data = ch;
        node->lchild = CreateBiTree(); // 递归创建左子树
        node->rchild = CreateBiTree(); // 递归创建右子树
        return node;
    }
    else {
        return NULL;
    }
}

Status PrintElement(TElementType e)
{
    printf("%c ", e);
    return OK;
}

//先序遍历
Status PreOrderTraverse(BiTree T, Status(* PrintElement)(TElementType e))
{
	if (T)
	{		
        if (PrintElement(T->data))
            if (PreOrderTraverse(T->lchild,PrintElement))
                if (PreOrderTraverse(T->rchild,PrintElement)) return OK;
        return OK;
    }
    else
    {
        return OK;
    }
}

//中序遍历
Status InOrderTraverse(BiTree T, Status(*PrintElement)(TElementType e))
{
    if (T)
    {
        if (InOrderTraverse(T->lchild, PrintElement))
            if (PrintElement(T->data))
                if (InOrderTraverse(T->rchild, PrintElement)) return OK;
        return OK;
    }
    else
    {
        return OK;
    }
}

//后序遍历
Status PostOrderTraverse(BiTree T, Status(*PrintElement)(TElementType e))
{
    if (T)
    {
        if (PostOrderTraverse(T->lchild, PrintElement))
            if (PostOrderTraverse(T->rchild, PrintElement)) 
                if (PrintElement(T->data)) return OK;
        return OK;
    }
    else
    {
        return OK;
    }
}

//层序遍历
void LevelOrderTraversal(BiTree T, Status(*PrintElement)(TElementType e)) {
    if (T == NULL) return;

    SqQueue Q;
    InitQueue(&Q);	//初始化辅助队列
    EnQueue(&Q, T);	//将根节点入队

    while (!IsEmpty(&Q)) {	//队列不空则循环
        BiTNode* frontNode = DeQueue(&Q);	//队头结点出队
        PrintElement(frontNode->data);	//访问出队结点
        if (frontNode->lchild != NULL) {
            EnQueue(&Q, frontNode->lchild);	//左子树不空,则左子树根节点入队
        }
        if (frontNode->rchild != NULL) {
            EnQueue(&Q, frontNode->rchild);	//右子树不空,则右子树根节点入队
        }

    }
}



int main()
{
	BiTree T = NULL;
	T = CreateBiTree(); // abc##de#g##f###
    printf("----------先序---------------\n");
    PreOrderTraverse(T, PrintElement); // a  b  c  d  e  g  f
    printf("\n----------中序---------------\n");
    InOrderTraverse(T, PrintElement); // c  b  e  g  d  f  a
    printf("\n----------后序---------------\n");
    PostOrderTraverse(T, PrintElement); // c  g  e  f  d  b  a
    printf("\n----------层序---------------\n");
    LevelOrderTraversal(T, PrintElement); // a  b  c  d  e  f  g
	return 0;
}

非递归的思路:

先序:

在这里插入图片描述

中序:

在这里插入图片描述

后序:

在这里插入图片描述

/*  非递归  */
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

#define MAX_TREE_SIZE 100
#define MAXQSIZE 10

#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define ERROR -2
#define TRUE 1
#define FALSE 0

typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */

typedef char TElementType;
typedef struct BiTNode {
	TElementType data;
	struct BiTNode* lchild, * rchild; //左孩子、右孩子指针
}BiTNode, * BiTree;

// 模拟栈结构
typedef struct Stack {
    BiTNode* nodes[100]; // 假设最多100个节点
    int top;
} Stack;

// 初始化栈
void StackInit(Stack* s) {
    s->top = -1;
}

// 判断栈是否为空
int IsEmpty(Stack* s) {
    return s->top == -1;
}

// 压栈
void Push(Stack* s, BiTNode* node) {
    if (s->top < 99) { // 防止栈溢出
        s->nodes[++(s->top)] = node;
    }
}

// 出栈
BiTNode* Pop(Stack* s) {
    if (!IsEmpty(s)) {
        return s->nodes[(s->top)--];
    }
    return NULL;
}

//先序
BiTree CreateBiTree() {
    char ch;
    if (scanf(" %c", &ch) != 1) { // 检查输入是否成功
        // 输入失败,可能是文件结束符或错误输入
        exit(EXIT_FAILURE);
    }
    if (ch != '#') {
        BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode)); // 分配内存
        if (node == NULL) {
            // 内存分配失败
            perror("内存分配失败");
            exit(EXIT_FAILURE);
        }
        node->data = ch;
        node->lchild = CreateBiTree(); // 递归创建左子树
        node->rchild = CreateBiTree(); // 递归创建右子树
        return node;
    }
    else {
        return NULL;
    }
}

Status PrintElement(TElementType e)
{
    printf("%c ", e);
    return OK;
}


// 非递归先序遍历:	
void PreOrderTraverse(BiTNode* root) {
    if (root == NULL) {
        return;
    }

    Stack stack;
    StackInit(&stack);
    Push(&stack, root);

    while (!IsEmpty(&stack)) {
        root = Pop(&stack);
        PrintElement(root->data);
        if (root->rchild != NULL) {
            Push(&stack, root->rchild);
        }
        if (root->lchild != NULL) {
            Push(&stack, root->lchild);
        }
    }
}

//中序遍历
Status InOrderTraverse(BiTNode* head) {
    if (head == NULL) {
        return;
    }

    Stack stack;
    StackInit(&stack);
    BiTNode* current = head;

    while (current != NULL || !IsEmpty(&stack)) {
        // 将当前节点压入栈中,并遍历其左子树
        while (current != NULL) {
            Push(&stack, current);
            current = current->lchild;
        }

        // 访问栈顶节点,并遍历其右子树
        current = Pop(&stack);
        PrintElement(current);
        current = current->rchild;
    }
}

//后序遍历
Status PostOrderTraverse(BiTNode* root) {
    if (root == NULL) {
        return;
    }

    Stack s1, s2;
    StackInit(&s1);
    StackInit(&s2);

    Push(&s1, root);

    while (!IsEmpty(&s1)) {
        root = Pop(&s1);
        Push(&s2, root);
        if (root->lchild != NULL) {
            Push(&s1, root->lchild);
        }
        if (root->rchild != NULL) {
            Push(&s1, root->rchild);
        }
    }

    while (!IsEmpty(&s2)) {
        PrintElement(Pop(&s2)->data);
    }
}



int main()
{
	BiTree T = NULL;
	T = CreateBiTree(); // abc##de#g##f###
    printf("----------先序非递归---------------\n");
    PreOrderTraverse(T, PrintElement); // a  b  c  d  e  g  f
    printf("\n----------中序非递归---------------\n");
    InOrderTraverse(T, PrintElement); // c  b  e  g  d  f  a
    printf("\n----------后序非递归---------------\n");
    PostOrderTraverse(T, PrintElement); // c  g  e  f  d  b  a
	return 0;
}

参考:

教材:

严蔚敏《数据结构》(C语言版).pdf

离散数学及其应用(原书第8版) (Kenneth H.Rosen) .pdf

《代码随想录》回溯算法(V3.0).pdf

视频:

https://www.bilibili.com/video/BV1Zf4y1a77g/?spm_id_from=333.788&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV1b7411N798/?p=51&spm_id_from=333.880.my_history.page.click&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV1pu411c7pf/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=a89593e8d33b31a56b894ca9cad33d33

https://www.bilibili.com/video/BV17F411T7Ao?p=195&vd_source=a89593e8d33b31a56b894ca9cad33d33

博客:

https://blog.csdn.net/Real_Fool_/article/details/113930623

https://blog.csdn.net/m0_73633088/article/details/133443742

https://blog.csdn.net/wangrunmin/article/details/7831318

https://blog.csdn.net/Colorful___/article/details/133603913

https://blog.51cto.com/u_13360/6732847

https://zhuanlan.zhihu.com/p/600665635

https://blog.csdn.net/crr411422/article/details/129932889

https://blog.51cto.com/u_15773967/6148952

https://blog.csdn.net/2401_82584055/article/details/138349195

https://mp.weixin.qq.com/s?__biz=Mzg5MjkxNTA5MA==&mid=2247484467&idx=2&sn=3ea1749b1c7c301289a40dac4497e87e&chksm=c03798cef74011d8ead16e27ebea8e3a1df2a9a0242385d328f0ec05973e9b12ef1fa7254e14&scene=27

工具:

https://www.cs.usfca.edu/~galles/visualization/DisjointSets.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2152424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024“智衡屋” 智能感知挑战赛决赛即将来袭

2024“智衡屋” 智能感知挑战赛决赛将于 2024 年 9 月 24 日在安徽省合肥市举行&#xff0c;决赛将作为 2024 年中国计量测试学会首届人工智能计量学术大会的重要环节率先举行。 2024“智衡屋” 智能感知挑战赛自启动以来&#xff0c;吸引了700余支高校学生、科研机构研究人员以…

Spring Boot框架在心理教育辅导系统中的应用

3 系统分析 3.1可行性分析 在进行可行性分析时&#xff0c;我们通常根据软件工程里方法&#xff0c;通过四个方面来进行分析&#xff0c;分别是技术、经济、操作和法律可行性。因此&#xff0c;在基于对目标系统的基本调查和研究后&#xff0c;对提出的基本方案进行可行性分析。…

weblogic CVE-2018-2894 靶场攻略

漏洞描述 Weblogic Web Service Test Page中⼀处任意⽂件上传漏洞&#xff0c;Web Service Test Page 在 "⽣产模式"下默认不开启&#xff0c;所以该漏洞有⼀定限制。 漏洞版本 weblogic 10.3.6.0 weblogic 12.1.3.0 weblogic 12.2.1.2 28 weblogic 12.2.1.3 …

ChromaDB教程_2024最新版(下)

前言 Embeddings&#xff08;嵌入&#xff09;是表示任何类型数据的AI原生方式&#xff0c;它非常适用于各种AI驱动的工具和算法中。它们可以表示文本、图像&#xff0c;很快还可以表示音频和视频。有许多创建嵌入的选项&#xff0c;无论是在本地使用已安装的库&#xff0c;还是…

LabVIEW 可以同时支持脚本编程和图形编程

LabVIEW 可以同时支持脚本编程和图形编程&#xff0c;但主要依赖其独特的 图形编程 环境&#xff08;G语言&#xff09;&#xff0c;其中程序通过连线与节点来表示数据流和功能模块。不过&#xff0c;LabVIEW 也支持通过以下方式实现脚本编程的能力&#xff1a; 1. 调用外部脚本…

openCV3.0 C++ 学习笔记补充(自用 代码+注释)---持续更新 三(61-)

环境&#xff1a;OpenCV3.2.0 VS2017 61、轮廓集合重排序(按轮廓面积从小到大) //对轮廓集合面积从大到小排序 bool compareValue_bs(const std::vector<cv::Point> & c1, const std::vector<cv::Point> & c2) {int area1 cv::contourArea(c1);int area…

Vue 组件通信指南:Props 和 $emit,Vuex(状态管理),EventBus(事件总线),Provide/Inject(依赖注入)

引言 在 Vue 中&#xff0c;组件是构建应用的基本单元&#xff0c;而组件通信则是构建复杂应用的关键。组件通信是指在不同的 Vue 组件之间传递数据、交互和共享状态的过程&#xff0c;它在构建大型应用和组织代码方面起着至关重要的作用。 在开发过程中&#xff0c;我们经常…

一招教你挑代理IP的秘诀

逛乎&#xff0c;一直刷到这类问题&#xff1a; 本质上&#xff0c;都是在面对市面上那么多代理IP服务提供商&#xff0c;挑得眼花缭乱了&#xff0c;而代理IP直接影响到我们数据采集任务的效率、安全性和成功率&#xff0c;所以我们在挑选服务提供商的时候都会谨慎一些。索性我…

VScode安装和使用教程,2024最新最全,零基础入门到精通,看完这一篇就够了!

# VSCode 安装使用教程&#xff08;图文版&#xff09; 工欲善其事&#xff0c;必先利其器 对于我们每一位软件工程师来说&#xff0c;都要有自己顺手的 IDE 开发工具&#xff0c;它就是我们的武器。 一个好用的 IDE 不仅能提升我们的开发效率&#xff0c;还能让我们保持愉悦…

推送 Git Remote: 内部服务错误解决方案

Git Remote: 内部服务错误起因 拉取阿里云云效仓库代码的时候&#xff0c;之前一直拉取仓库并且推送都没有任何问题&#xff0c;但是最近在云效里面新建了一个仓库&#xff0c;也能成功拉取下来&#xff0c;但就是推送不上去&#xff0c;但是其它仓库都可以随意推送没有任何问…

IPv6(三)

文章目录 IPv6报文 IPv6报文 IPv6基本报头有8个字段&#xff0c;固定大小为40字节&#xff0c;&#xff0c;每个IPv6数据都必须包含报头&#xff0c;基本报头提供报文转发的基本信息&#xff0c;会被转发路径上面的所有路由器解析 IPv6报头长度为40字节Version&#xff1a;版本…

如何实现一个流畅的滚动列表

如何实现一个流畅的滚动列表 在网页开发中&#xff0c;滚动列表是展示大量数据时常用的交互方式。通过结合CSS动画和视觉设计&#xff0c;我们可以让列表内容自动滚动&#xff0c;为用户提供顺畅的浏览体验。今天&#xff0c;我将带你一步步实现一个流畅、富有视觉吸引力的滚动…

MySQL中的LIMIT与ORDER BY关键字详解

前言 众所周知&#xff0c;LIMIT和ORDER BY在数据库中&#xff0c;是两个非常关键并且经常一起使用的SQL语句部分&#xff0c;它们在数据处理和分页展示方面发挥着重要作用。 今天就结合工作中遇到的实际问题&#xff0c;回顾一下这块的知识点。同时希望这篇文章可以帮助到正…

[备忘]测算.net中对象所占用的内存

.net 基础库中应该是没有直接提供计算某个对象所占内存的方法。简单查了下&#xff0c;找到几种方式&#xff1a; 1、运行态用工具进行内存分析 比如&#xff0c;微软这篇教程中有介绍。《使用 .NET 对象分配工具分析内存使用情况》https://learn.microsoft.com/zh-cn/visuals…

Tomcat CVE-2017-12615漏洞复现

1.开启环境 cd /vulhub/tomcat/CVE-2017-12615 docker-compose up -d 一键启动环境 2.在首页进行抓包 修改为put方式提交 Tomcat允许适用put方法上传任意文件类型&#xff0c;但不允许jsp后缀文件上传&#xff0c;因此我们需要配合 windows的解析漏洞. 使用put /shell.jsp…

着色器ShaderMask

说明 实现一个渐变进度条&#xff0c;要求&#xff1a; 颜色渐变的过程是循序渐进的&#xff0c;而不是看起来像是将渐变条逐渐拉长了。 效果 源码 // 渐变进度条Stack(children: [// 背景色板Container(width: 300,height: 8,decoration: BoxDecoration(borderRadius: Bord…

【华为杯】2024华为杯数模研赛E题 解题思路

题目 高速公路应急车道紧急启用模型 问题背景 高速公路拥堵现象的原因众多&#xff0c;除了交通事故外&#xff0c;最典型的就是部分路段出现瓶颈现象&#xff0c;主要原因是车辆汇聚&#xff0c;而拥堵后又容易蔓延。高速公路一些特定的路段容易形成堵点&#xff0c;如匝道…

(done) 什么是 GMM? Gaussian Mixture Model,高斯混合模型

来源&#xff1a;https://www.bilibili.com/video/BV13b411w7Xj/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 &#xff08;视频质量很高&#xff0c;一共四个视频&#xff0c;我只看了一个&#xff09; 直接看视频吧&#xff0…

Qt (17)【Qt 文件操作 读写保存】

阅读导航 引言一、Qt文件概述二、输入输出设备类三、文件读写类四、文件和目录信息类五、自定义“记事本” 引言 在上一篇文章中&#xff0c;我们学习了Qt的事件处理机制&#xff0c;知道了如何响应用户的操作。但应用程序常常还需要处理文件&#xff0c;比如读写数据。所以&a…

国内可以使用的ChatGPT服务【9月持续更新】

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…