算法 - webdancer's Blog

回溯法

回溯法:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。

回溯算法:

 

  1. 形式化解的结构,根据形式描述,建立解树。解树的每个非叶子节点描述一类可行解,而叶子节点通常描述一个可行解。
  2. 利用深度优先搜索解空间。算法根据目标函数,确定节点所代表解的优化程度,当访问叶子节点时,解就唯一的确定了;访问中间节点时还没法唯一的确定,无法计算目标函数值。利用启发函数和分支定界,避免移动到不可能产生解的子空间。问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。

解空间:

回溯算法的解空间一般分为:子集树和排列树。

子集树:二叉树来表示,较为熟悉。

排列树:第一层树有n个选择,第二层有n-1个选择,以此类推,直到叶子节点。

 

子集树和排列树都是“隐式图”。

 

子集树遍历的伪代码:

void backtrack(int  t)

{
     if(t>n)   
        output(x);
     else
        for(int i=0; i<=1; i++)
      {  
             x[t] = i; 
             backtrack(t+1);
       }  
}

排列树遍历的伪代码:

     void backtrack(int  t)

    {
       if(t>n)  

           output(x);
       else      

          for(int i=t; i<=n; i++)
          {
            swap(x[t], x[i]);
            backtrace(t+1);
            swap(x[t], x[i]);       

         }     

   }

 

 

 

 

0/1背包的非递归动态规划算法

上次记录了,0/1背包问题动态规划的递归实现,下面记录一下也是动态规划的非递归实现。主要是对f函数结果的记录,使得不用像递归中的重复计算。

 

#include<stdio.h>
#include<stdlib.h>

#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
#define N 3
#define C 116

int p[N+1];
int w[N+1];
int f[N+1][C+1];
int x[N+1];
void bag(){
    int y,ymax,i;

    ymax=min(w[N]-1,C);
    for(y=0;y<ymax;y++)
        f[N][y]=0;
    for(y=w[N];y<C;y++)
        f[N][y]=p[N];

    for(i=N-1;i>1;i--){
        ymax=min(w[i]-1,C);
        for(y=0;y<ymax;y++)
            f[i][y]=f[i+1][y];
        for(y=w[N];y<C;y++)
            f[i][y]=max(f[i+1][y],f[i+1][y-w[i]]+p[i]);
    }
    f[1][C]=f[2][C];
    
    if(C>w[1])
        f[1][C]=max(f[2][C],f[2][C-w[1]]+p[1]);
}

void traceback(){
    int i,c;
    
    c=C;
    for(i=1;i<N;i++)
        if(f[i][c]==f[i+1][c])
            x[i]=0;
        else{
            x[i]=1;
            c-=w[i];
        }
        x[N]=(f[1][C])?1:0;
    }

int main(){
    w[1]=100;
    w[2]=14;
    w[3]=10;
    p[1]=20;
    p[2]=18;
    p[3]=15;
    bag();
    printf("the max :%d\n",f[1][C]);
    traceback();
    int i;
    for(i=1;i<=N;i++)
        printf("%d ",x[i]);
    printf("\n");
    return 0;
}
    

时间复杂度:bag是:O(nc)。如果c很大,则时间要比递归的长。

0/1背包问题

问题:背包容量c,从n个物品中选取装入背包,每个物品i的重量为wi,价值pi。在背包中物品不超过c的条件下,求装入背包中的。

分析:采用动态规划算法,假设f(i , y):表示背包剩余容量为y,剩余物品为i,i+1,。。。,n。则状态转移方程为:

f(i , y)=max{f(i+1 ,y) , f(i+1 ,y-wi)+pi}  if y>=wi;  

f(i , y)=f(i+1 , y) if y<wi;

代码如下:

#include<stdio.h>

#define N 3
int w[N+1];
int p[N+1];

int max(int x ,int y){
    return (x>y)?x:y;
}

int maxf(int i,int y){
   if(i==N)
        return (y>=w[N])?p[N]:0;
   if(y<w[i])
       return maxf(i+1,y);
   else
       return max(maxf(i+1,y),maxf(i+1,y-w[i])+p[i]);
}

