数据结构之树——二叉树

本文介绍了二叉树的基本概念,包括二叉树的定义、特性,以及如何创建二叉树。重点讲解了二叉树的四种遍历方式:先序遍历、中序遍历、后序遍历和层次遍历,包括递归和非递归实现。此外,还提到了二叉树的一些常见操作,如确定高度、元素个数和复制二叉树等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、  二叉树定义

一棵二叉树(binary tree)t是有限个元素的集合(可以为空)。当二叉树非空时,其中有一个元素称为根,余下的元素被划分成两颗二叉树,分别称为t的左子树和右子树。


二叉树与树的根本区别:

  • 二叉树的每颗子树都恰好有两颗子树(其中有一个或者两个可能为空)。而树的每个元素可由任意数量的子树
  • 在二叉树中每个元素的子树都是有序的,有左子树和右子树之分。而树的子树是无序的。
  • 二叉树可以为空,树不能为空

二、 二叉树的特性

  1. 二叉树有n个元素,n > 0,它有n-1条边。
  2. 一棵二叉树的高度为h,h≥0,最少有h个元素,最多有2的h次方减1个元素。
  3. 一棵二叉树有n个元素,n>0,它的高度最大为n,最小为一棵二叉树有n个元素,n>0,它的高度最大为n,最小为[log_2(n + 1)](表示向下取整)
  4. 设一完全二叉树的一元素编号为i,1≤i≤n。有以下关系成立:
  • 如果i= 1,该元素为二叉树的根。若i>1,则父节点的编号为[i/2](向上取整)
  • 如果2i>n,则该元素无左孩子。否则左孩子的编号为2i.
  • 如果2i+1>n,则该元素无右孩子。否则右孩子的编号为2i+1。

三、二叉树的创建

1.#符号法创建二叉树

(1)二叉树的递归创建

根据输入的符号进行树的创建。当得到的字符是“#”符号时,表示该左子树已经是叶子节点,然后进行右子树的创建,以此类推,直到整个树创建完成。

//二叉树的创建“#”符号法,递归创建
template<class T>
void CreateBiTree(binaryTreeNode<T> **root)
{
       char a;
       cin >> a;
       if ('#' == a)
       {
              root = NULL;
       }
       else
       {
              *root = new binaryTreeNode<T>;
              (*root)->element = a;
              CreateBiTree(&(*root)->leftChild);//递归创建左子树
              CreateBiTree(&(*root)->rightChild);//递归创建右子树
       }
}

(2)二叉树的非递归创建

通过栈的辅助来完成树的创建。
步骤:
1 首先获取根节点的元素的值,将该元素插入到二叉树中作为根节点。并且将该节点压入栈中。直到 左子树完全创建成功进行出栈操作。
2 继续获取节点元素,首先判断获取的元素符号是否为“#”符号,如果不是“#”符号,进行左子树 的插入操作。左子树的插入之前:要判断左子树的根节点的左子树是否为空,如果为空则进行左子        树的插入操作,并且将该元素压入栈中。否则栈顶元素进行出栈操作,进行右子树的创建工作,并        且将该元素压入栈中。如果是“#”符号,表示左子树已经创建完成,进行元素的出栈操作,开始        进行右子树的的创建。
     3 获取元素,重复进行步骤2,直到栈中的元素为空,也就是说树的左子树和右子树创建完成。
     4 完成二叉树的创建工作。

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

#define STACKSIZE 50
#define bitree_size(tree)  ((tree)->size)   
#define bitree_root(tree)  ((tree)->root)
#define bitree_left(node)  ((node)->left)
#define bitree_right(node) ((node)->right)

/*
*  定义二叉树结点以及二叉树的结构
*
*/
typedef struct BiTreeNode_
{
	char data;
	struct BiTreeNode_ *left;
	struct BiTreeNode_ *right;
}BiTreeNode;

typedef struct BiTree_//定义树的头结点
{
	int size;
	BiTreeNode *root;
}BiTree;

/*
*  定义栈的结构
*
*/
typedef struct Stack_
{
	BiTreeNode * base[STACKSIZE];
	int top;
	int stacksize;
}Stack;

void stack_init(Stack *stack)
{
	stack->top = -1;
	stack->stacksize = 0;
}

