考研机试准备--《王道论坛机试指南》学习笔记

一.代码能力培养的层次结构

1.会编写(默写)经典程序的代码。
2.将自己的想法和抽象的问题转换为代码实现。
3.编写出的代码在大量的,多种多样的测试用例之前仍然具有健壮性。

 二.常见概念

1.特殊判题:表示该题有多个符合条件的题解,最后结果只需输出一个题解即可。
2.复杂度估计:最常见的时间限制为1s的运行时限,对于该时限,要求我们所设计的算法复杂度不要超过百万级别,即不     能超过一千万。例如,算法的时间复杂度为O(n^2),则此时n不应大于3000.
3.堆复杂度logn
   C++绕过对堆的实现,可用标准模板库中的相应的标准模板—优先队列
   利用语句(#include<queue>)
   Priority_queue<int>Q建立最大堆
   Priority_queue<int,vector<int>,greater<int>>Q;建立最小堆
   在使用了优先队列之后,求哈夫曼的过程使时间复杂度降低到nlogn

4.二叉排序树
   由于各个数字插入的顺序不同,所得到的二叉排序树的形态也可能不同。
   所有的二叉排序树都有一个共同的特点:若对二叉排序树进行中序遍历,那么其遍历结果必然是一个递增序列。

5.判断两棵二叉树是否相同:
   不能简单的用某一种遍历结果比较去判断两颗树是否相同。例如:数字相同而插入顺序不同的两棵二叉排序树,它们的     中序遍历是一样的。但包括中序遍历在内的两种遍历结果可以唯一确定一棵二叉树。因此对两棵树包括中序遍历在内的     两种遍历,若两种遍历结果都相同,则两棵树完全相同。

6.要删除二叉排序树上的某一个节点,按如下步骤进行:
  1).利用某种遍历找到该节点。
  2).若该节点为叶子节点,则直接删除,即将其双亲节点中指向其的指针改为NULL。释放节点空间。
  3).若该节点仅存在左子树,则直接将其左子树的根节点代替其位置,删除该节点。即将其双亲节点指向其的指针指向其         左子树树根。
  4).若该节点存在右子树,则找到右子树最右下的节点(即中序遍历中该子树上第一个被遍历到的节点),将被删除节点         的数值改为右子树上最右下的节点数值后,删除最右下节点。

7.最大公约数的求法:(欧里几得算法)
   思想:a,b的公约数可以整除a除以b的余数。

8.判断素数的复杂度:
   普通:遍历到n-1,此时复杂度为O(n)
   优化:遍历到sqrt(n),此时复杂度为O(sqrt(n))

9.省时技巧:对于某个中间出现而后续程序中多次用到其值的变量,此变量的值应一次计算出来,而不应该在后续的程序     中出现计算其值的表达式。

10.素数筛法:(以2到10000为例)从2开始遍历所有整数,若当前整数没有因为它是某个小于其的素数的倍数而被标记成       非素数,则判定其为素数,并标记它所有的倍数为非素数。然后继续遍历下一个数,直到遍历完2到10000区间内所有         的整数。

11.%04d,可实现输出前导0,位数不足4位的前填充0.

 


   

 

三.程序评判结果与相应解决方案

1.AC(Accepted ) :答案正确。
2.Wrong Answer:程序对若干组或全部测试数据没有输出正确结果。
      解决方法:1)考虑程序健壮性,例如边界数据,程序中变量出现溢出等。
                        2)算法本身正确性。
3.Presentation Error(格式错误):没有严格按照题目中要求的输出格式进行输出。
4.Time Limit Exceeded(超时):
     解决方法:1)考虑死循环、边界数据等。
                       2)设计的算法时间复杂度高于题目中的要求。
5.Runtime Error(运行时):程序在计算答案的过程中由于出现了某种致命额原因异常终止。
      解决方法:1)考虑程序是否访问了不该访问的内存地址,例如访问数组下标越界。
                        2)程序中是否出现了整数0做除数导致程序异常。
                        3)程序是否出现因为递归过深等原因导致栈溢出。
6.Compile Error(编译错误):存在语法错误等导致程序没有通过系统的编译。
7.Memory Limit Exceeded(使用内存超出限制)
 

 四.C++常用函数

        注:调用库函数时,需要在程序中引入库文件algorithm

