二叉树——王道真题P149-P150

算法笔记——二叉树

核心:四大非递归&递归遍历算法

非递归不要习惯性地用递归子树思想

非递归一定是一步步的执行逻辑,每一步仅能看到当前。宏观上则需要拿到之前的结点

数据结构定义

typedef char ElemType;
//二叉树定义
typedef struct BitNode
{
	ElemType data;
	struct BitNode* lchild, * rchild;
}BitNode,*BitTree;
//线索二叉树定义
typedef struct BithrNode
{
	ElemType data;
	struct BithrNode* lchild, * rchild;
	int ltag = 1;
	int rtag = 1;
}BithrNode,* BithrTree;
//链式队列
typedef struct node//结点定义
{
	BitNode *data;
	struct node* next;
}LinkNode;
typedef struct queue
{
	LinkNode* head, * hail;
}Queue;
//入队
void InQue(queue& q,BitNode *n)
{
	node* qnew = (node*)calloc(1,sizeof(node));
	qnew->data = n;
	qnew->next = NULL;
	q.hail->next = qnew;
	q.hail = qnew;
}
//出队
BitNode* OutQue(queue& q)
{
	if (q.head!= NULL)
	{
		BitNode* pnew = q.head->data;
		q.head = q.head->next;
		return pnew;
	}
	else {
		return NULL;
	}
}
//栈
typedef struct Stack
{
	BitTree data[100];
	int length=0;
}Stack;
//入栈
void PushStack(Stack& S, BitNode* n)
{
	S.data[S.length++] = n;//指针数组(数据类型为指针)
}
//出栈 
BitNode* PopStack(Stack& S)
{
	BitNode* n;
	if (S.length > 0)
	{
		n = S.data[--S.length];
		return n;
	}
	else
	{
		cout << "栈空!!!" << endl;
	}
}
1、中序遍历非递归
难点
  • 左子树重复入栈问题——结点指针每次打印完后重置,需要时出栈获得;或者控制出栈顺序,左子树访问完就能出栈
  • 右子树访问完根节点丢失问题——访问右子树前打印当前出栈元素结点
主要思想
  • 左子树一路入栈到叶子结点,开始出栈访问根节点
  • 用上述方法遍历右子树
void InOrder_3(BitTree T)
{
	Stack s;
	BitNode* p = T;
	while (s.length > 0 || p)
	{
		while (p)
		{
			PushStack(s, p);
			p = p->lchild;
		}
		p = PopStack(s);
		if (p->rchild)
		{
			cout << p->data << endl;
			p = p->rchild;
		}
		else
		{
			cout << p->data << endl;
			p = NULL;
		}
	}
	
}
优化
  • 合并相同逻辑
    • 可以出栈后无条件访问右子树,因为左子树的访问条件(当前结点不为空)已经对右子树是否存在判断了
    • 每次左子树入完后可以直接出栈访问右子树,右子树没有就直接出栈访问上一层根节点
void InOrder_3(BitTree T)//优化版
{
	Stack s;
	BitNode* p = T;
	while (s.length > 0 || p)
	{
		if(p)
		{
			PushStack(s, p);
			p = p->lchild;
		}
		else
		{
            p = PopStack(s);
			cout << p->data;
			p = p->rchild;
		}
	}
	
}

2、先序遍历非递归
主要思想
  • 根节点打印并入栈,向左子树遍历
  • 出栈,访问出栈元素右节点
void PreOrder2(BitTree T)
{
	Stack s;
	BitNode* p = T;
	while(s.length>0||p)
	{
		if (p)
		{
			cout << p->data;
			PushStack(s, p);
			p = p->lchild;
		}
		else
		{
			p = PopStack(s);
			p = p->rchild;
		}
	}
}

3、后序遍历非递归
难点
  • 如何解决父节点与左子树循环访问问题——先指针重置,每次右子树访问时拿到栈顶元素
  • 如何知道右子树是否被访问过——辅助指针记录最近访问过的结点(判断当前结点右节点是否被访问过)
易错
  • 记录已经访问过的右子树时刻是在每个元素出栈时,这样使得当前proximate右孩子结点被记录
主要思想
  1. 访问左子树,直至叶节点,过程中依次入栈

  2. 否则,考虑右子树和根节点访问问题

    1. 先拿到访问结点(栈顶元素)——由于后面的指针重置

    2. 若右子树存在且未被访问过

      • 访问右子树,进行后续遍历。
    3. 若右子树不存在或者访问过

      • 打印当前出栈元素

      • 记录当前出栈元素(作为判别右子树是否访问到的依据)

      • 访问结点重置(防止重复遍历左子树)

循环上述直至当前结点为NULL或栈空

void PostOrder_2(BitTree T)
{
	Stack s;
	BitNode* p = T, * r = NULL;//辅助指针
	while (p || s.length > 0)
	{
		if (p)
		{
			PushStack(s, p);
			p = p->lchild;
		}
		else 
		{
			p = s.data[s.length-1];//指针回归到当前栈顶元素
			if (p->rchild && r != p->rchild)//右子树是否已经被访问或不存在
					p = p->rchild;
			else
			{
				r=PopStack(s);//记录当前出栈
				cout << r->data;
				p = NULL;//指针重置,防止重复进入左子树
			}
		}
	}
}

4、二叉树自右向左、自底向上遍历
难点
  • 怎么搜索到最底层——使用逆序层次遍历方式,这样尾元素即为最底层最右
易错
  • 链式队列,入队思想是先创建结点,在把结点加入尾结点的next,此时head不参与;因此在初始化时需要

你可能感兴趣的:(王道,考研,算法,数据结构,链表,c++)