二叉树非递归遍历的经典求解

#include 
#include 
typedef int datatype;
typedef struct node {
	datatype data;
	struct node *left, *right;
}BitTree;

//*************************Begin*************
//添加辅助栈的结构:主要是用来保存树的节点
//简言之,这个就是用来存树节点的栈
typedef BitTree BitTreeNode;
typedef int BOOL;
#define TRUE 1
#define FALSE 0
//-——————————修订V0.01——changed by dingst
//BitTreeNode data;那么一定是侵入式的,为什么=》逐个深拷贝每一个分量
//只要是BitTreeNode* Addr;在32位中,这个值永远是4字节
//保存的都是传入的源节点的地址值的一个副本(c/C++语言,都是指拷贝形式)
typedef struct linknode {
	BitTreeNode* _NodeAddrCopy;
	struct linknode* pNext;
}LinkStack;

LinkStack* InitLkStack(void) {
	LinkStack* p = NULL;
	p = (LinkStack*)malloc(sizeof(LinkStack));
	if (p != NULL)
		p->pNext = NULL;
	return p;
}//建立一个带头节点的链表作为链栈

BOOL IsEmptyLkMazeStack(LinkStack* top) {
	return (top->pNext == NULL);
}

LinkStack* PushLkStack(LinkStack* top, BitTreeNode* elem) {
	LinkStack* p = NULL;
	//深拷贝的bug修订,如果传入的是空指针,那么不能访问,修订人丁宋涛
	//elem就是将当前树节点的地址值考一份:比如0x00d500578,我们现在
	//栈就是把这个0x00d500578拷贝一份下来,放到我们_NodeAddrCopy
	//及时栈节点出栈,此时也是我么你的_NodeAddrCopy这个变量被释放
	//与原先的树节点没有关系,不会影响
	//elem是这个函数的形参,它将调用者的实参拷贝一份放入到elem中
	//所以elem就是这个0x00d500578
	//我们用p->_NodeAddrCopy = elem;的操作
	//就把0x00d500578保存下来了,此时他就是原节点地址的一个副本
	if (elem != NULL)
	{
		p = (LinkStack*)malloc(sizeof(LinkStack));
		//todo:我们用新建节点,对传入的二叉树节点做一个深拷贝
		//changed by dingst,我们要做非侵入式的设计
		/*p->data.data = elem->data;
		p->data.left = elem->left;
		p->data.right = elem->right;*/
		p->_NodeAddrCopy = elem;
		p->pNext = top; //p节点尾插入lkStack
		top = p;//top栈顶指针上移
	}



	//我们思考,既然我们允许空指针进入到我们的栈结构,那么我们如何修订
	//我们的出栈?
	//top为空以为着?
	//意味着是否和栈空条件冲突了?
	//如果我们让我们的NULL进入栈空间,那么出栈也会造成空指针
	//这意味着当前的节点不动

	return top;
}

//出栈:pData是传入的一个“临时”空间用来接收出栈的节点信息。
LinkStack* PopLkStack(LinkStack* top, BitTreeNode *pData) {
	LinkStack* p;
	if (top != NULL) {
		//*pData = top->data;
		//----changed by dingst
		/*pData->data = top->data.data;
		pData->left = top->data.left;
		pData->right = top->data.right;*/
		pData = top->_NodeAddrCopy;
		//pData实际上深拷贝了一份节点信息,所以释放栈中节点,不会影响
		//节点数据
		p = top;
		top = p->pNext;
		free(p);
	}
	return top;
}
//*************************End***************
//辅助队列Q,这是用来存关系的
BitTree* Q[16];//这是一个指针数组,它将缓存节点的地址,因为这个地址将以
			   //left域,或者right域进入二叉链表,它本身不维护i,2i,2i+1的关系
			   //他的关系通过front,rear来维护

			   //按照直观的建立,我们首先想到的是层次法
			   //我们根据层次,来逐一将二叉树建立
			   //输入的数组是按照完全二叉树的编号规则设立,即
			   //数组角标反映了节点位置关系(存联系)
			   //逐个扫描数组,直到所有的节点都存取完毕之后,就结束
			   //约定,0表示数组当前元素为空,最后一个节点标志是-999
