ACM基础算法复习(STL + DFS + BFS + 并查集 + 快速幂 + 欧几里得算法)

从进队到现在,师哥们陆陆续续讲了很多基础算法,对我这种菜鸡而言没有什么基础,感觉都挺难的,所以还是复习复习,看看还有多少没还给师哥的。。。

上课的内容大致有以下几个模块(C语言基础和python姑且不算)
1. STL
2. DFS + BFS
3. 并查集
4. 快速幂 + 欧几里得算法

1.STL之vector
把搜到关于vector的基本知识点列一下
1.push_back 在数组的最后添加一个数据

2.pop_back 去掉数组的最后一个数据

3.at 得到编号位置的数据

4.begin 得到数组头的指针

5.end 得到数组的最后一个单元+1的指针

6.front 得到数组头的引用

7.back 得到数组的最后一个单元的引用

8.max_size 得到vector最大可以是多大

9.capacity 当前vector分配的大小

10.size 当前使用数据的大小

11.resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值

12.reserve 改变当前vecotr所分配空间的大小

13.erase 删除指针指向的数据项

14.clear 清空当前的vector

15.rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)

16.rend 将vector反转构的结束指针返回(其实就是原来的begin-1)

17.empty 判断vector是否为空

18.swap 与另一个vector交换数据

vector c.
c.clear() 移除容器中所有数据。

c.empty() 判断容器是否为空。

c.erase(pos) 删除pos位置的数据

c.erase(beg,end) 删除[beg,end)区间的数据

c.front() 传回第一个数据。

c.insert(pos,elem) 在pos位置插入一个elem拷贝

c.pop_back() 删除最后一个数据。

c.push_back(elem) 在尾部加入一个数据。

c.resize(num) 重新设置该容器的大小

c.size() 回容器中实际数据的个数。
c.begin() 返回指向容器第一个元素的迭代器
c.end() 返回指向容器最后一个元素的迭代器

