目录

一.算法概述

1.算法的定义与特性

2.数学证明法

3.算法复杂性分析方法

4.渐进分析

二.递归与分治策略

1.递归概念

2.递归算法设计示例

3.递归算法分析

4.分治基本思想

5.分治算法设计示例

三.动态规划

四.贪心算法

五.回溯法

1.基本概念

2.回溯法解空间树

3.回溯算法设计框架

4.回溯算法示例--子集树

5.回溯算法示例--排列树

六.分支限界法

七.随机算法


一.算法概述

算法设计的优劣决定软件系统的性能。算法设计与分析的任务:

①设计阶段

该阶段的主要任务:对给定问题,如何设计解决此问题的有效算法。

②分析阶段

研究算法质量判定准则。

引例:求两正整数m,n的最大公约数

int gcd(int m,int n){int r;while(n!=0){r=m%n;m=n;n=r;}return m;
} 

递归实现:

int gcd(int m,int n)
{return (m%n==0)?n:gcd(n,m%n);} 

1.算法的定义与特性

Def:算法是有规则的有限集合,是为解决特定问题而规定的一系列操作。

特性:(1)有限性:一个算法必须保证执行有限步后结束;

(2)确定性:算法的每一步必须有确切的定义,不能有歧义;

(3)可行性:算法原则上能精确地运行,在现有条件下是可以实现的;

(4)输入:一个算法有0个或多个输入,以刻画运算对象的初始状态;

(5)输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。

2.数学证明法

1.反证法:

例:证明希尔排序的稳定性

证明:假设希尔排序是稳定的,对于 2,1,1,4排序结果,两个1的顺序发生了变化,故希尔排序是不稳定的算法。

2.数学归纳法:

例:证明前n个奇数的和为n^2,即求证 (2i-1)=    n^2。

证明:当i=1时,有1=1^2;

设i=n-1,有         (2i-1)     =    (n-1)^2;

当i=n时,有         (2i-1)=      (2i-1)+2n-1      =(n-1)^2+2n-1     =n^2。

3.算法复杂性分析方法

1.时间复杂度

排序:O(1) < O(logn)< O(n) < O(nlogn) < O(n^2 ) < O(n^3 ) < O(2^n ) < O(n!) <O(n^n )

时间复杂度分析步骤:
(1)找算法中的基本语句(执行频率最高);
(2)计算其执行次数,整理成问题规模n的函数;
(3)保留最高次幂,作为渐进时间复杂度。

例1:

时间复杂度:

(1)基本语句:while

(2)执行次数:设while循环语

句执行的次数为m,i从1开始递

增,最后取值为1+2m,有:

i=1+2m>n

即m>(n-1)/2

(3)T(n)=O(n)

