1. 程式人生 > >和二叉樹相關的面試題

和二叉樹相關的面試題

所謂的二叉樹就是樹中的每個節點最多有兩個孩子節點。滿二叉樹:除最後一層沒有子節點外,其它層的節點都具有兩個子節點。完全二叉樹:若二叉樹的高度為h,除最後一層外,其它層的節點數目都達到最大,並且最後一層的節點都集中在樹的左側。

二叉樹的節點結構:

typedef struct node
{
	char data;
	struct node *lchild, *rchild;
}Btree_node,*BTree;

1、二叉樹的建立

先序:先建立根節點,再建立左子樹,最後建立右子樹

void first_create(BTree *root)
{
	char temp;
	cin >> temp;

	if (temp != '#')
	{
		*root = (BTree)malloc(sizeof(Btree_node));
		(*root)->data = temp;
		(*root)->lchild = NULL;
		(*root)->rchild = NULL;

		first_create( &(*root)->lchild);
		first_create( &(*root)->rchild);
	}
}

2、二叉樹的遍歷

先序:先遍歷根節點,再遍歷左子樹,最後遍歷右子樹

/*先序遍歷---遞迴*/
void first_traverse(BTree root)
{
	if (root != NULL)
	{
		cout << root->data;
		first_traverse(root->lchild);
		first_traverse(root->rchild);
	}
}
/*先序遍歷--非遞迴*/
void first_traverse_no(BTree root)
{
	stack<BTree> sk;
	while ( !sk.empty() || root != NULL)//棧非空
	{
		if (root != NULL)
		{
			cout << root->data;
			sk.push(root);
			root = root->lchild;
		}
		else
		{
			root = sk.top();
			sk.pop();
			root = root->rchild;
		}
	}
}

中序:先遍歷左子樹,再遍歷根節點,最後遍歷右子樹

/*中序遍歷---遞迴*/
void mid_traverse(BTree root)
{
	if (root != NULL)
	{
		mid_traverse(root->lchild);
		cout << root->data;
		mid_traverse(root->rchild);
	}
}
/*中序遍歷---非遞迴*/
void mid_traverse_no(BTree root)
{
	stack<BTree> sk;

	while (!sk.empty() || root != NULL)
	{
		if (root != NULL)
		{
			sk.push(root);
			root = root->lchild;
		}
		else
		{
			root = sk.top();
			sk.pop();
			cout << root->data;
			root = root->rchild;
		}
	}
}

後序:先遍歷左子樹,再遍歷右子樹,最後遍歷根節點

void last_traverse(BTree root)
{
	if (root != NULL)
	{
		last_traverse(root->lchild);
		last_traverse(root->rchild);
		cout << root->data;
	}
}

層次遍歷:一層一層的遍歷

/*按層次遍歷*/
void level_traverse(BTree root)
{
	queue<BTree> qe;
	if (root != NULL)
		qe.push(root);

	while ( !qe.empty())
	{
		root = qe.front();
		qe.pop();
		cout << root->data;
		if (root->lchild != NULL)
			qe.push(root->lchild);
		if (root->rchild != NULL)
			qe.push(root->rchild);
	}
}

3、二叉樹的節點的個數

int count_node_num(BTree root)
{
	if (root == NULL)
		return 0;
	int numleft, numright, num;

	numleft = count_node_num(root->lchild);
	numright = count_node_num(root->rchild);
	num = numleft + numright + 1;

	return num;
}

4、二叉樹葉子節點的個數

葉子節點的特徵是既沒有左孩子也沒有右孩子,我們根據這個特徵在層次遍歷的過程中統計出葉子節點的個數

int get_leaf(BTree root)
{
	int leaf = 0;//用來記錄樹中葉子節點的個數
	queue<BTree> qu;
	if (root == NULL)
		return true;
	qu.push(root);
	BTree tmp;

	while ( !qu.empty() )
	{
		tmp = qu.front();
		qu.pop();

		if (tmp->lchild != NULL || tmp->rchild != NULL)
		{
			if (tmp->lchild != NULL)
				qu.push(tmp->lchild);

			if (tmp->rchild != NULL)
				qu.push(tmp->rchild);
		}
		else
			leaf++;
	}
	return leaf;
}
5、二叉樹的高度
int cout_high(BTree root)
{
	if (root == NULL)
		return 0;
	int left_high, right_high, high;

	left_high = cout_high(root->lchild);
	right_high = cout_high(root->rchild);
	high = 1 + (left_high > right_high ? left_high : right_high);

	return high;
}

