算法笔记---树的总结

  • 树与二叉树

pat中树最实用的性质:满足连通,边数等于顶点数减一的图一定是一棵树(这个性质在图里面可能会考)。其他普通树的性质一般会在题目中提及,所以不必记忆。

pat中树的写法:1. 指针写法:适用于所有二叉树, 但是普通的树最好用静态写法。

                           2. 静态写法:普通树一般都是用这种写法(最简形式是不带数据域,直接一个vector数组描述节点关系)

                           3. 数组写法:最常用于完全二叉树(数组的存储顺序就是二叉树的层序遍历),普通二叉树也可以当作完全二叉                                                     树,具体看题目要求。

  • 二叉树的遍历

由于pat考试的性质,二叉树只会考和遍历有关的东西,所以拿到一个二叉树的题目第一想到的是怎么用遍历去解决。

一个特别重要的题型:根据中序遍历和前/后序遍历确定一棵二叉树,这是个很固定的题型。

题目:Tree traversal

具体做法太熟悉了,直接给出这题递归结束条件(递归结束条件是重中之重)当左子树指针比右子树指针还大的时候结束递归。

#include<cstdio>
#include<cstdlib>
#include<queue>
using namespace std;
typedef struct treenode{
    int data;
    struct treenode *Left;
    struct treenode *Right;
}TreeNode;
typedef TreeNode *Tree;
Tree create_tree(int p1, int p2, int i1, int i2, int *post, int *in);
void levelOrder(int N, Tree root);
int Count = 0;
int main()
{
    int N;
    int post[31];
    int in[31];
    scanf("%d", &N);
    for(int i = 0; i < N; i++)
        scanf("%d", &post[i]);
    for(int j = 0; j < N; j++)
        scanf("%d", &in[j]);
    Tree root = create_tree(0, N - 1, 0, N - 1, post, in);
    levelOrder(N, root);
    return 0;
}
void levelOrder(int N, Tree root)
{
    queue<Tree> q;
    q.push(root);
    while(!q.empty())
    {
        Tree current = q.front();
        q.pop();
        if(current != NULL)
        {
            printf("%d", current -> data);
            Count++;
            if(Count < N)
                printf(" ");
            q.push(current -> Left);
            q.push(current -> Right);
        }
    }
}
Tree create_tree(int p1, int p2, int i1, int i2, int *post, int *in)
{
    if(i1 > i2)
        return NULL;
    Tree root = (Tree)malloc(sizeof(TreeNode));
    root -> data = post[p2];
    int position;
    for(position = i1; position <= i2; position++)
    {
        if(in[position] == post[p2])
            break;
    }
    int left_length = position - i1;
    int right_length = i2 - position;
    root -> Left = create_tree(p1, p1 + left_length - 1, i1, position - 1, post, in);
    root -> Right = create_tree(p2 - right_length, p2 - 1, position + 1, i2, post, in);
    return root;
}

题目:Tree traversal again

之前说过,看到二叉树,就要想到四种遍历和根据前/后序遍历和中序遍历确定一棵二叉树。这题对于怎么构建一棵二叉树的条件弄得特别隐晦。

这个题目已经把中序遍历给你了,现在想怎么构建一棵二叉树出来,只得寻找前序遍历或者后序遍历。结果发现前序遍历就是push的顺序。