BitTree* CreateBinTree(int arr[]) {
	int i = 1;
	//只要我们没有扫描完元素,那么这个二叉链表就没有完成
	//只要扫描,我们就malloc一个节点,然后把这个节点存入left域或者right域
	int front = 1, rear = 0;
	BitTree* root = NULL;
	BitTree* s;//暂存节点
	while (arr[i] != -999) {
		s = NULL;
		if (arr[i] != 0) {//意味着这个不是空节点,那么我们就要分配空间
			s = (BitTree*)malloc(sizeof(BitTree));
			s->data = arr[i];//存数值
			s->left = NULL;
			s->right = NULL;

		}
		//要让我们新节点入队,进入缓存,等待分配双亲的left域和right域
		Q[++rear] = s;

		if (rear == 1)
		{
			root = s;
		}
		else {
			if (s != NULL && Q[front]) {
				if (rear % 2 == 0) {
					Q[front]->left = s;
				}
				else {
					Q[front]->right = s;
				}
			}
			if (rear % 2 == 1)
				front++;
		}

		i++;
	}
	return root;
}
//树遍历问题:非线性结构的输出问题:前序,中序,后序
void preorder(BitTree* t) {
	if (t) {
		//根左右
		printf("%d ", t->data);
		preorder(t->left);
		preorder(t->right);
	}
}
void NonRecrvPreOrder(BitTree* root) {
	LinkStack* pTop = NULL;
	//BitTreeNode TempNode;//用来接栈中对应的二叉树节点的
	//-----change by dingst
	BitTreeNode* pNode; //我要让pNode推进树遍历,而且pNode就是
	//真实地址的副本
	pTop = InitLkStack();
	
	//pTop = PushLkStack(pTop, root);
	//---change by dingst
	pNode = root;
	pTop = PushLkStack(pTop, pNode);
	while (!IsEmptyLkMazeStack(pTop)) {
		pNode = pTop->_NodeAddrCopy;//178行的这个赋值操作,就将真实的节点地址赋给了pNode这个函数的局部变量
		pTop = PopLkStack(pTop, pNode);
		if (pNode != NULL) {
			printf("%d ",pNode->data);
			//对右子树压栈
			pTop = PushLkStack(pTop, pNode->right);
			pTop = PushLkStack(pTop, pNode->left);
		}
	}

}
void inorder(BitTree *t) {
	if (t) {
		inorder(t->left);
		printf("%d ", t->data);
		inorder(t->right);
	}
}

/*
中序就是左根右,这就意味着,当我们从根节点出发,应当首先走向左孩子
,而根的左孩子,是根的左子树的根节点,因此
又必须,继续访问其左孩子,直到左孩子为空
当这个节点访问之后,按照相同的规则,访问其右子树
我们借助的栈,就应该按照左根右来压栈
对于任意节点p
1 如果其左孩子不为空,则将p入栈,然后将p的左孩子置为当前的p,然后
再对p进行相同的处理
2 若左孩子为空,则取栈顶元素进行出栈操作,访问当前栈顶节点,然后
将当前的p置为栈顶节点的右孩子
3 直到p为NULL为止
*/
void NonRecvInorder(BitTree *root) {
	LinkStack* pTop = NULL;
	BitTreeNode TempNode;//用来接收栈中的对应的二叉树的节点

	pTop = InitLkStack();
	BitTree *p = root;
	while (p != NULL || !IsEmptyLkMazeStack(pTop)) {
		while (p != NULL) {
			pTop = PushLkStack(pTop, p);
			p = p->left;
		}
		if (!IsEmptyLkMazeStack(pTop)) {
			//因为此时我们在输出的节点信息,就一定要从栈中
			//取出元素
			//这个是取出的元素的_NodeAddrCopy分量就是树中节点的地址
			//因此对这个节点使用->就可以获得真实的数据
			printf("%d ", pTop->_NodeAddrCopy->data);
			p = pTop->_NodeAddrCopy; //因为我们设计的p就是要走完root全部节点,因此
									//p必须获得的是树节点的真实地址
			pTop = PopLkStack(pTop, &TempNode);//当我们把左子树的左叶子节点走完以后,
											   //此时栈顶一定是这个左孩子的根,所以根据左根右,这个根要出栈
			//----change by dingst
											   //p = &TempNode; 因为tmpenode是bitnode的对象,
			//所以tmpenode的地址值可以赋值给p这个指针变量,但是这个tempnode的这个地址值
			//是肯定不等于真实树节点的地址值的
			p = p->right;//走向根的右孩子 
		}
	}
}