6、二叉樹是否為平衡二叉樹

對於樹中的任意節點,其左右子樹的高度差不大於1。

int count_high(BTree root)
{
	if (root == NULL)
		return 0;
	int left_high, right_high, high;

	left_high = count_high(root->lchild);
	right_high = count_high(root->rchild);
	high = 1 + (left_high > right_high ? left_high : right_high);

	return high;
}

bool banlance_tree(BTree root)
{
	if (root == NULL)
		return true;

	int left_high, right_high,diff;

	left_high = count_high(root->lchild);
	right_high = count_high(root->rchild);
	diff = left_high - right_high;

	if (diff > 1 || diff < -1)
		return false;

	return (banlance_tree(root->lchild) && banlance_tree(root->rchild));
}

7、把二叉樹轉換成雙向連結串列

void Btree_to_list(BTree root,BTree *head)
{
	if (root == NULL)//如果是棵空樹
	{
		*head = NULL;
		return;
	}
	
	/*如果不是空樹則尋找此樹的最左邊的節點,作為連結串列的頭節點*/
	BTree tmp = root;
	while (tmp->lchild != NULL)
		tmp = tmp->lchild;
	*head = tmp;

	BTree last_node = NULL;//用來記錄連結串列的最後一個節點
	convert_node(root, &last_node);
}

void convert_node(BTree root, BTree *last_node)
{
	if (root == NULL)
		return;
	/*先轉換樹的左子樹*/
	if (root->lchild != NULL)
		convert_node(root->lchild, last_node);

	root->lchild = *last_node;

	if (*last_node != NULL)
		(*last_node)->rchild = root;
	/*調整last_node指向連結串列的最後一個元素*/
	*last_node = root;
	/*對樹的右子樹進行調整*/
	if (root->rchild != NULL)
		convert_node(root->lchild, last_node);
}

8、二叉樹是否對稱

左右子樹同時遍歷,如果出現不一致,則不是對稱的

bool jude_tree(BTree root)
{
	if (root == NULL)
		return true;
	return jude(root->lchild, root->rchild);
}

bool jude(BTree left, BTree right)
{
	if (left != NULL && right != NULL)
	{
		if (jude(left->lchild, right->rchild) && jude(left->rchild, right->lchild))
			return true;
		return false;
	}

	else if (left == NULL && right == NULL)
		return true;
	else
		return false;
}

9、兩個節點的最近公共祖父節點

最近公共祖先(Lowest Common Ancestor)指的是:給定一棵有根樹T和兩個節點x、y,找到一個距離T最遠的節點z,使得z即是x的祖父節點同時也是y的祖父節點。

BTree get_lowest_common_ancestor(BTree root, BTree a, BTree b)
{
	if (root == NULL || root == a || root == b)
		return root;

	BTree left = get_lowest_common_ancestor(root->lchild, a, b);
	BTree right = get_lowest_common_ancestor(root->rchild, a, b);

	if (left && right)
		return root;

	return left ? left : right;
}

10、二叉樹中和為某一值的路徑

void get_path(BTree root, int target)
{
	if (root == NULL)
		return;

	static int sum = 0;
	static deque<BTree> qu;
	/*如果當前路徑為目標值*/
	if (sum + root->data == target)
	{
		/*打印出路徑*/
		for (int i = 0; i < qu.size(); i++)
			cout << qu[i] << "->";
		cout << root->data << endl;
		return;
	}
	else if (sum + root->data > target)
		return;
	else//當前路徑值小於目標值
	{
		qu.push_back(root);
		sum += root->data;

		get_path(root->lchild, target);
		get_path(root->rchild, target);

		qu.pop_back();
		sum -= root->data;
	}
}

11、求二叉樹中第K層的節點個數

int get_Klevel_nodenum(BTree root, int k)
{
	if (root == NULL || k <= 0)
		return 0;
	if (k == 1)
		return 1;
	return(get_Klevel_nodenum(root->lchild, k - 1) + get_Klevel_nodenum(root->rchild, k - 1));
}