#include<cstdio>
#include<cstdlib>
#include<stack>
#include<cstring>
using namespace std;
typedef struct treenode{
    int data;
    struct treenode *Left;
    struct treenode *Right;
}TreeNode;
typedef TreeNode *Tree;
int sum = 0;
Tree treeCreate(int p1, int p2, int i1, int i2, int *pre, int *in);
void postOrder(int N, Tree root);
int main()
{
    int N;
    scanf("%d", &N);
    int pre[31], in[31];
    stack<int> s;
    char str[5];
    int node, p = 1, q = 1;
    for(int i = 1; i <= N * 2; i++)
    {
        scanf("%s", str);//注意scanf输入字符串时,以空格和回车作为结束标志,所以会直接接着下一步的数字输入
        if(strcmp(str, "Push") == 0)
        {
            scanf("%d", &node);
            s.push(node);
            pre[p++] = node;
        }
        else
        {
            node = s.top();
            s.pop();
            in[q++] = node;
        }
    }
    Tree root = treeCreate(1, N, 1, N, pre, in);
    postOrder(N, root);
    return 0;
}
void postOrder(int N, Tree root)
{
    if(root != NULL)
    {
        postOrder(N, root -> Left);
        postOrder(N, root -> Right);
        printf("%d", root -> data);
        sum++;
        if(sum < N)
            printf(" ");
    }
}
Tree treeCreate(int p1, int p2, int i1, int i2, int *pre, int *in)
{
    if(i1 > i2)
        return NULL;
    Tree current = (Tree)malloc(sizeof(TreeNode));
    current -> data = pre[p1];
    int x;
    for(x = i1; x <= i2; x++)
    {
        if(pre[p1] == in[x])
            break;
    }
    int len_left = x - i1;
    int len_right = i2 - x;
    current -> Left = treeCreate(p1 + 1, p1 + len_left, i1, x - 1, pre, in);
    current -> Right = treeCreate(p2 - len_right + 1, p2, x + 1, i2, pre, in);
    return current;
}

题目:invert tree

这个题目有两种解法,第一种是暴力解法,直接从根一路反转到叶子,虽说暴力,但是一点也不复杂。第二种就要想到树的遍历,只要看到二叉树就要想到树的遍历方式,用后序遍历可以解决,之要访问到根,就反转左右儿子,方法很巧妙。

代码不给出来了,太简单了。

  • 树的遍历

树看起来比二叉树复杂多了,但是其实考法极为单一,只有两种:树的先根遍历和树的层序遍历。树的建立方式也特别的单一,大概只有静态写法了吧!具体的一些有用的点结合题目来说。

题目:path of equal weight

第一:深度优先搜索相比于广度优先搜索的好处就是,深度优先搜索查找顺序是按照一条树中存在的路径来寻找节点的(利用一个path数组来记录访问路径)而不像广度优先搜索需要一个数组来反向记录路径,然后再用递归给出路径。并且深度优先搜索的父节点和子节点是在递归过程中紧密关联的,所以,碰到树的遍历题目,第一要想到的是深度优先遍历。

第二:要善于利用外部数据结构来存储树在遍历过程中所产生的数据,比如这一题的路径就要存储在path数组中,但是一般树本身的数据比如:节点层数,节点权值等,记录在树的结点中访问比较方便。

这个题目就是根据上面两点写出来的。

#include<cstdio>//这个题目真的有点难度
#include<vector>
#include<algorithm>
using namespace std;
typedef struct treenode{
    int weight;
    vector<int> child;
}Tree;
Tree head[100];
bool com(int x, int y);
void DFS(int root, int sum, int lever, int *path, int S);
int main()
{
    int N, M, S;
    scanf("%d%d%d", &N, &M, &S);
    int w;
    for(int m = 0; m < N; m++)
    {
        scanf("%d", &w);
        head[m].weight = w;
    }
    int root = 0, node, num, c;
    for(int i = 0; i < M; i++)
    {
        scanf("%d%d", &node, &num);
        for(int j = 0; j < num; j++)
        {
            scanf("%d", &c);
            head[node].child.push_back(c);
        }
        sort(head[node].child.begin(), head[node].child.end(), com);//注意前面两个项是起始位置,传进函数的是起始位置对应的元素
    }
    int path[100];
    DFS(root, head[root].weight, 1, path, S);
    return 0;
}
void DFS(int root, int sum, int lever, int *path, int S)
{
    if(sum < S)
        path[lever] = root;
    else if(sum == S && head[root].child.size() == 0)
    {
        path[lever] = root;
        for(int i = 1; i <= lever; i++)
        {
            if(i != lever)
                printf("%d ", head[path[i]].weight);
            else
                printf("%d", head[path[i]].weight);
        }
        printf("\n");
    }
    int lable;
    for(int j = 0; j < head[root].child.size(); j++)
    {
        lable = head[root].child[j];
        DFS(lable, head[lable].weight + sum, lever + 1, path, S);
    }
}
bool com(int x, int y)
{
    return head[x].weight > head[y].weight;
}

题目:The largest generation

这题主要是要利用好外部数据结构来记录遍历过程中产生的数据,这题利用了一个hashTable数组来记录每层产生的叶子个数。

