一、算法
经典的算法:
1.递归算法:
1)数据定义按递归定义 fibonacci
2)问题揭发按递归解决 回溯算法
3)数据结构按递归定义 树遍历图搜索
经典题-整数划分:
2.分治算法:
对于规模为n的问题分解成k个规模小的问题他们彼此独立,然后再将它们合并
分治策略的算法设计模式
Divide_and_Conquer(P)
{
if (|P|<=n0 ) return adhoc(P);
divide P into smaller substances P1,P2,…,Pk;
for (i=1; i<=k; k++)
yi=Divide-and-Conquer(Pi) //递归解决Pi
Return merge(y1,y2,…,yk) //合并子问题
}
二分搜索:
Public static int binarysearch(int[]data,intbeginindex,intendindex)
Int beginindex=0;int endindex =n-1;
While(beginindex<endindex)
{ int mid =(beginindex+endindex)/2
If(x==a[mid] return mid;
If(x>a[mid] beginindex=mid+1;
Else endindex=mid-1;}
Return -1
a[0:n-1] 找出第k小的元素 快速排序算法是分治算法的应用
3.动态规划
1)分析最优子结构:
2)重叠子问题;
题-最长公共子序列:
void LCSLength (int m, int n, const char x[],char y[])
{
int i,j;
//数组c的第0行、第0列置0
……;
//根据递推公式构造数组c
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++)
{
if (x[i]==y[j])
{c[i][j]=c[i-1][j-1]+1; b[i][j]=1; }
else if (c[i-1][j]>=c[i][j-1])
{c[i][j]=c[i-1][j]; b[i][j]=2; }
else { c[i][j]=c[i][j-1]; b[i][j]=3; }
}
} if (i ==0 || j==0) return;
if (b[i][j]== 1){ LCS(i-1,j-1,x); printf("%c",x[i]); }
else if (b[i][j]== 2) LCS(i-1,j,x);
else LCS(i,j-1,x);
if (i ==0 || j==0) return;
if (b[i][j]== 1){ LCS(i-1,j-1,x); printf("%c",x[i]); }
else if (b[i][j]== 2) LCS(i-1,j,x);
else LCS(i,j-1,x);
题-最大子段和:
B[j]=max{b[j-1]+a[j],a[j]}//条件b[i-j]>0
int MaxSum(int n)
{
int sum=0;
int b=0;
for (int i=1;i<=n;i++){
if (b>0) b+=a[i]; else b=a[i];
if (b>sum) sum=b;
}
return sum;
int MaxSum(int n, int &besti, int &bestj)
{
int sum=0;
int b=0;
int begin = 0;
for (int i=1; i<=n; i++){
if (b>0) b+=a[i];
else {b=a[i]; begin = i;}
if (b>sum) {//得到新的最优值时,更新最优解
sum = b;
besti = begin;
bestj = i;
}
}
return sum;
}
题0-1背包问题:
第一个式子是不装入物品i,也可能物品i无法装入,背包的容量不变,转化为前i+1个物品放与容量为j的背包中
第二个式子装入物品i则新增加价值vi,但容量变位j-w,对于最后一个物品n,若j》=w
题:最长单调子系列
辅助数组b[i]表示以a[i]为结尾的最长递增子序列长度,则序列L的最长递增子序列的长度:max {b[i]}
B[1]=1;
B[i]=max{b[k]}+1
int LIS_n2(int n) //教材上这一句丢掉了
{
int b[NUM]={0}; //辅助数组b
int i,j;
b[1] = 1;
int max = 0; //数组b的最大值
for (i=2;i<=n; i++)
{
int k = 0;
for (j=1; j<i; j++) //0~i-1之间,b的最大值
if (a[j]<=a[i] && k<b[j]) k=b[j];
b[i] = k+1;
if (max<b[i]) max=b[i];
}
return max;
}
4.贪心算法
贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优选择,即贪心选择来达到
Greedy(A)
{
S={ }; //初始解集合为空集
while (not solution(S)) //集合S没有构成问题的一个解
{
x = select(A); //在候选集合A中做贪心选择
if feasible(S, x) //判断集合S中加入x后的解是否可行
S = S+{x};
A = A-{x};
}
return S;
}
5.贪心算法背包问题
double knapsack(int n, bag a[], double c)
{
double cleft = c;
int i = 0;
double b = 0;
while(i<n && a[i].w<=cleft)
{
cleft -= a[i].w;
b += a[i].v;
//物品原先的序号是a[i].index,全部装入背包
a[a[i].index].x = 1.0;
i++;
}
if (i<n) {
a[a[i].index].x = 1.0*cleft/a[i].w;
b += a[a[i].index].x*a[i].v;
}
return b;
}
6.回溯算法0-1背包问题
令cw(i)表示目前搜索到第i层已经装入背包的物品总重量,即部分解(x1, x2 , …, xi)的重量:
对于左子树, xi =1 ,其约束函数为:
若constraint(i)>W,则停止搜索左子树,否则继续搜索。
对于右子树,为了提高搜索效率,采用上界函数Bound(i)剪枝。
令cv(i)表示目前到第i层结点已经装入背包的物品价值:
令r(i)表示剩余物品的总价值:
则限界函数Bound(i)为:
回溯算法0-1背包问题回溯算法数据结构
#define NUM 100
int c; //背包的容量
int n; //物品的数量
int cw; //当前重量
int cv; //当前价值
int bestv; //当前最优价值
//描述每个物品的数据结构
struct Object{
int w; //物品的重量
int v; //物品的价值
double d; //物品的单位重量价值比
}Q[NUM]; //物品的数组
输入样例 输出样例
50 3 200
10 60
30 120
20 100
0
回溯算法0-1背包问题回溯算法实现
//形参i是回溯的深度,从0开始
void backtrack(int i)
{
//到达叶子结点时,更新最优值
if (i+1>n) {bestv = cv; return;}
//进入左子树搜索
if (cw+Q[i].w<=c)
{
cw += Q[i].w;
cv += Q[i].v;
backtrack(i+1);
cw -= Q[i].w;
cv -= Q[i].v;
}
//进入右子树搜索
if (Bound(i+1)>bestv) backtrack(i+1);
回溯算法界限函数BOUND()的实现
//形参i是回溯的深度
int Bound(int i)
{
int cleft = c-cw; //背包剩余的容量
int b = cv; //上界
//尽量装满背包
while (i<n && Q[i].w<=cleft)
{
cleft -= Q[i].w;
b += Q[i].v;
i++;
}
//剩余的部分空间也装满
if (i<n) b += 1.0*cleft*Q[i].v/Q[i].w;
return b;
}
7.算法分支界限算法
1)算法分支限界算法分支节点的选择
从活结点表中选择下一个活结点作为新的扩展结点,分支限界算法通常可以分为两种形式:
1]FIFO(First In First Out)分支限界算法
–按照先进先出(FIFO)原则选择下一个活结点作为扩展结点,即从活结点表中取出结点的顺序与加入结点的顺序相同。
2]最小耗费或最大收益分支限界算法
–在这种情况下,每个结点都有一个耗费或收益。
–根据问题的需要,可能是要查找一个具有最小耗费的解,或者是查找一个具有最大收益的解。
限界函数:
求max维护或节点,从活节点选择一个节点作为扩展节点,对扩展节点的每个分支i,计算其上界值,若当前最大的目标函数值best不小于bound(i)不会放入活节点,否则放入,所以若bound(i)《=best表示正在搜索的节点i为死节点减掉
深度优先
广度优先 无回溯快
二、数据结构
1.链式存储结构是顺序存储结构(随机/顺序/索引/散列表存储)
2.Trie树(字典树)快速检索的多茶树
3.排序算法:
1)直接插入排序
• 如下图所示,每次选择一个元素K插入到之前已排好序的部分A[1…i]中,插入过程中K依次由后向前与A[1…i]中的元素进行比较。若发现发现A[x]>=K,则将K插入到A[x]的后面,插入前需要移动元素。