void push(Stack *stack, BiTreeNode *node)
{
	if (stack->stacksize == STACKSIZE)
		exit(-1);

	stack->base[++stack->top] = node;
	stack->stacksize++;
}

int stack_empty(Stack *stack)//清空栈
{
	if ((-1 == stack->top) && (0 == stack->stacksize))
		return 1;
	else
		return 0;
}

BiTreeNode* pop(Stack *stack)//树的节点出栈
{
	if (stack->top < 0)
		exit(-1);
	else
	{
		stack->stacksize--;
		return stack->base[stack->top--];
	}
}

BiTreeNode* get_stack_top(Stack *stack)//获取栈顶元素
{
	if (stack->top < 0)
		exit(-1);
	return stack->base[stack->top];
}

void bitree_init(BiTree *tree)//初始化树
{
	tree->size = 0;
	tree->root = NULL;
}

//给树tree的某个结点node插入数据域为data的左子树
int bitree_ins_left(BiTree *tree, BiTreeNode *node, const char data)
{
	BiTreeNode *new_node, **position;

	if (NULL == node)
	{
		if (bitree_size(tree) > 0)//如果树的size>0返回-1
			return -1;
		position = &tree->root;//position
	}
	else
	{
		if (bitree_left(node) != NULL)
			return -1;
		position = &node->left;
	}

	if (NULL == (new_node = (BiTreeNode *)malloc(sizeof(BiTreeNode))))
		return -1;
	new_node->data = data;
	new_node->left = NULL;
	new_node->right = NULL;
	*position = new_node;
	tree->size++;

	return 0;
}

//给树tree的某个结点node插入数据域为data的右子树
int bitree_ins_right(BiTree *tree, BiTreeNode *node, const char data)
{
	BiTreeNode *new_node, **position;

	if (NULL == node)
	{
		if (bitree_size(tree) > 0)
			return -1;

		position = &tree->root;
	}
	else
	{
		if (bitree_right(node) != NULL)
			return -1;

		position = &node->right;
	}

	if (NULL == (new_node = (BiTreeNode *)malloc(sizeof(BiTreeNode))))
		return -1;

	new_node->data = data;
	new_node->left = NULL;
	new_node->right = NULL;

	*position = new_node;

	tree->size++;
	return 0;
}

// 先序遍历函数
void pre_order(const BiTreeNode *node)
{
	if (node)
	{
		printf("%c", node->data);
		pre_order(node->left);
		pre_order(node->right);
	}
}

// 中序遍历函数
void in_order(const BiTreeNode *node)
{
	if (node)
	{
		in_order(node->left);
		printf("%c", node->data);
		in_order(node->right);
	}
}

// 后序遍历函数
void end_order(const BiTreeNode *node)
{
	if (node)
	{
		end_order(node->left);
		end_order(node->right);
		printf("%c", node->data);
	}
}

int CreateBiTree()
{
	char data;
	Stack stack;
	BiTree tree;
	BiTreeNode *node;

	stack_init(&stack);
	bitree_init(&tree);

	printf("请以先序顺序输入结点值: \n");

	data = getchar();

	if (0 == bitree_size(&tree))
	{
		if (' ' == data)
			return -1;
		bitree_ins_left(&tree, NULL, data);
		push(&stack, tree.root);
	}
	while (!stack_empty(&stack))
	{
		data = getchar();

		if (' ' == data)
		{
			node = pop(&stack);

			if (NULL == node->left)
			{
				data = getchar();
				if (' ' != data)
				{
					bitree_ins_right(&tree, node, data);
					push(&stack, node->right);
				}
			}
		}
		else
		{
			node = get_stack_top(&stack);
			if (NULL == node->left)
			{
				bitree_ins_left(&tree, node, data);
				push(&stack, node->left);
			}
			else
			{
				node = pop(&stack);
				bitree_ins_right(&tree, node, data);
				push(&stack, node->right);
			}
		}
	}
}