#include<cstdio>//此题题目都看错,导致浪费太多时间
#include<vector>
#include<queue>
using namespace std;
typedef struct treenode{
    int level;
    vector<int> child;
}Tree;
int hashTable[100] = {0};
void BFS(int root, Tree *ptr);
int main()
{
    int N, M;
    scanf("%d%d", &N, &M);
    Tree head[N + 1];
    int node, num, root = 1, c;
    head[root].level = 1;
    for(int i = 1; i <= M; i++)
    {
        scanf("%d%d", &node, &num);
        for(int j = 1; j <= num; j++)
        {
            scanf("%d", &c);
            head[node].child.push_back(c);
        }
    }
    BFS(root, head);
    int Max = -1, level;
    for(int m = 0; m < 100; m++)
    {
        if(hashTable[m] > Max)
        {
            Max = hashTable[m];
            level = m;
        }
    }
    printf("%d %d", Max, level);
    return 0;
}
void BFS(int root, Tree *ptr)
{
    queue<int> q;
    q.push(root);
    int current, lable;
    while(!q.empty())
    {
        current = q.front();
        hashTable[ptr[current].level]++;
        q.pop();
        for(int i = 0; i < ptr[current].child.size(); i++)
        {
            lable = ptr[current].child[i];
            ptr[lable].level = ptr[current].level + 1;
            q.push(lable);
        }
    }
}
  • 二叉查找树

二叉查找树的性质(往后继续补充):1. 二叉查找树的中序遍历是有序的。

                                                             2. 确定一棵二叉查找树之后,根据这颗树的先序遍历再去以二叉查找树的构建方法构建一                                                                   棵树,和原来的树相同。

二叉树的插入,删除,查找这些性质一定要多些,很重要。

题目:Search tree?

这个题目其实就是利用了性质2,书里面没有介绍。代码过于简单,不粘贴了。

题目:Complete Binary Search Tree

这个题目已经说明了要你给出一个完全二叉树,如何构建一个完全二叉树,答案很明显,给一个数组就已经是一个完全二叉树了,所以此题只要给出数据进行排序,然后根据二叉查找树中序遍历有序来逐步填充数据就行了。

最后要求给出层序遍历,直接顺序输出数组就行了。(代码里没这么干,因为当时没想到)

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int child = 1, num = 1;
bool comp(int x, int y);
void inOrder(int N, int root, int *in, int *tree);
void levelOrder(int N, int root, int *tree);
int main()
{
    int N;
    scanf("%d", &N);
    int in[N + 1];
    for(int i = 1; i <= N; i++)
        scanf("%d", &in[i]);
    sort(in + 1, in + N + 1, comp);//注意是从开始位置到结束位置的下一位
    int tree[N + 1];//数组本身就是层序,靠
    inOrder(N, 1, in, tree);
    levelOrder(N, 1, tree);
    return 0;
}
void levelOrder(int N, int root, int *tree)
{
    queue<int> q;
    q.push(root);//存的是下标
    int current;
    while(!q.empty())
    {
        current = q.front();
        if(num < N)
        {
            printf("%d ", tree[current]);
            num++;
        }
        else
            printf("%d", tree[current]);
        q.pop();
        if(current * 2 <= N)
            q.push(current * 2);
        if(current * 2 + 1 <= N)
            q.push(current * 2 + 1);
    }
}
void inOrder(int N, int root, int *in, int *tree)
{
    if(root <= N)
    {
        inOrder(N, root * 2, in, tree);
        tree[root] = in[child++];
        inOrder(N, root * 2 + 1, in, tree);
    }
}
bool comp(int x, int y)
{
    return x < y;
}
  • 并查集

 

  • 优先队列

1.将无序的数组调整成堆:把每一个子树都调整成堆(这个思想可以运用在很多算法上),整个无序的数组就会被调整成堆了。从下往上,从右往左调整!!!

void adjust_heap(void)
{
    for(int i = Size / 2; i >= 1; i--)
        downAdjust(i);
}

2.堆的插入:把元素插在数组尾部,向上调整,upAdjust不会影响堆序性。

3.堆的删除:将尾部元素覆盖根节点,然后再把这个节点向下调整,downAdjust不会影响堆序性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值