C数据结构-二叉树

二叉树的概念:

一颗二叉树是节点的有限集合。二叉树由左子树和右子树组成,每一个非空的子树都可以称作一个独立的二叉树。

二叉树的特点:

  • 每个节点最多有两颗子树,即树的度最大为2;
  • 子树右左右之分,次序不能颠倒。

二叉树的分类:

满二叉树:不存在度为1的节点。只可能度为0和2

完全二叉树:具有N个节点的结构与满二叉树的前n个节点的结构相同。称为完全二叉树。


二叉树的性质:

(1)若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有2^(i - 1)个节点。

(2)若规定只有根节点的二叉树深度为1,则深度为K的二叉树的最大节点数是2^K - 1。

(3)对任何一个二叉树,如果其叶节点个数为n0,度为2的非叶节点个数为n2,则有n0 = n2 + 1,n总 = n0 + n1 + n2;

(4)具有n个节点的完全二叉树的深度k为log2(n + 1)上取整。

(5)对于具有n个节点的完全二叉树,入股按照从上到下,从左到右的顺序对所有节点从0开始编号,则对于序号为i的节点有,

  • 若i > 0,双亲序号:(i - 1)/ 2 ;i = 0, 为根节点,无双亲节点
  • 若2i + 1 < n,左孩子编号为2i + 1,否则无左孩子。
  • 若2i + 1 < n,右孩子编号为2i + 2,否则无右孩子。
二叉树的存储结构:
顺序存储:
缺点:遇到单支树和一般二叉树会浪费大量存储空间。

链式存储:

优点:能很好的利用存储空间。适用于任何二叉树的存储


常见二叉树节点的定义:通常使用左右孩子表示法来描述二叉树

typedef BTNode{
    struct BTNode* LChild;
    struct BTNode* RChild;
    DataType data;
};
双亲表示法:
typedef BTNode{
    struct BTNode* Parent;
    struct BTNode* LChild;
    struct BTNode* RChild;
    DataType data;
};
二叉树的基本操作:

(1)创建二叉树

(2)先序遍历二叉树

(3)中序遍历二叉树

(4)后序遍历二叉树

(5)拷贝一颗二叉树

(6)层序遍历

(7)二叉树的销毁

(8)二叉树的镜像非递归

(9)求二叉树中节点的个数

(10)求二叉树中叶子节点的个数

(11)求二叉树中第K层节点的个数

(12)求二叉树的高度

(13)检测一颗二叉树是否为完全二叉树

(14)根据数据获取二叉树中的节点

(15)检测一个节点是否在二叉树中

(16)前序遍历的非递归实现

(17)中序遍历的非递归实现

(18)后续遍历的非递归实现

(19)二叉树的镜像递归实现

具体实现:

(1)创建二叉树(必须遵循先序遍历规则)

思路:

  • 先将数据按先序遍历的顺序存储在一个数组中。
  • 创建根节点,将数据赋值给根节点。
  • 创建左子树
  • 创建右子树
  • 遇到无用的操作符,该节点不被创建,并且函数返回。

代码如下:

void BTNodeCreate(BTDataType* array, PBTNode* pRoot, int length, int *index, BTDataType invalid) {
	if (*index < length && array[*index] != invalid) {
		*pRoot = (PBTNode)malloc(sizeof(BTNode));

		if (*pRoot == NULL)
			return;
		(*pRoot)->LChild = NULL;
		(*pRoot)->RChild = NULL;
		(*pRoot)->data = array[*index];

		++(*index);
		BTNodeCreate(array, &(*pRoot)->LChild, length, index, invalid);
		++(*index);
		BTNodeCreate(array, &(*pRoot)->RChild, length, index, invalid);
	}
}


(2)先序遍历二叉树

思路:先遍历根节点,再遍历左子树,再遍历右子树

void PreOrder(PBTNode pRoot) {
	if (pRoot) {
		printf("%c ", pRoot->data);
		PreOrder(pRoot->LChild);
		PreOrder(pRoot->RChild);
	}
}


(3)中序遍历二叉树