int main(){
    int c;

    c=116;
    w[1]=100;
    w[2]=14;
    w[3]=10;
    p[1]=20;
    p[2]=18;
    p[3]=15;
    printf("the max :%d\n",maxf(1,c));

    return 0;
}

时间复杂度:O($$2^n$$)。 

由于使用了递归,空间复杂度也很大。

子数组的最大和

 

题目:输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
分析:由于数组里有正数也有负数,所以不存在全为负数情况;所以,依据+正数变大,+负数变小的原理。代码如下:
#include<stdio.h>

int maxsubarr(int a[],int n){
    int i,cur,max;
    
    cur=0;
    max=0;
    for(i=0;i<n;i++){
        cur+=a[i];
        if(cur>max)
            max=cur;
        if(cur<0)
            cur=0;
    }
    return max;
}

int main(){
    int a[]={-1,0,-2,1,2,3,4,-2,3,4,-1,2,4};
    int n=sizeof a/sizeof a[0];
    printf("the maxsum of the sub:%d\n",maxsubarr(a,n));

    return 0;
}

 

拓扑排序2

    前一篇,学习了根据DFS,来进行拓扑排序的方法;今天看一下,按照贪婪算法来进行拓扑排序。主要的思想:找没有入度的顶点,放到队列里面;然后,逐渐从队列里删除顶点,加入一个数组,如果产生了新的没有入度的顶点,也加入队列,重复直到队列为空。

 

#include<stdio.h>
#include<stdlib.h>
#include"graph.c"
#include"queue.c"
#define MAX 100

int indeg[MAX];
int vl[MAX];
int *q;
void topolsort(Graph g){
    int i,nv;

    nv=g->nv;
    for(i=0;i<nv;i++){
        struct node * vnode;
        vnode=g->adj[i];
        while(vnode){
            int u;
            u=vnode->v;
            indeg[u]++;
            vnode=vnode->next;
        }
    }
    q=initq(nv);
    
    for(i=0;i<nv;i++)
        if(indeg[i]==0)
            add(q,i);
            
    int iv;
    iv=0;
    while(!isempty()){
        int u;
        u=delete(q);
        vl[iv++]=u;

        struct node * unode;
        unode=g->adj[u];
        while(unode){
            int v;
            v=unode->v;
            if(!(--indeg[v]))
                add(q,v);
            unode=unode->next;
        }
    }
     
}

int main(){
    Graph  g;
    int n=6;
    g=init(n);

    edge e[8];
    e[0].u=0;e[0].v=2;
    e[1].u=0;e[1].v=3;
    e[2].u=1;e[2].v=3;
    e[3].u=1;e[3].v=4;
    e[4].u=2;e[4].v=3;
    e[5].u=2;e[5].v=5;
    e[6].u=3;e[6].v=5;
    e[7].u=4;e[6].v=5;


    int i;
    for(i=0;i<g->ne;i++)
        insert(g,e[i]);
    topolsort(g);
    for(i=0;i<g->nv;i++)
      printf("%d ",vl[i]);
    printf("\n");
    return 0;
}

时间复杂度分析:有向图的表示采用了邻接链表,所以时间复杂度为:Θ(V+E)。 

拓扑排序

   对一个DAG进行拓扑排序,结果为一个所有顶点构成的线性序列。序列满足:

         对于<u,v>∈E(G),在序列中U出现在V的前面。

在算法导论中,给出的算法是:对DAG进行DFS遍历,计算每个顶点结束的时间,然后每完成一个顶点,将其加入到链表的头部。结果就是链表中的序列。

   因此,对前面的DFS实现,稍加修改就可以了。此外,注意需要对原来的无向图实现修改。

代码:

 

