二叉树
注:
完全二叉树的性质:
- 1号位存放的必须是根节点。
- 完全二叉树当中的任何一个结点(编号为x),其孩子结点的编号一定是2x,右孩子的编号一定是2x+1。
- 判断某个结点为叶节点的标志:该结点(下标为root)的左孩子结点(root*2)大于结点总个数n。
- 完全二叉树与层次遍历有关系。
1.存储结构
struct node
{
int data; // 数据域
node* lchild; // 指向左子树根节点指针
node* rchild; // 指向右子树根节点指针
};
node* root = NULL; // 建树前根节点指向NULL
2.二叉树的建立
node* Create(int data[], int n) // 二叉树建立
{
node* root = NULL;
for(int i=0; i<n; i++)
{
insert(root, data[i]); // 结点插入函数
}
return root;
}
void insert(node* &root, int x) // 根结点指针一定要使用引用&,否则插入不会成功
{
if(root == NULL)
{
root = newNode(x);
return ;
}
if(...)
{
insert(root->lchild, x);
}
else
{
insert(root->rchild, x);
}
}
node* newNode(int v) // 新建结点
{
node* Node = new node; // 申请空间
Node->data = v;
Node->lchild = Node->rchild = NULL;
return Node;
}
3.二叉树的遍历
1)先序遍历
void preorder(node* root)
{
if(root == NULL)
return ;
printf("%d\n", root->data);
preorder(root->lchild);
preorder(root->rchild);
}
2)中序遍历
void inorder(node* root)
{
if(root == NULL)
return ;
inorder(root->lchild);
printf("%d\n", root->data);
inorder(root->rchild);
}
3)后序遍历
void postorder(node* root)
{
if(root == NULL)
return ;
postorder(root->lchild);
postorder(root->rchild);
printf("%d\n", root->data);
}
4)层次遍历
void layerorder(node* root)
{
queue<node*> q; // 队列中存结点的地址
q.push(root);
while(!q.empty())
{
node* now = q.front();
q.pop();
printf("%d\n", now->data);
if(now->lchild != NULL)
q.push(now->lchild);
if(now->rchild != NULL)
q.push(now->rchild);
}
}
eg1. PAT A1020 Tree Traversals
题目大意:
给出二叉树的后序遍历、中序遍历序列,求其层次遍历的序列。
思路分析:
根据后序遍历、中序遍历,递归重建二叉树,再对其进行层次遍历。重建过程中,数值域的值为根节点的值,左右孩子分别递归求得。详细解析请移步另一篇blog: https://blog.csdn.net/qq_41995258/article/details/89006449
代码(AC)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 35;
int level[maxn], in[maxn], post[maxn];
struct node
{
int data;
node* lchild;
node* rchild;
};
node* Create(int post_L, int post_R, int in_L, int in_R)
{
if(post_L > post_R) // 后序遍历序列长度为0,结束
{
return NULL;
}
node* root = new node; // 新建结点
root->data = post[post_R]; // 新结点的值为根节点的值
int k;
for(k=in_L; k<=in_R; k++)
{
if(in[k] == post[post_R])
{
break;
}
}
int num_left = k - in_L; // 左子树结点数量
root->lchild = Create(post_L, post_L+num_left-1, in_L, k-1); // 左子树
root->rchild = Create(post_L+num_left, post_R-1, k+1, in_R); // 右子树
return root;
}
void layerorder(node* root) // 层次遍历
{
queue<node*> q;
q.push(root);
int cnt = 0;
while(!q.empty())
{
node* now = q.front();
q.pop();
level[cnt++] = now->data;
if(now->lchild != NULL)
{
q.push(now->lchild);
}
if(now->rchild != NULL)
{
q.push(now->rchild);
}
}
}
void print(int n) // 显示结果
{
for(int i=0; i<n; i++)
{
printf("%d", level[i]);
if(i != n)
{
printf(" ");
}
}
}
int main()
{
freopen("input.txt", "r", stdin);
int n;
scanf("%d", &n);
for(int i=0; i<n; i++)
{
scanf("%d", &post[i]);
}
for(int i=0; i<n; i++)
{
scanf("%d", &in[i]);
}
node* root = Create(0, n-1, 0, n-1);
layerorder(root);
print(n);
fclose(stdin);
return 0;
}
eg2. PAT A1102 Invert a Binary Tree
题目大意:
建立二叉树,将二叉树对称翻转,求其层次遍历、中序遍历序列。
思路分析:
本题适于使用静态二叉树方法。
根节点: 不是任何结点子结点的结点。
二叉树翻转: 后序遍历过程中,交换结点,实现二叉树反转。
void postorder(int root) // 后序遍历过程中,交换结点,实现二叉树反转
{
if(root == -1)
return ;
postorder(Node[root].lchild);
postorder(Node[root].rchild);
swap(Node[root].lchild, Node[root].rchild); // 交换左右孩子结点
}
格式化输出: 利用计数器num,记录已输出结点个数,要记得清零!
int num = 0;
void print(int id)
{
printf("%d", id);
num++;
if(num < n)
printf(" ");
else
printf("\n");
}
代码(AC)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 15;
int hashtable[maxn] = {0};
int num = 0; // 当前已输出结点的个数
int n;
struct node
{
int lchild, rchild;
}Node[maxn];
void print(int id)
{
printf("%d", id);
num++;
if(num < n)
printf(" ");
else
printf("\n");
}
void postorder(int root) // 后序遍历过程中,交换结点,实现二叉树反转
{
if(root == -1)
return ;
postorder(Node[root].lchild);
postorder(Node[root].rchild);
swap(Node[root].lchild, Node[root].rchild); // 交换左右孩子结点
}
void inorder(int root)
{
if(root == -1)
return ;
inorder(Node[root].lchild);
print(root);
inorder(Node[root].rchild);
}
void levelorder(int root)
{
queue<int> q;
q.push(root);
while(!q.empty())
{
int now = q.front();
q.pop();
print(now);
if(Node[now].lchild != -1)
q.push(Node[now].lchild);
if(Node[now].rchild != -1)
q.push(Node[now].rchild);
}
}
int main()
{
freopen("input.txt", "r", stdin);
//int n;
scanf("%d", &n);
int lc, rc;
for(int i=0; i<n; i++)
{
getchar();
scanf("%c %c", &lc, &rc);
if(lc == '-')
{
Node[i].lchild = -1;
}
else
{
Node[i].lchild = lc - '0';
hashtable[lc-'0']++;
}
if(rc == '-')
{
Node[i].rchild = -1;
}
else
{
Node[i].rchild = rc - '0';
hashtable[rc-'0']++;
}
}
int root; // 根节点
for(root=0; root<n; root++) // 不是任何结点孩子结点的是根节点
{
if(hashtable[root] == 0)
{
break;
}
}
postorder(root);
levelorder(root);
num = 0;
inorder(root);
fclose(stdin);
return 0;
}
二叉排序树(BST)
二叉排序树,又称为二叉搜索树、二叉排序树、二叉查找树。
要么是空树,要么左子树数据域 < 根节点数据域 < 右子树数据域。
1.BST建立
node* Create(int data[], int n) // BST建立
{
node* root = NULL;
for(int i=0; i<n; i++)
{
insert(root, data[i]);
}
return root;
}
void insert(node* &root, int x)
{
if(root == NULL)
{
root = newNode(x);
return ;
}
if(x == root->data)
return ;
else if(x < root->data)
{
insert(root->lchild, x);
}
else
{
insert(root->rchild, x);
}
}
node* newNode(int v)
{
node* Node = new node;
Node->data = v;
Node->lchild = Node->rchild = NULL;
return Node;
}
2.BST的性质
对于同一组数值,二叉查找树不唯一,但对二叉查找树进行中序遍历,遍历结果是有序的且唯一。
eg1. 《王道》P55——二叉排序树
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 105;
int data[maxn], index;
int hashtable[maxn];
int n;
struct node
{
int data;
node* lchild;
node* rchild;
};
void init()
{
fill(data, data+maxn, -1);
fill(hashtable, hashtable+maxn, 0);
index = 0; // 存储数组的下标
}
node* newNode(int v)
{
node* Node = new node;
Node->data = v;
Node->lchild = NULL;
Node->rchild = NULL;
return Node;
}
void insert(node* &root, int v)
{
if(root == NULL)
{
root = newNode(v);
return ;
}
if(v == root->data)
return ;
else if(v < root->data)
insert(root->lchild, v);
else
insert(root->rchild, v);
}
node* Create(int data[], int m)
{
node* root = NULL;
for(int i=0; i<m; i++)
{
insert(root, data[i]);
}
return root;
}
void preorder(node* root)
{
if(root == NULL)
return ;
printf("%d ", root->data);
preorder(root->lchild);
preorder(root->rchild);
}
void inorder(node* root)
{
if(root == NULL)
return ;
inorder(root->lchild);
printf("%d ", root->data);
inorder(root->rchild);
}
/*void postorder(node* root)
{
if(root->lchild != NULL)
postorder(root->lchild);
if(root->rchild != NULL)
postorder(root->rchild);
printf("%d ", root->data);
}*/
void postorder(node* root) // 不知道为什么,最开始用这个后序遍历总是卡死在程序里,之后换成上边那种写法便可以正常运行。
{ // 再换回到这种写法又可以正常运行,我佛了
if(root == NULL)
return ;
postorder(root->lchild);
postorder(root->rchild);
printf("%d ", root->data);
}
int main()
{
freopen("input.txt", "r", stdin);
while(scanf("%d", &n) != EOF)
{
init(); // 初始化
int temp;
for(int i=0; i<n; i++)
{
scanf("%d", &temp);
if(hashtable[temp] == 0) // 未出现过的数字
{
data[index++] = temp;
hashtable[temp]++;
}
}
node* root = Create(data, index);
preorder(root);
printf("\n");
inorder(root);
printf("\n");
postorder(root);
printf("\n");
root = NULL; // BST清空
}
fclose(stdin);
return 0;
}
eg2.《王道》P58
思路分析:
分别建立各串的BST,前序遍历和中序遍历都相同的代表是相同的树(一定要用中序遍历,另一个任选)。
吐槽:
这题简直把我给整懵逼了。循环存储对比串的前、中序遍历结果时,总是存不进数。最后没办法只能还存在pre_a[],in_a[]中,把原串的结果再转存到pre_b[],pre_a中。我真的不知道这是为什么????看代码中的调试过程就知道我有多崩溃55555
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 15;
const int maxm = 25;
char a[maxn], b[maxn];
char pre_a[maxn], in_a[maxn];
char pre_b[maxn], in_b[maxn];
int num_in = 0, num_pre = 0; // 已输出的结点个数
int pre = 0, in = 0;
struct node
{
char data;
node* lchild;
node* rchild;
};
void init()
{
fill(a, a+maxn, 0);
fill(b, b+maxn, 0);
fill(pre_a, pre_a+maxn, 0);
fill(in_a, in_a+maxn, 0);
fill(pre_b, pre_b+maxn, 0);
fill(in_b, in_b+maxn, 0);
num_in = 0;
num_pre = 0;
in = 0;
pre = 0;
}
node* newNode(char v)
{
node* Node = new node;
Node->data = v;
Node->lchild = NULL;
Node->rchild = NULL;
return Node;
}
void insert(node* &root, char v)
{
if(root == NULL)
{
root = newNode(v); // wtmd!这里写错了(现在是对的)
return ;
}
if(v == root->data)
{
return ;
}
else if(v < root->data)
{
insert(root->lchild, v);
}
else
{
insert(root->rchild, v);
}
}
node* Create(char data[], int n)
{
node* root = NULL;
for(int i=0; i<n; i++)
{
insert(root, data[i]);
}
return root;
}
void preorder(node* root)
{
if(root == NULL)
return ;
//printf("%c", root->data);
pre_a[num_pre++] = root->data;
preorder(root->lchild);
preorder(root->rchild);
}
void inorder(node* root)
{
if(root == NULL)
return ;
inorder(root->lchild);
//printf("%c", root->data);
in_a[num_in++] = root->data;
inorder(root->rchild);
}
int main()
{
freopen("input.txt", "r", stdin);
int n;
while(scanf("%d", &n)!=EOF && n!=0)
{
init(); // 初始化
scanf("%s", a);
node* root = Create(a, strlen(a));
inorder(root); // 中序遍历
strcpy(in_b, in_a);
//cout << endl;
preorder(root); // 前序遍历
strcpy(pre_b, pre_a);
//cout << endl;
//cout << "#" << (in_b) << endl;
//cout << "#" << (pre_b) << endl;
for(int i=0; i<n; i++)
{
num_in = 0;
num_pre = 0;
scanf("%s", b);
node* root_new = Create(b, strlen(b));
inorder(root_new);
//cout << endl;
preorder(root_new);
//cout << endl;
//cout << "#" << (in_a) << endl;
//cout << "#" << (pre_a) << endl;
if(strcmp(in_a, in_b)==0 && strcmp(pre_a, pre_b)==0)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
root_new = NULL;
}
root = NULL;
}
fclose(stdin);
return 0;
}
哈夫曼树
每个n个结点和它们的权值,以它们为叶子结点,从根结点到该结点的长度*权值之和,即二叉树的带权路径长度最小。
哈夫曼树的实现,可以使用“小顶堆”的优先队列。每次将最小的两个结点求和,再加入到优先队列中,直至队列中只剩下一个结点,该结点就是根节点。
带权路径长度 = 除根节点外其余结点的权值之和
eg1.《王道》——P49
代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
priority_queue< int, vector<int>, greater<int> > q;
void pq_clear()
{
while(!q.empty())
{
q.pop();
}
}
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
pq_clear(); // 清空优先队列
for(int i=0; i<n; i++)
{
int temp;
scanf("%d", &temp);
q.push(temp);
}
int ans = 0;
while(q.size() > 1)
{
int x = q.top();
q.pop();
int y = q.top();
q.pop();
q.push(x+y);
ans += (x+y);
}
printf("%d\n", ans);
}
return 0;
}