考试复习整理

最近数据结构课要考试了,这学期基本没去过数据结构课堂,咳咳,拿出书和课件来翻翻,不少内容都比CLRS繁琐而不清晰。
简单整理了一些要点,发现这些东西或许还是有些分享价值的。。。虽说大多非常水。

稀疏矩阵

  • 非零元素/(m * n) < 0.05 可认为是稀疏的,当然实际上是一个经验判断,这个数值仅供参考而已

  • 采用三元组进行存储
    整体是一个数组,每个元素是一个struct,成员包括value、行、列

  • 转置:
    一般转置算法:

    原矩阵为A,转置后为B:

    1. 初始化B,B.rows=A.cols;B.cols=A.rows;

    j=0 //用来扫描B的index
    for(k : 0 to A.cols) //按照原矩阵的列扫描
    for(i : 0 to terms) //terms是矩阵中非零元素的个数
    if(A.matrix[i].col == k)
    B.matrix[j].row=A.matrix[i].col
    B.matrix[j].col=A.matrix[i].row
    B.matrix[j].value=A.matrix[i].value
    j++

    1. 快速转置(类似于桶排序)
      前一个转置是很慢的,时间复杂度高达O(cols*terms)
      我们可以先扫描A,确定A中每一列有几个元素,
      如: col 0 1 2 3 4 5
      元素个数 1 1 1 3 0 2

      则此时可以计算出一个数组help,第i个元素代表A中第i列的元素放在B的help[i]位置上
      help 0 1 2 3 6 6

      for(i : 0 to terms)
      pos=help[ A.matrix[i].col ]
      B.matrix[pos].col = A.matrix[i].row
      B.matrix[pos].row = A.matrix[i].col
      B.matrix[pos].value = A.matrix[i].value
      help[A.matrix[i].col]++
      //这里让help数组中对应元素自增;这样遇到原A中和此元素同一列的元素时可以自动放在它的下一个位置

广义表

  • 这个数据结构在C++ 中用的不多,但是在scheme中已经见识到了这样一个 闭包结构的威力,看过SICP的孩子们看到广义表肯定倍感亲切呀。
    比如随手写一个: (cons a (cons b (cons c nil))) 就是一个链表, (cons (list a b ) (list c d)) 就是一个二叉树

  • 深度:括号的嵌套层次

  • 长度:最外层的子表个数

  • example: depth length

    • A () 1 0
    • B (a b) 1 2
    • C (a b (d e f) ) 2 3
    • D (A B C) 3 3
    • E (B D) 4 2
    • F (h F) 乄 2
      *也就是说,一个广义表的深度=它的所有子表里面深度最大的一个的深度+1而广义表的长度则不管子表的内容,子表和原子作为同样看待,看有几个

利用栈模拟实现二叉树的非递归遍历

这是一个非常经典的问题,借考试之故做个整理也是极好的

前序遍历

前序的改写最为容易,因为前序的右子树遍历是尾递归,而左子树遍历也接近尾递归,可以轻易的写为迭代而不借助栈
比如:
void preorder(treenode *node){
    while(node!=NULL){
        if(node->left!=NULL){
         node=node->left;
         break;
     }
        node=node->right;
}
这样的代码对于我们理解栈模拟没有任何用处,它只是用到了尾递归优化技巧,并且对于我们写出中序和后序也没有帮助
思考下面的方法:
void stack_preorder(treenode *node){
    stack  s;
    while(true){
         while(node!=NULL){
            visit(node);
            s.push(node->right);
            node=node->left;
        }
        if(s is empty) break;
        node=s.pop();
    }
}
当然,这里的两侧while可以写成一层:
void stack_preorder(treenode *node){
    stack  s;
    while(true){
         if(node!=NULL){
            visit(node);
            s.push(node->right);
            node=node->left;
        }
        else if(s is empty) break;
        else node=s.pop();
    }
}

中序遍历

前序的后一种方法很容易让我们写出利用栈模拟的中序遍历算法:
void stack_inorder(treenode *node){
    stack s;
    while(true){
        if(node != NULL){
            s.push(node);
            node=node->left;
        }
        else if(s is empty)
            break;
        else {
            treenode *p=s.pop();
            visit(p);
             node=node->right;
         }
    }
}
到这里我们需要思考下一个问题,栈模拟是在帮助我们记住返回的路径,有没有办法不用栈呢?
(之前有篇模版二叉树的博文里面的operator<< 实际上就是一个非递归不用栈的二叉树遍历)
void inorder(treenode *node){
    
    while(true){
        if(node->left!=NULL){
            node=node->left;
        }
        else {
            visit(p);
           while(node->right==NULL)
            {
                node=node->succ();
                if(node==NULL)
                   break;
                else visit(node);
               
           }
           node=node->right;
        }
        
    }
}
 解释一下,首先循环开始:
 从当前开始不断的向左,直至最左结点;
 访问最左结点;
 最左的结点没有右孩子:
     访问它中序下的后继;
     循环这一过程
 访问它的右孩子(此时将右孩子作为和根结点一样的地位,进行遍历) (实际上中序的右子树遍历是直接尾递归优化来的,很容易理解)

后序遍历

后序遍历就稍微麻烦一些,因为它的左子树和右子树遍历均不是尾递归。
这时候我们要解决一个问题,从栈中退出的时候我们接下来要访问什么?
我们无法确定是原递归的左子树调用还是右子树调用,因此需要添加一个标记。
标记取为enum 的两个常量:from_left_to_right  from_right_to_root分别对应当前结点是从左子树遍历中退出应该去右子树、以及遍历完右子树应该访问它自身两种情况

void stack_postorder(treenode *node){
    stack s;
    s.push(node);
    while(s is not empty){
        while(node->left != NULL){
            node=node->left;
            s.push(node);
        }
       
        while(true){
            node=s.pop();
            if(node.tag== from_left_to_rigth){
                s.push(node);
                node.tag=frome_right_to_root;
                node=node->right;
                break;
            }
            else if(node.tag==frome_right_to_root){
                visit(node);
            }
        }
    }
}

二叉树计数和退栈可能数

  • n个元素能够组成多少个不同的二叉树?
  • 给定n个元素的进栈顺序,有多少种合法的出栈顺序?

上面两个问题实际上是等价的,先来看第二个问题:

  • 首先我们要明确什么样的出栈顺序不合法:

如入栈顺序为i,j,k,出栈顺序为pk < pi < pj,则不合法,
比如 123 入栈,则312不合法

  • 考虑n个元素 1 2 3 ……n 进栈,合法的出栈顺序数满足下列递推:

f(n)= f(n-1) * f(0) + f(n-2) * f(1) + f(n-3) * f(2) + …… + f(0)f(n-1)
解释一下:
左边:f(n)表示1 2 3 …… n的入栈顺序下的出栈可能的数目
右边: 我们按照最后一个出栈元素的不同来分类:
1. 最后一个出栈的是n ,则此时出栈的种类数=f(n-1)
2. 最后一个出栈的是n-1,则此时出栈的种类数等于f(n-1)
f(1)
3. 依次类推,直至f(0)*f(n-1)

上述的f(n)实际上就是catalan数,其通项公式为C(n,2n)/(n+1)

而二叉树的种类数也是上面的catalan数

posted @ 2014-11-03 17:30  羽加迪姆勒维奥萨  阅读(157)  评论(0编辑  收藏  举报