例2:
T(n)=O(

例3:

T(n)=O(nlogn)

2. 空间复杂度:

在对算法进行存储空间分析时, 只考察临时变量所占空间 。 
递归算法空间复杂度=每层需要的辅助空间*递归深度

4.渐进分析

当问题规模很大,且趋于无穷时对算法性能的分析。

1.函数阶渐进形态的3种表示法

(1)低阶O:若存在一个正常数C和n0,对所有的n>n0,都有f(n)≤Cg(n),则记作f=O(g),即f的阶不高于g的阶;

(2)高阶Ω:若存在一个正常数C和n0,使得n≥n0,f(n)≥Cg(n),记作f=Ω(g),即f的阶高于g的阶;

(3)等阶:当且仅当f(n)=O(g(n)),且f(n)=Ω(g(n))时,f与g同阶。

2.阶的证明方法

(1)反证法

例:对于n^3,n^2,证明n^3不是O(n^2)

证明:假设n^3是O(n^2),则有n^3≤Cn^2,  但对于n≥n0,n≤C,不成立,所以假设不成立。

(2)极限法:

3.求函数的渐进表达式

  1. 将函数中所有的加法项常数都去掉。

  2. 在修改后的函数中,只保留最高阶项。

  3. 如果最高阶项存在,那么去除高阶项前面的系数。

例题:

求函数的渐进表达式。

(1) 3n^2+10n        O(n^2)

(2) n^2/100+2^n     O(2^n)

(3) 21+1/n           O(1)

(4) logn 3          O(logn)

(5) 10log3^n      O(n)

二.递归与分治策略

1.递归概念

Def:函数,过程,子程序在运行过程中直接或间接调用自身而产生的重入现象。

引例1:设计求n!(n为正整数)的递归算法。

int fun(int n){if(n==1){return 1;}else{return fun(n-1)*n;}
} 
引例2:求一个不带头结点的单链表L的所有data域(假设为int型)之和的递归 算法如下:
int Sum(LinkList *L)
{ if (L==NULL)return 0;else return(L->data+Sum(L->next));
}

2.递归算法设计示例

1.集合全排列问题

思想:将求取1234(4个元素)全排列的问题转换为:分别取1234交换到第一个位置作为“开头”,求取剩余元素(3个)的全排列问题(递归体)当只有一个元素的时候其全排列就是该元素本身(递归出口)。

/在list数组中产生从元素k~m(下标从0开始)的全排列:

void Perm(int list[], int k, int m){if(k==m) {for(int i=0;i<=m;i++)cout<<list[i]<<" ";cout<<endl;}else{for(int j=k;j<=m;j++){swap(list[k],list[j]);Perm(list,k+1,m);swap(list[k],list[j]);}}
}

2.整数划分问题(把一个正整数n表示成一系列正整数之和)

int split(int n,int m)
{if(n==1||m==1) return 1;else if (n<m) return split(n,n);else if(n==m) return split(n,n-1)+1;else return split(n,m-1)+split(n-m,m);
}

3.选择排序的递归算法

void SelectSort(int a[],int i,int n){  int j,min;if (i==n) return; //递归出口else{ min=i; for (j=i+1;j<=n;j++){if(a[j]<a[min])min=j;}   if (min!=i){swap(a[i],a[min]);}SelectSort(a,i+1,n);}
}

4.冒泡排序的递归算法

void BubbleSort(int a[],int i,int n)
{   int j;bool exchange;if (i==n) return; //递归出口else{ exchange=false; //置exchange为falsefor(j=0;j<=n-i;j++){if(a[j]>a[j+1]){swap(a[j],a[j+1]);exchange=true;}}if(exchange==false) //未发生交换时直接返回   return;elseBubbleSort(a,i+1,n);}
}

3.递归算法分析

当一个算法包含对自身的递归调用时,该算法的运行时间复杂度可用递归方程进行描述。

1.替换法

根据递归规律,将递归公式通过方程展开、反复代换子问题的规模变量,通过多项式整理,如此类推,从而得到递归方程的解。

例:

例:采用特征方程求解以下递归方程T(n)
        T(0)=0;T(1)=1;T(2)=2
        T(n)=T(n-1)+9T(n-2)-9T(n-3) 当n>2时

假设T(n)=x^n,将其代入递归方程

得:x^3-x^2-9x+9=0,解得x1=3,x2=-3,x3=1.

因此,通解为:f(n)=C1*3^n+C2*(-3)^n+C3.

根据初始条件T(0)=0,T(1)=1,T(2)=2.因此:T(n)=3^(n-1)-(-3)^n/12-1/4.

2.递归树法

例:

 PS:如何根据递归关系确定递归树的高度?

如果递归的形式为T(n)= aT(n / b)+ f(n),则树的深度为n的对数底b。

例如,2T(n / 2)+ n递归将具有深度为lg(n)的树(n的对数底数2)。

3.主方法

主方法(master method)提供了解如下形式递归方程的 一般方法:T(n)=aT(n/b)+f(n)

其中a≥1,b>1为常数,该方程描述了算法的执行时间,算法将规模为n的问题分解成a个子问题,每个子问题的大小为 n/b

例如,对于递归方程T(n)=3T(n/4)+n 2,有:a=3,b=4 ,f(n)=n 2 。

例:

4.分治基本思想

将问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

5.分治算法设计示例

1.快速排序

#include <iostream>using namespace std;const int N = 100010;int q[N];void quick_sort(int q[], int l, int r)
{if (l >= r) return;int i = l - 1, j = r + 1, x = q[l + r >> 1];while (i < j){do i ++ ; while (q[i] < x);do j -- ; while (q[j] > x);if (i < j) swap(q[i], q[j]);}quick_sort(q, l, j);quick_sort(q, j + 1, r);
}

2.归并排序

void merge(int a[],int left,int mid,int right){int i=left,j=mid+1,k=0;int *temp=(int *)malloc((right-left+1)*sizeof(int));while(i<=mid&&j<=right){if(a[i]<=a[j]){temp[k++]=a[i++];}else{temp[k++]=a[j++];}}while(i<=mid)temp[k++]=a[i++];while(j<=right)temp[k++]=a[j++];for(k=0,i=left;i<=right;k++,i++)a[i]=temp[k];free(temp);
}
void MergeSort(int a[],int left,int right){if(left<right){int mid=(left+right)>>1;MergeSort(a,left,mid);MergeSort(a,mid+1,right);merge(a,left,mid,right);}
}

3.查找最大元素和次大元素

思路:

void solve(int a[],int left,int right,int &max1,int &max2){if(left==right){max1=a[left];max2=-1;}else if(left==right-1){max1=max(a[left],a[right]);max2=min(a[left],a[right]);}else{int mid=left+right>>1;int lmax1,lmax2;solve(a,left,mid,lmax1,lmax2);int rmax1,rmax2;solve(a,mid+1,right,rmax1,rmax2);if(lmax1>rmax1){max1=lmax1;max2=max(lmax2,rmax1);}else{max1=rmax1;max2=max(lmax1,rmax2);}}
}

4.折半查找

int fun(int a[],int left,int right,int k){if(left<=right){int mid=left+right>>1;if(a[mid]==k)return mid;else if(a[mid]<k)return fun(a,mid+1,right,k);else return fun(a,left,mid-1,k);}return -1;
}

5.寻找序列中第k小元素

思路:

 对于序列a[s..t],在其中查找第k小元素的过程如下:

  将a[s]作为基准(即最小下标位置的元素为数轴)进行一次划分,

对应的划分位置下标为i。3种情况:

k=i-s+1 a[i]即为所求,返回a[i]。

k<i-s+1 ,第k小的元素应在a[s..i-1]子序列中,递归在

该子序列中求解并返回其结果。

k>i-s+1,第k小的元素应在a[i+1..t]子序列中,递归在该

子序列中求解并返回其结果。

int QuickSelect(int a[],int left,int right,int k){int i=left,j=right,temp;if(left<right){temp=a[left];while(i!=j){while(i<j&&a[j]>=temp)j--;a[i]=a[j];while(i<j&&a[i]<=temp)i++;a[j]=a[i];}a[i]=temp;if(i-left+1==k)return a[i];else if(i-left+1>k)return QuickSelect(a,left,i-1,k);else return QuickSelect(a,i+1,right,k-(i-left+1));}else if(left==right&&left==k-1)return a[k-1];
}

6.寻找两等长序列的中位数

对于含有n个元素的有序序列a[s..t],当n为奇数时,中

位数是出现在m=【(s+t)/2】处;当n为偶数时,中位数下标有

m=【(s+t)/2】(下中位)和m=【(s+t)/2】+1(上中位)两个。为了简

单,仅考虑中位数为m=【(s+t)/2】处。

int midNum(int a[],int l1,int r1,int b[],int l2,int r2)
{ //求两个有序序列a[s1..e1]和b[s2..e2]的中位数int m1,m2;if (l1==r1 && l2==r2) //两序列只有一个元素时返回较小者return a[l1]<b[l2]?a[l1]:b[l2];else{  m1=(l1+r1)/2; //求a的中位数位置m2=(l2+r2)/2; //求b的中位数位置if (a[m1]==b[m2]) //两中位数相等时返回该中位数return a[m1];if (a[m1]<b[m2]) //当a[m1]<b[m2]时{if((l1+r1)%2==0) l1=m1; //a取后半部分,奇数个元素else l1=m1+1; //偶数个元素r2=m2; //b取前半部分return midNum(a,l1,r1,b,l2,r2);}else //当a[m1]>b[m2]时{r1=m1; //a取前半部分if((l2+r2)%2==0) l2=m2; //b取后半部分,奇数个元素else l2=m2+1; //偶数个元素return midNum(a,l1,r1,b,l2,r2);}}
}

7.求最大连续子序列和

对于含有n个整数的序列a[0..n-1],若n=1,表示该序列仅含一

个元素,如果该元素大于0,则返回该元素;否则返回0。

n>1,采用分治法求解最大连续子序列时,取其中间位置mid=(n-

1)/2,该子序列只可能出现3个地方。

(1)该子序列完全落在半部即a[0..mid]中。采用递归求出其最大连续子

序列和maxLeftSum。

(2)该子序列完全落在右半部a[mid+1..n-1]中。采用递归求出其最

大连续子序列和maxRightSum。

(3)该子序列跨越序列a的中部而占据左右两部分。

max3 ( maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum )
long maxSubSum(int a[],int left,int right)
{int i,j;long maxleftsum,maxrightsum;long maxleftbordersum,leftbordersum;long maxrightbordersum,rightbordersum;if(left==right){if(a[left]>0){return a[left];}else{return 0;}}int mid=left+right>>1;maxleftsum=maxSubSum(a,left,mid);maxrightsum=maxSubSum(a,mid+1,right);maxleftbordersum=0,leftbordersum=0;for(i=mid;i>left;i--){leftbordersum+=a[i];if(leftbordersum>maxleftbordersum)maxleftbordersum=leftbordersum;}maxrightbordersum=0,rightbordersum=0;for(j=mid+1;j<=right;j++){rightbordersum+=a[j];if(rightbordersum>maxrightbordersum)maxrightbordersum=rightbordersum;}return max(max(maxleftsum,maxrightsum),maxrightbordersum+maxleftbordersum);
}

三.动态规划

四.贪心算法

五.回溯法

1.基本概念

搜索法:是利用计算机的高性能来有目的地枚举一个问题的部分或所有可能情况,从而找到问题的解的一种算法设计方法

搜索法的搜索策略:①穷举搜索,②深度优先搜索(回溯法),③广度优先搜索(分支限界法)。

回溯法的本质是 先序遍历一棵状态树 过程,状态树不是遍 历前预先建立,而是隐含在遍历过程中,使用它可以避免 穷举式搜索,适用于解一些 排列和组合数相当大 的问题。

回溯法解空间:问题的解由解向量X={x1,x2, … ,xn}组成,其中分量xi表示第i步的操作(确定解向量第i个分量xi )。

• 所有满足约束条件的解向量组构成了问题的解空间。

• 问题的解空间一般用树形式来组织,也称为解空间树或状态空间,树中的每一个结点确定所求解问题的一个问题状态。

•当搜索到第n+1层时,即到了叶子结点,则找到了问题的一个解。

2.回溯法解空间树

1.子集树

所给问题是从n个元素的集合中找出满足某种性质的子集时,相应的解空间树称为子集树。

2.排列树

例如,旅行售货员问题:一售货员周游若干城市销售商品,已知各个城市之间的路程,选择一个路线,使其经过每个城市仅一次,最后返回原地,并且总路程最小。

3.满M叉树

当所给问题的n个元素中每一个元素均有m种选择,要求确定其中的一种选择,使得对这n个元素的选择结果组成的向量满足某种性质,即寻找满足某种特性的n个元素取值的一种组合。这类问题的解空间树称为满m叉树。

3.回溯算法设计框架

回溯法是对解空间树的深度优先搜索法,通常有两种实现的算法。

递归回溯:采用递归的方法对解空间树进行深度优先 遍历来实现回溯。

迭代回溯:采用非递归迭代过程对解空间树进行深度 优先遍历来实现回溯。

1.子集树和m叉树的算法框架

【例】有一个含n个整数的数组a,所有元素均不相同,设计一个算法求其所有子集

【算法思想】 解:显然本问题的解空间为子集树,每个元素只有两种扩 展,要么选择,要么不选择。 采用深度优先搜索思路。

解向量为x[],x[i]=0表示不选择a[i], x[i]=1表示选择a[i]。

i扫描数组a,也就是说问题的初始状态是(i=1,x的元素均为0),目标状 态是(i=n+1x为一个解)。从状态(ix)可以扩展出两个状态:不选择a[i]元素下一个状态为(i+1,x[i]=0)。 选择a[i]元素下一个状态为(i+1,x[i]=1)。

void display(int a[],int n,int x[]){cout<<"{";for(int i=0;i<n;i++){if(x[i]==1)cout<<a[i]<<" "; }cout<<"}"<<endl;
}
void dfs(int a[],int n,int i,int x[])//i初始为1,x初始为全0
{if(i>=n)display(a,n,x);else{x[i]=0;dfs(a,n,i+1,x);x[i]=1;dfs(a,n,i+1,x);}
}
叶子 结点,

回溯法搜索子集树和m叉树的算法性能 :

时间复杂度分析:n个分量,每个分量有m种取值,则树中共有(m^n )个叶子结点,总结点数为(O(m^n )),因此,遍历m叉树的时间复杂度为(m^(n+ 1)-1) 。

空间复杂度:递归的深度,O(n)

2.排列树的算法框架

【例】有一个含n个整数的数组a,所有元素均不相同,求其所有元素的全排列。

思路:

将求取1234(4个元素)全排列的问题转换为:分别取1234交换到第一 个位置作为“开头”,求取剩余元素(3个)的全排列问题(递归体) 当只有一个元素的时候其全排列就是该元素本身(递归出口)

void display(int a[],int n){cout<<"{";for(int i=0;i<n;i++){cout<<a[i]<<" "; }cout<<"}"<<endl;
}
void dfs(int a[],int n,int i)
{if(i>=n)display(a,n);else{for(int j=i;j<n;j++){swap(a[i],a[j]);dfs(a,n,i+1);//给第i个位置选择了一个元素后,进行从第i+1~n位置的元素求全排列。swap(a[i],a[j]);//若退层回来,则恢复之前换给下标i位置的元素值,进入下一次循环,给第i个位置选取一个新值。。。}}
}

为了提高搜索效率,需要不断使用剪枝函数

约束函数:剪去不满足约束(显式、隐式)条件的子树

限界函数:剪去不能得到最优解的子树

回溯法的关键之一是设计合适的剪枝函数
非递归回溯框架:

归纳起来,用回溯法解题的一般步骤如下:

1对于给定的问题,确定问题的解空间,确定解空间树类型(子集树or排列树orM叉树),问题的解空间树应至少包含问题的一个(最优)解。

2、确定结点的扩展规则。

3、以深度优先方式搜索解空间树,并在搜索过程中可以采用剪枝函数来避免无效搜索。

3.回溯算法的时间分析

4.回溯算法示例--子集树

1.子集和问题

给定n个不同的正数集W={w(i)|1≤i≤n}和正数C,子集和问题是要求找出正数集W子集S,使该子集中所有元素的和为C,即

思路:

为解决该问题可以使用递归回溯的方法来构造最优解,设cs为当前子集和,在解空间树中进行搜索时,若当前层i>n时,算法搜索至叶节点,其相应的子集和为cs。当cs=c,则找到了符合条件的解。当i<=n时,当前扩展结点Z是子集树的内部结点。该结点有x[i]=1和x[i]=0两个儿子结点。其左儿子结点表示x[i]=1的情形。

剪枝函数图示:(设置了一个全局变量r,记录剩余元素的和,初值为全部元素的和)

剪左支:若当前子集和+w[i]的和大于C,左子树的所有情况都可以排除;

剪右支:若当前子集和+剩余元素的和小于C,右子树的所有情况都可以排除。

//伪代码
void SubSum(int n){if(i>=n){if(cs==c){for(int j=1;j<=n;j++){if(x[j]==1)cout<<w[j]<<" ";cout<<endl;}return;}}r-=w[i];//递归进层搜索,既然是对第i个元素做决策,则r中要减去w[i]if(cs+w[i]<=c){//检测左子树 x[i]=1;cs+=w[i];SubSum(i+1);cs-=w[i];}if(cs+r>=c){//检测右子树 x[i]=0;SubSum(i+1);}r+=w[i];//递归退层时将该段加入剩余路径r中即第i层的扩展结点return;
}

2.0-1背包问题

n个重量分别为{w1,w2, … ,wn}的物品,它 们的价值分别为{v1,v2, … ,vn},给定一个容量为W的背包。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且满足重量限制具有最大的价值。

思路:

w[1..n]/v[1..n]存放物品信息,x[1..n]数组存放最优 解,其中每个元素取1或0,x[i]=1表示第i个物品放入背包中, x[i]=0表示第i个物品不放入背包中。

case1:放入物品重量等于W。

int n=4;         //4种物品
int W=6;           //限制重量6
int w[]={0,5,3,2,1};//存放4个物品重量
int v[]={0,4,4,3,1};//存放4个物品价值
int x[MAXN];        //存放最终解
int maxv;           //存放最优解void dfs(int i,int tw,int tv,int op[]) {if(i>n){if(tw==W&&tv>maxv){maxv=tv;for(int j=1;j<=i;j++){x[j]=op[j];}}}else{op[i]=1;dfs(i+1,tw+w[i],tv+v[i],op);op[i]=0;dfs(i+1,tw,tv,op);}
}

T(n)=O(2^n);

增加剪枝函数:

左支:对于第i层的有些结点,tw+w[i]已超过了W W=6),显然 再选择w[i]是不合适的。

仅扩展满足tw+w[i]<=W的左孩子结点。

右支rw=w[i]+w[i+1]++w[n],

当不选择物品i(第i个物品的决策已做出,即不选择第i个物品时:若tw+rw-w[i]<W,也就是说即使选择后面的所有 物品,重量也不会达到W,因此不必要再考虑扩展这样的结点。

仅仅扩展满足 tw+rw- w [ i ]≥W 的右孩子结点。
int n=4;           //4种物品
int W=6;           //限制重量6
int w[]={0,5,3,2,1};//存放4个物品重量
int v[]={0,4,4,3,1};//存放4个物品价值
int x[MAXN];        //存放最终解
int maxv;           //存放最优解
int rw;             //存放剩余物品重量,初始为所有物品重量。void dfs(int i,int tw,int tv,int op[]) {if(i>n){if(tw==W&&tv>maxv){maxv=tv;for(int j=1;j<=i;j++){x[j]=op[j];}}}else{if(tw+w[i]<=W){//左剪枝 op[i]=1;dfs(i+1,tw+w[i],tv+v[i],op);}if(tw+rw-w[i]>=W){//右剪枝 op[i]=0;dfs(i+1,tw,tv,op);}}
}

case2:放入物品重量小于W。

前面左剪枝的方式不变,但右剪枝不再有效,需要改为上界函数进行右剪枝。

假设当前求出最大价值maxv,若 bound(i)≤maxv,则右剪枝,否则继续扩展。

显然r越小,bound(i)也越小,剪枝越多,为了构造更小的r,将所有物品以单位重量价值递减排列。

int n=4;           //4种物品
int W=6;           //限制重量6
int w[]={0,5,3,2,1};//存放4个物品重量
int v[]={0,4,4,3,1};//存放4个物品价值
int x[MAXN];        //存放最终解
int maxv;           //存放最优解
int rw;             //存放剩余物品重量,初始为所有物品重量。
int A[n];           //递减排列物品单位重量int bound(int i,int tw,int tv){i++;while(i<=n&&tw+A[i].w<=W){tw+A[i].w;tv+A[i].v;i++;}if(i<=n)return tv+(W-tw)*A[i].p;//其实是背包达不到的一个最大价值数else return tv;}
void dfs(int i,int tw,int tv,int op[]) {if(i>n){if(tw==W&&tv>maxv){maxv=tv;for(int j=1;j<=i;j++){x[j]=op[j];}}}else{if(tw+w[i]<=W){//左剪枝 op[i]=1;dfs(i+1,tw+A[i].w,tv+A[i].v,op);}if(bound(i,tw,tv)>maxv){//右剪枝 op[i]=0;dfs(i+1,tw,tv,op);}}
}

5.回溯算法示例--排列树

1.N后问题

N皇后问题,是一个古老而著名的问题,是回溯算法的典型例题,可以简单的描述为:在N*N格的棋盘上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?

思路:

假设皇后t放在第t行上,用n元组x[1:n]表示n皇后问题的解,x[t]表示皇后t放在第t行的第x[t]列上。

剪枝函数:对于两个皇后i,j    两个皇后肯定不同行

两个皇后不同列:x[i] ≠x[j]

两个皇后不同一条斜线: abs(i-j) ≠ abs(x[i]-x[j])

int place(int j){for(int i=1;i<j;i++){if(abs(j-i)==abs(a[j]-a[i])||x[j]==x[i])return 0;}return 1;
}
void queen(int t){//排列树框架 if(t>=n)sum++;//可行解加一 else{for(int j=t;j<=n;j++){swap(&x[j],&x[t]);if(place(t))queen(t+1);swap(&x[j],&x[t]);}}
}

满n叉树:

void queen(int t){//排列树框架 if(t>=n)sum++;//可行解加一 else{for(int j=t;j<=n;j++){swap(&x[j],&x[t]);if(place(t))queen(t+1);swap(&x[j],&x[t]);}}
}

2.哈密尔顿回路问题

设G=(V,E)是一个有n个结点的无向图。一条哈密顿回路是通过图中每个结点仅且一次的回路。设计算法,给定一个图,求其是否存在一条哈密顿回路。如果是,输出每一条哈密顿回路;否则给出提示。

思路:

用数组x[n]表示该问题的一组解,x[i]表示在 第i次访问的结点号。

当k=n+1时,当前扩展结点是叶结点。此时需要检测无向图G是否存在一条从顶点x[n]到顶点x[1]的边,如果存在,则找到一条哈密顿回路;否则回溯。当k<=n时,当前扩展结点位于排列树的第k层,若图G中存在从顶点x[k-1]到顶点x[k]的邻接的边时,x[1:k]就构成了图G的一条路径。

void hamilton(int k){if(k>n&&g[x[n]][x[1]]==1)output(x);elsefor(int j=k;j<=n;j++){swap(x[k],x[j]);if(g[x[k-1]][x[k]]==1)hamilton(k+1);swap(x[j],x[k]);}
}

6.总结

六.分支限界法

1.分支限界法概述

1.分支限界法与回溯法类似,也是对问题在解空间树上的解决,不同之处在于,回溯法是对解空间树进行深度优先搜索,而分支限界法是对解空间树的广度优先搜索。回溯法的目的是找出所有满足条件的解,而分支限界法目的是找出最优解。

Q:求最优解时,如何选择子结点进行再扩展?

A:设计一个限界函数,计算限界函数值,选择一个最有利的子结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。

分支限界法与回溯法比较:

2.设计思想

①设计合适的限界函数

在搜索解空间树时,每个活结点可能有很多孩子结点,其中有些孩子结点搜索下去是可能产生问题解或最优解。通过设计好的限界函数在扩展时删除不必要的孩子结点,从而提高搜索效率。

②组织活结点表

Ⅰ普通队列式分支限界法

队列式分支限界法将活结点表组织成一个队列,并按照队列先

进先出(FIFO)原则选取下一个结点为扩展结点。步骤如下:

将根结点加入活结点队列。

从活结点队中取出队头结点,作为当前扩展结点。

对当前扩展结点,先从左到右地产生它的所有孩子结点,用约

束条件检查,把所有满足约束条件的孩子结点加入活结点队列。

重复步骤②和③,直到找到一个解或活结点队列为空为止。

Ⅱ优先队列式分支限界法

  优先队列式分支限界法的主要特点是将活结点表组成一个优先

队列,并选取优先级最高的活结点成为当前扩展结点。步骤如下:

计算起始结点(根结点)的优先级并加入优先队列(与特定问题相

关的信息的函数值决定优先级)。

从优先队列中取出优先级最高的结点(队头)作为当前扩展结点,使

搜索朝着解空间树上可能有最优解的分枝推进,以便尽快地找出一

个最优解。

对当前扩展结点,先从左到右地产生它的所有孩子结点,然后用约

束条件检查,对所有满足约束条件的孩子结点计算优先级并加入优

先队列(进队)

重复步骤②和③,直到找到一个解或优先队列为空为止。

PS:

优先队列利用堆来实现。堆可以看作一棵完全二叉树的顺序存储结构。在这棵完全二叉树中:Ø 若树非空,且根结点的值均大于其左右孩子结点的值,则称之为大根堆;且其左、右子树分别也是大根堆。Ø 若树非空,且根结点的值均小于其左、右孩子结点的值,则称之为小根堆;且其左、右子树分别也是小根堆。

优先队列的两个基本操作:

出队:堆顶出队,最后一个元素代替堆顶位置,重新调整成堆;

进队:新进队元素放在堆末尾之后,重新调整成堆

优先队列出队操作(以大根堆为例)

• 出队:堆顶出队,最后一个元素代替堆顶位置。除了堆顶外,其他结点均满足大根堆定义,只需将堆顶执行“下沉”操作即可调整为堆。

• “下沉” :堆顶与其左、右孩子进行比较,若满足堆定义,则不需调整。若不满足,则与其值较大的孩子进行交换,交换后,继续向下调整,直到满足大根堆定义为止

优先队列进队操作(以大根堆为例)

• 进队:进队操作后,除了新进队的元素外,其他结点都满足大根堆的定义,需要将新元素执行“上浮”操作,调整成堆。

• “上浮” :新进队元素与其双亲比较,如果值小于其双亲,则满足堆定义,无需调整。如果其值比双亲大,则与双亲交换,交换到新位置后,继续向上比较、调整,直到满足大根堆定义为止。

③.确定最优解的解向量x

两个X均要入队保存的两种方法:

①对每个扩展结点保存从根结点到该结点的“路径” 。每个结点带有一个可能的解向量。这种做法比较浪费空间,但实现起来简单。(0-1背包问题为例)

②在搜索过程中构建搜索经过的树结构。每个结点记录其双亲的信息,当找到最优解时,通过双亲信息找到对应的最优解向量。(单源最短路径为例)

3.算法性能

在最坏情况下,时间复杂性是指数阶。通过设计限界函数和 约束进行剪枝,提高算法效率

2.分支限界法示例

1.0-1背包问题

n个重量分别为{w1,w2,…,wn}的物品,它 们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。 设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且重量和不超过W具有最大的价值。

设计限界函数:

对于第i层的某个结点e,用e.w表示结点e时已装入的总重量,用e.v表示已装入的总价值。E.i=i即对应表示结点e的状态是在确定解向量的e.X[E.i]分量;计算当前结点e对应的状态所能达到的价值上界ub。如果所有剩余的物品都能装入背包, 那么价值的上界e.ub=e.v+ (v[i+1]+…+v[n]) 如果所有剩余的物品不能全部装入背包, 那么价值的上界e.ub=e.v+ (v[i+1]+…+v[k])+(物品k+1装入的部分重量)× 物品k+1的单位重量价值。

注:为了计算出来的上界更加紧凑,需要物品按单位重量价值递减有序。

2.图的单源最短路径问题

给定一个带权有向图G=(V,E),其中每条边的权是一个正整数。另外,还给定V中的一个顶点v,称为源点。计算从源点到其他所有顶点的最短路径长度。这里的长度是指路上各边权之和。

①普通队列式

设计记录路径path的解向量:即用prev数组存放最短路径,prev[i]表示源点v到顶点i的最短路径中顶点i的前驱顶点。

用dist数组存放源点v出发的最短路径长度,dist[i]表示源 点v到顶点i的最短路径长度,初始时所有dist[i]值为∞。

#define INF 0x3f3f3f3f //表示∞
#define MAXN 51
int n; //图顶点个数
int a[MAXN][MAXN];//图的邻接矩阵
int v; //源点
int dist[MAXN]; //dist[i]源点到顶点i的最短路径长度
int prev[MAXN]; //prev[i]表示源点到j的最短路径中顶点j的前驱顶点struct Node{int vno;int length;
};void bfs(int v){Node e1,e;queue<Node>q;e.vno=v;e.length=0;//建立源点结点e(根结点)q.push(e);//根结点入队 dist[v]=0;while(!q.empty()){e=q.front();q.pop();for(int j=0;j<n;j++){if(a[e.vno][j]<INF && e.length+a[e.vno][j]<dist[j]){//与出队的顶点有边相连//e.vno到顶点j有边并且路径长度更短dist[j]=e.length+a[e.vno][j];prev[j]=e.vno;e1.vno=j; //建立相邻顶点j的结点e1e1.length=dist[j];q.push(e1); //结点e1进队}}}
}

②优先队列式

采用STL的priority_queue<Node>容器作为优先队列(小根堆),优先队列结点类型与前面的相同,添加比较重载函数,即按结点的length成员值越小越优先出队,为此设计Node结构体的比较重载函数如下:

#define INF 0x3f3f3f3f //表示∞
#define MAXN 51
int n; //图顶点个数
int a[MAXN][MAXN];//图的邻接矩阵
int v; //源点
int dist[MAXN]; //dist[i]源点到顶点i的最短路径长度
int prev[MAXN]; //prev[i]表示源点到j的最短路径中顶点j的前驱顶点struct Node{int vno;int length;bool operator < (const Node& b) const {return length>b.length;}
};
void bfs(int v){Node e1,e;priority_queue<Node> pq;e.vno=v;e.length=0;//建立源点结点e(根结点)pq.push(e);//根结点入队 dist[v]=0;while(!pq.empty()){e=pq.top();pq.pop();for(int j=0;j<n;j++){if(a[e.vno][j]<INF && e.length+a[e.vno][j]<dist[j]){//与出队的顶点有边相连//e.vno到顶点j有边并且路径长度更短dist[j]=e.length+a[e.vno][j];prev[j]=e.vno;e1.vno=j; //建立相邻顶点j的结点e1e1.length=dist[j];pq.push(e1); //结点e1进队}}}
}

3,小结

分支限界法与回溯法的主要区别。

分支限界法对问题的解空间树进行搜索的过程

Ø一次性产生当前扩展结点的所有孩子结点;

Ø在产生的孩子结点中,抛弃那些不可能产生可行解(约束)或最优解的结点(限界);

Ø将其余的孩子结点加入活结点表;

Ø从活结点表中选择下一个活结点作为新的扩展结点。

Ø重复上述步骤,直到找到一个解或优先队列为空为止。

分支限界法的关键问题

Ø如何确定合适的限界函数。

Ø如何组织待处理活结点表(普通队列和优先队列)。

Ø如何确定最优解中的各个分量。

七.随机算法

算法设计与分析--期末复习重点总结相关推荐

  1. 哈工大2019年春算法设计与分析期末复习

    本文原载于我的博客,地址:https://blog.guoziyang.top/archives/22/ 第二章 算法分析的数学基础 2.1 复杂性函数的阶 阶为复杂性函数的主导项. 如函数 T ( ...

  2. 算法设计与分析期末复习不挂科

    算法的基本概念 算法概念 通俗讲:算法是解决问题的一种方法或一个过程 严格讲:算法是解某一特定问题的一组有穷规则的集合 且满足以下性质: 有限性:算法在执行有限步之后必须终止 确定性:算法的每一个步骤 ...

  3. 国科大计算机算法设计与分析陈玉福,中科院陈玉福计算机算法设计与分析期末简答题答案.pdf...

    中科院陈玉福计算机算法设计与分析期末简答题答案 1. 贪心算法和动态规划算法有什么共同点和区别?它们都有那些优势和劣势? 共通点:动态规划和贪心算法都是一种递推算法 ,均有局部最优解来推导全局最优解 ...

  4. 算法设计与分析课程复习笔记11——单源最短路径

    算法设计与分析课程复习笔记11--单源最短路径 单源最短路径 最短路径问题 输入:有权有向图G=(V,E) 路径p={ v 0 , v 1 , . . . , v k v_0, v_1, . . . ...

  5. 2020-2021中科院陈玉福算法设计与分析期末考试

    2020-2021中科院陈玉福算法设计与分析期末考试 中科院沈阳计算所 时文康 于2020.12.31 一.(20 分)简答题 1,陈述算法在最坏时间下的时间复杂度和平均时间复杂度:这两种评估算法复杂 ...

  6. 计算机算法设计与分析期末考试试卷,算法设计与分析期末考试卷及答案a

    <算法设计与分析期末考试卷及答案a>由会员分享,可在线阅读,更多相关<算法设计与分析期末考试卷及答案a(15页珍藏版)>请在人人文库网上搜索. 1.一填空题(每空2分,共30分 ...

  7. 国科大计算机算法设计与分析陈玉福,中科院陈玉福计算机算法设计与分析期末简答题答案...

    中科院陈玉福计算机算法设计与分析期末简答题答案 1. 贪心算法和动态规划算法有什么共同点和区别?它们都有那些优势和劣势? 共通点:动态规划和贪心算法都是一种递推算法 ,均有局部最优解来推导全局最优解 ...

  8. 计算机算法设计与分析期末试题,算法设计与分析期末考试试卷(D卷)(含答案).doc...

    算法设计与分析期末考试试卷(D卷) 一.选择题(0分,每题分) .D A.n2/2 + 2n的渐进表达式上界函数是O(2n) B.n2/2 + 2n的渐进表达式下界函数是Ω(2n) C.logn3的渐 ...

  9. 算法设计与分析考前复习

    算法设计与分析考前复习 qiwang的NOJ系统在考前一天崩了,强烈建议开发新OJ 另外,由于以下这些题是我上完数电实验用了下午和晚上时间写的,可能会出一些错. 分治法复习 二分查找 描述 给定一个单 ...

最新文章

  1. 台州计算机考试现场确认,台州市2018年下半年中小学教资考试笔试网上报名及现场确认时间...
  2. 基于先验LiDAR点云地图的单目VIO定位(IROS 2020)
  3. 各种数据库连接jdbc
  4. 01 | Spring Data JPA 初识
  5. JVM菜鸟进阶高手之路
  6. 【讲师专访】Oracle ACE 总监侯圣文:不懂开发的运维终将被淘汰
  7. IPD+CMMI企业产品开发系统性解决方案
  8. static关键字作用总结
  9. 用好binutils之gporf
  10. yolo迭代次数_你一定从未看过如此通俗易懂的YOLO系列(从v1到v5)模型解读 (中)
  11. 中压变频器行业现状调研及趋势分析报告
  12. P12 | N-path filter (ISSCC2021 JSSC-2011-03)
  13. matlab插值实验目的,matlab插值实验报告数学实验.doc
  14. 详解深度学习中的梯度消失、爆炸原因及其解决方法
  15. Mac 重启后自动启动 docker container
  16. 金九银十!“68道 Redis+168道 MySQL”精品面试题(带解析),你背废了吗?
  17. 反汇编代码分析--函数调用
  18. 每一个设计师需知的40个设计素材站
  19. 结对-爬取大麦网演唱会信息-设计文档
  20. Adobe Lightroom Classic 入门教程(七)修改照片 --- 暗角颗粒与相机配置校正

热门文章

  1. 图像滤镜艺术---漫画滤镜
  2. 如何截取指定号码的短信,并且不让系统截取到通知用户
  3. Windows Server2019安装详细步骤
  4. [附源码]Java计算机毕业设计SSM个性化旅游线路推荐系统
  5. Pyspark 报错 “系统找不到指定的路径“
  6. C语言如何生成随机数——随机数原理分析
  7. 星特朗望远镜怎么样_我们,来看一看月亮?——星特朗SCTW-70天文望远镜测评...
  8. 大量名片图片转excel表格文件怎么转?有什么好办法?
  9. “小”企业上ERP必读
  10. 前端发送请求到后端报400错误问题