您的位置:首页 > 其它

考试复习整理

2014-11-03 17:30 190 查看
最近数据结构课要考试了,这学期基本没去过数据结构课堂,咳咳,拿出书和课件来翻翻,不少内容都比CLRS繁琐而不清晰。

简单整理了一些要点,发现这些东西或许还是有些分享价值的。。。虽说大多非常水。

稀疏矩阵

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

采用三元组进行存储

整体是一个数组,每个元素是一个struct,成员包括value、行、列

转置:

一般转置算法:


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

初始化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++



快速转置(类似于桶排序)

前一个转置是很慢的,时间复杂度高达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数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: