/**
* 二分搜索核心代码
*
* @param a: 查询数组
* @param x: 查询的值
* @param n: 数组a的个数
* @return 查找到的下标值,若没有找到返回-1
*/
int BinarySearch(Type a[], const Type& x, int n)
{
int left = 0;
int right = n-1;
while(left <= right)
{
int middle = (left+right)/2;
if(x == a[middle])
return middle;
else if(x > a[middle])
left = middle + 1;
else
right = middle - 1;
}
return -1;
}
/**
* 合并排序算法
*
* @param a: 查询数组
* @param left: 左指针下标
* @param right: 右指针下标
* @return
*/
void MergeSort(Type a[], int left, int right)
{
if(left < right)
{
int i = (left+right)/2; //取中点
MergeSort(a, left, i);
MergeSort(a, i+1, right);
Merge(a, b, left, i, right);//合并到数组b
Copy(a, b, left, right); //复制回数组a
}
}
/**
* 合并数组:将c[1:m]和c[m+1:r]合并到d[1:r]
*
* @param c 待合并数组
* @param d 合并完成的数组
* @param l 左指针下标
* @param m 中间指针下标
* @param r 右指针下标
* @return
*/
void Merge(Type c[], Type d[], int l, int m, int r)
{
int i = l;
int j = m+1;
int k = l;
while((i <= m) && (j <= r))
{
if(c[i] <= c[j])
d[k++] = c[i++];
else
d[k++] = c[j++];
}
if(i>m) //前半段全都取走了,后半段剩余直接搬走
{
for(int q=j; q<=r; q++)
d[k++] = c[q];
}
else
{
for(int q=i; q<=m; q++)
d[k++] = c[q];
}
}
取中间元素分为左右两段,分别对左右两段排序,合并两段有序的序列,对左右两段的排序是递归使用这个方法
开始对于一个大数组进行排序,将其切分成两个小数组,对这两个小数组进行排序
/**
* 快排核心代码
*
* @param a: 待排序数组
* @param p: 左边界
* @param r: 右边界
* @return
*/
void QuickSort(Type a[], int p, int r)
{
if(p < r)
{
int q = Partition(a, p, r);
QuickSort(a, p, q-1); //对左半段排序
QuickSort(a, q+1, r); //对右半段排序
}
}
/**
* 找标杆点位置
*
* @param a: 目标数组
* @param p: 左边界
* @param r: 右边界
* @return 标杆下标
*/
int Partition(Type a[], int p, int r)
{
int i = p;
int j = r+1;
Type x = a[p];
// 将小于x的元素交换到左边区域,将大于x的元素交换到右边区域
while(true)
{
while(a[++i]<x && i<r) ;//找到大于等于标杆元素 退出
while(a[--j]>x) ; //找到小于标杆元素 退出
if(i >= j)
break; //i,j异常,则退出
Swap(a[i], a[j]); //交换
}
//把标杆换到位
a[p] = a[j];
a[j] = x;
return j;
}
对于输入的数组,首先选取一个标杆,然后用两个指针指向剩余的元素两段,左指针找到比标杆大的值,右指针找到比标杆小的值,然后交换,左右指针异常时,就确定了标杆的位置在右指针位置。然后对于该标杆左右两段继续使用上述方法
本来是对一个大数组进行排序,一轮下来变成对两个小数组进行排序即可
老师所给示例
书上示例
/**
* 矩阵连乘核心代码
*
* @param p: 矩阵列数(第一个为数为 矩阵行数)
* @param n: 矩阵个数
* @param m: m[i][j]表示第i个矩阵到第j个矩阵,这样的矩阵串最优方案时,所需的最少数乘次数
* @param s: 断点位置
* @return
*/
void MatrixChain(int *p, int n, int **m, int **s)
{
for(int i=1; i<=n; i++) //填充对角线
m[i][i] = 0;
for(int r=2; r<=n; r++) //r是段长
{
for(int i=1; i<=n-r+1; i++) //i是段起点
{
int j=i+r-1; //j是段终点
// 第一个矩阵一段,后面矩阵一段,计算数乘次数
m[i][j] = m[i+1][j] + p[i-1]*p[i]*p[j];
s[i][j] = i;
for(int k=i+1; k<j; k++) //遍历所有断裂的情况
{
// 在k处断裂,计算此时数乘次数
int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if(t < m[i][j]) //更新
{
m[i][j] = t;
s[i][j] = k;
}
}
}
}
}
开始对于1到n个矩阵求最少数乘次数,裂开变成对两段矩阵求最少数乘次数加上两段的乘积次数
/**
* 图像压缩核心代码
*
* @param n: 像素个数
* @param p: 每个像素的值
* @param s: s[i]表示前i个像素的最优存储位数
* @param l: l[i]表示第i段的最优段长
* @param b: 每个像素的长度
* @return
*/
void Compress(int n, int p[], int s[], int l[], int b[])
{
int Lmax = 256, header = 11;
s[0] = 0;
for(int i=1; i<=n; i++)
{
b[i] = length(p[i]);
int bmax = b[i];
s[i] = s[i-1] + bmax; //一个像素为一段
l[i] = 1;
for(int j=2; j<=i&&j<=Lmax; j++) //j是各种合法段长的长度
{
if(bmax < b[i-j+1])
bmax = b[i-j+1]; //最长的位数
if(s[i] > s[i-j] + j*bmax) //若更优,则更新
{
s[i] = s[i-j] + j*bmax;
l[i] = j;
}
}
s[i] += header;
}
}
开始是对n个像素进行压缩的最优方案,转变为前面n-k个像素压缩和最后k个像素单独压缩之和的最优方案
/**
* o-1背包问题的动态规划算法
*
* @param v: 各物品的价值
* @param w: 各物品的重量
* @param c: 背包容量
* @param n: 物品数量
* @param m: m[i][j]表示容量为j,从物品i~n中选择时的最优选法
* @return
*/
void Knapsack(Type v, int w, int c, int n, Type **m)
{
int jMax = min(w[n]-1, c);
//填写最后一行
for(int j=0; j<=jMax; j++)
m[n][j] = 0;
for(int j=w[n]; j<=c; j++)
m[n][j] = v[n];
for(int i=n-1; i>1; i--) //从倒数第2行填到顺数第2行
{
jMax = min(w[i]-1, c);
for(int j=0; j<=jMax; j++)
m[i][j] = m[i+1][j]; //装不下物品i
for(int j=w[i]; j<=c; j++) //能装入 在装入和放弃中选优
m[i][j] = max(m[i+1][j], m[i+1][j-w[i]]+v[i]);
}
m[1][c] = m[2][c];
if(c >= w[1])
m[1][c] = max(m[1][c], m[2][c-w[1]]+v[1]); //算第一行
}
开始是从n个物品中选择最优的选择方案,变成从后面n-1个物品中选择的最优方案与第1个物品装入与否之间的最优方案比较,依次递归
这一部分似乎没有考核内容
实现代码
//4.5 单源最短路径
#include
using namespace std;
#define maxint 10000
template<class Type>
/**
* 单源最短路径
*
*@param n 结点个数
*@param v 源点编号
*@param dist[i] 源结点v到结点i的最短路径长度
*@param prev[i] 到达结点i最短路径时,上一个结点编号
*@param c[i][j] 存放有向图中i到j的边的权
*@return
*/
void Dijkstra(int n, int v, Type *dist, int *prev, Type (*c)[6])
{
bool s[maxint]; //用于记录红点集
/*
初始化
*/
for(int i=1; i<=n; i++)
{
dist[i] = c[v][i];
s[i] = false;
if(dist[i] == maxint)
prev[i] = 0;
else
prev[i] = v;
}
dist[v] = 0;
s[v] = true;
/*
core
*/
for(int i=1; i<n; i++)
{
int temp = maxint;
int u = v;
for(int j=1; j<=n; j++)
if((!s[j]) && (dist[j]<temp)) //选出不在红点集 且 路径最短的结点
{
u = j;
temp = dist[j];
}
s[u] = true;
for(int j=1; j<=n; j++) //更新到所有未加入红点集节点的最短距离
{
if((!s[j]) && (c[u][j] < maxint))
{
Type newdist = dist[u] + c[u][j];
if(newdist < dist[j])
{
dist[j] = newdist;
prev[j] = u;
}
}
}
}
}
int main()
{
int n=5;
int v=1;
int dist[6] = {0};
int prev[6] = {0};
int c[6][6];
c[1][2]=10; c[1][4]=30; c[1][5]=100;
c[2][3]=50;
c[3][5]=10;
c[4][3]=20; c[4][5]=60;
Dijkstra(n, v, dist, prev, c);
for(int i=2; i<=n; i++){
cout<<"结点1到结点"<<i<<"的最短路径:"<<dist[i]<<"\t";
cout<<"前一个结点是"<<prev[i]<<endl;
}
return 0;
}
/**
* 查看第k行皇后是否与上面冲突
*
* @param k 行数
* @return 是否合法
*/
bool Queen::Place(int k)
{
for(int j=1; j<k; j++)
if((abs(k-j) == abs(x[j]-x[k])) || (x[j] == x[k])) //对角线或同一列
return false;
return true;
}
/**
* n后问题核心算法
*
* @param t 当前处理的行数
* @return
*/
void Queen::Backtrack(int t)
{
if(t > n)
sum++;
else
{
for(int i=1; i<=n; i++)
{
x[t] = i;
if(Place(t))
Backtrack(t+1);
}
}
}
/**
* 计算上界
*
* @param i 选择物品从i往后
* @return 返回价值上界
*/
template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)
{
Typew cleft = c-cw; //背包剩余空间
Typep b = cp; //当前价值
while(i<=n && w[i]<=cleft) //以物品单位重量价值递减序装入物品
{
cleft -= w[i];
b += p[i];
i++;
}
if(i <= n) //分割物品,装满背包
b += p[i]*cleft/w[i];
return b;
}
/**
* 染色是否冲突
*
* @param k 顶点k
* @return 是否
*/
bool Color::Ok(int k)
{
for(int j=1; j<=n; j++)
if((a[k][j]==1) && (x[j]==x[k])) //检查相邻和颜色是否相同
return false;
return true;
}
/**
* 染色问题核心代码
*
* @param t 第t个顶点
* @return
*/
void Color::Backtrack(int t)
{
if(t>n) //完成所有点的染色
{
sum++; //合法的解个数
for(int i=1; i<=n; i++)
cout<< x[i] << ' ';
cout<<endl;
}
else //未完成所有点的染色
{
for(int i=1; i<=m; i++)
{
x[t] = i; //给t号结点染第i种颜色
if(Ok(t)) //判断是否合法
Backtrack(t+1); //无冲突,则继续处理第t+1个结点
x[t] = 0; //洗白,恢复现场
}
}
}
略微模糊
为了更加清晰易看,下面图是上图的部分切分版本
x数组的变化情况
j=2, j=3
及其子节点回溯情况
j=2, j=4
及其子节点回溯情况
length
(当前路长,或使用cc
当前开销)首先将源节点入队列,该队列是按照当前路长最短进行排列的优先队列,每次都从队列头部取出一个节点,将其扩展的子节点入队,然后再从队列头部取出一个节点,重复上述过程,直到终点节点第一次出现在队头为止,此时该终点的路径为最优
uprofit
(结点的价值上界)uprofit
,即 根结点的 uprofit
比子孙的 uprofit
都大
上图圈圈中有三个数,最顶上那个数是当前背包重量,中间的那个数是当前背包价值,最底下那个数是当前背包的上界
首先各物品按其单位重量价值从大到小排序
uprofit
由大到小进入最大堆lcost
(最低消费)lcost
是以该结点为根的子树中所有结点对应的lcost
的一个下界
说明:
minout
为每个顶点的最小费用出边lcost
计算是当前开销+离开其余未到达的点的最小值lcost
=当前开销(30元)+离开2的钱最少(5元)+离开3的钱最少(5元)+离开4的钱最少(4)= 44首先先统计出每个顶点的最小费用出边minout
lcost
由小到大进入优先队列lcost
,孩子入队cd
(当前密度)cd
是以该结点为根的子树中所有结点对应的cd
的一个下界
说明:解空间树中第一层的含义是1号槽放第1/2/3号电路板,其余层同理
cd
由小到大进入优先队列cd
,孩子入队cd
≥ \geq ≥bestd
,则优先队列中其余结点都不可能导致最优解,算法结束说明:bestd
表示目前遇到的每块板子插好时的最优密度
/**
* 随机放置n个皇后的拉斯维加斯算法
*
* @return 是否有解
*/
bool Queen::QueensLV(void)
{
RandomNumber rnd; //随机数产生器
int k = 1; //下一个放置的皇后编号
int count = 1;
while((k<=n) && (count>0)) //上行有解,且未到最后一行
{
count = 0;
for(int i=1; i<=n; i++) //统计并记录当前本行的所有合法位置
{
x[k] = i; //k行i列
if(Place(k)) //判断是否位置是否合法
y[count++] = i;
}
if(count>0)
x[k++] = y[rnd.Random(count)]; //从合法位置中随机选一个
}
return (count>0); //count>0表示所有皇后放置成功
}
相关知识
费尔马小定理:如果p是一个素数,且0p-1≡1(mod p)
- 利用费尔马小定理,对于给定的整数n,可以设计素数判定算法
- 通过计算d=2n-1mod n 来判定整数n的素性
- 当d≠1时,n肯定不是素数
- 当d=1时,n 很可能是素数
- 费尔马小定理毕竟只是素数判定的一个必要条件,满足费尔马小定理条件的整数n未必全是素数。
- 有些合数也满足费尔马小定理的条件,这些合数被称为Carmichael数,前3个Carmichael数是561、1105、1729。Carmichael数是非常少的,在1~100 000 000的整数中,只有255个Carmichael数
二次探测定理:如果p是一个素数,且0
2≡1(mod p)的解为x=1,p-1
- 事实上,x2≡1(mod p)等价于x2-1≡0(mod p)。由此可知(x-1)(x+1)≡0(mod p),故p必须整除x-1或x+1,由于p是素数且0
- 利用二次探测定理,可以在利用费尔马小定理计算an-1mod n 的过程中增加对整数n的二次探测。一旦发现违背二次探测条件,即可得出n不是素数的结论
- 下面是算法power用于计算apmod n,并在计算过程中实施对n的二次探测
/** * 费尔马小定律并实施n的二次探测 * a^p mod n =result * * @param a: 费尔马小定律中的底数 * @param p: 需要判断是否为素数的数字 * @param n: * @param result: 计算结果 * @param composite: * @return */ void power(unsigned int a, unsigned int p, unsigned int n, unsigned int &result, bool &composite) { unsigned int x; if(p==0) result = 1; else { power(a, p/2, n, x, composite); // 递归计算 result = (x*x)%n; //二次探测 (A*B)%n = (A%n)*(B%n) if((result==1) && (x!=1) && (x!=n-1)) composite = true; if((p%2)==1) result = (result*a) % n; } }