1. 程式人生 > >資料結構—二叉樹相關概念及經典面試題

資料結構—二叉樹相關概念及經典面試題

二叉樹概念

一棵二叉樹是結點的有限集合,該集合或者為空, 或者是由根結點加上兩棵分別稱為左子樹和右子樹的二叉樹構成

二叉樹的特點:

  1. 每個結點最多有兩棵子樹,即二叉樹不存在度大於2的結點
  2. 二叉樹的子樹有左右之分,其子樹的次序不能顛倒

滿二叉樹、完全二叉樹

滿二叉樹:在一棵二叉樹中,如果所有分支結點都存在左子樹和右子樹,並且所有葉子節點都在同一層上

完全二叉樹: 如果一棵具有N個結點的二叉樹的結構與滿二叉樹的前N個 結點的結構相同,稱為完全二叉樹

二叉樹性質

  • 若規定根節點的層數為1,則一棵非空二叉樹的第i層上最多有2^{i-1} (i>0)個結點
  • 若規定只有根節點的二叉樹的深度為1,則深度為K的二叉樹的最大結點數是2^{k}-1 (k>=0)  
  • 對任何一棵二叉樹, 如果其葉結點個數為 n0, 度為2的非葉結點個數 為 n2,則有n0=n2+1
  • 具有n個結點的完全二叉樹的深度k為\lg (n+1)上取整

對於具有n個結點的完全二叉樹,如果按照從上至下從左至右的順序 對所有節點從0開始編號,則對於序號為i的結點有:

  1. 若i>0,雙親序號:(i-1)/2; 
  2. i=0,i為根節點編號,無雙親結點
  3. 若2i+1<n,左孩子序號:2i+1,否則無左孩子
  4. 若2i+2<n,右孩子序號:2i+2,否則無右孩子 

二叉樹儲存結構

 二叉樹有順序儲存和鏈式儲存結構

順序儲存結構:

對於一棵完全二叉樹所有結點按照層序自頂向下,同一層自左向右順 序編號,就得到一個節點的順序序列

優點:儲存完全二叉樹,簡單省空間

缺點:儲存一般二叉樹尤其單支樹,儲存空間利用不高 

鏈式儲存結構:

二叉鏈結構結點的定義:

typedef int DataType;
typedef struct BinaryTreeNode  //二叉樹結構定義
{
        struct BinaryTreeNode* _left;  //指向當前結點左孩子
        struct BinaryTreeNode* _right;  //指向當前結點右孩子
        DataType _data;  //當前結點資料
}BTNode;

三叉鏈結構結點定義:

typedef int DataType;
typedef struct BinaryTreeNode  //二叉樹結構定義
{
          struct BinaryTreeNode* _pParent;  //指向當前節點的雙親結點
          struct BinaryTreeNode* _pLeft;  //指向當前結點左孩子
          struct BinaryTreeNode* _pRight;  //指向當前結點右孩子
          ataType _data;  //當前結點資料
}BTNode;

二叉樹的操作 (面試題) 

建立二叉樹:

對二叉樹進行其他操作的基礎是要創建出二叉樹,我們採用遞迴的方式建立二叉樹,從根結點開始先處理它的左子樹,再處理它的右子樹(對子樹也是同樣的),遞迴是比較抽象的,所以我先給出實現的程式碼,然後詳細說一下遞迴是如何實現二叉樹建立的

// 建立二叉樹 
//a:陣列指標  pIndex:下標指標  invalib:給的非結點元素,用其表示一個結點無孩子(左、右)
BTNode* CreateBTree(DataType* a, size_t* pIndex, DataType invalid)
{
	assert(a);
	if (a[*pIndex] == invalid)
		return NULL;

	BTNode* root = BuyBTNode(a[*pIndex]);

	++(*pIndex);
	root->_left = CreateBTree(a,pIndex,invalid);

	++(*pIndex);
	root->_right = CreateBTree(a, pIndex, invalid);

	return root;
}

思考:建立二叉樹是引數為什麼給的是指標變數 pIndex,而不是變數index?

我在走遞迴過程時以上圖中已經很好的體現了,相信對這塊的理解沒有問題

前序遍歷

  • 遞迴實現
void BTreePrevOrder(BTNode* root)  //前序遍歷
{
	if (root == NULL)
		return;

	printf("%d  ",root->_data);  

	BTreePrevOrder(root->_left);  
	BTreePrevOrder(root->_right); 
}
  • 非遞迴實現
void BTreePrevOrderNonR(BTNode* root)  //非遞迴前序遍歷
{
	assert(root);

	Stack s;
	StackInit(&s);

	BTNode* cur = root;
	while (cur != NULL || StackEmpty(&s) != 0) {

		while (cur) {
			printf("%d  ",cur->_data);
			StackPush(&s,cur);

			cur = cur->_left;
		}

		BTNode* top = StackTop(&s);
		cur = top->_right;
		StackPop(&s);
	}
}

中序遍歷

  •  遞迴實現
void BTreeInOrder(BTNode* root)  //中序遍歷
{
	if (root == NULL)
		return;

	BTreeInOrder(root->_left);
	printf("%d  ",root->_data);
	BTreeInOrder(root->_right);
}
  • 非遞迴實現
