【Java】 劍指offer(36) 二叉搜索樹與雙向鏈表

本文參考自《劍指offer》一書,代碼採用Java語言。html

更多:《劍指Offer》Java實現合集  java

題目 

  輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能建立任何新的結點,只能調整樹中結點指針的指向。node

思路

  二叉搜索樹、排序鏈表,想到使用中序遍歷。函數

  要實現雙向鏈表,必須知道當前結點的前一個結點。根據中序遍歷能夠知道,當遍歷到根結點的時候,左子樹已經轉化成了一個排序的鏈表了,根結點的前一結點就是該鏈表的最後一個結點(這個結點必須記錄下來,將遍歷函數的返回值設置爲該結點便可),連接根結點和前一個結點,此時鏈表最後一個結點就是根結點了。再處理右子樹,遍歷右子樹,將右子樹的最小結點與根結點連接起來便可。左右子樹的轉化採用遞歸便可。post

  大概思想再理一下:首先想一下中序遍歷的大概代碼結構(先處理左子樹,再處理根結點,以後處理右子樹),假設左子樹處理完了,就要處理根結點,而根結點必須知道左子樹的最大結點,因此要用函數返回值記錄下來;以後處理右子樹,右子樹的最小結點(也用中序遍歷獲得)要和根結點連接。測試

  注意搞清楚修改後的中序遍歷函數的意義(見代碼註釋)this

測試算例 url

  1.功能測試(一個結點;左右斜樹;徹底二叉樹;普通二叉樹)spa

  2.特殊測試(根結點爲null)指針

Java代碼

//題目:輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求
//不能建立任何新的結點,只能調整樹中結點指針的指向。

public class ConvertBinarySearchTree {
	public class TreeNode {
	    int val = 0;
	    TreeNode left = null;
	    TreeNode right = null;

	    public TreeNode(int val) {
	        this.val = val;
	    }
	}
	
	public TreeNode convert(TreeNode head) {
		if(head==null)
			return head;
		TreeNode lastNodeInList=null;
		lastNodeInList=convertHelper(head,lastNodeInList);
		TreeNode firstNodeInList=lastNodeInList;
		while(firstNodeInList.left!=null) {
			firstNodeInList=firstNodeInList.left;
		}	
		return firstNodeInList;
	}
	
	//將以node爲根結點的樹轉化爲排序鏈表,鏈表頭部與lastNode連接,並返回最後一個結點
	private TreeNode convertHelper(TreeNode node,TreeNode lastNode) {
		//處理左子樹,得到最大結點
		if(node.left!=null)
			lastNode=convertHelper(node.left, lastNode);
		//連接最大結點和根結點
		node.left=lastNode;
		if(lastNode!=null)
			lastNode.right=node;
		//處理右子樹
		lastNode=node;
		if(node.right!=null)
			lastNode=convertHelper(node.right, lastNode);
		return lastNode;
	}
}

  

上面的代碼是參考《劍指OFFER》寫的,下面的代碼是複習時從新寫過的,思路比較簡潔一點。非遞歸方法的核心是中序遍歷的非遞歸實現

public class Solution {
	/*
	* 遞歸版本
	* 1.已知函數返回的是轉換好的雙向鏈表頭結點
	* 2.左子樹處理完後與根結點鏈接
	* 3.右子樹處理,也與根結點鏈接
	* 4.最後返回頭結點
	*/
	public TreeNode Convert(TreeNode root) {
		if (root == null)
			return root;
		// 處理左子樹,得到左子樹鏈表的頭結點
		TreeNode left = Convert(root.left);
		TreeNode p = left;
		if (left != null) {
			// 找到左子樹鏈表的末尾結點
			while (p.right != null)
				p = p.right;
			// 鏈接結點
			p.right = root;
			root.left = p;
		}
		// 處理右子樹,得到右子樹鏈表的頭結點
		TreeNode right = Convert(root.right);
		// 鏈接結點
		if (right != null) {
			root.right = right;
			right.left = root;
		}
		return left == null ? root : left;
	}

	/* 非遞歸版本
	 * 1.利用非遞歸中序遍從來鏈接結點
	 */
	public TreeNode Convert1(TreeNode root) {
		TreeNode head = null;
		TreeNode pre = null;
		LinkedList<TreeNode> stack = new LinkedList<>();
		while (root != null || !stack.isEmpty()) {
			// 把root看成指針使用
			while (root != null) {
				stack.push(root);
				root = root.left;
			}
			TreeNode node = stack.pop();
			if (head == null) {
				head = node;
				pre = node;
			} else {
				node.left = pre;
				pre.right = node;
				pre = node; // 別漏寫了
			}
			root = node.right;
		}
		return head;
	}
}

  

收穫

  題目較複雜時,不要慌。這道題和中序遍歷有關,把樹分爲三部分:根結點、左子樹和右子樹,思考在中序遍歷中根結點應該如何處理,這是關鍵——要將左子樹的最大結點、根結點、右子樹的最小結點連接起來。左右子樹的處理是相同的,所以採用遞歸。

 

更多:《劍指Offer》Java實現合集 

相關文章
相關標籤/搜索