void postorder(BitTree* t) {
	if (t) {
		//左右根
		postorder(t->left);
		postorder(t->right);
		printf("%d ", t->data);
	}
}

/*
后序:左右根
根节点只有在左孩子和右孩子都访问以后才能访问
因此,对于任何一个节点,都将其入栈,如果p不存在左孩子和右孩子,直接访问
或者p存在左孩子或者右孩子,但是他的左孩子和右孩子都已经被访问过了
同样的,这个节点也可以直接访问.
如果,上述条件都不满足,就要将这个p的右孩子,左孩子依次入栈
这样就能保证,每次取栈顶元素的时候,左孩子都在右孩子之前被访问,
左右孩子访问之后,根才被访问
*/

void NonRecvPostorder(BitTree* root) {
	BitTree* cur;//当前节点==>cur 保存的就是树节点的地址
	BitTree* pre = NULL;//前一次访问的节点

	LinkStack* pTop = NULL;
	BitTreeNode TempNode;//注意我们就是用tempnode来接里面的内容,他是不能参与地址比较的,他的地址一定和我们栈中的地址不一样
	pTop = InitLkStack();
	pTop = PushLkStack(pTop, root);

	while (!IsEmptyLkMazeStack(pTop)) {
		cur = pTop->_NodeAddrCopy;//拿到栈中深拷贝获得的节点
						  //在访问中,由于我们是深拷贝树的节点,因此,pre和cur的地址不一样,我们只能比较器元素内容
						  //小技巧:利用短路,确保程序的可靠性
		if ((cur->left == NULL && cur->right == NULL) ||
			(pre != NULL && (pre == cur->left|| pre == cur->right))
			) {
			printf("%d ", cur->data);
			pTop = PopLkStack(pTop, &TempNode);
			pre = cur;
		}
		else {
			if (cur->right != NULL)
				pTop = PushLkStack(pTop, cur->right);
			if (cur->left != NULL)
				pTop = PushLkStack(pTop, cur->left);
		}
	}
}

//求叶子节点:树的遍历是一种非线性遍历,在业务编程中,从实现角度上
//用递归遍历三个方法之一,判断节点属性,就可以完成暴力搜索树
int LeafCount = 0;
void LeafCount_DLR(BitTree* t) {
	if (t) {
		if(t->left == NULL && t->right == NULL){
			printf("%d ", t->data);
			LeafCount++;
		}
		LeafCount_DLR(t->left);
		LeafCount_DLR(t->right);
	}
}

//因为我们现在能够不重复,不遗漏获取每一个节点,只有前中后序3种方法,所以,我们首先先写出
//先序递归获取树的深度
int hight = 0;//它可以在多个函数之间共享
//前序是根左右,我们把每一个树从root出发的树,可以看出左右两个子树
//无论我们如何遍历,我们前进到每一个节点都是潜在的根节点。
//我们可以认为每到一个新节点,就是到了一个新树,
//如果说,我们对新节点的发现,我们就认为到了一个新层,那么我们累加
//记录就可以了
//我用level作为当前树的高度
//当我用root,0传入的时候,就意味着已经有第一层的计数起始器
//我们用打擂台的思路,用h比对,就可以完成记录
/*
找一组数中的最大值
for(){
	if(max < a[i]) 
		max =a[i]
	}
*/
int TreeDepth_DLR(BitTree *pNode, int level) {
	if (pNode) {
		level++;
		if (level > hight)
			hight = level;//这一就意味伴随着level的不断深入,我们的hight始终获得的是树中最深的层次
		printf("%d ", pNode->data);
		
		//从327开始到333行,我们认为这个函数的终止条件完成了
		//根据我们前面的观点,递归函数本身就是答案
		//因此hight就应该接递归函数的返回值
		hight = TreeDepth_DLR(pNode->left, level);
		hight = TreeDepth_DLR(pNode->right, level);//因为所有节点的遍历都是跟左右,所以和当前树节点传入的同时,它当前做在的层数我们要传入
	}
	return hight;
}