void InOrder(PBTNode pRoot) {
	if (pRoot) {
		InOrder(pRoot->LChild);
		printf("%c ", pRoot->data);
		InOrder(pRoot->RChild);
	}
}


(4)后序遍历二叉树

void PostOrder(PBTNode pRoot) {
	if (pRoot) {
		PostOrder(pRoot->LChild);
		PostOrder(pRoot->RChild);
		printf("%c ", pRoot->data);
	}
}


(5)拷贝一颗二叉树(先序遍历规则)

思路:先拷贝根节点,再拷贝左子树,再拷贝右子树。

void CopyBinTree(PBTNode pRoot, PBTNode* newRoot) {
	if (pRoot) {
		*newRoot = (PBTNode)malloc(sizeof(BTNode));
		if (*newRoot == NULL)
			return;
		(*newRoot)->LChild = NULL;
		(*newRoot)->RChild = NULL;

		(*newRoot)->data = pRoot->data;

		CopyBinTree(pRoot->LChild, &(*newRoot)->LChild);
		CopyBinTree(pRoot->RChild, &(*newRoot)->RChild);
	}
}


(6)层序遍历

思路:先遍历根节点,每遇到一个节点都先依次判断他的左右孩子是否为空,然后将其左孩子和右孩子加入到队列中。然后将队头节点出队列

// 层序遍历二叉树
void LevelOrder(PBTNode pRoot) {
	Queue* q = (Queue*)malloc(sizeof(Queue));
	QueueInit(q);
	QueuePush(q, pRoot);
	PBTNode tmp = NULL;
	while (!QueueEmpty(q)) {
		PBTNode cur = QueueFront(q);
		printf("%c ", cur->data);
		if (cur->LChild != NULL)
			QueuePush(q, cur->LChild);
		if (cur->RChild != NULL)
			QueuePush(q, cur->RChild);
		QueuePop(q, &tmp);
	}
}


(7)二叉树的销毁(必须遵循后续遍历规则,否则会出现空指针异常)

思路:要销毁一颗二叉树,应该先删除他的左右子树,再删除根节点。

void DetroyBinTree(PBTNode* pRoot) {
	if (*pRoot) {

		DetroyBinTree(&(*pRoot)->LChild);
		DetroyBinTree(&(*pRoot)->RChild);
		free(*pRoot);
		*pRoot = NULL;
	}
}


(8)二叉树的镜像非递归

思路:和层序遍历一样,当前节点入队列,左孩子入队列,右孩子入队列,交换队头节点的左右孩子

// 二叉树的镜像---非递归 
void MirrorBinTreeNor(PBTNode pRoot) {
	Queue* q = (Queue*)malloc(sizeof(Queue));
	QueueInit(q);
	QueuePush(q, pRoot);
	PBTNode tmp = NULL;
	while (!QueueEmpty(q)) {
		PBTNode cur = QueueFront(q);

		if (cur->LChild != NULL)
			QueuePush(q, cur->LChild);
		if (cur->RChild != NULL)
			QueuePush(q, cur->RChild);
		swapBTNodeNode(&(cur->LChild), &(cur->RChild));
		QueuePop(q, &tmp);
	}
}


(9)求二叉树中节点的个数

思路:总节点个数等于 ==  左孩子个数 + 右孩子个数 + 根节点个数(1),递归结束条件,当前节点为空,返回0(优化:当左右孩子为空时,直接返回,可以大幅减少递归深度);

// 求二叉树中结点的个数 
int BinTreeSize(PBTNode pRoot) {
	if (pRoot == NULL)
		return 0;
	if (pRoot->LChild == NULL && pRoot->RChild == NULL)
		return 1;
	return BinTreeSize(pRoot->LChild) + BinTreeSize(pRoot->RChild) + 1;
}

(10)求二叉树中叶子节点的个数

思路:叶子节点个数 == 叶子节点中左孩子的个数 + 叶子节点中右孩子的个数

