第八篇博客

| 这个作业属于哪个班级 |


| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 卢伟杰 |

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);
            }
        }
    }
}

posted @ 2021-05-02 20:43  昨日云流  阅读(63)  评论(0编辑  收藏  举报