C的回归基础学习——数据结构(4)树.1
前言
终于到树了一个虐人虐得不要不要但是你又总会惊叹前任大佬们的天才想法。总之,慢慢来吧。
二叉树
先说一般的树就是没任何特点的,实现这种树你甚至可以用图的实现方法,基本上我不知道与其有关的用法。
二叉树很强,很多树高级的树都是二叉树。因为要求不一样有时建树的需求不一样,既可以数组建树也可指针建树。
struct Node{
int val;
Node* left;
Node* right;
};
struct Node{
int val,left,right;
}Tree[N];//N为节点数
至于如何建树得看数据给的怎么样。
1.二叉树的遍历
层序遍历我在队列时打过了,直接上剩下三个代码
#include <cstdio>
#define N 2<<9
using namespace std;
struct Node{
int val,r,l;
}tr[N];
void dfs1(int now)
{
printf("%d",tr[now].val);
if(tr[now].l != -1) dfs1(tr[now].l);
if(tr[now].r != -1) dfs1(tr[now].r);
}
void dfs2(int now)
{
if(tr[now].l != -1) dfs2(tr[now].l);
printf("%d",tr[now].val);
if(tr[now].r != -1) dfs2(tr[now].r);
}
void dfs3(int now)
{
if(tr[now].l != -1) dfs3(tr[now].l);
if(tr[now].r != -1) dfs3(tr[now].r);
printf("%d",tr[now].val);
}
int main() {
freopen("a.txt","r",stdin);
int n,f,l,r,val;
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d%d%d",&val,&l,&r);
tr[i].l = l;
tr[i].r = r;
tr[i].val = val;
}
dfs1(1);//先序遍历
printf("\n");
dfs2(1);//中序遍历
printf("\n");
dfs3(1);//后序遍历
//先后中是指处理根节点的位置
return 0;
}
一道与二叉树遍历有关的题
leetcode101.对称的二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
分析:
1.递归法:
- 实际上我们会发现只需要比较根节点下面的两颗子树是否镜像。以往我们遍历时三种遍历方法都是先左后右,而只要以同一种遍历方法先右后左的遍历出的结果和前一个相同即是镜像。所以同时遍历根的左子树和右子树即可。
- 要注意的是空树也算是对称。所以对于NULL的地方要自己判一哈
bool dfs(TreeNode* l,TreeNode* r) {
if(l == NULL && r == NULL) return true;
if(l == NULL || r == NULL) return false;
if(l->val != r->val) return false;
/*1*/ return dfs(l->left,r->right) && dfs(l->right,r->left);//这其实相当于左子树中序遍历
}
bool isSymmetric(TreeNode* root) {
if(root == NULL) return true;
return dfs(root->left,root->right);
}
和题解比较后,这里多说几句:
- 官方的代码1处用的是
return (l->val != r->val) && dfs(l->left,r->right) && dfs(l->right,r->left);
其实更加简洁,这里使用了&&的短路器,即前面的是假,则不进行后面的了。
2.迭代法:
思路一样,只是操作我没有见过所以想要学一下。
此处使用队列实现迭代,每次从队列里抽出两个点,如果相同就把他们的四个子节点按照镜像对应的方式存进去,一旦队列为空或者有不同就结束。
bool isSymmetric(TreeNode* root) {
if(root == NULL) return true;
queue<TreeNode*> q;
q.push(root->left);
q.push(root->right);
while(!q.empty())
{
TreeNode* t1 = q.front();
q.pop();
TreeNode* t2 = q.front();
q.pop();
if(t1 == NULL && t2 == NULL) continue;
if(t1 == NULL || t2 == NULL) return false;
if(t1->val != t2->val) return false;
q.push(t1->left);
q.push(t2->right);
q.push(t1->right);
q.push(t2->left);
}
return true;
}
2.堆和堆排序
- 堆其实就是一棵完全二叉树(若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边),定义为:具有n个元素的序列(h1,h2,…hn),当且仅当满足根节点永远大(小于)于子节点。
- 实际上有时只用数组就可以实现二叉树。
- 堆的操作一般就是:初始化,插入一个数并维护,删除最根结点的数并维护。而实际上后两者的复杂度是惊人的O(logn)。
- 我们知道根肯定是最值,那么我们每次把根输出来,然后删除根并维护就可以得到排了序的数组,这就是堆排序,复杂度为O(nlogn)
#include <cstdio>
#include <cstdlib>
using namespace std;
inline void swap(int& a,int& b) {
int t = a;
a = b;
b = t;
}
struct Heap{ //最小堆
int* heap;
int size;
int maxsize;
void init(int n) {
heap = (int*)malloc(sizeof(int)*n);
maxsize = n;
size = 0;
return ;
}
void insert(int num) {
if(size + 1 > maxsize) return ;//预防溢出,但没有实际操作
size++;
int pos = size;
heap[pos] = num;
while (heap[pos] < heap[pos/2] && pos != 1) {
swap(heap[pos],heap[pos/2]);
pos /= 2;
}
return ;
}
int top() {
return heap[1];
}
void pop() {
heap[1] = heap[size];
size--;
int pos = 1,son = pos * 2;
while(son <= size) {
if(heap[son] > heap[son+1]) son++;
if(heap[pos] > heap[son]) {
swap(heap[pos],heap[son]);
pos = son;
son = pos * 2;
}
else break;
}
}
}h;
int main()
{
freopen("a.txt","r",stdin);
int n,x;
scanf("%d",&n);
h.init(n);
for(int i = 0;i < n;i++)
{
scanf("%d",&x);
h.insert(x);
}
for(int i = 0;i < n;i++)
{
printf("%d ",h.top());
h.pop();
}
return 0;
}
3.二叉搜索树
二叉搜索树顾名思义,是与二叉搜索有着紧密联系的树,有:
定义
二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:
- 若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
- 若其右子树存在,则其右子树中每个节点的值都不小于该节点值。
一道相关题目
LeetCode题号:96. 不同的二叉搜索树
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
先不急着搞二叉搜索树的实现,先做一道与二叉搜索树有关的题。
其实这题是个递推(也算dp),定义f[i]为i个不同的数可以组成二叉搜索树的个数。其次我们得知道一个二叉搜索树的子树也是二叉搜索树。最后我们定义一个空树也是一颗二叉搜索树发f[0] = 1。
所以对于一个数n我们考虑:
- 当1为根时,没有比1小的数,所以1没有左子树,但是有由n-1个数组成的右子树所以 f[i]+=f[0]*f[n-1];
- 当2为根时,有1比2小,所以有由1个数组成的左子树,有n-2个数组成的右子树所以 f[i]+=f[1]*f[n-2];
… - 当m(m<n)为根时,所以有由m-1个数组成的左子树,有n-m个数组成的右子树所以 f[i]+=f[m-1]*f[n-m];
…
据说这道题还和卡特兰数有关。。。。
#include <cstdio>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
int* f = new int(n);
f[0] = 1;
for(int i = 1;i <= n;i++)
{
f[i] = 0;
for(int j = 0;j < i; j++)
f[i] += f[j]*f[i-j-1];
}
printf("%d",f[n]);
return 0;
}
又一道有关的题
LeetCode题号: 99. 恢复二叉搜索树
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
例子
输入: [1,3,null,null,2]
1
/
3
\
2
输出: [3,1,null,null,2]
3
/
1
\
2
分析:
这道题真的需要对搜索树有一定的了解,不然连题都读不懂(比如我)
划重点:一个二叉搜索树的中序遍历是单调递增的
所以只需要中序遍历一遍就知道那两个错误的点了,然后注意一下交换的操作
TreeNode* w1;
TreeNode* w2;
TreeNode* pre = NULL;
void dfs(TreeNode* root) {
if(root == NULL) return;
dfs(root->left);
if(pre != NULL && pre->val > root->val) {
if(w1 == NULL) {
w1 = pre;
w2 = root;
}else {
w2 = root;
}
}
pre = root;
dfs(root->right);
}
void recoverTree(TreeNode* root) {
dfs(root);
int tmp = w1->val;
w1->val = w2->val;
w2->val = tmp;
}
这道题可能难的地方是这个吧
进阶:
- 使用 O(n) 空间复杂度的解法很容易实现。
- 你能想出一个只使用常数空间的解决方案吗?
有时间再做吧(咕咕)
结语
这点点东西只是冰山一角。
爬树之旅任重而道远。。。。