void BTreeInOrderNonR(BTNode* root)  //非遞迴中序遍歷
{
	assert(root);

	Stack s;
	StackInit(&s);

	BTNode* cur = root;
	while (cur != NULL || StackEmpty(&s) != 0) {

		while (cur) {
			StackPush(&s, cur);

			cur = cur->_left;
		}

		BTNode* top = StackTop(&s);
		printf("%d  ", top->_data);
		cur = top->_right;
		StackPop(&s);
	}
}

後序遍歷

  •  遞迴實現
void BTreePostOrder(BTNode* root)  //後序遍歷
{
	if (root == NULL)
		return;

	BTreePostOrder(root->_left);
	BTreePostOrder(root->_right);
	printf("%d  ",root->_data);
}
  • 非遞迴實現
void BTreePostOrderNonR(BTNode* root)  //非遞迴後序遍歷
{
	Stack s;
	StackInit(&s);

	BTNode* cur = root;
	BTNode* prev = NULL;  //記錄被遍歷的前一個結點
	while (cur != NULL || StackEmpty(&s) != 0) {

		while (cur) {
			StackPush(&s, cur);

			cur = cur->_left;
		}

		BTNode* top = StackTop(&s);
		if (top->_right == NULL || top->_right == prev)
		{
			printf("%d  ", top->_data);
			StackPop(&s);
			prev = top;
		}
		else
			cur = top->_right;
	}
}

層序遍歷 

void BTreeLevelOrder(BTNode* root)  //層序遍歷
{
	assert(root);

	Queue q;
	QueueInit(&q);

	QueuePush(&q,root);

	while (QueueEmpty(&q) != 0) {

		BTNode* head = QueueHead(&q);
		printf("%d  ",head->_data);
		QueuePop(&q);

		if (head->_left != NULL)
			QueuePush(&q,head->_left);

		if (head->_right != NULL)
			QueuePush(&q, head->_right);

	}
}

結點個數 

size_t BTreeSize(BTNode* root)  //結點個數
{
	if (root == NULL)
		return 0;

        //左子樹的個數+右子樹的個數
	return 1 + BTreeSize(root->_left) + BTreeSize(root->_right);
}

 葉子結點個數

size_t BTreeLeafSize(BTNode* root)  //葉子結點的個數
{
	if (root == NULL)  //這個返回條件不能不寫
		return 0;
	if (root->_left == NULL && root->_right == NULL)
		return 1;

	//左子樹葉結點+右子樹葉結點
	return BTreeLeafSize(root->_left) + BTreeLeafSize(root->_right);
}

 第K層結點個數

size_t BTreeKLevelSize(BTNode* root, size_t k)  //第k層的結點個數
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	//左子樹第k-1層的結點數+右子樹第k-1層的結點樹
	return BTreeKLevelSize(root->_left,k-1) + BTreeKLevelSize(root->_right,k-1);
}

二叉樹的深度 

size_t BTreeDepth(BTNode* root)  //二叉樹的深度
{
	if (root == NULL)
		return 0;

	//返回左子樹和右子樹中較深的(加1:根節點)
	return (1 + BTreeDepth(root->_left)) >= (1 + BTreeDepth(root->_right)) ? (1+BTreeDepth(root->_left)) : (1+BTreeDepth(root->_right));
}

判斷一個節點是否在一棵二叉樹中

BTNode* BTreeFind(BTNode* root, BTNDataType x)  //檢視某個結點是否存在
{
	if (root == NULL)
		return NULL;

	if (root->_data == x)
		return root;

	BTNode* ret;
	ret = BTreeFind(root->_left,x);
	if (ret != NULL)
		return ret;

	ret = BTreeFind(root->_right,x);
	return ret;
}

 判斷一棵二叉樹是否是完全二叉樹

int IsCompleteBTree(BTNode* root)  //判斷是否是完全二叉樹
{
	assert(root);

	Queue q;
	QueueInit(&q);

	QueuePush(&q,root);

	while (QueueEmpty(&q) != 0) {

		BTNode* head = QueueHead(&q);
		QueuePop(&q);
		if (head == NULL)
			break;

		if (head->_left != NULL || head->_right != NULL)
		{
			QueuePush(&q,head->_left);
			QueuePush(&q,head->_right);
		}
	}

	while (QueueEmpty(&q) != 0) {

		BTNode* head = QueueHead(&q);
		if (head == NULL)
		{
			if (QueueEmpty(&q) != 0)
				return 0;  //返回0表示不是完全二叉樹

			else
				return 1;  //返回1表示是完全二叉樹
		}
		else
			QueuePop(&q);
	}

	return 1;
}

求二叉樹中兩個結點的最近公共祖先結點

判斷一棵二叉樹是否是平衡二叉樹

求二叉樹中最遠的兩個結點之間的距離

有前序遍歷和中序遍歷重建二叉樹(前序遍歷結果:1,2,3,4,5,6 中序 遍歷結果:4,2,5,1,6,3)

求二叉樹的映象

將二叉搜尋樹轉換成一個排序的雙向連結串列。要求:不能建立任何新的 結點,只能調整樹種結點指標的指向