int main()
{
	char data;
	Stack stack;
	BiTree tree;
	BiTreeNode *node;

	stack_init(&stack);
	bitree_init(&tree);

	printf("请以先序顺序输入结点值: \n");

	data = getchar();//获取根节点

	if (0 == bitree_size(&tree))//插入第一个节点
	{
		if ('#' == data)
			return -1;
		bitree_ins_left(&tree, NULL, data);//左子树的创建
		push(&stack, tree.root);//将树压入栈
	}
	while (!stack_empty(&stack))
	{
		data = getchar();

		if ('#' == data)//出栈插入右子树
		{
			node = pop(&stack);//出栈操作

			if (NULL == node->left)
			{
				data = getchar();
				if ('#' != data)
				{
					bitree_ins_right(&tree, node, data);
					push(&stack, node->right);//入栈操作
				}
			}
		}
		else//进行左子树的插入
		{
			node = get_stack_top(&stack);//获取栈顶元素
			if (NULL == node->left)
			{
				bitree_ins_left(&tree, node, data);
				push(&stack, node->left);//入栈操作
			}
			else
			{
				node = pop(&stack);//出栈
				bitree_ins_right(&tree, node, data);
				push(&stack, node->right);//入栈操作
			}
		}
	}
	printf("-----先序遍历二叉树-----\n");
	pre_order(tree.root);
	putchar('\n');

	printf("-----中序遍历二叉树-----\n");
	in_order(tree.root);
	putchar('\n');

	printf("-----后序遍历二叉树-----\n");
	end_order(tree.root);
	putchar('\n');

	return 0;
}

2 通过先序遍历和中序创建二叉树
步骤:
(1) 从先序遍历中找到根节点
(2) 在中序遍历中查找根节点,根节点的左侧为根节点的左子树,根节点的右侧为根节点的右子树。
(3) 建立左子树,传入数据为中序遍历中根节点左侧的数据,如果传入的数据的长度不为0。重复步骤1,2;
(4) 建立右子树,传入数据为中序遍历中根节点右侧的数据,如果传入的数据的长度不为0。重复步骤1,2;
(5) 完成树的创建。
BinaryTreeNode * CreateBinaryTree(ElemType * pre, ElemType *in, int length)//此种建立二叉树的方法本质上是先建立根结点 再建立左子树,再建立右子树
{
	if (length <= 0 || pre == NULL || in == NULL)////递归出口长度为0时表示建树完毕
	{
		return NULL;
	}
	ElemType rootValue = pre[0];
	BinaryTreeNode * root = new BinaryTreeNode();//建立根节点
	root->data = rootValue;
	root->left = root->right = NULL;
	int leftLength, rightLength, i = 0;//左子树的长度,右子树的长度
	while (i<length&&in[i] != rootValue)
	{
		i++;
	}
	leftLength = i;
	rightLength = length - leftLength - 1;//注意对个一个遍历的序列一定满足rightLength+leftLength+1=length;左子树长度加上右子树长度加一个根结点的长度等于树所有结点的长度。
	if (leftLength>0)//建立左子树
	{
		root->left = CreateBinaryTree(pre + 1, in, leftLength);////此处的关键在于寻找右子树后序序列的起始地址,和右子树的中序序列的起始地址
	}
	if (rightLength>0)//建立右子树
	{
		root->right = CreateBinaryTree(pre + length - rightLength, in + length - rightLength, rightLength);////此处的关键在于寻找右子树后序序列的起始地址,和右子树的中序序列的起始地址
																										   // 上一句等价于root->right =CreateBinaryTree(pre+1+leftLength,in+1+leftLength,rightLength);
	}
	return root;
}

3 通过后序遍历和中序遍历创建树
步骤:
(1) 从后序遍历中找到根节点
(2) 在中序遍历中查找根节点,根节点的左侧为根节点的左子树,根节点的右侧为根节点的右子树。
(3) 建立左子树,传入数据为中序遍历中根节点左侧的数据,如果传入的数据的长度不为0。重复步骤1,2;
(4) 建立右子树,传入数据为中序遍历中根节点右侧的数据,如果传入的数据的长度不为0。重复步骤1,2;
(5) 完成树的创建。
BinaryTreeNode *CreateBinaryTree2(ElemType *post, ElemType * in, int length)//此种建立二叉树的方法本质上是先建立根节点再建立左子树,再建立右子树。
{
	if (post == NULL || in == NULL || length <= 0)//递归出口长度为0时表示建树完毕
	{
		return NULL;
	}
	ElemType rootValue = post[length - 1];
	BinaryTreeNode *root = new BinaryTreeNode();//建立根节点
	root->data = rootValue;
	root->left = root->right = NULL;
	int leftLength, rightLength;//左子树的长度,右子树的长度
	int i = 0;
	while (i<length&&in[i] != rootValue)//遍历中序序列寻找根结点从而得到左子树的长度,和右子树的长度
	{
		i++;
	}
	leftLength = i;
	rightLength = length - leftLength - 1;//右子树的长度为总长度减去左子树长度和一个根结点长度
	if (leftLength>0)//建立左子树
	{
		root->left = CreateBinaryTree2(post, in, leftLength);//此处的关键在于寻找左子树后序序列的起始地址,和中序序列的起始地址
	}
	if (rightLength>0)//建立右子树
	{
		root->right = CreateBinaryTree2(post + leftLength, in + length - rightLength, rightLength);//此处的关键在于寻找右子树后序序列的起始地址,和右子树的中序序列的起始地址
																								   //等价于root->right=CreateBinaryTree2(post+leftLength,in+leftLength+1,rightLength);
	}
	return root;//最后返回根节点
}