1.sort(buf,buf+n)(快排且升序排序,若降序则升序后再倒序)
   sort(buf,buf+n,cmp)(cmp可定义降序)

bool cmp(int x,int y){
  return x>y;
}

 

五.经典入门

1.排序(自定义,sort)
2.日期类问题
3.Hash的应用
4.排版题
5.查找
6.贪心算法

 六.数学问题

1.%运算符:a%b的结果与a的符号相同
2.数位拆解

七.图论 

1.用邻接矩阵来保存结点个数为n的图,其空间复杂度为O(n*n),缺点:找某结点相邻的所有结点时,需遍历某行。
2.邻接链表空间复杂度为O(n(点的数量)+e(边的数量))缺点:当需要判断Vi与Vj间是否存在关系时,需要遍历Vi和Vj(无向图时任选一个)所有的邻接结点。
3.可以利用STL中的vector来实现邻接链表。需包含头文件vector。
  

struct Edge{
int nextNode;
int cost;
};

//利用如下语句为每一个结点都建立一个vector对象
vector<Edge>edge[N];

//对单链表的初始化
for(int i=0;i<N;i++){
    edge[i].clear();
}

//调用vector::push_back(Edge)向其中添加信息
Edge tmp;
tmp.nextNode=3;
tmp.cost=38;
edge[1].push_back(tmp);

//当我们需要查询某个结点的所有邻接信息时,则对vector进行遍历
for(int i=0;i<edge[2].size();i++){
    int nextNode=edge[2][i].nextNode;
    int cost=edge[2][i].cost;
}

//当我们需要删除单链表中某些边的信息是,调用vector::erase
//即vector.erase(vector.begin()+第一个要删除的元素编号,vector.begin()+最后一个要删
//除的元素编号+1
edge[1].erase(egde[1].begin()+i,edge[1].begin()+i+1);

4.并查集:这种数据结构用来表示集合信息,用以实现如确定某个集合中含有哪些元素,判断某两个元素是否存在在同一个集合当中,求集合中元素相等的数量等。
      利用树结构保存数据信息,用数组来存储,存储的信息为其双亲节点。(若两个元素的双亲结点相同则在同一集合)
      对于合并两个集合的要求,只需将一棵树的根结点变为另一棵树的根结点的子结点。但是有可能会出现,查某一节点的复杂度过高,因此在合并的过程中需优化,把所有结点均指向其根节点。
 

//定义一个数组,用双亲表示法来表示各棵树,若Tree[i]=-1表示为根结点
int Tree[N];

//为查找结点x所在树的根节点,可以定义如下函数:
int findRoot(int x){
    if(Tree[x]==-1)
         return x;
    else
         return findRoot(Tree[x]);
}

//若需要在查找过程中添加路径压缩的优化,可以修改为:
int findRoot(int x){
    if(Tree[x]==-1) return x;
    else{
        int tmp=findRoot(Tree[x]);
        Tree[x]=tmp;
        return tmp;
    }
}

5.最小生成树:在无向连通图中,如果存在一个连通子图包含原图中所有的结点和部分边,且这个子图不存在回路,那么这个子图就是原图的一棵生成树,在带权图中,所有的生成树中边权的和最小的那棵(或几棵)被称为最小生成树。|
最小生成树Kruskal(克鲁斯卡尔)算法的原理:
1)初始时所有的结点属于孤立的集合。
2)按照边权递增的顺序遍历所有的边,若遍历的边的两个结点仍属于两个不同的集合,则确定该边为最小生成树的一条边,并将这两个结点合并。
3)遍历完所有的边以后,原图中所有结点属于同一个集合则被选取的边和原图中所有的结点构成最小生成树;否则原图不连通,最小生成树不存在。
因此最小生成树的算法实现思路可以分为:
1)升序排序边的权值。
2)从小到大遍历边的两边结点,若两结点不在同一集合,则利用合并集算法将两结点合并在同一集合中。
3)最后在一个集合中包含所有结点则为最小生成树。

6.运算符重载写法(以=为例): operator =(const Edge &a)const

7.最短路径问题:
(1)Floyd(弗洛伊德算法)代码如下
 

