1.题目
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
示例:
输入:3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
提示:0 <= n <= 8
c语言函数头:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
struct TreeNode** generateTrees(int n, int* returnSize){
}
来源:力扣(LeetCode)戳我前往题目
2.题目分析
二叉搜索树又是:二叉排序树,二叉查找树。
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
这里难就难在,返回的是所有排序二叉树组成的的数组,而不是单纯的创建一个二叉排序树。但是创建里面的二叉排序树,一般情况下我们都会用递归来创建二叉排序树。所以这里仍然首先考虑递归。
- 所以不妨再创建一个
buildTree
的函数来创建排序二叉树,因为是从1到n,所以我们创建的函数可以是struct TreeNode** buildTree(int start,int end, int *returnSize)
,start
为1,end
为n。 - 因为二叉排序树左右子树的特殊,我们可以通过
i
遍历1-n为根结点(既然是所有排序二叉树,所以根结点都会把1~n遍历一遍),然后通过这个函数的递归生成左右子树的集合。左子树小于根结点,所以左子树集合就应该是start
到i-1
,右子树比根结点大,所以右子树应该是i+1
到end
。 - 找到
i
根结点的左右子树结合,就可以直接通过遍历左右子树集合,和当时i
的根结点结合。就生成一个新的二叉排序树。再将这个二叉排序树加在将要返回的二叉树数组中。 - 记得每次遍历完第
i
个根结点中,生成的左右子树集合要free()。不同的i
的根结点生成的左右子树都是不同的,新建的。
3.代码实现
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
struct TreeNode** buildTree(int start,int end, int *returnSize)
{
//整数小于0,返回[[]]
if(start > end){
*returnSize = 1;
struct TreeNode** ret = malloc(sizeof(struct TreeNode*));
ret[0] = NULL;
return ret;
}
//只有一个数1,只能生成一个树
if(start == end){
*returnSize = 1;
struct TreeNode** ret = malloc(sizeof(struct TreeNode*));
ret[0] = malloc(sizeof(struct TreeNode));
ret[0][0].val = start;
ret[0][0].left = NULL;
ret[0][0].right = NULL;
return ret;
}
*returnSize = 0;//定义一个生成树的初始值
struct TreeNode** allTree = malloc(0);//初始化总的要返回树的初始值
//遍历根结点
for(int i=start;i<=end;i++){
//获得所有可行的左子树集合,应该比根结点小
int leftSize;
struct TreeNode** leftTree = buildTree(start,i-1,&leftSize);
//获得所有可行的左子树集合,应该比根结点大
int rightSize;
struct TreeNode** rightTree = buildTree(i+1,end,&rightSize);
//开始创建二叉排序树,右子树集合和左子树集合中遍历,拼接在根结点上
for(int right = 0; right < rightSize; right++){
for(int left = 0; left < leftSize; left++){
//创建一个二叉排序树
struct TreeNode* ret = malloc(sizeof(struct TreeNode));
ret->val = i;
ret->left = leftTree[left];
ret->right = rightTree[right];
//创建完了一个二叉排序树后,扩展返回二叉树数组空间,把这个二叉树存放在返回树数组中
(*returnSize)++;
allTree = realloc(allTree, sizeof(struct TreeNode*) * (*returnSize));
allTree[(*returnSize)-1] = ret;
}
}
//遍历一遍后,清空左右子树集合
free(leftTree);
free(rightTree);
}
return allTree;
}
struct TreeNode** generateTrees(int n, int* returnSize){
//在n为0时,输出为[]
if(!n){
(*returnSize) = 0;
return NULL;
}
return buildTree(1,n,returnSize);
}
4.题后总结
- n的值为0的时候是返回[],一个空数组。而n的值为负,返回的是[[]],一个数组中的空数组。刚开始还没注意到,所以在
generateTrees
函数里没有加n = 0时的返回值。小小的注意一下qwq。
- 里面有个函数 realloc()。是为了给返回的二叉树数组扩展空间,如果空间不够,就拓展。我还去百度了一下。realloc原型是
extern void *realloc(void *mem_address, unsigned int newsize);
功能:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。 - 总的来说,如果的二叉树首先考虑用另一个函数用递归法。