四、二叉树常用操作

    (1)二叉树节点定义:

template<class T>
struct binaryTreeNode
{
	T element;
	binaryTreeNode *leftChild, *rightChild;//左子树,右子树
	binaryTreeNode() { leftChild = rightChild = NULL; }
	binaryTreeNode(const T &Telement):element(Telement), leftChild(NULL), rightChild(NULL){}
	binaryTreeNode(const T &Telement, binaryTreeNode *TheLeftChild, binaryTreeNode *TheRightChild) :element(Telement), leftChild(TheLeftChild), rightChild(TheRightChild) {}
};

(2)确定高度

int DeepthOfTree(BinaryTreeNode* proot)
{
	if (proot == NULL)
	{
		return 0;
	}
	int leftLength = DeepthOfTree(proot->left);
	int rightLength = DeepthOfTree(proot->right);
	return (leftLength>rightLength) ? leftLength + 1 : rightLength + 1;
}

(3)确定元素个数

//求所有结点的个数
int countAll(BinaryTreeNode *root)
{
	if (root == NULL)
		return 0;
	return countAll(root->left) + countAll(root->right) + 1;
}

(4)复制二叉树

//复制二叉树
BinaryTreeNode *copyBTree(BinaryTreeNode *root)
{
	BinaryTreeNode* p, *lchild, *rchild;
	if (root == NULL)
		return NULL;
	lchild = copyBTree(root->left);
	rchild = copyBTree(root->right);
	p = (BinaryTreeNode *)malloc(sizeof(BinaryTreeNode));
	p->data = root->data;
	p->left = lchild;
	p->left = rchild;
	return p;
}

(5)显示打印二叉树(二叉树的遍历,下面)

(6)确定两棵树是否一样

bool Is_equal(BinaryTreeNode* T1, BinaryTreeNode* T2)
{
	bool t = 0;
	if (NULL == T1 && NULL == T2)
	{
		t = 1;
	}
	else
	{
		if (NULL != T1 &&NULL != T2)
		{
			if (T1->data == T2->data)
			{
				if (Is_equal(T1->left, T2->right))
				{
					t = Is_equal(T1->left, T2->right);
				}
			}
		}
	}
	return t;
}


(7)删除整颗树

void clearTree(BinaryTreeNode** root)
{
	BinaryTreeNode*  pl, *pr;
	if (*root == NULL)
		return;
	pl = (*root)->left;
	pr = (*root)->right;
	(*root)->left = NULL;
	(*root)->right = NULL;
	free((*root));
	*root = NULL;
	clearTree(&pl);
	clearTree(&pr);
}


四、二叉树的遍历

1 先序遍历

(1)先序遍历的递归遍历

//先序遍历
template<class T>
void preOrder(binaryTreeNode<T>*t)
{
	//前序遍历二叉树
	if (t != NULL)
	{
		visit(t);//访问树根
		preOrder(t->leftChild);
		preOrder(t->rightChild);
	}
}

(2)先序遍历的非递归遍历

以下为二叉树的先序的非递归遍历算法
对于任一结点pcur:
1)访问结点Pcur,并将结点pcur入栈,结点pcur一直左走
2)判断结点pcur的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当  前的结点pcur,循环至 1);若不为空,则将P的左孩子置为当前的结点pcur;
3)直到P为NULL并且栈为空,则遍历结束。