for(int k=1;k<=n;k++){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(ans[i][k]==无穷||ans[k][j]==无穷)continue;
            if(ans[i][k]==无穷||ans[i][k]+ans[k][j]<ans[i][j])
                ans[i][j]=ans[i][k]+ans[k][j];
        }
    }
}

Floyd算法时间复杂度为O(n^3),空间复杂度为O(N^2)。
Floyd算法特点:
1)由于其时间复杂度,所以在大部分机试试题的时间允许范围内,被要求其求解图的大小不大于200个结点。
2)用邻接矩阵存储边的信息。注意当两个结点之间有多余一条边时要选择长度最小的边存入邻接矩阵。
3)可解全源最短路径问题。可以计算出所有结点对之间的最短路径长度。

(2)Dijkstra算法(迪杰斯特拉算法)具体实现参考csdn博文-最短路径问题
特点:
1)只能求得某特定结点到其他所有结点之间的最短路径长度,即单源最短路径问题。
2)时间复杂度为O(N^2),若在查找最小值处利用堆进行优化,则时间复杂度可以降到O(N*logN).
3)空间复杂度为O(N)(不包括图所需的空间)。它同时适用于邻接矩阵和邻接链表形式保存的有向图和无向图。

8.拓扑排序
算法原理:
1)选择一个入度为0的结点,作为序列的第一个结点。然后将该点及该点的连边从图中删去。
2)重复第一步。
3)若在所有结点尚未删去即出现了找不到入度为0的结点的情况,则说明剩余的结点形成了一个环路,拓扑排序失败。

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//邻接链表,只需存在与其邻接的结点编号即可
vector<int>edge[501];
//保存入度为0的结点的队列
queue<int>Q;
int main(){
    int inDegree[501];//统计每个结点的入度
    int n,m;
    while(scanf("%d %d",&n,&m)!=EOF&&n!=0&&m!=0){
        for(int i=0;i<n;i++){
            inDegree[i]=0;
            edge[i].clear();
        }
        while(m--){
            int a,b;
            cin>>a>>b;
            inDegree[b]++;
            edge[a].push_back(b);
        }
        //若队列非空,则一直弹出队头元素,该操作的目的是为清空队列中所有的元素(可能为上一组测试数据中遗留的数据)
        while(Q.empty()==false)Q.pop();
        for(int i=0;i<n;i++){//统计所有结点的入度
            if(inDegree[i]==0)
                Q.push(i);
        }
        int cnt=0;
        while(Q.empty()==false){
            int nowP=Q.front();
            Q.pop();
            cnt++;
            for(int i=0;i<edge[nowP].size();i++){
                inDegree[ edge[nowP][i]]--;
                if(inDegree[edge[nowP][i]]==0){
                    Q.push(edge[nowP][i]);
                }
            }
        }
        if(cnt==n)cout<<"拓扑成功"<<endl;
        else cout<<"拓扑失败"<<endl;
    }
}

 

八. 动态规划

1.错牌公式:F(n)=(n-1)*F(n-1)+(n-1)*F(n-2)
   应用:所有的信都装错了信封。

九.其他技巧

1.标准模板库
 1)String:
支持+、==、<=、cout<<,
字符串的长度:size();
s.erase(10,8):删除s[10]到s[17]的字符,即从s[10]开始的8个字符;
int pos = a.find(b,startPos):若能找到b字符串则返回其第一次出现的下标,否则返回一个常数string::npos.其中b也可以为字符数组。
a.insert(2,b):在a中下标为2的字符插入b字符串,其中b也可以为字符数组。
a[i]=tolower(a[i]);把大写转换为小写
gets(str):gets依次读入遗留在输入缓冲中的数据直到出现换行符,并将除换行符外的所有已读字符保存在字符数组中,同时从输入缓冲中去除该换行符。
scanf("%s",str):读取输入缓冲的字符直到出现空格、换行字符、它将读到的字符保存在字符数组str中,但并不删除缓冲中紧接的空格与换行。
所以在使用gets时,对之前输入遗留在输入缓冲的换行符要特别关注,确定其是否会对gets造成危险,例如:
scanf("%d%d",&a,&b)
getchar()
gets(str)
基于以上原因,我们应尽可能避免使用gets,除非输入要求输入“包括空格”的一整行,否则尽可能使用scanf("%s",str)。


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值