#include<stdio.h>
#include"search.c"
extern int f[MAXV];
int * vq;
void reverse(int * a,int n){
    int i,j;
    for(i=1,j=n-1;i<j;i++,j--){
        int t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
void dfsv1(Graph g,int u){
    color[u]=GRAY;
    time++;
    d[u]=time;
    struct node * vnode;
    vnode=g->adj[u];
    while(vnode){
        int vv;
        vv=vnode->v;
        if(color[vv]==WHITE){
            p[vv]=u;
            dfsv1(g,vv);
        }
        vnode=vnode->next;
    }
    color[u]=BLACK;
    time++;
    f[u]=time;
    add(vq,u);
    //printf("%d  ",u);
}
void topolsort(Graph g){
    int i,v;
    
    v=g->nv;
    for(i=0;i<v;i++){
        color[i]=WHITE;
        p[i]=-1;
    }
    vq=initq(NUM);
    time=0;
    for(i=0;i<v;i++)
        if(color[i]==WHITE){
            dfsv1(g,i);
        }
    reverse(vq,v+1);    
}

int main(){
    Graph  g;
    int n=5;
    g=init(n);

    edge e[7];
    e[0].u=0;e[0].v=1;
    e[1].u=0;e[1].v=3;
    e[2].u=1;e[2].v=2;
    e[3].u=1;e[3].v=3;
    e[4].u=1;e[4].v=4;
    e[5].u=4;e[5].v=2;
    e[6].u=3;e[6].v=4;

    int i;
    for(i=0;i<7;i++)
        insert(g,e[i]);
    topolsort(g);
    for(i=1;i<=g->nv;i++)
        printf("%d ",vq[i]);
    printf("\n");
    return 0;
}

找出数组中重复次数最多的元素并打印

    题目:找印。

这道题其实在做汇编实验时,已经做过了。但是当时的思路还是太简单,现在又想了一下,可以先排序,然后遍历。这样如果采用快排,时间复杂度应为:O(nlogn+n)。代码:

 

#include<stdio.h>
#include<stdlib.h>

int int_compare(const void * x1,const void * x2){
	int * a=(int *)x1;
	int * b=(int *)x2;
	return (*a-*b);
}

void findremax(int a[],int n,int *m,int *c){
	int index,count,maxc,i;

	index=0;
	count=1;
	maxc=count;
	//printf("%d\n",(sizeof a )/(sizeof a[0])); Error
	for(i=1;i<n;i++){
		if(a[i]==a[i-1])
			count++;
		else{
			if(count>maxc){
				maxc=count;
				index=i-1;
			}
			count=1;
		}
	}
	if(count>maxc){
		maxc=count;
		index=i-1;
	}

	*m=index;
	*c=maxc;
	
}

int main(){
	int maxindex,c;
	int a[]={1,1,2,2,3,3,3,3,4,5,6,7,7,7,7,7,7};
	int n=(sizeof a)/(sizeof a[0]);
	maxindex=0;
	c=0;
	qsort(a,n,sizeof(a[0]),int_compare);
	findremax(a,n,&maxindex,&c);
	printf("%d appears %d times.\n",a[maxindex],c);

	return 0;
}

总结:1.排序的应用,排序可以用来对相同的元素分类,使他们相邻,从而解决问题。

         2.语言问题:在代码中注释掉的://printf("%d\n",(sizeof a )/(sizeof a[0])); Error。没有注意到:在函数中,a是个指针,而不是数组,分了错误,所以:在32位的机器上:(sizeof a )/(sizeof a[0])=1。

图的搜索

   最近学习图论,课程片中理论。但是,觉得还是实现一下好。就根据书上的写了图的BFS和DFS搜索算法。

1.图的实现,邻接链表。

 

#include<stdlib.h>

typedef struct {int u;int v;} edge;
struct node{int v;struct node * next;}; 
struct graph{int nv;int ne;struct node ** adj;};
typedef struct graph * Graph;

Graph init(int n){
    Graph g=malloc(sizeof *g);// sizeof graph

    g->nv=n;
    g->ne=0;
    g->adj=malloc((sizeof *(g->adj))*n);
    int i;
    for(i=0;i<g->nv;i++)
        g->adj[i]=NULL;
    
    return g;
}

void insert(Graph g,edge e){
    int u,v;

    u=e.u;
    v=e.v;
    
    struct node* unode=malloc(sizeof *unode);
    unode->v=v;
    unode->next=g->adj[u];

    g->adj[u]=unode;

    struct node* vnode=malloc(sizeof *vnode);
    vnode->v=u;
    vnode->next=g->adj[v];

    g->adj[v]=vnode;

    g->ne++;
}

2.队列实现。

 

#include<stdlib.h>

static int maxsize=0;
static int front=0;
static int rear=0;
int * queue;

int * initq(int max){
    maxsize=max+1;
    queue=malloc(sizeof(int)*maxsize);
    front=rear=0;
    return queue;
}

int add(int * q,int v){
    if((rear+1)%maxsize==front)
        return -1;
    rear=(rear+1)%maxsize;
    q[rear]=v;
    return v;
}

int delete(int *q){
    if(front==rear)
        return -1;
    front=(front+1)%maxsize;
    return q[front];
}

int isempty(){
    return (front==rear)?1:0;
}

3.搜索算法。

 

#include<stdlib.h>
#include<stdio.h>
#include"graph.c"
#include"queue.c"
/*bfs,dfs algos. from "introduction to algos".
*/

#define MAXV 1000   
#define BIG  (MAXV+1)
#define NUM 100
int color[MAXV];
int d[MAXV];
int p[MAXV];
int f[MAXV];
enum cl {WHITE,GRAY,BLACK};
int * q;
int time;

void bfs(Graph g,int s){
    int i;

    for(i=0;i<g->ne;i++){
        color[i]=WHITE;
        d[i]=-1;
        p[i]=-1;
    }
    color[s]=GRAY;
    d[s]=0;
    p[s]=-1;

    q=initq(NUM);
    add(q,s);

    while(!isempty()){
        int u;
        u=delete(q);
        printf("%d u \n",u);
        struct node * vnode=g->adj[u];
        while(vnode){
            int v;
            v=vnode->v;
            if(color[v]==WHITE){
                color[v]=GRAY;
                d[v]=d[u]+1;
                p[v]=u;
                add(q,v);
            }
            vnode=vnode->next;
        }
        color[u]=BLACK;
    }
}

void dfsv(Graph g,int u){
    color[u]=GRAY;
    time++;
    d[u]=time;
    struct node * vnode;
    vnode=g->adj[u];
    while(vnode){
        int vv;
        vv=vnode->v;
        if(color[vv]==WHITE){
            p[vv]=u;
            dfsv(g,vv);
        }
        vnode=vnode->next;
    }
    color[u]=BLACK;
    time++;
    f[u]=time;
}

void dfs(Graph g){
    int i,v;

    v=g->nv;
    for(i=0;i<v;i++){
        color[i]=WHITE;
        p[i]=-1;
    }
    time=0;
    for(i=0;i<v;i++){
        if(color[i]==WHITE)
            dfsv(g,i);
    }
}


void printpath(Graph g,int s,int v){
    if(v==s)
        printf("%d ",s);
    else if(p[v]==-1)
        printf("no path from s to v\n");
    else
        {
            printpath(g,s,p[v]);
            printf("%d ",v);
        }
}

int main(){
    Graph  g;
    int n=5;
    g=init(n);

    edge e[7];
    e[0].u=0;e[0].v=1;
    e[1].u=0;e[1].v=3;
    e[2].u=1;e[2].v=2;
    e[3].u=1;e[3].v=3;
    e[4].u=1;e[4].v=4;
    e[5].u=2;e[5].v=4;
    e[6].u=3;e[6].v=4;

    int i;
    for(i=0;i<7;i++)
        insert(g,e[i]);
    bfs(g,0);
    printpath(g,0,4);
    printf("\n");
    dfs(g);
    printpath(g,0,4);
    printf("\n");
    
    return 0;
}

CODE过程中,指针的出错还是有,一定要注意动态分配内存的问题,在这个地方很容易出错。还有就是指向指针的指针问题。对于C来说,指针部分得多练多想呀!不知道,谁有好的理解。




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee