a) 欧几里德算法求最大公约数
欧几里德算法也就是辗转相除法,有着2000年的历史了。欧几里德算法依据的算法理论是一个定理:gcd(a,b) = gcd(b,a mod b)。
实现源码为:
//递归实现 int gcd(int m,int n) { if (m < n) { int tmp = m; m = n; n = tmp; }
if (n == 0) return m; else return gcd(n,m % n); } |
//非递归实现 int gcd2(int m,int n) { if (m < n) { int tmp = m; m = n; n = tmp; }
if (n == 0) return m;
while (n > 0) { int tmp = m % n; m = n; n = tmp; }
return m; } |
这里给出了最大公约数的算法,那怎么求最大公倍数呢?其实知道了最大公约数,最小公倍数的求法就简单了:
int gbs(int m,int n)
{
return m*n/gcd(m,n);
}
b) 筛法求素数
基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。
素数为0.. 合数为1..void getprim()
{
int i ;
for ( i = 2; i * i < N; i++)
{
if ( flag[i]) continue;
for (int j = i; i * j < N; j++)
flag[i*j] = 1;
}
}
//用了筛法的方法:
#include
#include
#define N 10000001
bool prime[N];
int main()
{
int i, j; prime=false; printf("%d ",i); 1.普通求素数法 public int prime(int num){ c) 康托展开 1.Cantor公式: e) 同余定理 两个整数,,若它们除以正整数所得的余数相等,则称,对于模同余 记作 读作同余于模,或读作与关于模同余。 比如。 同余于的符号是同余相等符号 ≡。统一码值为 U+2261。但因为方便理由,人们有时会把它(误)写为普通等号 (=)。 (即是说 a 和 b 之差是 m 的倍数) f) 次方求模 3. 计算几何初步 a) 三角形面积 b) 三点顺序 利用矢量叉积判断是逆时针还是顺时针。 4. 学会简单计算程序的时间复杂度与空间复杂度 5. 二分查找法 void bisearch(int a[],int n,int x) 6. 简单的排序算法 /* 程序里有几点注意的地方: 一,在程序里,交换2个数,我使用了异或来处理。这个可以根据个人喜好。为了避免产生临时变量,可以使用如下几种方式来交换2个数: a=a^b; 或者 使用第二种也挺好的。第一种异或的方式,只适用于,2个数都为int型的,a,b可以正可以负,这个没有关系,但是必须是int类型。 二, sleep()函数是包含在windows.h里面的,要加入 #include 三, 关于随机数生成的2个函数 srand()种子发生器函数,还有rand()随机数生成器函数,自己可以参考相关文档。 四, Demo宏来控制是演示还是比较性能用的。当把N调整的很小,比如10的时候,可以设置Demo为1,那样就能打印数组了,可以看到比较前后的情况。当把N调整到很大比如10000的时候,就把Demo设置为0,那样就不打印数组,直接比较性能。 7. 贪心算法经典题目 顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。 问题表述:设有n个活动的集合E = {1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si < fi 。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si >= fj或sj >= fi时,活动i与活动j相容。 由于输入的活动以其完成时间的非减序排列,所以算法greedySelector每次总是选择具有最早完成时间的相容活动加入集合A中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。 算法greedySelector的效率极高。当输入的活动已按结束时间的非减序排列,算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。 例:设待安排的11个活动的开始时间和结束时间按结束时间的非减序排列如下: 算法greedySelector 的计算过程如下图所示。图中每行相应于算法的一次迭代。阴影长条表示的活动是已选入集合A的活动,而空白长条表示的活动是当前正在检查相容性的活动。 若被检查的活动i的开始时间Si小于最近选择的活动j的结束时间fi,则不选择活动i,否则选择活动i加入集合A中。 贪心算法并不总能求得问题的整体最优解。但对于活动安排问题,贪心算法greedySelector却总能求得的整体最优解,即它最终所确定的相容活动集合A的规模最大。这个结论可以用数学归纳法证明。 活动安排问题实现: 对于一个具体的问题,怎么知道是否可用贪心算法解此问题,以及能否得到问题的最优解呢?这个问题很难给予肯定的回答。 但是,从许多可以用贪心算法求解的问题中看到这类问题一般具有2个重要的性质:贪心选择性质和最优子结构性质。 所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。 动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。 对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。 当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。 贪心算法和动态规划算法都要求问题具有最优子结构性质,这是2类算法的一个共同点。但是,对于具有最优子结构的问题应该选用贪心算法还是动态规划算法求解?是否能用动态规划算法求解的问题也能用贪心算法求解?下面研究2个经典的组合优化问题,并以此说明贪心算法与动态规划算法的主要差别。 给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。 与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1 <= i <= n。 这2类问题都具有最优子结构性质,极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。 首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。 void Knapsack(int n,float M,float v[],float w[],float x[]) Sort(n,v,w); int i; for (i = 1 ; i <= n ; i++) x[i] = 0; float c=M; for (i=1;i<=n;i++) { if (w[i] > c) break; x[i]=1; c-=w[i]; if (i <= n) x[i]=c / w[i]; 算法knapsack的主要计算时间在于将各种物品依其单位重量的价值从大到小排序。因此,算法的计算时间上界为 O(nlogn)。 为了证明算法的正确性,还必须证明背包问题具有贪心选择性质。 对于0-1背包问题,贪心选择之所以不能得到最优解是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。实际上也是如此,动态规划算法的确可以有效地解0-1背包问题。 哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%~90%之间。哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。 给出现频率高的字符较短的编码,出现频率较低的字符以较长的编码,可以大大缩短总码长。 3*(45+13+12+16+9+5) = 300 千位 1*45+3*13+3*12+3*16+4*9+4*5 = 224 千位 对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀。这种编码称为前缀码。 编码的前缀性质可以使译码方法非常简单。 表示最优前缀码的二叉树总是一棵完全二叉树,即树中任一结点都有2个儿子结点。 f(c)表示字符c出现的概率,dt(c)表示c的码长 平均码长定义为: 使平均码长达到最小的前缀码编码方案称为给定编码字符集C的最优前缀码。 哈夫曼提出构造最优前缀码的贪心算法,由此产生的编码方案称为哈夫曼编码。 哈夫曼算法以自底向上的方式构造表示最优前缀码的二叉树T。 算法以|C|个叶结点开始,执行|C|-1次的“合并”运算后产生最终所要求的树T。 以f为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树T。 算法huffmanTree用最小堆实现优先队列Q。初始化优先队列需要O(n)计算时间,由于最小堆的removeMin和put运算均需O(logn)时间,n-1次的合并总共需要O(nlogn)计算时间。因此,关于n个字符的哈夫曼算法的计算时间为O(nlogn) 。 要证明哈夫曼算法的正确性,只要证明最优前缀码问题具有贪心选择性质和最优子结构性质。 (1)贪心选择性质 (2)最优子结构性质 给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其它各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。 Dijkstra算法是解单源最短路径问题的贪心算法。 其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。 初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。 例如,对下图中的有向图,应用Dijkstra算法计算从源顶点1到其它顶点间最短路径的过程列在下表中。 Dijkstra算法的迭代过程: dist[2] dist[3] dist[4] dist[5] maxint {1,2} {1,2,4} {1,2,4,3} {1,2,4,3,5} 2、算法的正确性和计算复杂性 (1)贪心选择性质 (2)最优子结构性质 (3)计算复杂性 对于具有n个顶点和e条边的带权有向图,如果用带权邻接矩阵表示这个图,那么Dijkstra算法的主循环体需要O(n)时间。这个循环需要执行n-1次,所以完成循环需要O(n)时间。算法的其余部分所需要时间不超过O(n^2)。 参考资料 《算法分析与设计》 王晓东编著
for(i=2; i
else prime=true;
for(i=3;i<=sqrt(N); i+=2)
{ if(prime)
for(j=i+i; j
}
for(i=2; i<100;i++)//由于输出将占用太多io时间,所以只输出2-100内的素数。可以把100改为N
if( prime )
return0;
}
int n,m,i=0,s=0;
label1:
for(n=2;n<=num;n++)
{
for(m=2;m<=n/2;m++)
{
if(n%m==0)
continue label1;
}
s++;
i++;
System.out.println("第"+i+"个素数是:"+n);
}
return s;
}
把一个整数X展开成如下形式:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!
其中,a为整数,并且0<=a[i] 举例
例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884.
解释:
排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!
排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!
以此类推,直至0*0!
用途
显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。
康托展开的逆运算
既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。
如n=5,x=96时:
首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!)
用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.
用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.
用5去除2!得到2余1,类似地,这一位是3.
用1去除1!得到1余0,这一位是2.
最后一位只能是1.
所以这个数是45321.
按以上方法可以得出通用的算法。
康托展开:
int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};
int cantor(int[] a, int k) {
int i, j, tmp, num = 0;
for (i = 0; i < k; i++) {
tmp = 0;
for (j = i + 1; j < k; j++)
if (a[j] < a[i])tmp++;
num += fac[k - i - 1] * tmp;
}
return num;
}
代码
逆康托展开:
int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};
int[] uncantor(int x, int k) {
int res[] = new int[9];
int i, j, l, t;
boolean h[] = new boolean[12];
for (i = 1; i <= k; i++) {
t = x / fac[k - i];
x -= t * fac[k - i];
for (j = 1, l = 0; l <= t; j++)
if (!h[j])l++;
j--;
h[j] = true;
res[i - 1] = j;
}
return res;
}整除性
换句话说,[注 1]传递性
保持基本运算
除法原理
例子
比如a的b次方对c求模
我们可以把b 化为二进制形式看那一位有1
比如b=10101则 a^b=a^(10000)*a^(100)*a^(1)
以函数形式体现:
long long a,b,c;
void han()
{
long long t,s;
for(t=a,s=1;b;b>>=1,t*=t,t%=c)//用b>>=1查看b中1
if(b&1){s*=t;s%=c;}
printf("%lld\n",s%c);
}
if(a+b>c&&a+c>b&&c+b>a)
{p=(a+b+c)/2;
p=p*(p-a)*(p-b)*(p-c);
s=sqrt(p);
printf("%f",s);}
else printf("你输入的三边,不能构成三角形");
return 0;
{
int low,mid,high;
int i;
low=0;
high=n-1;
int succeed=0;
while(low<=high)
{
mid=(low+high)/2;
if(x==a[mid])
{
succeed=1;
printf("折半查找成功 \n此数值%d在数组里第%d个位置\n",x,mid+1);
break;
}
if(x>a[mid])
{
low=mid+1;
}
if(x {
high=mid-1;
}
}
if(succeed==0)
printf("折半查找失败 即数值%d不存在于数组里\n");
}
** 常见排序算法比较
*/
#include
#include
#include <time.h>
#include
#define N 10
#define Demo 1
void BubbleSort(int arr[], int n);
void SelectSort(int arr[], int n);
void QuickSort(int arr[], int n);
void PrintArray(int arr[], int n);
void GenerateArray(int arr[], int n);
int main(int argc, char *argv[])
{
int arr[N];
GenerateArray(arr, N);
#if Demo
printf("Before the bubble sort------------------------\n");
PrintArray(arr, N);
#endif
printf("Start Bubble sort----------------------\n");
clock_t start_time1=clock(); //开始计时
BubbleSort(arr, N);
clock_t end_time1=clock(); // 结束计时
printf("Running time is: %lf ms\n", (double)(end_time1-start_time1)/CLOCKS_PER_SEC*1000); //输出运行时间
#if Demo
printf("After the bubble sort------------------------\n");
PrintArray(arr, N);
#endif
printf("-----------------------------------------------------------\n");
sleep(1000); // 单位是毫秒即千分之一秒
GenerateArray(arr, N);
#if Demo
printf("Before the selection sort------------------------\n");
PrintArray(arr, N);
#endif
printf("Start selection sort----------------------\n");
clock_t start_time2=clock(); //开始计时
SelectSort(arr, N);
clock_t end_time2=clock(); // 结束计时
printf("Running time is: %lf ms\n", (double)(end_time2-start_time2)/CLOCKS_PER_SEC*1000); //输出运行时间
#if Demo
printf("After the selection sort------------------------\n");
PrintArray(arr, N);
#endif
printf("-----------------------------------------------------------\n");
sleep(1000); // 单位是毫秒即千分之一秒
GenerateArray(arr, N);
#if Demo
printf("Before the quick sort------------------------\n");
PrintArray(arr, N);
#endif
printf("Start quick sort----------------------\n");
clock_t start_time3=clock(); //开始计时
QuickSort(arr, N);
clock_t end_time3=clock(); // 结束计时
printf("Running time is: %lf ms\n", (double)(end_time3-start_time3)/CLOCKS_PER_SEC*1000); //输出运行时间
#if Demo
printf("After the quick sort------------------------\n");
PrintArray(arr, N);
#endif
system("PAUSE");
return 0;
}
// 产生随机列表
void GenerateArray(int arr[], int n)
{
int i;
srand((unsigned)time(0));
for(i = 0; i
arr[i] = rand(); // 生成随机数 范围在0-32767之间
}
}
// 打印列表
void PrintArray(int arr[], int n)
{
int i = 0;
for(i = 0; i < n; i++)
printf("%6d", arr[i]);
printf("\n");
}
// 经典
void BubbleSort(int arr[], int n)
{
int i = 0, j =0;
for(i = 0; i < n; i++)
for(j = 0; j < n - 1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
arr[j] = arr[j] ^ arr[j+1];
arr[j+1] = arr[j] ^ arr[j+1];
arr[j] = arr[j] ^ arr[j+1];
}
}
}
// 快速排序的递归实现
void QuickSort(int arr[], int n)
{
if(n <= 1)
return;
int i =0 , j = n - 1;
int key = arr[0];
int index = 0;
while(i < j)
{
// 从后向前搜索
while(j > i && arr[j] >= key)
j--;
if(j == i)
break;
else
{
//交换 a[j] a[i]
arr[j] = arr[j] ^arr[i];
arr[i] = arr[j] ^arr[i];
arr[j] = arr[j] ^arr[i];
index = j;
}
// 从前向后搜索
while(i < j && arr[i] <= key)
i++;
if(i == j)
break;
else
{
// 交换 a[i] a[j]
arr[j] = arr[j] ^arr[i];
arr[i] = arr[j] ^arr[i];
arr[j] = arr[j] ^arr[i];
index = i;
}
}
QuickSort(arr, index);
QuickSort(arr + index + 1, n - 1 - index);
}
// 选择排序
void SelectSort(int arr[], int n)
{
int i, j;
int min;
for(i = 0; i < n - 1; i++)
{
int index = 0;
min = arr[i];
for(j = i + 1; j < n; j++) //找出 i+1 - n 无序区的最小者与arr[i]交换
{
if(arr[j] < min)
{
min = arr[j];
index = j;
}
}
if(index != 0) //表明无序区有比arr[i]小的元素
{
arr[i] = arr[i]^arr[index];
arr[index] = arr[i]^arr[index];
arr[i] = arr[i]^arr[index];
}
}
}
b=a^b;
a=a^b;
a=a+b;
b=a-b;
a=a-b;
问题一、活动安排问题
贪心算法的基本要素
1、贪心选择性质
2、最优子结构性质
3、贪心算法与动态规划算法的差异
0-1背包问题:
背包问题:
用贪心算法解背包问题的基本步骤:
问题二、 哈夫曼编码
1、前缀码
2、构造哈夫曼编码
3、哈夫曼算法的正确性
问题三、单源最大路径
1、算法基本思想