第八篇博客
| 这个作业属于哪个班级 |
| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 卢伟杰 |
0.PTA得分截图
1.本周学习总结(5分)
1.1 二叉树结构
-
二叉树的2种存储结构
- 二叉树的存储结构有两种:顺序存储结构和链式存储结构
-
顺序存储结构
顺序结构实现二叉树时,采用一个一维数组来存储所有结点,需要将所有结点按照在树中的位置安排成一个恰当的序列,使其能反应结点之间相互的逻辑关系,通常使用编号的方法;
-
链式存储结构
由二叉树的定义可知,二叉树的结点由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包含3个域:数据域和左、右指针域;
-
树的顺序存储和链式存储结构,并分析优缺点
-
顺序存储
优点:读取某个指定的节点的时候效率比较高O(0)
缺点:会浪费空间(在非完全二叉树的时候)
-
链式存储
优点:读取某个指定节点的时候效率偏低O(nlogn)
缺点:相对二叉树比较大的时候浪费空间较少
二叉树的顺序存储,寻找后代节点和祖先节点都非常方便,但对于普通的二叉树,顺序存储浪费大量的存储空间,同样也不利于节点的插入和删除。因此顺序存储一般
用于存储完全二叉树。链式存储相对顺序存储节省存储空间,插入删除节点时只需修改指针,但寻找指定节点时很不方便。不过普通的二叉树一般是用链式存储结构。
-
-
二叉树的构造
-
创建二叉树
void CreateBTree(BTNode *&b, char *str)
{
BTNode *p, *St[MaxSize];
int k, j = 0, top = -1;
char ch;
b = NULL;
ch = str[j];
while (ch != '\0')
{
switch (ch)
{
case'(':top++; St[top] = p; k = 1; break;
case ',':k = 2; break;
case ')':top--; break;
default:
p = (BTNode *)malloc(sizeof(BTNode));
p->data = ch;
p->lchild = p->rchild = NULL;
if (b == NULL)
{
b = p;
}
else
{
switch (k)
{
case 1:St[top] = p->lchild; break;
case 2:St[top] = p->rchild; break;
}
}
}
j++;
ch=str[j];
}
}
- 销毁二叉树
void DestroyBTree(string str, int &i)
{
if (b != NULL)
{
DestroyBTree(b->lchild);
DestroyBTree(b->rchild);
free(b);
}
}
- 查找节点
BTNode * FindNode(BTNode *b, Elemtype x)
{
BTNode* p;
if (b == NULL)
{
return NULL;
}
else if(b->data == x)
return b;
else
{
p = FindNode(b->lchild, x);
if(p != NULL)
return p;
else
return FindNode(b->rchild, x);
}
}
- 找孩子节点
void FindChild(BiTree T)
{
if(T->lChild == NULL && T->rChild == NULL)
{
cout << T->data << " ";
return ;
}
FindChild(T->lChild);
FindCHild(T->rChild);
}
- 求高度
int BTHeight(BTNode *b)
{
int lchild,rchild;
if(b == NULL)
return(0);
else
{
lchild = BTHeight(b->lchild);
rchild = BTHeight(b->rchild);
return (lchild>rchild)?(lchild+1):(rchild+1);
}
}
-
二叉树的遍历
-
先序遍历
先序遍历的递归过程为:若二叉树为空,遍历结束。否则:
a.访问根结点; b.先序遍历根结点的左子树; c.先序遍历根结点的右子树。 简单来说先序遍历就是在深入时遇到结点就访问。
先序遍历的递归算法:
void PreOrder(BTree bt)
{
if (bt!=NULL)
{
printf("%c ",bt->data);
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
-
中序遍历
中序遍历的递归过程为:若二叉树为空,遍历结束。否则:
a.中序遍历根结点的左子树; b.访问根结点; c.中序遍历根结点的右子树。 简单来说中序遍历就是从左子树返回时遇到结点就访问。
中序遍历的递归算法:
void InOrder(BTree bt)
{
if (bt!=NULL)
{
InOrder(bt->lchild);
printf("%c ",bt->data);
InOrder(bt->rchild);
}
}
-
后序遍历
后序遍历的递归过程为:若二叉树为空,遍历结束。否则:
a.后序遍历根结点的左子树; b.后序遍历根结点的右子树; c.访问根结点。 简单来说后序遍历就是从右子树返回时遇到结点就访问。
后序遍历的递归算法:
void PostOrder(BTree bt)
{
if (bt!=NULL)
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
printf("%c ",bt->data);
}
}
-
层次遍历
这棵二叉树的层次遍历次序为:A、B、C、D、F、G 每次出队一个元素,就将该元素的孩子节点加入队列中,
直至队列中元素个数为0时,出队的顺序就是该二叉树的层次遍历结果.层次遍历的递归算法
void Create_Level(Node* &t){
queue<Node*> q;
int x;
cin>>x;
if (x!=0) {
Create_Node(t,x);
q.push(t);
}
while (!q.empty()){
Node* s=q.front();
cin>>x;
if (x!=0){
Create_Node(s->leftchild,x);
q.push(s->leftchild);
}
cin>>x;
if (x!=0){
Create_Node(s->rightchild,x);
q.push(s->rightchild);
}
q.pop();
}
}
- 线索二叉树
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,
最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到
前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。
- 结构体定义
typedef struct node
{ ElemType data;
int ltag,rtag;
struct node *lchild;
struct node *rchild;
} TBTNode;
-
优势
利用线索二叉树进行中序遍历时,不必采用堆栈处理,速度较一般二叉树的遍历速度快,且节约存储空间。 任意一个结点都能直接找到它的前驱和后继结点。
-
劣势
结点的插入和删除麻烦,且速度也较慢。
线索子树不能共用。
void InThreading(BiThrTree*p);
BiThrNodeType*pre;
BiThrTree*InOrderThr(BiThrTree*T)
{
BiThrTree*head;
head=(BitThrNodeType*)malloc(sizeof(BitThrNodeType));
head->ltag=0;head->rtag=1;
head->rchild=head;
if(!T)
head->lchild=head;
else
{
head->lchild=T;pre=head;
InThreading(T);
pre->rchild=head;
pre->rtag=1;
head->rchild=pre;
}
returnhead;
}
voidInThreading(BiThrTree*p)
{
if(p)
{
InThreading(p->lchild);
if(p->lchild==NULL)
{
p->ltag=1;
p->lchild=pre;
}
else
p->ltag=0;
if(p->rchild==NULL)
p->rtag=1;
else
p->rtag=0;
if(pre!=NULL&&pre->rtag==1)
pre->rchild=p;
pre=p;
InThreading(p->rchild);
}
}
- 二叉树的应用--表达式树
先序遍历表达式树,得到的是前缀表达式
中序遍历表达式树,得到的是中缀表达式
后序遍历表达式树,得到的是后缀表达式
-
构建表达式树
可以从后缀表达式来构建一个表达式树,如果是中缀表达式,则可以先转化为后缀表达式;
createExpressionTree(suffixExpression)
stack s <- empty stack;
for each element E in suffixExpression do
if(E is 操作数 )
Node tree = new Node(E)
s.push(tree)
else if(E is 运算符)
Node secondOperand = s.pop()
Node firstOperand =s.pop()
Node tree = new Node(E)
tree.setLeft(firstOperand)
tree.setRight(secondOperand)
s.push(tree);
return s.pop()
1.2 多叉树结构
-
多叉树结构:树的每个节点可以有两个以上的子节点,称为m阶的多叉树,或者称为m叉树。
-
多叉树遍历
-
递归
public static void Recursion(TreeNode root)
{
System.out.print(root.getName());
for (TreeNode treeNode : root.getChildren())
{
Recursion(treeNode);
}
}
- 广度优先
public static void breadthFirst(TreeNode root)
{
Deque<TreeNode> nodeDeque = new LinkedList<>();
TreeNode node = root;
nodeDeque.push(node);
while (!nodeDeque.isEmpty())
{
node = nodeDeque.pop();
System.out.print(node.getName());
for (TreeNode treeNode : node.getChildren())
{
nodeDeque.addLast(treeNode);
}
}
}
- 深度优先
public static void depthFirst(TreeNode root)
{
Deque<TreeNode> nodeDeque = new LinkedList<>();
TreeNode node = root;
nodeDeque.push(node);
while (!nodeDeque.isEmpty())
{
node = nodeDeque.pop();
System.out.print(node.getName());
for (TreeNode treeNode : node.getChildren())
{
nodeDeque.push(treeNode);
}
}
}
1.3 哈夫曼树
-
哈夫曼树定义:给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,
也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 -
哈夫曼树的结构体
typedef struct
{
char data;
double weight;
int parent;
int lchild;
int rchild;
}HTNode;
- 哈夫曼树构建及哈夫曼编码
typedef struct {
int weight;
int parent, lc, rc;
} HTNode, *HuffmanTree;
void Select(HuffmanTree &HT, int n, int &s1, int &s2)
{
int minum;
for(int i=1; i<=n; i++)
{
if(HT[i].parent == 0)
{
minum = i;
break;
}
}
for(int i=1; i<=n; i++)
{
if(HT[i].parent == 0)
if(HT[i].weight < HT[minum].weight)
minum = i;
}
s1 = minum;
for(int i=1; i<=n; i++)
{
if(HT[i].parent == 0 && i != s1)
{
minum = i;
break;
}
}
for(int i=1; i<=n; i++)
{
if(HT[i].parent == 0 && i != s1)
if(HT[i].weight < HT[minum].weight)
minum = i;
}
s2 = minum;
}
void CreatHuff(HuffmanTree &HT, int *w, int n)
{
int m, s1, s2;
m = n * 2 - 1;
HT = new HTNode[m + 1];
for(int i=1; i<=n; i++)
{
HT[i].weight = w[i];
HT[i].parent = 0;
HT[i].lc = 0;
HT[i].rc = 0;
}
for(int i=n+1; i<=m; i++)
{
HT[i].weight = 0;
HT[i].parent = 0;
HT[i].lc = 0;
HT[i].rc = 0;
}
printf("\nthe HuffmanTree is: \n");
for(int i = n+1; i<=m; i++)
{
Select(HT, i-1, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lc = s1;
HT[i].rc = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
printf("%d (%d, %d)\n", HT[i].weight, HT[s1].weight, HT[s2].weight);
}
printf("\n");
}
int main()
{
HuffmanTree HT;
int *w, n, wei;
printf("input the number of node\n");
scanf("%d", &n);
w = new int[n+1];
printf("\ninput the %dth node of value\n", n);
for(int i=1; i<=n; i++)
{
scanf("%d", &wei);
w[i] = wei;
}
CreatHuff(HT, w, n);
return 0;
}
1.4 并查集
-
并查集:是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
-
并查集解决什么问题,优势在哪里?
并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图
的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。 -
并查集的结构体、查找、合并操作如何实现?
template<typename T> class Node{
public:
T value;
Node<T>* father;
int size;
Node(T value, T* father){
this->value = value;
this->father = father;
this->length = 0;
}
Node(T value){
this->value = value;
this->father = NULL;
this->length = 0;
}
Node(){
this->value = 0;
this->father = 0;
this->size = 0;
}
};
2.PTA实验作业(4分)
2.1 二叉树
- 输出二叉树每层节点
#include<iostream>
#include<string>
#include<queue>
using namespace std;
typedef char ElemType;
typedef struct BiTNode
{
ElemType data;
struct BiTNode *lchild,*rchild;
}*BTree,BiTree;
BTree CreatTree(string str ,int &i);
void PreOrder(BTree b,int h);
void LevelOrder(BTree bt);
int wpl = 0;
int main()
{
string str;
cin>>str;
int MaxSize;
int i=0;
BTree bt =CreatTree(str,i);
LevelOrder(bt);
return 0;
}
BTree CreatTree(string str,int &i)
{
BTree bt;
if(i > str.size()-1)
{
return NULL;
}
if(str[i]=='#')
{
return NULL;
}
bt = new BiTree;
bt->data=str[i];
bt->lchild=CreatTree(str,++i);
bt->rchild=CreatTree(str,++i);
return bt;
}
void PreOrder(BTree b,int h)
{
if(b!=NULL)
{
cout<<b->data<<",";
PreOrder(b->lchild,h+1);
PreOrder(b->rchild,h+1);
}
}
void LevelOrder(BTree bt)
{
BTree p,ptr;
ptr = new BiTree;
ptr->lchild = NULL;
ptr->rchild = NULL;
ptr->data='0';
p=new BiTree;
p->lchild = NULL;
p->rchild = NULL;
p->data = '0';
queue<BTree>q;
if(bt != NULL)
{
q.push(bt);
cout<<"1:"<<bt->data<<",";
ptr=bt;
}
else
{
cout<<"NULL";
return;
}
int i=2;
int flag=0;
while (!q.empty())
{
p=q.front();
q.pop();
if (flag==1)
{
cout<<p->data<<",";
}
flag=1;
if(p->lchild!=NULL)
{
q.push(p->lchild);
}
if(p->rchild!=NULL)
{
q.push(p->rchild);
}
if (p == ptr)
{
if(!q.empty())
{
cout << endl << i << ":";
ptr = q.back();
}
i++;
}
}
}
- 解题思路及伪代码
BinTree CreatBT(string str,int &i)
{
当i>len-1或str[i]='#'
返回NULL
定义树的结构变量BT
申请结点BTNode
将str[i]的值赋给BT->data
递归调用函数CreatTree构建左右孩子
返回bt
}
void Print(BinTree BT)
{
定义树的结构变量curNode,lastNode
flag==1,表示该层输出完成,level表示该结点第几层
把树赋给curNode,lastNode,然后让树中的结点进栈
遍历栈,对头赋给curNode,判断为左孩子还是右孩子或者与lastNode相等,进行相应的赋值
用flag控制树层
最后输出栈顶
}
3.阅读代码(0--1分)
3.1 题目及解题代码
- 翻转二叉树以匹配前序遍历
3.2 该题的设计思路及伪代码
- 解题思路:
该题也是递归思想的应用。按照题目要求进行前序遍历,一旦遇到对应值与目标数组结果不同时,翻转遍历,
接着继续遍历,如果最终结果依然不匹配则返回false,否则返回true。可截图,或复制代码,需要用代码符号渲染。
- 代码
class Solution {
private int index;
private int[] voyage;
private List<Integer> result;
public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
// index = 0;
this.voyage = voyage;
result = new ArrayList<>();
dfs(root);
// System.out.println("result = "+result);
if(result.size() > 0 && result.get(result.size()-1) == -1)
return new ArrayList<Integer>(Arrays.asList(-1));
return result;
}
public void dfs(TreeNode root) {
if(root == null)
return;
if(root.val != voyage[index++])
result.add(-1);
else {
if(root.left != null && root.left.val != voyage[index]) {
result.add(root.val);
dfs(root.right);
dfs(root.left);
} else {
dfs(root.left);
dfs(root.right);
}
}
}
}