// 求二叉树中叶子结点的个数 
int BinTreeLeaf(PBTNode pRoot) {
	if (pRoot == NULL)
		return 0;
	if (pRoot->LChild == NULL && pRoot->RChild == NULL)
		return 1;
	return BinTreeLeaf(pRoot->LChild) + BinTreeLeaf(pRoot->RChild);
}


(11)求二叉树中第K层节点的个数

思路:和求叶子节点个数类似,第K层中左孩子个数 + 第K层中右孩子个数;只是将结束条件变成了K == 1;(K最小为1时,节点为根节点,个数为1)

// 求二叉树中第K层结点的个数 
int BinTreeKLevelNode(PBTNode pRoot, int K) {
	if (K <= 0)
		return 0;
	if (K == 1 && pRoot != NULL)
		return 1;
	if (pRoot == NULL)
		return 0;
	return BinTreeKLevelNode(pRoot->LChild, K - 1) + BinTreeKLevelNode(pRoot->RChild, K - 1);
}

(12)求二叉树的高度

思路:判断当前节点是否为空,为空返回0,不为空返回1,将返回值赋值给high,比较左右子树的较大值,取大的。

// 求二叉树的高度 
int BinTreeHeight(PBTNode pRoot) {
	int leftHigh, rightHigh;
	if (pRoot == NULL)
		return 0;
	if (pRoot->LChild == NULL && pRoot->RChild == NULL)
		return 1;
	leftHigh = BinTreeHeight(pRoot->LChild);
	rightHigh = BinTreeHeight(pRoot->RChild);
	return leftHigh > rightHigh ? leftHigh + 1 : rightHigh + 1;
}


(13)检测一颗二叉树是否为完全二叉树

思路:完全二叉树的某个节点之前一定是一颗满二叉树。所有的节点度都为2。

找到这个节点。

这个节点的可能情况为:只有左孩子,没有左孩子也没有右孩子。

利用层序遍历,从上到下从左到右,就可以找到这个点。如果在这个点之后出现了任何一个节点有孩子,那这个二叉树就不是完全二叉树。

// 检测一棵树是否为完全二叉树 
int IsCompleteBinTree(PBTNode pRoot) {
	if (pRoot == NULL)
		return 0;
	// 标识是否找到了这个特殊的节点。
	int flag = 0;
	PBTNode cur;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, pRoot);
	while (!QueueEmpty(&q)) {
		QueuePop(&q, &cur);
		if (flag == 1 && (cur->LChild != NULL || cur->RChild != NULL))
			return 0;
		if (cur->LChild != NULL && cur->RChild != NULL && flag == 0) {
			QueuePush(&q, cur->LChild);
			QueuePush(&q, cur->RChild);
		}
		if (cur->LChild == NULL && cur->RChild != NULL)
			return 0;
		if ((cur->RChild == NULL && cur->LChild == NULL) ||
			(cur->RChild == NULL && cur->LChild != NULL))
		{
			// 找到分界点。
			flag = 1;
		}	
	}
	return 1;
}


(14)根据数据获取二叉树中的节点

思路:先找左孩子,没找到(node == NULL)才找右孩子。

// 在二叉树中查找值为data的结点,找到返回该结点,否则返回空 
PBTNode Find(PBTNode pRoot, BTDataType data) {
	PBTNode node;
	if (pRoot == NULL)
		return NULL;
	if (pRoot->data == data)
		return pRoot;
	node = Find(pRoot->LChild, data);
	if (node == NULL) {
		// 没找到就找右子树
		Find(pRoot->RChild, data);
	}
}


(15)检测一个节点是否在二叉树中

思路:和获取二叉树中的节点相同,但是要判断pNode是否为空。

// 检测一个节点是否在二叉树中 
int IsNodeInBinTree(PBTNode pRoot, PBTNode pNode) {
	PBTNode node;
	if (pNode == NULL)
		return 0;
	if (pRoot == NULL)
		return 0;
	if (pRoot == pNode)
		return 1;
	node = IsNodeInBinTree(pRoot->LChild, pNode);
	if (node == NULL) {
		// 没找到就找右子树
		IsNodeInBinTree(pRoot->RChild, pNode);
	}
}


(16)前序遍历的非递归实现

