基本概念:树是 n ( n > = 0 ) n(n>=0) n(n>=0)结点的有限集
其他概念:
二叉树是另一种树形结构,每个结点至多只有两棵子树,且有左右之分,需要注意的是,从树的概念衍生来的二叉树是可以为空树的。
bool PreOrderTraverse(BinTree T)
{
if (!T)
return 1;
else
{
Visit(T->data);
PreOrderTraverse(T->left); s1
PreOrderTraverse(T->right); s2
}
return 1;
}
遍历算法的不同之处仅仅为访问结点的先后关系,其实递归执行过程是完全一样的,仿照递归执行状态中递归栈的变化可写出相应的非递归算法,若栈顶记录的指针非空,则遍历左子树将左指针入栈;若栈顶记录的指针为空,说明应当返回上一层,若从右子树返回,不必保存当前的根指针,直接修改指针即可。
bool InOrderTraverse(BinTree *p)
{
std::stack<BinTree *> stk;
auto T = p;
while(T||!stk.empty())
{
if(T)
{
stk.push(T);
T=T->left;
}
else
{
T=stk.top();
stk.pop();
T=T->right;
}
}
}
层序遍历
//一个队列保存节点,另一个队列保存节点深度
vector<vector<int>> levelOrder(TreeNode* root) {
std::deque<TreeNode*> queue;
std::deque<int> depth;
std::vector<std::vector<int>> res;
std::vector<int> curr;
if(!root)
return std::vector<std::vector<int>>();
else
{
int prev_depth=0;
queue.push_back(root);
depth.push_back(0);
while(!queue.empty())
{
auto dep=depth.front();
depth.pop_front();
if(dep!=prev_depth)
{
res.push_back(curr);
curr.clear();
prev_depth=dep;
}
auto node=queue.front();
queue.pop_front();
curr.push_back(node->val);
if(node->left)
{
queue.push_back(node->left);depth.push_back(dep+1);}
if(node->right)
{
queue.push_back(node->right);depth.push_back(dep+1);}
}
res.push_back(curr);
}
return res;
}
二叉链表作为存储结构时,不能找到结点的前驱和后继信息,所以,将树的空链域用来存放结点的前驱和后继信息。规定:如果结点有左子树,则lchild域指示其左孩子,否则指示前驱,rchild域指示右孩子,否则指示后继。为避免混淆,再增加两个标志域
这种结点结构组成的二叉链表作为二叉树的存储结构,叫做线索链表,指向前驱和后继的指针称为线索,树也为线索二叉树
//遍历 中序线索树
void InOrderTraverse(Tree *T)
{
auto p=T->lchild;
while(p!=T)
{
while(p->LTag==Link)
p=p->lchild;
std::cout<<p->data<<std::endl;
while(p->RTag==Thread&&p->rchild!=T)
{
p=p->rchild;
std::cout<<p->data<<std::endl;
}
p=p->rchild;
}
}
firstchild
和nextsibling
域,求结点孩子时,先访问firstchild
域找到第一个孩子,再向兄弟结点域走i-1
步就可访问到结点的第 i i i个孩子可以看出,与树对应的二叉树的右子树必为空
class UnionFind{
private:
vector<int> parent;
int count; // 连通分量的数量
public:
UnionFind(int n) {
count = n;
parent.resize(n);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int p) {
while(p!=parent[p])
{
parent[p]=parent[parent[p]];
p=parent[p];
}
return parent[p];
}
bool isConnected(int p, int q) {
return find(p) == find(q);
}
void Union(int p, int q) {
int proot = find(p);
int qroot = find(q);
if (proot == qroot) {
return;
}
parent[proot] = qroot;
count--;
}
};
前缀编码:任意一个字符的编码都不是另一个字符的编码的前缀
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode, *HuffmanTree;
typedef char **HuffmanCode;
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
{
if(n<=1)
return;
m = 2*n-1;//对于n个子结点的哈夫曼树,共有2n-1个结点
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
/*初始化结点*/
for(p=HT,i=1;i<n;++i,++p,++w)
*p={
*w, 0, 0, 0};
for(;i<m;++i)
*p={
0, 0, 0, 0};
for(i=n+1;i<=m;++i)
{
Select(HT,i-1,s1,s2);//该函数从HT中取出weight最小的两个结点,parent为0 ,分别设置为s1 s2
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight+HT[s2].weight;
}
HC=(HuffmanCode)malloc(n+1*sizeof(char*));
cd = (char*)malloc(n*sizeof(char));
cd[n-1]="\0";
for(i=1;i<=n;++i)
{
start = n-1;
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)//从叶子结点开始,到根结点逆向求编码
if(HT[f].lchild==c)
cd[--start]="0";
else
cd[--start]="1";
HC[i]=(char*)malloc((n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
}
这个算法通过叶子结点到根对编码逆向处理,也可以从根出发遍历整个哈夫曼树获得各个叶子节点对应的字符
HC = (HuffmanCode)malloc(n+1*sizeof(char*));
p=m;//第m个结点是根节点
cdlen=0;
for(i=1;i<=m;++i)
HT[i].weight=0;//结点状态标识
while(p)
{
if(HT[p].weight==0)
{
HT[p].weight = 1;
if(HT[p].lchild!=0)
{
p=HT[p].lchild;//如果左孩子不为空 向左访问 由于是else if 访问后进入下一循环
cd[cdlen++] = "0";
}
else if(HT[p].rchild==0)//向左访问条件不成立,说明没有左孩子,检查右孩子,如果没有,就说明该节点为叶子节点,产生编码
{
HC[p] = (char*)malloc((cdlen+1)*sizeof(char));
cd[cdlen]="\0";
strcpy(HC[p],cd);
}
}
else if(HT[p].weight==1)
{
HT[p].weight=2;
if(HT[p].rchild!=0)//如果右孩子不为空 向右访问 由于是else if 访问后进入下一循环
{
p=HT[p].rchild;
cd[cdlen++]="1";
}
else//右孩子条件不成立,说明在上一个代码块已经拷贝了编码了,返回上层即可
{
HT[p].weight=0;
p=HT[p].parent;
--cdlen;
}
}
}
回溯法是设计递归过程的重要方法,实际上是先序建立一个状态树的过程,这棵树是隐含在遍历过程中的
void PowerSet(int i,int n)//求含n个元素集合A的幂集
{
if(i>n)
cout<<setA<<ends;
else
{
setA[i]=A[i];
PowerSet(i+1,n);//最先输出的是取所有A中元素,然后erase掉一个(返回了上层结点),再输出
setA.drop(i);//舍弃掉第i个元素,如果存在set中,就删除该元素
PowerSet(i+1,n);
}
}
回溯法生成的状态树是一颗满二叉树,每个叶子结点的状态都是求解过程中可能出现的,但是有些问题描述求解过程的状态树不是一棵满二叉树,当试探过程中的状态和问题所求解产生矛盾时,不再继续试探,这时出现的叶子结点不是求解的最终状态。
这类问题在约束条件下进行先序遍历,在遍历过程中剪去那些不满足条件的分支 四皇后问题:
树的计数问题:具有n个结点的不同形态的树有多少棵?我们先探讨问题的关键,如何定义不同形态?
相似:两个二叉树都为空树或者都不为空树,且左右子树分别相似
等价:两者不仅相似,且所有对应结点上的数据元素均相同
树的计数问题就是讨论具有n个结点,互不相似的二叉树的数目 b n b_n bn
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
auto ptr=root;
if(ptr==NULL||p==ptr||q==ptr)
return ptr;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left!=NULL&&right!=NULL)
return ptr;
else
{
if(left)
return left;
else if(right)
return right;
return NULL;
}
}
通过先序中序得到后序
TreeNode* build(vector<int>& preorder, int pl,int pr,vector<int>& inorder,int ml,int mr)
{
if(pl>pr||ml>mr)
return nullptr;
TreeNode *ptr=new TreeNode(preorder[pl]);
int i=0;
for(i=ml;i<=mr;i++)//找到中序的根结点
{
if(inorder[i]==preorder[pl])
break;
}
int length=i-ml;
ptr->left=build(preorder,pl+1,pl+length,inorder,ml,i-1);
ptr->right=build(preorder,pl+length+1,pr,inorder,i+1,mr);
return ptr;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
return build(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
}
二叉树翻转
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root==NULL)
return NULL;
else
{
TreeNode* left=invertTree(root->left);
TreeNode* right=invertTree(root->right);
root->left=right;
root->right=left;
return root;
}
}
};