void PreOrderBinaryTree(BinaryTreeNode* proot)
{
	if (proot == NULL)//此句其实是多余的。
	{
		return;
	}
	stack<BinaryTreeNode*> stackTreeNode;
	BinaryTreeNode *pcur = proot;//指向当前要访问的结点
	while (pcur != NULL || !stackTreeNode.empty())
	{
		while (pcur != NULL)//一直往左走
		{
			printf("%d ", pcur->data);
			stackTreeNode.push(pcur);
			pcur = pcur->left;
		}
		if (!stackTreeNode.empty())//在出栈和访问栈顶元素之前要先检查是否为空
		{
			pcur = stackTreeNode.top();//pcur刚跳出上面的while循环时值为NULL 故要将其pucr当前的栈顶结点
			stackTreeNode.pop();
			pcur = pcur->right;
		}
	}

}
2中序遍历

(1)中序遍历递归遍历

//中序遍历
template<class T>
void inOrder(binaryTreeNode<T> *t)
{
	inOrder(t->leftChild);
	visit(t);
	inOrder(t->rightChild);
}

(2)中序遍历的非递归遍历

// 以下为二叉树的中序的非递归遍历算法
void InOrderBinaryTree(BinaryTreeNode* proot)
{
	if (proot == NULL)//此句其实是多余的。
	{
		return;
	}
	stack<BinaryTreeNode*> stackTreeNode;
	BinaryTreeNode *pcur = proot;//指向当前要访问的结点
	while (pcur != NULL || !stackTreeNode.empty())
	{
		while (pcur != NULL)//一直向左走
		{
			stackTreeNode.push(pcur);
			pcur = pcur->left;
		}
		if (!stackTreeNode.empty())//在出栈和访问栈顶元素之前要先检查是否为空
		{
			pcur = stackTreeNode.top();//pcur刚跳出上面的while循环时值为NULL 故要将其pucr当前的栈顶结点
			printf("%d ", pcur->data);
			stackTreeNode.pop();
			pcur = pcur->right;
		}
	}

}

3 后序遍历

(1)后序遍历递归遍历

//后序遍历
template<class T>
void postOrder(binaryTreeNode<T> *t)
{
	postOrder(t->leftChild);
	postOrder(t->rightChild);
	visit(t);
}

(2)后续遍历的非递归遍历

以下为二叉树的后序的非递归遍历算法 
思路:设立两个指针pcur ,和preVisted,,对于任一结点pcur,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,此时要判断此节点是否满是输出条件即:当前节点的右孩子如果为空或者已经被访问,则访问当前节点  否则则将pcur置为其右结点

void PostOrderBinaryTree(BinaryTreeNode* proot)
{
	if (proot == NULL)//此句其实是多余的。
	{
		return;
	}
	stack<BinaryTreeNode*> stackTreeNode;
	BinaryTreeNode *pcur = proot;//指向当前要访问的结点
	BinaryTreeNode *preVisted = NULL;//指向前一结点被访问的结点
	while (pcur != NULL || !stackTreeNode.empty())
	{
		while (pcur != NULL)//一直向左走
		{
			stackTreeNode.push(pcur);
			pcur = pcur->left;
		}
		if (!stackTreeNode.empty())//在出栈和访问栈顶元素之前要先检查是否为空
		{
			pcur = stackTreeNode.top();//pcur刚跳出上面的while循环时值为NULL 故要将其pucr当前的栈顶结点
			if (pcur->right == NULL || pcur->right == preVisted)//当前节点的右孩子如果为空或者已经被访问,则访问当前节点
			{
				printf("%d\t", pcur->data);
				stackTreeNode.pop();
				preVisted = pcur;// 
				pcur = NULL;//此句不要忘记
			}
			else//如果当前结点的右子结点存在且没被访问过
			{
				pcur = pcur->right;
			}
		}
	}

}

4 层次遍历

//二叉树的层序遍历
void LevelOrderBinaryTree(BinaryTreeNode*proot)
{
	if (proot == NULL)
	{
		return;
	}
	queue<BinaryTreeNode*>queueTreeNode;
	queueTreeNode.push(proot);
	while (!queueTreeNode.empty())
	{
		BinaryTreeNode *pNode = queueTreeNode.front();
		queueTreeNode.pop();
		printf("%d ", pNode->data);
		if (pNode->left != NULL)
		{
			queueTreeNode.push(pNode->left);
		}
		if (pNode->right != NULL)
		{
			queueTreeNode.push(pNode->right);
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值