思路:先将当前节点的右孩子入栈,然后一直往左遍历(左孩子不入栈),如果遇到的节点为空,就出栈。并将当前节点设置为栈中的节点。

// 循环先序遍历二叉树
void PreOrderNor(PBTNode pRoot) {
	if (pRoot == NULL)
		return;
	PBTNode cur = pRoot;
	Stack s;
	StackInit(&s);
	while (1) {
		printf("%c ", cur->data);
		if (cur->RChild != NULL)
			StackPush(&s, cur->RChild);
		cur = cur->LChild;
		if (cur == NULL) {
			int ret = StackPop(&s, &cur);
			if (ret == 0)
				return;
		}
	}
}


(17)中序遍历的非递归实现

思路:

(1)一直找到当前树的最左节点,并将遇到的节点入栈。

(2)出栈打印左孩子,判断当前节点有无右孩子,无继续出栈,若有,则继续第一步

//循环中序遍历二叉树
void InOrderNor(PBTNode pRoot) {
	if (pRoot == NULL)
		return;
	PBTNode cur = pRoot;
	PBTNode tmp = NULL;
	Stack s;
	StackInit(&s);
	while (1) {

		while (cur != NULL) {
			StackPush(&s, cur);
			cur = cur->LChild;
		}

		// ret表示pop的状态,0表示空栈
		int ret = StackPop(&s, &tmp);
		if (!ret)
			return;

		printf("%c ", tmp->data);
		if (tmp->RChild)
			cur = tmp->RChild;

	}
	
}


(18)后续遍历的非递归实现

思路:

(1)将当前节点入栈,将当前节点的右孩子入栈,直到找到当前树的最左节点。

(2)看最左节点有无右孩子,有跳到(1),无则打印这个最左节点。

(3)出栈,将出栈的这个节点赋值给当前节点,判断有无左右孩子,有跳到(1)

//循环后续遍历二叉树
void PostOrderNor(PBTNode pRoot) {
	if (pRoot == NULL)
		return;
	Stack s;
	StackInit(&s);

	PBTNode cur = pRoot;

	// 当前节点
	PBTNode tmp = NULL;

	// 用来记录上一次出栈的节点,以便判断这个节点是否应该继续向下遍历
	PBTNode preTmp = NULL;

	//放入根节点
	StackPush(&s, cur);

	while (1) {

		// 一直向左,入栈顺序为,cur cur->RChild  cur->LChild
		while (cur != NULL)
		{
			if (cur->RChild)
				StackPush(&s, cur->RChild);
			if (cur->LChild)
				StackPush(&s, cur->LChild);
			cur = cur->LChild;
		}
		if (cur == NULL) {

			tmp = StackTop(&s);

			//栈为空退出
			if (tmp == NULL)
				return;

			//判断当前点的子节点是否是上一次遍历的。如果是则代表这颗树的子节点已经遍历完了,就直接输出当前节点
			if (tmp->LChild == preTmp || tmp->RChild == preTmp) {
				printf("%c ", tmp->data);
				StackPop(&s, &tmp);
				preTmp = tmp;
				continue;
			}

			// 不直接遍历则看他的左右孩子是否为空,不为空就入栈
			if (tmp->LChild || tmp->RChild) {
				cur = tmp;
				preTmp = tmp;
				continue;
			}

			//叶子节点,直接打印。
			StackPop(&s, &tmp);
			preTmp = tmp;
			printf("%c ", tmp->data);

		}		
	}
}


(19)二叉树的镜像递归实现

思路:遇到一个非空的节点就交换他们的左右孩子。

// 二叉树的镜像---递归 
void MirrorBinTree(PBTNode pRoot) {
	if (pRoot) {
		swapBTNodeNode(&(pRoot->LChild), &(pRoot->RChild));
		MirrorBinTree(pRoot->LChild);
		MirrorBinTree(pRoot->RChild);
	}
}

以上程序的运行结果:


完整代码在:github.com/Niceug/Algorithm/




猜你喜欢

转载自blog.csdn.net/bugggget/article/details/80149973