//根据前序、中序重建二叉树
BitTree* reConstructBinTree(int pre[], int startPre, int endPre,
	int in[], int startIn, int endIn) {
	//如果起始的下标大于结束的下标,那么就退出
	if (startPre > endPre || startIn > endIn) {
		return NULL;
	}

	//前序找到根节点
	BitTree* pNode = (BitTree*)malloc(sizeof(BitTree));
	pNode->left = NULL;
	pNode->right = NULL;
	pNode->data = pre[startPre];//前根当中的首元素一定是当前子树的根节点

	for (int i = startIn;i <= endIn;i++) {
		if (in[i] == pre[startPre]) {
			//i-startIn就是左子树节点的个数
			//i -1就是中序遍历左子树的终点
			
			pNode->left = reConstructBinTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1);
			//前序右子树的遍历起始值:startPre+i-startIn+1
			//中序遍历右子树的起始值:i+1,endIn(i理解成你第几次切刀)
			pNode->right = reConstructBinTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn);
		}
	}
	return pNode;
}


//求根到所有叶子节点的路径
BitTree* Paths[10] = { 0 };


void OutPutPath(){
	int i = 0;
	while (Paths[i] != NULL) {
		printf("%d ", Paths[i]->data);
		i++;
	}
	printf("\n");
}

//求路径的函数
void LeavesPath(BitTree* tree, int level) {
	if (tree == NULL)
		return;
	Paths[level] = tree;//这句话就意味着,只要走到这里,这个子树的根就被记录下来
	//求路径,意味着找到叶子节点,就输出
	if ((tree->left == NULL) && (tree->right == NULL)) {
		//当我确定到已经从树根节点root走到了一个叶子节点,那么它以前所有的根节点都存在paths里面
		Paths[level + 1] = NULL;//通过level传递了路线信息
		OutPutPath();
		return;
	}
	LeavesPath(tree->left, level + 1);
	LeavesPath(tree->right, level + 1);
}


//判断两个二叉树是否相等
//提问如果两个二叉树的前序遍历相等,能否说明,这两个树相等?
//答案是否定的,只有这两个树的 A前序=B前序,A中序=B中序才可以。
BOOL isEqualTree(BitTree* t1, BitTree* t2) {
	if (t1 == NULL && t2 == NULL)
		return TRUE;
	if (!t1 || !t2)//t1,t2一个空一个不空,肯定不相同
		return FALSE;
	if (t1->data == t2->data)
		return isEqualTree(t1->left, t2->left) && isEqualTree(t1->right, t2->right);
	else
		return FALSE;
}

//使用二叉链表建立二叉搜索树:注意,我们目前这个树不支持相同元素
BitTree* insertIntoBinSearchTree(BitTree* root, int node) {
	BitTree* newNode;
	BitTree* currentNode;
	BitTree* parentNode;

	newNode = (BitTree*)malloc(sizeof(BitTree));
	newNode->data = node;
	newNode->left = NULL;
	newNode->right = NULL;

	if (root == NULL)
		return newNode;
	else {
		currentNode = root;//然后我们对当前节点进行定位
		//注意,传入root是根节点,我们要根据根节点,按照左小右大的逻辑找到节点位置
		//首先通过while找到新节点的位置
		while (currentNode != NULL) {
			parentNode = currentNode;
			if (currentNode->data > node) {
				currentNode = currentNode->left;
			}
			else {
				currentNode = currentNode->right;
			}
		}
		//其次,我们将父节点和子节点做连接
		if (parentNode->data > node) {
			parentNode->left = newNode;
		}
		else {
			parentNode->right = newNode;
		}


	}
	return root;
}