关于vector的用法我只能算了解,以后多做题多积累
这里挂一道oj上刚做的题
sdnu 1057 树的查询
Description
给定 n(1 <= n <= 1000000), m(1 <= m <= 10) 分别表示一棵树中节点的个数及查询的数量,每个节点的编号为给定的顺序,之后给定每个节点的父节点编号,及 m 个查询,每个查询中,给定一个节点编号,对于每个查询,按编号从小到大输出该节点所有子节点。
Input
第一行两个整数n, m,之后n行,每行两个整数a, b,表示编号为a的节点的父节点是b,b为0表示没有父节点,数据保证编号为1至n的节点均在a位置出现一次,之后一行m个整数,表示每个查询要查询的节点编号。
Output
m行,每行若干个从小到大排好序的、用一个空格隔开的整数,表示该次查询的节点的所有子节点。
Sample Input
5 1
1 0
4 1
2 1
5 1
3 1
1
Sam Output
2 3 4 5

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
vector<int>v[1010010];
int main()
{
    int n,m;
    int a,b;
    int first;
    scanf("%d%d",&n,&m);
    for(int i = 0; i < n; i ++)
        {
            cin>>a>>b;
            if(b == 0)continue;
            v[b].push_back(a);

        }
    while(m--)
    {
        cin>>a;
        first = 1;
        for(int j = 0; j < v[a].size(); j ++)
        sort(v[a].begin(),v[a].end());
        for(int j = 0; j < v[a].size(); j ++)
        {
            if(first == 1)
            {
                cout<0;
                continue;
            }
            cout<<' '<cout<return 0;
}

都是基础用法就不解释了,很水。
这算复习完vector?那接下来stack,map,queue就不一个一个找题了,和vector有很多共性,一定要多积累。

SET基本操作:
begin()    ,返回set容器的第一个元素
end()      ,返回set容器的最后一个元素
clear()    ,删除set容器中的所有的元素
empty()    ,判断set容器是否为空
max_size()   ,返回set容器可能包含的元素最大个数
size()      ,返回当前set容器中的元素个数
rbegin     ,返回的值和end()相同
rend()     ,返回的值和rbegin()相同

QUEUE基本操作:
back()返回最后一个元素
empty()如果队列空则返回真
front()返回第一个元素
pop()删除第一个元素
push()在末尾加入一个元素
size()返回队列中元素的个数
queue入队,如例:q.push(x); 将x 接到队列的末端。
queue出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
访问queue队首元素,如例:q.front(),即最早被压入队列的元素。
访问queue队尾元素,如例:q.back(),即最后被压入队列的元素。
判断queue队列空,如例:q.empty(),当队列空时,返回true。
访问队列中的元素个数,如例:q.size()

STACK基本操作
stack 的基本操作有:
入栈,如例:s.push(x);
出栈,如例:s.pop();注意,出栈操作只是删除栈顶元素,并不返回该元素。
访问栈顶,如例:s.top()
判断栈空,如例:s.empty(),当栈空时,返回true。
访问栈中的元素个数,如例:s.size()

MAP基本操作:
pair类型是在有文件utility中定义的,pair类型包含了两个数据值,通常有以下的一些定义和初始化的一些方法:
pair

/** 
 * DFS核心伪代码 
 * 前置条件是visit数组全部设置成false 
 * @param n 当前开始搜索的节点 
 * @param d 当前到达的深度 
 * @return 是否有解 
 */  
bool DFS(Node n, int d){  
    if (isEnd(n, d)){//一旦搜索深度到达一个结束状态,就返回true  
        return true;  
    }  

    for (Node nextNode in n){//遍历n相邻的节点nextNode  
        if (!visit[nextNode]){//  
            visit[nextNode] = true;//在下一步搜索中,nextNode不能再次出现  
            if (DFS(nextNode, d+1)){//如果搜索出有解  
                //做些其他事情,例如记录结果深度等  
                return true;  
            }  

            //重新设置成false,因为它有可能出现在下一次搜索的别的路径中  
            visit[nextNode] = false;  
        }  
    }  
    return false;//本次搜索无解  
}  

该模板转载自:rapheal@iteye

bfs模板:

/** 
 * 广度优先搜索 
 * @param Vs 起点 
 * @param Vd 终点 
 */  
bool BFS(Node& Vs, Node& Vd){  
    queue Q;  
    Node Vn, Vw;  
    int i;  

    //初始状态将起点放进队列Q  
    Q.push(Vs);  
    hash(Vw) = true;//设置节点已经访问过了!  

    while (!Q.empty()){//队列不为空,继续搜索!  
        //取出队列的头Vn  
        Vn = Q.front();  

        //从队列中移除  
        Q.pop();  

        while(Vw = Vn通过某规则能够到达的节点){  
            if (Vw == Vd){//找到终点了!  
                //把路径记录,这里没给出解法  
                return true;//返回  
            }  

            if (isValid(Vw) && !visit[Vw]){  
                //Vw是一个合法的节点并且为白色节点  
                Q.push(Vw);//加入队列Q  
                hash(Vw) = true;//设置节点颜色  
            }  
        }  
    }  
    return false;//无解  
}  

该模板转载自:raphealguo@CSDN:http://blog.csdn.net/raphealguo

找两道题做做?

哎。。太菜了。。找一道入门题做做
dfs入门题:
Description:
现给定一个含有n个元素的数组A,要求:从这n个数中选择一些数,这些数的和恰好为k
Input:
多组测试数据。第一行为n(1<=n<=20) 第二行为n个整数,每个数的范围为(-10^8≤A[i]≤10^8) 第三行为整数k(-10^8≤k≤10^8).
Output:
如果能够达到目的,输出”Of course,I can!”; 否则输出”Sorry,I can’t!”.
Sample Input:
4
1 2 4 7
13
4
1 2 4 7
15
Sample Output:
Of course,I can!
Sorry,I can’t!

#include 
#include
#include
#include
using namespace std;
int k,n;
int aa;
int a[1010];
int book[1010];
int dfs(int i, int sum)
{
    if(i < n && book[i] == 0)
    {
        book[i] = 1;

        if(sum + a[i] == k)return aa = 1;
        else if(sum + a[i] < k)
        {
            dfs(i + 1, sum);
            dfs(i + 1, sum + a[i]);
        }
        else
            dfs(i + 1, sum);
        book[i] = 0;//**回溯时取消标记**
    }
    return aa;
}
int main()
{

    while(scanf("%d",&n)!=EOF)
    {
        for(int i = 0; i < n; i ++)
            scanf("%d",&a[i]);
        scanf("%d",&k); aa = 0;
        memset(book,0,sizeof(book));
        if(dfs(0, 0))cout<<"Of course,I can!"<if(!dfs(0, 0)) cout<<"Sorry,I can't!"<return 0;
}

基础题目,思路简单(还是做了好久啊。。),记得利用好标记,就是回过头来复习复习。。。

然后是dfs基础题目:
之前做过的一道电梯问题。。复习一下吧
There is a strange lift.The lift can stop can at every floor as you want, and there is a number Ki(0 <= Ki <= N) on every floor.The lift have just two buttons: up and down.When you at floor i,if you press the button “UP” , you will go up Ki floor,i.e,you will go to the i+Ki th floor,as the same, if you press the button “DOWN” , you will go down Ki floor,i.e,you will go to the i-Ki th floor. Of course, the lift can’t go up high than N,and can’t go down lower than 1. For example, there is a buliding with 5 floors, and k1 = 3, k2 = 3,k3 = 1,k4 = 2, k5 = 5.Begining from the 1 st floor,you can press the button “UP”, and you’ll go up to the 4 th floor,and if you press the button “DOWN”, the lift can’t do it, because it can’t go down to the -2 th floor,as you know ,the -2 th floor isn’t exist.
Here comes the problem: when you are on floor A,and you want to go to floor B,how many times at least he has to press the button “UP” or “DOWN”?
Input
The input consists of several test cases.,Each test case contains two lines.
The first line contains three integers N ,A,B( 1 <= N,A,B <= 200) which describe above,The second line consist N integers k1,k2,….kn.
A single 0 indicate the end of the input.
Output
For each case of the input output a interger, the least times you have to press the button when you on floor A,and you want to go to floor B.If you can’t reach floor B,printf “-1”.
Sample Input
5 1 5
3 3 1 2 5
0
Sample Output
3

#include 
#include 
#include 
#include 

using namespace std;
int a[300];
int boo[1000];
int b[300];
int n;
int bfs(int s,int e)
{
    int h;
    memset(b,0,sizeof(0));
    boo[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty())
    {
        h=q.front();
        q.pop();
        if(h==e)return b[h];

        if(h+a[h]<=n&&boo[h+a[h]]==0)
        {
            b[h+a[h]]=b[h]+1;
            boo[h+a[h]]=1;
            q.push(h+a[h]);
        }
        if(h-a[h]>=1&&boo[h-a[h]]==0)
        {
            b[h-a[h]]=b[h]+1;
            boo[h-a[h]]=1;
            q.push(h-a[h]);
        }
    }
    return -1;
}

int main()
{
    int s,e;
    while(cin>>n)
    {
        if(n==0)break;
        cin>>s>>e;
        memset(boo,0,sizeof(boo));
        memset(b,0,sizeof(b));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        cout<return 0;
}

多积累,我还是太菜了,这种问题都要琢磨半天。

接下来就是并查集了吧,听强哥说这是找老大的问题,就是这样吧。。。

主要是定义 find + join 两个函数。。。带权并查集还不是很会,简单的并查集还可以

直接上题吧。。。
还是基础题,感觉并查集是一个套路的呢
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
Input
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最少还需要建设的道路数目。
Sample Input
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
Sample Output
1
0
2
998

Huge input, scanf is recommended.
先写一个之前看大佬写的非常规的
把所有人的父亲都定义为 -1
然后join find。。。
最后所有“老大”的父亲都是 -1,不是老大的人的父亲不是-1
此时只要找有多少-1就知道有多少老大了,很神奇。。。

#include 
#include 
#include 
using namespace std;
int pre[10000];
int find(int a)
{
    if(pre[a]==-1) return a;
    return pre[a] = find(pre[a]);
}
void join(int b,int c)
{
    int i=find(b);
    int j=find(c);
    if(i!=j) pre[i]=j;
}

int main()
{
    int m,n,i,a,b,sum;
    while(scanf("%d",&n)!=EOF&&n!=0)
    {
        scanf("%d",&m);
        for(i = 1; i <= n; i ++)pre[i]=-1;
        while(m--)
        {
            scanf("%d %d",&a,&b);
            join(a,b);
        }
        sum=0;
        for(i = 1;i<=n;i++)
            if(pre[i]==-1)
            sum++;
        printf("%d\n",sum-1);
    }
    return 0;
}

然后常规写法
所有人的父亲开始都是自己
其实和上面的一样。。没什么不一样吧。。就看看自己的父亲还是不是自己就好了。。其实完全一样的。。
懒得写了从网上找个代码吧,。。。

#include  
using namespace std;  

const int MAX=1000;  
int father[MAX];  

void initial(int n)    //初始化  
{  
    for(int i=1;i<=n;i++)  
        father[i]=i;  
}  

int find(int x)    //查找  
{  
    while(father[x]!=x)  
        x=father[x];  

    return x;  
}  

void combine(int a,int b)   //合并  
{  
    int tmpa=find(a);  
    int tmpb=find(b);  

    if(tmpa!=tmpb)  
        father[tmpa]=tmpb;  
}  

int main()  
{  
    int i,n,m,a,b,tmp;  

    while(cin>>n,n)  
    {  
        initial(n);  

        cin>>m;  

        for(i=1;i<=m;i++)  
        {  
            cin>>a>>b;  
            combine(a,b);  
        }  

        tmp=0;  
        for(i=1;i<=n;i++)   //确定连通分量个数  
        {  
            if(father[i]==i)  
                tmp++;  
        }  

        cout<1<return 0;  
}  

你看我就说一样吧。。。

带权并查集好想要用到向量。。。然后就是矢量关系。。然后过一阵再学学,先不写这个。。

然后就是最近讲的快速幂了
这真的是不难实现。。但是想用精应该也是不容易(因为我见过比较变态的题)
基本实现方法:
以下以求a的b次方来介绍[1]
把b转换成二进制数。
该二进制数第i位的权为
2^(i-1)

例如

11的二进制是1011
11 = 2³×1 + 2²×0 + 2¹×1 + 2º×1
因此,我们将a¹¹转化为算 a^2^0 + a^2^1 + a^2^3

b & 1//取b二进制的最低位,判断和1是否相同,相同返回1,否则返回0,可用于判断奇偶

b>>1//把b的二进制右移一位,即去掉其二进制位的最低位

快速幂有递归实现,但是就学最快的最简洁就好了吧

int pow(int a,int b){
  int r=1,base=a;
  while(b){
    if(b&1) r*=base;
    base*=base;
    b>>=1;
  }
  return r;
}

然后听强哥的,数比较大的话能取模时就取模。。。

写的是不是有点简单了??
不管了反正还有一个就写完了。。。

欧几里得算法:

欧几里德算法又称辗转相除法,是指用于计算两个正整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)
其实记住这个公式就好了吧,但是理解原理更好,所以附上证明:

证法一
a可以表示成a = kb + r(a,b,k,r皆为正整数,且r

//功能:利用欧几里德算法,求整数a,b的最大公约数
//参数:整数a,b
//返回:a,b的最大公约数
int gcd(int a, int b){
if(a < b){ //保证a大于等于b,便于a%b的运算
int temp;
temp = a;
a = b;
b = temp;
}
while(a % b){ //如果余数不为0,就一直进行辗转相除
int r = a % b; //r为a和b的余数,即r = a mod(b);
a = b;
b = r;
r = a % b;
}
return b;
}

也算比较简单?
但是扩展欧几里得算法就不太懂了。。。
其实也能懂,不知道怎么才能用得到而已。。。
强哥只讲了用它解方程。
那就用它来解方程吧!

求解 x,y的方法的理解
设 a>b。
1,显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;
2,a>b>0 时
设 ax1+ by1= gcd(a,b);
bx2+ (a mod b)y2= gcd(b,a mod b);
根据朴素的欧几里德原理有 gcd(a,b) = gcd(b,a mod b);
则:ax1+ by1= bx2+ (a mod b)y2;
即:ax1+ by1= bx2+ (a - [a / b] * b)y2=ay2+ bx2- [a / b] * by2;
说明: a-[a/b]*b即为mod运算。[a/b]代表取小于a/b的最大整数。
也就是ax1+ by1 == ay2+ b(x2- [a / b] *y2);
根据恒等定理得:x1=y2; y1=x2- [a / b] *y2;
这样我们就得到了求解 x1,y1 的方法:x1,y1 的值基于 x2,y2.
上面的思想是以递归定义的,因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。
扩展欧几里德算法
扩展欧几里德算法是用来在已知a, b求解一组x,y使得ax+by = Gcd(a, b) =d(解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组,下面是一个使用C++的实现:

int exGcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    int r=exGcd(b,a%b,x,y);
    int t=x;x=y;y=t-a/b*y;
    return r;
}

emmm
END。。。

终于搞完了?多看几遍多看几遍多看几遍。。。
高数还没学好就来搞机,搞机也没搞好,很伤心
伤心归伤心,还是要接着搞啊

这学期师哥不讲课了,接下来多搞高数吧,因为一直在搞机,高数怕是要挂科了。。。

真叫人头大
!!!

你可能感兴趣的:(c语言,stl,bfs,dfs,欧几里得算法及其扩展,算法)