PAT-A1155

题目链接

PAT-A1155

题目概述

  • 给出一棵完全二叉树的层次遍历序列,打印从根节点到叶子结点的所有路径,作为右孩子的结点优先被打印;

  • 判断这棵二叉树是不是一个堆,如果是堆,判断是大顶堆还是小顶堆.

解题思路

打印路径

  • 打印路径应该使用DFS,从根节点开始递归向下,如果右孩子存在,那么先转向右孩子,重复这样的递归,直到右孩子为空,这样就得到了第一个遍历的路径,然后这个抵达递归基,返回上一级调用的地方,转向左子树,执行相同的动作,按照根->右孩子->左孩子的顺序进行遍历.可以用一个全局的数组保存此前已经访问的结点,在函数的结尾要有把当前遍历的结点弹出的操作,这样可以保证程序稳定的执行.可以通过这个打印这个数组的过程来处理判断这个是不是一个堆,是大顶堆还是小顶堆.这样判断不可行,需要单独写一个程序来判断这个堆是大顶堆还是小顶堆.

  • 经过上面的分析,觉得好像用value,lc,rc三元组表示的结点构成的数更容易实现,但是题目中给出的是以\(level\quad order\quad traversal\)表示的完全二叉树的序列,好像用数组做更方便些,但是如果用数组的话,完全二叉树的根结点与左右孩子之间存在这这样的关系(索引从1开始到n):$$index(lc) = 2* index(root), index(rc) = 2* index(root) + 1$$.边界条件特别重要,由其是要注意处理那种结点只有一个左孩子的情形.

  • 打印从根节点到叶子结点的程序如下:


const int N = 10005;
int buf[N];
int ans[N];
int n;
/**
 * root是当前结点在数组中的索引;
 * index是保存路径的数组的元素的数目,也是当前结点要存放的在数组的索引.
 */
void dfs(int root, int& index){
    //抵达叶子结点,打印路径.
    if( root > n ){
        cout << ans[0];
        for (int i = 1; i < index; i++){
            cout << " " << ans[i];
        }
        cout << endl;
        // --index;
        return;
    }
    //存储当前接结点
    ans[index++] = buf[root];
    //右孩子不存在,左孩子可能存在,也可能不存在.
    //如果左右孩子都不存在,那么只要在选择左孩子或右孩子深入一层就可以打印这条路径,但是不能全选左右孩子,否则这条路径会打印两遍.
    //如果左孩子存在,右孩子不存在,那么必须左转深入,然后情况转移到上面那种情况.
    //综上,这一处必须想左转,才能打印出正确的结果.
    if( 2 * root + 1 > n)
            dfs(2 * root, index);
    //中间过程的结点,左右孩子都存在,先右转,执行完后后再左转,确保右孩子先于左孩子打印.
    else{
        dfs(2 * root + 1, index);
        dfs(2 * root, index);
    }
    //程序执行到此处,表示以这个结点为中间结点(包含末尾结点的情况)的路径已经处理完毕,要回到这个结点的父节点,所以需要将这个结点弹出,也就是回溯.
    --index;
}

我觉得主要有两点需要注意:

  1. 使用index来计数以及表示回溯的动作;

  2. 要区分接结点有两个孩子,有一个孩子,以及没有孩子的情况.

    • 有两个孩子的是一般的递归情形,先右孩子,再左孩子;

    • 有一个孩子的话,一定是左孩子(完全二叉树),所以必须左转深入;

    • 没有孩子的话,向左向右都可以,但是只能向左或向右选择其一深入.

判断是否为堆

  • 判断是否为堆,以及是大顶堆还是小顶堆,我是根据堆的性质,写了一个冗长的递归程序:
//flag为false表示不是堆;
bool flag = true;
//m为1表示大顶堆,m为-1表示小顶堆,m为0表示暂未确定
int m = 0;

void isheap(int root){
    //当前结点是叶子结点,返回
    if( 2*root > n)
        return;
    //已经判断出不是堆,返回
    if (!flag){
        // m = 0;
        return;
    }
    //当前的结点只有左孩子,没有右孩子.
    if( 2 * root == n && 2*root + 1 > n){
        //此前判断是大顶堆(小顶堆),但是这个局部不满足
        if( m == 1 && buf[root] < buf[2*root]){
            flag = false;
            // return;
        }else if (m == -1 && buf[root] > buf[2*root]){
            flag = false;
            // return;
        }
        //此前尚未确定,根据这个局部的情况,来判断是大顶堆还是小顶堆,这个是一种边界情况.
        if( m == 0 && (buf[root] >= buf[2 * root] ))
            m = 1;
        else if( m == 0 && (buf[root] < buf[2 * root]))
            m = -1;
        return;
    }else{  //一般结点,左右孩子都存在
        //此前已确定是大顶堆(小顶堆),但是这个结点的局部不满足.
        if( m == 1 && (buf[root] < buf[2*root] || buf[root] < buf[2*root+1])){
            flag = false;
            return;
        }else if (m == -1 && (buf[root] > buf[2 * root] || buf[root] > buf[2 * root + 1])){
            flag = false;
            return;
        }
        //此前尚未确定,根据局部的情况判断是大顶堆还是小顶堆.
        if( m == 0 && (buf[root] > buf[2 * root] && buf[root] > buf[2*root+1]))
            m = 1;
        else if( m == 0 && (buf[root] < buf[2 * root] && buf[root] < buf[2*root + 1]))
            m = -1;
        //向左向右转向深入判断.
        isheap(2 * root);
        isheap(2 * root + 1);
    }
}

唯一要注意的是处理当前结点只有左孩子,没有右孩子的并且此前尚未确定是大顶堆还是小顶堆,最开始时没有考虑到这个情况,提交有一个测试点没通过,后面改正之后全部通过了.

  • 反思

    虽然根据利用完全二叉树实现的判断是不是堆,以及是大顶堆还是小顶堆的递归程序,虽然可行,但是总觉得好像太冗长了,就像上面那样,对于有些边界情况,因为没有考虑到,可能没法处理.那个递归的程序我认为是仅仅将性质转换为对应的程序语句,应该有更简洁的等价的迭代写法吧.

/**
     * 从全树的根节点的左孩子开始,Min和Max用于标志堆的特性;
     * 如果在某个局部buf[i/2] < buf[i]则说明这个完全二叉树不满足大顶堆,
     * 如果在某个局部buf[i/2] > buf[i]则说明这个完全二叉树不满足小顶堆.
     * 如果Min和Max都为false,则说明这个完全二叉树不是堆;
     * 当且仅当这个完全二叉树的所有元素都相同时,Min和Max才全部是true,
     * 否则Min和Max有且仅有一个为true(当这个完全二叉树是堆的时候).
     */
    bool Min = true;
    bool Max = true;
    for (int i = 2; i <= n; i++){
        if( buf[i/2] < buf[i])
            Max = false;
        if( buf[i/2] > buf[i])
            Min = false;
    }
    if( Min ){
        cout << "Min Heap" << endl;
    }else if( Max ){
        cout << "Max Heap" << endl;
    }else if (!Min && !Max){
        cout << "Not Heap" << endl;
    }

当然也可以在Min和Max都为false时提前终止.

猜你喜欢

转载自www.cnblogs.com/2018slgys/p/12790203.html
今日推荐