BitTree* create_LkBinTree(int data[], int length) {
	BitTree* root=NULL;
	int i;
	for (i = 0;i < length;i++) {
		root = insertIntoBinSearchTree(root, data[i]);
	}
	return root;
}


//二叉树镜像:交换两个子树
void SwapTree(BitTree* tree) {
	BitTree* pTemp;
	pTemp = tree->left;
	tree->left = tree->right;
	tree->right = pTemp;
}

void ExchangeSubTree(BitTree* tree) {
	if (tree == NULL)
		return;
	else {
		SwapTree(tree);
		ExchangeSubTree(tree->left);
		ExchangeSubTree(tree->right);
	}
}

int main(void) {

	int arr[17] = { 0,6,3,8,
		2,5,7,9,
		0,0,4,0,
		0,0,0,10,
		-999
	};
	BitTree *root = CreateBinTree(arr);
	/*printf("递归形式的先跟遍历\n");
	preorder(root);
	printf("\n非递归形式的先跟遍历\n");
	NonRecrvPreOrder(root);*/
	//NonRecvInorder(root);
	/*printf("递归形式的后根遍历\n");
	postorder(root);
	printf("\n非递归形式的后根遍历\n");
	NonRecvPostorder(root);*/

	//******************BEGIN:树的节点与深度****************
	/*LeafCount_DLR(root);
	printf("\n当前书总共有%d个叶子节点", LeafCount);*/
	
	//printf("\n当前树有%d层", TreeDepth_DLR(root, 0));
	给定一个二叉树,我要分别得到它的左子树的深度和右子树的深度
	我们用复用的思想,很容易就得到了内容
	注意,虽然复用的思想是好的,当时要注意全局变量的副作用
	要对全局变量清零
	//hight = 0;
	//printf("\n当前树的左子树有%d层\n", TreeDepth_DLR(root->left, 0));
	//hight = 0;
	//printf("\n当前树的右子树有%d层\n", TreeDepth_DLR(root->right, 0));
	当我们在使用递归遍历中,利用全局变量进行传递的时候
	函数的复用应当注意全局变量的置0
	//******************END 树的节点与深度****************

	//************Beign 二叉树重建
	//前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
	/*int pre[] = { 1,2,4,7,3,5,6,8 };
	int in[] = { 4,7,2,1,5,3,8,6 };
	BitTree* rootNew;
	rootNew = reConstructBinTree(pre, 0, 7, in, 0, 7);
	preorder(rootNew);
	printf("\n");
	inorder(rootNew);*/

	//*************End 二叉树重建

	//***********Begin 获取根节点到叶子节点
	//LeavesPath(root, 0);//0表示层级的传递,只要根据level延伸就能找到每一棵子树的根节点
	//************END获取根节点到叶子节点
	
	//***************Begin  判断两颗二叉树是否一致
	//如果,输入的数字序列前后不一致,那么使用二叉搜索树建立的也不是同一棵树
	/*int arrary[] = { 1,2,3 };
	BitTree* tree1 = create_LkBinTree(arrary, 3);
	int brr[] = { 1,2,3 };
	BitTree* tree2 = create_LkBinTree(brr, 3);

	printf("\n");
	inorder(tree1);
	printf("\n");
	inorder(tree2);
	printf("\n%d", isEqualTree(tree1, tree2));
	*/
	//***************End  判断两颗二叉树是否一致

	//*************Begin 二叉树镜像:交换一个二叉树的左右子树
	int arrary[] = { 5,4,7,2,3,6,8 };
	BitTree* tree1 = create_LkBinTree(arrary, 7);
	printf("\n");
	inorder(tree1);
	ExchangeSubTree(tree1);
	printf("\n");
	inorder(tree1);

	//*************End 二叉树镜像:交换一个二叉树的左右子树
	getchar();
	return 0;
}

在这里插入图片描述

你可能感兴趣的:(算法,链表,数据结构,二叉树)