12、求二叉樹中兩個節點最大距離

二叉樹中兩個節點的最大距離分為兩種情況:①路徑經過左子樹的最深節點,通過根節點,再經過右子樹的最深節點;②路徑不經過根節點,而是左子樹或右子樹的最大距離,取二者的最大值。

這個問題的核心是,情況①、②需要不同的資訊:情況①需要子樹的最大深度,情況②需要子樹的最大距離。如果程式能在同一個節點返回這兩個資訊,那麼演算法將會變得很簡單:

RESULT get_max_distance(BTree root)
{
	if (root == NULL)
	{
		RESULT empty = { 0, -1 };
		return empty;
	}

	RESULT rsl = get_max_distance(root->lchild);
	RESULT rsr = get_max_distance(root->rchild);

	RESULT resut;
	resut.maxDepth = max(rsl.maxDepth, rsr.maxDepth) + 1;
	resut.maxDistace = max(max(rsl.maxDistace, rsr.maxDistace), rsl.maxDepth + rsr.maxDepth + 2);

	return resut;
}

13、判斷二叉樹是否為滿二叉樹

滿二叉樹的總節點個數和層數之間滿足如下關係:節點個數 = 2^K - 1(其中K為二叉樹的層數)。我們可以利用此關係來判斷是否為滿二叉樹。

bool jude_full(BTree root)
{
	int level;;//用來記錄樹的層數
	int node_num = 0;//用來記錄樹中節點的個數
	queue<BTree> qu;
	if (root == NULL)
		return true;
	qu.push(root);
	BTree tmp;

	while ( !qu.empty() )
	{
		tmp = qu.front();
		qu.pop();

		node_num++;

		if (tmp->lchild != NULL)
			qu.push(tmp->lchild);

		if (tmp->rchild != NULL)
			qu.push(tmp->rchild);
	}

	level = get_depth(root);

	if (node_num == (pow(2, level) - 1))
		return true;
	return false;
}

14、判斷二叉樹是否為完全二叉樹

我們採用樹的按層次遍歷,那麼在遍歷的過程中,如果遇到沒有左孩子或右孩子的節點,我們設定標誌位,如果在後序的遍歷中遇到了有左孩子或右孩子的節點,那麼該樹不是完全二叉樹。

bool jude_full(BTree root)
{
	if (root == NULL)
		return true;

	queue<BTree> qu;
	qu.push(root);

	BTree tmp;
	int flag = 1;

	while ( !qu.empty())
	{
		tmp = qu.front();
		qu.pop();

		if (tmp->lchild != NULL)
		{
			if (flag == 0)
				return false;
			qu.push(tmp->lchild);
		}	
		else
			flag = 0;
		
		if (tmp->rchild != NULL)
		{
			if (flag == 0)
				return false;
			qu.push(tmp->rchild);
		}
		else
			flag = 0;
	}
	return true;
}

15、根據前序和中序遍歷結果,重建二叉樹

void rebuild(const char *preOrder, const char *midOrder, int nodeNum, BTree *root)
{
	if (preOrder == NULL || midOrder == NULL || nodeNum <= 0)
		return;
	/*用前序遍歷的第一個節點構造根節點*/
	(*root) = new Btree_node;
	(*root)->data = *preOrder;
	(*root)->lchild = NULL;
	(*root)->rchild = NULL;
	/*如果樹中還剩一個節點則結束*/
	if (nodeNum == 1)
		return;
	/*尋找子樹的長度*/
	int tmpLen = 0;
	const char *leftEnd = midOrder;
	/*找到左子樹的結尾*/
	while (*preOrder != *leftEnd)
	{
		tmpLen++;
		/*檢查是否溢位*/
		if (tmpLen > nodeNum)
			return;
		leftEnd++;
	}
	/*計算左子樹的節點個數*/
	int leftNode = leftEnd - midOrder;
	/*計算右子樹的節點個數*/
	int rightNode = nodeNum - leftNode - 1;
	/*重建左子樹*/
	if (leftNode > 0)
		rebuild( preOrder + 1, midOrder, leftNode, &((*root)->lchild) );
	/*重建右子樹*/
	if (rightNode > 0)
		rebuild(preOrder + leftNode + 1, midOrder + leftNode + 1, rightNode, &((*root)->rchild));
}