给定一个整数n,生成并返回所有N个节点组成并且节点值从1到n互不相同的不同二叉树,可以按照任意顺序
- 二叉树文章列表:
全排列问题
-
排列组合的问题在之前的文中我们已经求解过:
数据结构与算法–字符串的排列组合问题
数据结构与算法–代码完整性案例分析 -
在求数组全排列的过程中,我们将数组的每一位看成是独立的,接着每一位上分别排列1~9 的每一位树,一次对数组的n为进行递归,得到我们的全排列。
-
现题中需要让1~n 组成n 个阶段的不同二叉树,也就是我们需要求解二叉树的全排列
方法一:转为数组全排列实现
-
在构造二叉树时候,我们将比root 节点小的放左边,大的放右边,那么如果有 从1~n的数组依次对每一个节点构造二叉树的方式进行insert。那么我们能够得到一颗二叉树 T1
-
如果我们修改数组的顺序,修改成 k…k-1, 1, k+1,…n-1, n,接着生成二叉树T2
-
那么T1和 T2 大概率是不同的,特殊情况如下:
-
情况一:3,1,2,4,5
- 情况二:3,4,5,1,2
-
如上两种情况,在中间节点是root节点时,大节点列表4,5 与小节点列表 1,2 分别在两侧,不管数组如何排列,生成的二叉树是相同的。
-
经过如上分析,那么我们有如下的实现方案:
- 生成一个1~n的数组,并且求数组的全排列
- 每生成一个不同的数组排列,将该数组构建成一个二叉排序树,并且记录改二叉排序树的前序遍历生成的字符。并存储在数组 StrLIst中
- 如果 StrList中存在 元素 k 与当前生成的二叉树的前序遍历字符一致,那么说明这种情况以及生成过
- 如果StrList中不存在,那么我们将新构建的NewBinary添加到我们的二叉排序树数组 BinaryList中。
-
如上分析有如下代码:
/**
* 给定一个整数n,生成并返回所有N个节点组成并且节点值从1到n互不相同的不同二叉树,可以按照任意顺序
* 解析:求解1~n能组成多少个不同的二叉搜索树
* @author liaojiamin
* @Date:Created in 10:15 2021/7/22
*/
public class BuildGenerateTrees {
public static void main(String[] args) {
List<BinaryNode> binaryNodes = new ArrayList<>();
List<String> middleSearch = new ArrayList<>();
binaryNodes = generateTrees(4);
for (int i = 0; i < binaryNodes.size(); i++) {
StringBuilder strBu = new StringBuilder();
middleSearch.add(printTree(binaryNodes.get(i),strBu));
}
middleSearch.forEach(System.out::println);
}
/**
* 方法一
* 构造二叉树全排列:构造数组全排列,数组构造成二叉树,通过前序遍历值筛选二叉树
* */
public static List<BinaryNode> generateTrees(int n){
List<BinaryNode> binaryNodes = new ArrayList<>();
List<String> middleSearchStr = new ArrayList<>();
int[] array = new int[n];
for (int i = 1; i <= n; i++) {
array[i-1] = i;
}
return buildGenerateTree(array, binaryNodes, 0, middleSearchStr);
}
/**
* 生成数组的全排列,每种排列按顺序生成二叉树
* */
public static List<BinaryNode> buildGenerateTree(int[] array, List<BinaryNode> binaryNodes, int start, List<String> middleSearchStr){
if(array == null || array.length <=1 || start == (array.length -1)){
BinaryNode newNode = buildBinarySearchTree(array);
StringBuilder str = new StringBuilder();
String middleStr = printTree(newNode, str );
if(!middleSearchStr.contains(middleStr)){
middleSearchStr.add(middleStr);
binaryNodes.add(newNode);
}
}
for (int i = start; i < array.length; i++) {
int temp = array[i];
array[i] = array[start];
array[start] = temp;
buildGenerateTree(array, binaryNodes, start+1, middleSearchStr);
temp = array[i];
array[i] = array[start];
array[start] = temp;
}
return binaryNodes;
}
/**
* 前序遍历字符集合
* */
public static String printTree(BinaryNode t, StringBuilder strbu) {
if (t == null || t.getElement() == null) {
return strbu.toString();
}
for (int i = 0; i < t.getCount(); i++) {
strbu.append(t.getElement() + ":" + t.getHeight()+" ");
}
printTree(t.getLeft(), strbu);
printTree(t.getRight(), strbu);
return strbu.toString();
}
/**
* 根据数组构造二叉树
* */
public static BinaryNode buildBinarySearchTree(int[] array){
if(array == null || array.length <= 0){
return null;
}
BinaryNode node = new BinaryNode(null, null, null);
for (int i = 0; i < array.length; i++) {
node = insertNode(node, array[i]);
}
return node;
}
/**
* 插入节点构造二叉搜索树
* */
public static BinaryNode insertNode(BinaryNode node, Integer k){
if(k == null){
return node;
}
if(node == null || node.getElement() == null){
node = new BinaryNode(k, null, null);
}
int validateK = node.compareTo(k);
if(validateK > 0){
node.setLeft(insertNode(node.getLeft(), k));
}else if (validateK < 0){
node.setRight(insertNode(node.getRight(), k));
}
return node;
}
}
- 如上实现方案时间复杂度在O(n2), 空间复杂度存在两部分,一部分二叉搜索树数组,这部分取决于节点数量,数量不同排列的个数不同,另一部分在于存储二叉排序树的前序遍历字符串,因此空间复杂度也不会小。
方法二:分治法
- 方案一的时间复杂度,空间复杂度都不是太理想,第一想法实现方案往往题目没有这么简单,在方法一中我们实现我们是受到之前文章中思路的影响,将之前的排列组合的思路套用在 二叉搜索树的排列上,但是因为两种数据结构本身复杂度就相差很大,导致二叉搜索树的排列问题异常复杂。
- 我们换一种思路,这个也是看到别的思路,看到后就茅塞顿开,因为一旦思路被固定住,要想跳出来还是比较困难的,就和写小说的作者肯定不会去看别人写的小说是一个道理。
- 我们直接将数组全排列思想用到二叉树搜索树中:
- 在1~n中每个数字都有可能是根节点,确定根节点后 k,k 之前的数据 1 ~ k就是左子树,在k ~ n中每一个就是右子树
- 那么我们遍历1 ~ n中每一位,让每个数都构造成不同根的 一棵二叉搜索树
- 接着处理左子树 1 ~ k,同样,1~k中的左子树也是一棵二叉搜索树,依然套用以上逻辑,右子树同理。分别记录为leftList, rightLIst
- 那么构造完左右子树后,我们只需要在左子树 leftList,rightList进行组合(双循环构造每种排列方式),就得到了整个二叉树的排列
-如下图,列举其中一种情况:
- 经如上分析有如下代码:
/**
* 给定一个整数n,生成并返回所有N个节点组成并且节点值从1到n互不相同的不同二叉树,可以按照任意顺序
* 解析:求解1~n能组成多少个不同的二叉搜索树
* @author liaojiamin
* @Date:Created in 10:15 2021/7/22
*/
public class BuildGenerateTrees {
public static void main(String[] args) {
List<BinaryNode> binaryNodes = generateTreesBinary(1, 4);
BinarySearchTree binarySearchTree = new BinarySearchTree();
for (BinaryNode binaryNode : binaryNodes) {
binarySearchTree.printTree(binaryNode);
}
}
/**
* 方法二
* 分治法
* */
public static List<BinaryNode>generateTreesBinary(int start, int end){
List<BinaryNode> binaryNodes = new LinkedList<>();
if(start > end){
binaryNodes.add(null);
return binaryNodes;
}
for (int i=start;i<=end;i++){
List<BinaryNode> leftNode = generateTreesBinary(start, i-1);
List<BinaryNode> rightNode = generateTreesBinary(i+1, end);
for (BinaryNode left : leftNode) {
for (BinaryNode right : rightNode) {
binaryNodes.add(new BinaryNode(i, left, right));
}
}
}
return binaryNodes;
}
}
- 以上思路理解起来更清晰,代码实现方式也更简单,时间复杂度还是取决于n的大小,假设n个阶段能构建Cn棵二叉搜索树,那么时间复杂度一棵树生成需要O(n),Cn棵树O(n*Cn)
- 空间复杂度,每一棵树都有n个阶段,有Cn棵树,总空间复杂度O(n*Cn)