ik算法
① 简述算法的各种表示形式
一、什么是算法
算法是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。算法常常含有重复的步骤和一些比较或逻辑判断。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
算法的时间复杂度是指算法需要消耗的时间资源。一般来说,计算机算法是问题规模n 的函数f(n),算法执行的时间的增长率与f(n) 的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)。时间复杂度用“O(数量级)”来表示,称为“阶”。常见的时间复杂度有: O(1)常数阶;O(log2n)对数阶;O(n)线性阶;O(n2)平方阶。
算法的空间复杂度是指算法需要消耗的空间资源。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。
二、算法设计的方法
1.递推法
递推法是利用问题本身所具有的一种递推关系求问题解的一种方法。设要求问题规模为N的解,当N=1时,解或为已知,或能非常方便地得到解。能采用递推法构造算法的问题有重要的递推性质,即当得到问题规模为i-1的解后,由问题的递推性质,能从已求得的规模为1,2,…,i-1的一系列解,构造出问题规模为I的解。这样,程序可从i=0或i=1出发,重复地,由已知至i-1规模的解,通过递推,获得规模为i的解,直至得到规模为N的解。
【问题】 阶乘计算
问题描述:编写程序,对给定的n(n≤100),计算并输出k的阶乘k!(k=1,2,…,n)的全部有效数字。
由于要求的整数可能大大超出一般整数的位数,程序用一维数组存储长整数,存储长整数数组的每个元素只存储长整数的一位数字。如有m位成整数N用数组a[ ]存储:
N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100
并用a[0]存储长整数N的位数m,即a[0]=m。按上述约定,数组的每个元素存储k的阶乘k!的一位数字,并从低位到高位依次存于数组的第二个元素、第三个元素……。例如,5!=120,在数组中的存储形式为:
3 0 2 1 ……
首元素3表示长整数是一个3位数,接着是低位到高位依次是0、2、1,表示成整数120。
计算阶乘k!可采用对已求得的阶乘(k-1)!连续累加k-1次后求得。例如,已知4!=24,计算5!,可对原来的24累加4次24后得到120。细节见以下程序。
# include <stdio.h>
# include <malloc.h>
# define MAXN 1000
void pnext(int a[ ],int k)
{ int *b,m=a[0],i,j,r,carry;
b=(int * ) malloc(sizeof(int)* (m+1));
for ( i=1;i<=m;i++) b[i]=a[i];
for ( j=1;j<=k;j++)
{ for ( carry=0,i=1;i<=m;i++)
{ r=(i<a[0]?a[i]+b[i]:a[i])+carry;
a[i]=r%10;
carry=r/10;
}
if (carry) a[++m]=carry;
}
free(b);
a[0]=m;
}
void write(int *a,int k)
{ int i;
printf(“%4d!=”,k);
for (i=a[0];i>0;i--)
printf(“%d”,a[i]);
printf(“\n\n”);
}
void main()
{ int a[MAXN],n,k;
printf(“Enter the number n: “);
scanf(“%d”,&n);
a[0]=1;
a[1]=1;
write(a,1);
for (k=2;k<=n;k++)
{ pnext(a,k);
write(a,k);
getchar();
}
}
2.递归
递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。
能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。
【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。
斐波那契数列为:0、1、1、2、3、……,即:
fib(0)=0;
fib(1)=1;
fib(n)=fib(n-1)+fib(n-2) (当n>1时)。
写成递归函数有:
int fib(int n)
{ if (n==0) return 0;
if (n==1) return 1;
if (n>1) return fib(n-1)+fib(n-2);
}
递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n-2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。
在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。
在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。
由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。
【问题】 组合问题
问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1
(4)5、3、2 (5)5、3、1 (6)5、2、1
(7)4、3、2 (8)4、3、1 (9)4、2、1
(10)3、2、1
分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。
【程序】
# include <stdio.h>
# define MAXN 100
int a[MAXN];
void comb(int m,int k)
{ int i,j;
for (i=m;i>=k;i--)
{ a[k]=i;
if (k>1)
comb(i-1,k-1);
else
{ for (j=a[0];j>0;j--)
printf(“%4d”,a[j]);
printf(“\n”);
}
}
}
void main()
{ a[0]=3;
comb(5,3);
}
3.回溯法
回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。
【问题】 组合问题
问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。
采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:
(1) a[i+1]>a[i],后一个数字比前一个大;
(2) a[i]-i<=n-r+1。
按回溯法的思想,找解过程可以叙述如下:
首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:
【程序】
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a[i]=1;
do {
if (a[i]-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j<r;j++)
printf(“%4d”,a[j]);
printf(“\n”);
}
a[i]++;
continue;
}
else
{ if (i==0)
return;
a[--i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
4.贪婪法
贪婪法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。
例如平时购物找钱时,为使找回的零钱的硬币数最少,不考虑找零钱的所有各种发表方案,而是从最大面值的币种开始,按递减的顺序考虑各币种,先尽量用大面值的币种,当不足大面值币种的金额时才去考虑下一种较小面值的币种。这就是在使用贪婪法。这种方法在这里总是最优,是因为银行对其发行的硬币种类和硬币面值的巧妙安排。如只有面值分别为1、5和11单位的硬币,而希望找回总额为15单位的硬币。按贪婪算法,应找1个11单位面值的硬币和4个1单位面值的硬币,共找回5个硬币。但最优的解应是3个5单位面值的硬币。
【问题】 装箱问题
问题描述:装箱问题可简述如下:设有编号为0、1、…、n-1的n种物品,体积分别为v0、v1、…、vn-1。将这n种物品装到容量都为V的若干箱子里。约定这n种物品的体积均不超过V,即对于0≤i<n,有0<vi≤V。不同的装箱方案所需要的箱子数目可能不同。装箱问题要求使装尽这n种物品的箱子数要少。
若考察将n种物品的集合分划成n个或小于n个物品的所有子集,最优解就可以找到。但所有可能划分的总数太大。对适当大的n,找出所有可能的划分要花费的时间是无法承受的。为此,对装箱问题采用非常简单的近似算法,即贪婪法。该算法依次将物品放到它第一个能放进去的箱子中,该算法虽不能保证找到最优解,但还是能找到非常好的解。不失一般性,设n件物品的体积是按从大到小排好序的,即有v0≥v1≥…≥vn-1。如不满足上述要求,只要先对这n件物品按它们的体积从大到小排序,然后按排序结果对物品重新编号即可。装箱算法简单描述如下:
{ 输入箱子的容积;
输入物品种数n;
按体积从大到小顺序,输入各物品的体积;
预置已用箱子链为空;
预置已用箱子计数器box_count为0;
for (i=0;i<n;i++)
{ 从已用的第一只箱子开始顺序寻找能放入物品i 的箱子j;
if (已用箱子都不能再放物品i)
{ 另用一个箱子,并将物品i放入该箱子;
box_count++;
}
else
将物品i放入箱子j;
}
}
上述算法能求出需要的箱子数box_count,并能求出各箱子所装物品。下面的例子说明该算法不一定能找到最优解,设有6种物品,它们的体积分别为:60、45、35、20、20和20单位体积,箱子的容积为100个单位体积。按上述算法计算,需三只箱子,各箱子所装物品分别为:第一只箱子装物品1、3;第二只箱子装物品2、4、5;第三只箱子装物品6。而最优解为两只箱子,分别装物品1、4、5和2、3、6。
若每只箱子所装物品用链表来表示,链表首结点指针存于一个结构中,结构记录尚剩余的空间量和该箱子所装物品链表的首指针。另将全部箱子的信息也构成链表。以下是按以上算法编写的程序。
【程序】
# include <stdio.h>
# include <stdlib.h>
typedef struct ele
{ int vno;
struct ele *link;
} ELE;
typedef struct hnode
{ int remainder;
ELE *head;
Struct hnode *next;
} HNODE;
void main()
{ int n, i, box_count, box_volume, *a;
HNODE *box_h, *box_t, *j;
ELE *p, *q;
Printf(“输入箱子容积\n”);
Scanf(“%d”,&box_volume);
Printf(“输入物品种数\n”);
Scanf(“%d”,&n);
A=(int *)malloc(sizeof(int)*n);
Printf(“请按体积从大到小顺序输入各物品的体积:”);
For (i=0;i<n;i++) scanf(“%d”,a+i);
Box_h=box_t=NULL;
Box_count=0;
For (i=0;i<n;i++)
{ p=(ELE *)malloc(sizeof(ELE));
p->vno=i;
for (j=box_h;j!=NULL;j=j->next)
if (j->remainder>=a[i]) break;
if (j==NULL)
{ j=(HNODE *)malloc(sizeof(HNODE));
j->remainder=box_volume-a[i];
j->head=NULL;
if (box_h==NULL) box_h=box_t=j;
else box_t=boix_t->next=j;
j->next=NULL;
box_count++;
}
else j->remainder-=a[i];
for (q=j->next;q!=NULL&&q->link!=NULL;q=q->link);
if (q==NULL)
{ p->link=j->head;
j->head=p;
}
else
{ p->link=NULL;
q->link=p;
}
}
printf(“共使用了%d只箱子”,box_count);
printf(“各箱子装物品情况如下:”);
for (j=box_h,i=1;j!=NULL;j=j->next,i++)
{ printf(“第%2d只箱子,还剩余容积%4d,所装物品有;\n”,I,j->remainder);
for (p=j->head;p!=NULL;p=p->link)
printf(“%4d”,p->vno+1);
printf(“\n”);
}
}
5.分治法
任何一个可以用计算机求解的问题所需的计算时间都与其规模N有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算;n=2时,只要作一次比较即可排好序;n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
如果原问题可分割成k个子问题(1<k≤n),且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
分治法所能解决的问题一般具有以下几个特征:
(1)该问题的规模缩小到一定的程度就可以容易地解决;
(2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
(3)利用该问题分解出的子问题的解可以合并为该问题的解;
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
分治法在每一层递归上都有三个步骤:
(1)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
(2)解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
(3)合并:将各个子问题的解合并为原问题的解。
6.动态规划法
经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。
为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用。
【问题】 求两字符序列的最长公共字符子序列
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
代码如下:
# include <stdio.h>
# include <string.h>
# define N 100
char a[N],b[N],str[N];
int lcs_len(char *a, char *b, int c[ ][ N])
{ int m=strlen(a), n=strlen(b), i,j;
for (i=0;i<=m;i++) c[i][0]=0;
for (i=0;i<=n;i++) c[0][i]=0;
for (i=1;i<=m;i++)
for (j=1;j<=m;j++)
if (a[i-1]==b[j-1])
c[i][j]=c[i-1][j-1]+1;
else if (c[i-1][j]>=c[i][j-1])
c[i][j]=c[i-1][j];
else
c[i][j]=c[i][j-1];
return c[m][n];
}
char *buile_lcs(char s[ ],char *a, char *b)
{ int k, i=strlen(a), j=strlen(b);
k=lcs_len(a,b,c);
s[k]=’\0’;
while (k>0)
if (c[i][j]==c[i-1][j]) i--;
else if (c[i][j]==c[i][j-1]) j--;
else { s[--k]=a[i-1];
i--; j--;
}
return s;
}
void main()
{ printf (“Enter two string(<%d)!\n”,N);
scanf(“%s%s”,a,b);
printf(“LCS=%s\n”,build_lcs(str,a,b));
}
7.迭代法
迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:
(1) 选一个方程的近似根,赋给变量x0;
(2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;
(3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。
若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:
程序如下:
【算法】迭代法求方程组的根
{ for (i=0;i<n;i++)
x[i]=初始近似根;
do {
for (i=0;i<n;i++)
y[i] = x[i];
for (i=0;i<n;i++)
x[i] = gi(X);
for (delta=0.0,i=0;i<n;i++)
if (fabs(y[i]-x[i])>delta) delta=fabs(y[i]-x[i]); } while (delta>Epsilon);
for (i=0;i<n;i++)
printf(“变量x[%d]的近似根是 %f”,I,x[i]);
printf(“\n”);
} 具体使用迭代法求根时应注意以下两种可能发生的情况:
(1)如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;
(2)方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。
8.穷举搜索法
穷举搜索法是对可能是解的众多候选解按某种顺序进行逐一枚举和检验,并从众找出那些符合要求的候选解作为问题的解。
【问题】 将A、B、C、D、E、F这六个变量排成如图所示的三角形,这六个变量分别取[1,6]上的整数,且均不相同。求使三角形三条边上的变量之和相等的全部解。如图就是一个解。
程序引入变量a、b、c、d、e、f,并让它们分别顺序取1至6的整数,在它们互不相同的条件下,测试由它们排成的如图所示的三角形三条边上的变量之和是否相等,如相等即为一种满足要求的排列,把它们输出。当这些变量取尽所有的组合后,程序就可得到全部可能的解。程序如下:
# include <stdio.h>
void main()
{ int a,b,c,d,e,f;
for (a=1;a<=6;a++) {
for (b=1;b<=6;b++) {
if (b==a) continue;
for (c=1;c<=6;c++) {
if (c==a)||(c==b) continue;
for (d=1;d<=6;d++) {
if (d==a)||(d==b)||(d==c) continue;
for (e=1;e<=6;e++) {
if (e==a)||(e==b)||(e==c)||(e==d) continue;
f = 21-(a+b+c+d+e);
if ((a+b+c==c+d+e))&&(a+b+c==e+f+a)) {
printf(“%6d,a);
printf(“%4d%4d”,b,f);
printf(“%2d%4d%4d”,c,d,e);
scanf(“%*c”);
}
}
}
}
}
}}
按穷举法编写的程序通常不能适应变化的情况。如问题改成有9个变量排成三角形,每条边有4个变量的情况,程序的循环重数就要相应改变。
② 算法:求最快的算法,我的算法超时了,2s,64MB
申请一个长度为n的数组A,用于存储某个列的元素。(这步没必要,只是为了说明清晰)
把第一列装进A,扫描第二列,并与第一列相应元素比较。统计第1列时为1且第二列时也为1的次数m,以及第1列时为0且第二列时也为0的次数n,则出现在第一列和第二列的满足题意矩阵个数为:
m(m-1)/2 + n(n-1)/2
继续扫描第三列,第四列...第n列(这里复杂度为n^2,因为要扫描整个矩阵),并将每次扫描后计算出的数值累计求和。
上面只包括了那些矩阵,它的左上和左下元素在第一列。上述过程不断迭代即可(这里复杂度为n,因为到迭代n列)
算法复杂度O(n^3),对于这么小的矩阵,不可能超时。
③ 一个算法的‘计算量’该如何量化
这问题提的好。衡量算法开销通常使用O()运算符由于同一个算法运行于不同的机器上所耗费的实际时间是不同的,所以不能使用实际时间单位衡量算法运行效率,而应使用逻辑单位。描述算法复杂度的参数为算法的输入数据规模,通常用n来表示,那么算法的复杂度可表示为一个关于n的函数。通常最常用的描述算法复杂度的符号为O符号,即将复杂度表示为O(f(n))。其中f(n)用函数形式描述算法执行命令条数与输入规模n的关系,而O()起到估算化简的作用。比如某个算法经过逻辑分析后,其指令数可表示为f(n)=8n^2+10n+500,那么可以使用O(f(n))来简化其表达,O()符号运算性质有多条,总体来说就是保留增长率最高的项且忽略常数系数,上面的表达式化简结果为O(n^2)。当然O()符号不能完美描述算法开销,因为它忽略了常数的影响,当某些项前的常数系数非常非常大时,会对算法复杂度的判断造成误差,这就要具体问题具体分析了。下面简单说一下具体如何分析。for (i = 0;ik *= i;}这段代码每次循环中执行一次乘法两次赋值(假定乘法使用单周期乘法器实现),循环开始执行一次赋值,那么共计执行指令数3n+1,即复杂度为O(n)。for (i = 0;ifor (j = 0;jk += i * j;}循环嵌套时,内层循环执行3n+1条指令,外层循环n次,共n*(3n+1)+1=3n^2 + n +1条指令,即O(n^2)。
④ 行列式按列展开的方法是跟按行展开的一样吗
是一样的,展开都是正确的。第一张图里的错误步骤在第二行。
一、错误指导:
(1)+(3) x 7/3,应该是
| 0 4 -10/3 |
|0 -5 5 |
|3 9 2 |
第一行第二列的10,算错了,应该是4= -17-(-7/3)*9。
用4代入,最后算出的结果会是10,而不是100。
二、行列式算法:
1、为了计算更高阶行列式,我们需要引入两个概念:全排列和逆序数。
全排列比较简单,在高中就学过:n个不同元素的不同排列法一共有
2、全排列:在这些排列中,如果规定从小到大是标准次序,则每有两个元素不是标准次序就称为一个“逆序”。比如32514中,3在2前面,3在1前面,5在1前面,5在4前面,2在1前面。逆序数就是排列中逆序的数目,用t表示。
3、逆序数:逆序数没有计算方法,就是靠数出来的!每次看一个数,看前面有比它大的有几个。如果逆序数是奇数,这个排列叫奇排列,否则叫偶排列。标准次序逆序是0,所以是偶排列。
4、n阶行列式,n阶行列式的值,n阶行列式一共有n!项(因为是a的第二个下标的全排列),每一项都是不同行不同列的n个元素的积,当第二下标的排列是奇排列符号为负,否则为正。
(4)ik算法扩展阅读:
一、行列式的性质:
1、行列式A中某行(或列)用同一数k乘,其结果等于kA。
2、行列式A等于其转置行列式AT(AT的第i行为A的第i列)。
3、若n阶行列式|αij|中某行(或列);行列式则|αij|是两个行列式的和,这两个行列式的第i行(或列),一个是b1,b2,…,bn;另一个是с1,с2,…,сn;其余各行(或列)上的元与|αij|的完全一样。
4、行列式A中两行(或列)互换,其结果等于-A。 ⑤把行列式A的某行(或列)中各元同乘一数后加到另一行(或列)中各对应元上,结果仍然是A。
二、行列式数学定义:
1、若n阶方阵A=(aij),则A相应的行列式D记作D=|A|=detA=det(aij)
2、若矩阵A相应的行列式D=0,称A为奇异矩阵,否则称为非奇异矩阵.
3、标号集:序列1,2,...,n中任取k个元素i1,i2,...,ik满足1≤i1<i2<...<ik≤n(1)
4、i1,i2,...,ik构成{1,2,...,n}的一个具有k个元素的子列,{1,2,...,n}的具有k个元素的满足(1)的子列的全体记作C(n,k),显然C(n,k)共有个子列。
5、因此C(n,k)是一个具有个元素的标号集(参见第二十一章,1,二),C(n,k)的元素记作σ,τ,...,σ∈C(n,k)。
6、表示σ={i1,i2,...,ik}是{1,2,...,n}的满足(1)的一个子列.若令τ={j1,j2,...,jk}∈C(n,k),则σ=τ表示i1=j1,i2=j2,...,ik=jk。
参考资料来源:网络-行列式
⑤ 古典密码安全算法有哪些
世界上最早的一种密码产生于公元前两世纪。是由一位希腊人提出的,人们称之为
棋盘密码,原因为该密码将26个字母放在5×5的方格里,i,j放在一个格子里,具体情
况如下表所示
1 2 3 4 5
1 a b c d e
2 f g h i,j k
3 l m n o p
4 q r s t u
5 v w x y z
这样,每个字母就对应了由两个数构成的字符αβ,α是该字母所在行的标号,β是列
标号。如c对应13,s对应43等。如果接收到密文为
43 15 13 45 42 15 32 15 43 43 11 22 15
则对应的明文即为secure message。
另一种具有代表性的密码是凯撒密码。它是将英文字母向前推移k位。如k=5,则密
文字母与明文与如下对应关系
a b c d e f g h i j k l m n o p q r s t u v w x y z
F G H I J K L M N O P Q R S T U V W X Y Z A B C D E
于是对应于明文secure message,可得密文为XJHZWJRJXXFLJ。此时,k就是密钥。为了
传送方便,可以将26个字母一一对应于从0到25的26个整数。如a对1,b对2,……,y对
25,z对0。这样凯撒加密变换实际就是一个同余式
c≡m+k mod 26
其中m是明文字母对应的数,c是与明文对应的密文的数。
随后,为了提高凯撒密码的安全性,人们对凯撒密码进行了改进。选取k,b作为两
个参数,其中要求k与26互素,明文与密文的对应规则为
c≡km+b mod 26
可以看出,k=1就是前面提到的凯撒密码。于是这种加密变换是凯撒野加密变换的
推广,并且其保密程度也比凯撒密码高。
以上介绍的密码体制都属于单表置换。意思是一个明文字母对应的密文字母是确定
的。根据这个特点,利用频率分析可以对这样的密码体制进行有效的攻击。方法是在大
量的书籍、报刊和文章中,统计各个字母出现的频率。例如,e出现的次数最多,其次
是t,a,o,I等等。破译者通过对密文中各字母出现频率的分析,结合自然语言的字母频
率特征,就可以将该密码体制破译。
鉴于单表置换密码体制具有这样的攻击弱点,人们自然就会想办法对其进行改进,
来弥补这个弱点,增加抗攻击能力。法国密码学家维吉尼亚于1586年提出一个种多表式
密码,即一个明文字母可以表示成多个密文字母。其原理是这样的:给出密钥
K=k[1]k[2]…k[n],若明文为M=m[1]m[2]…m[n],则对应的密文为C=c[1]c[2]…c[n]。
其中C[i]=(m[i]+k[i]) mod 26。例如,若明文M为data security,密钥k=best,将明
文分解为长为4的序列data security,对每4个字母,用k=best加密后得密文为
C=EELT TIUN SMLR
从中可以看出,当K为一个字母时,就是凯撒密码。而且容易看出,K越长,保密程
度就越高。显然这样的密码体制比单表置换密码体制具有更强的抗攻击能力,而且其加
密、解密均可用所谓的维吉尼亚方阵来进行,从而在操作上简单易行。该密码可用所谓
的维吉尼亚方阵来进行,从而在操作上简单易行。该密码曾被认为是三百年内破译不了
的密码,因而这种密码在今天仍被使用着。
古典密码的发展已有悠久的历史了。尽管这些密码大都比较简单,但它在今天仍有
其参考价值。
⑥ 矩阵算法是什么
矩阵算法指矩阵与算法。
矩阵乘法是一种高效的算法可以把一些一维递推优化到log( n ),还可以求路径方案等,所以更是是一种应用性极强的算法。矩阵,是线性代数中的基本概念之一。
一个m×n的矩阵就是m×n个数排成m行n列的一个数阵。由于它把许多数据紧凑的集中到了一起,所以有时候可以简便地表示一些复杂的模型。矩阵乘法看起来很奇怪,但实际上非常有用,应用也十分广泛。
矩阵乘法的两个重要性质:
一,矩阵乘法不满足交换律。
二,矩阵乘法满足结合律。矩阵乘法不满足交换律,因为交换后两个矩阵有可能不能相乘。它又满足结合律,假设你有三个矩阵A、B、C,那么(AB)C和A(BC)的结果的第i行第j列上的数都等于所有A(ik)*B(kl)*C(lj)的和(枚举所有的k和l)。
⑦ 这是什么加密算法
电报。。。
⑧ 条件模拟
设已知RFZ(u)在n个点处的观测值为z(uα),α=1,…,n。随机模拟的目的是建立一系列的与Z(u)等概率的高清晰度的实现
地质勘探三维可视化技术及系统开发
这样的实现可以取若干个,ι代表其中的每个实现。
如果在原给数据点uα处的模拟结果与原始数据相同,即
地质勘探三维可视化技术及系统开发
就说这模拟是条件模拟。
这段程序的目的是针对Z(u)是连续或离散的情形,给出计算条件模拟(7.3.1)的各种算法。
一、序列高斯模拟
这种方法适用于连续型的RF Z(u)。共分如下几个步骤:
(1)建立单变量cdfFz(z)(histsmth),它代表Z(u)在整个研究区的变化规律。设Z(u)的n个样本值按递增顺序排列成序列
地质勘探三维可视化技术及系统开发
则对应于第k个大值的累计频率为
地质勘探三维可视化技术及系统开发
其中的wj是人为给定的一组权值。
(2)用Fz(z)对z作正态得分变换(nscore及backtr)设RF Y(u)是正态分布的,它的单变量cdf是正态的,即
地质勘探三维可视化技术及系统开发
是标准正态分布函数,均值为0,方差为1。我们要建立一个变换Y=φ(Z),原则是Y与Z的对应于p分位数的概率相等
地质勘探三维可视化技术及系统开发
即
地质勘探三维可视化技术及系统开发
其中是
(3)序列模拟
(ⅰ)定义一个随机通道,它通过N个待模拟的节点u1,…,u N中的每一个。设这些点的随机顺序为
(ⅱ)对于每个待模拟的点
(ⅲ)根据正态得分数据y(ui)计算变差函数,再用克立格方法计算RF Y
(ⅳ)根据这分布函数提取模拟值
(ⅴ)将此模拟值y(1)
地质勘探三维可视化技术及系统开发
并作为下一个模拟点的条件数据,返回(ⅱ),再抽取出模拟植(yι)
对模拟值进行一些内插或外推常常是必要的。
如果必要的话,可以进行多次模拟,得{Z(1)(uj),J=1,…,N},1=1,2,…,L。每次模拟的随机通道可以是相同的,但最好还是不同的。
二、序列指示模拟
为了在每个待模拟点
(1)离散变量的模拟
设Z(u)是离散RF,对于
地质勘探三维可视化技术及系统开发
这时,具体的观测值i(u,sk)应满足如下条件
地质勘探三维可视化技术及系统开发
前已指出,对指示变量的Krige估计给出Z(u)取各种状态的条件概率的估计值
地质勘探三维可视化技术及系统开发
图7-15 序列高斯模拟计算流程(L—模拟次)
其中Pk=E{I(u,sk)=1}=P{Z(u)=sk}可取为z(uα)中的取sk的点所占的比例。
对于每个待模拟的点
地质勘探三维可视化技术及系统开发
其中的条件(*)包括原始数据和已有的模拟结果,这些条件概率的和不一定等于1,将它们作归一化处理,并将K个类任意排序,仍记为1,2,…,K,令
地质勘探三维可视化技术及系统开发
在区间[0,1]内建立cdf型尺度,即将它割成K个子区间,各区间长度分别为P1,P2,…,Pk:
地质勘探三维可视化技术及系统开发
在[0,1]内取均匀分布的随机数p,p所落入的区间决定了在点
(2)连续变量的模拟
将连续变量Z(u)离散化成K个互不相交的区间sk:(zk-1,zk],k=1,…,K,这可以将Z(u)看成是含有K类的离散变量,并按前述方法进行模拟。这样做造成的类内差异的损失可用某种先验的类内分布来部分地补赏。
将连续变量分成K段的优点是可以在每类内用不同的变差函数模型来刻划空间分布,从而可以处理多母体混合的问题。另一个优点是这种估计和模拟方法可以处理软数据。
对于纯的离散变量来说,它的各类没有任何顺序,而这里的各类都有一定的顺序。因此,最好用相应的“累计类指示值”来刻划各类。令
地质勘探三维可视化技术及系统开发
类(zk-1,zk]用乘积I(u,zk)[1-I(u,zk-1)]=1来定义。除了第一类和最后一类,累计指示变差函数的推断要比“类”指示变差函数的推断容易,尤其当某些类有小的边缘概率时。累计指示值与所研究的连续变量的ccdf直接有关。
对于随机通道上的每个待模拟的点
地质勘探三维可视化技术及系统开发
图7-16 序列指示模拟计算流程
用类内插值的方法,可以对所有可能的阈值z∈[zmin,zmax]得到估计
用蒙特—卡罗法可得模拟值z()ι
地质勘探三维可视化技术及系统开发
它使
地质勘探三维可视化技术及系统开发
将已模拟的结果作为已知数据,修改指示数据集合,再模拟下一个点。
三、概率场模拟
在序列模拟中,将每个模拟值都看成是下一次模拟的条件数据,因此,在每个模拟点处必须重新计算ccdf。如果每次模拟的条件数据都只是原始数据,就可以保持ccdf不变,从而提高计算速度。这里,用于从ccdf中抽取模拟值的概率本身是相关的,而不象序列模拟那样是相互独立的。
设F(u,z|(n))和
地质勘探三维可视化技术及系统开发
它使
地质勘探三维可视化技术及系统开发
概率值p(1)(u)和p(ι)的空间相关性是因为它们来自一个概率场或RF P(u)的同一个实现(ι),这P(u)服从[0,1]内的平稳的均匀分布,而且它的协方差函数来自数据的均匀变换的样本协方差。
注意,概率场的实现{p(1)(u),u∈A}不要求条件,因为ccdf本身就是以原始数据为条件的。在数据点uα处,ccdf F(uα,z|(n))具有零方差,从而集中于数据值z(uα),因此不管概率值p(1)(uα)是什么,那个ccdf总是返回到数据值z(1)(uα)=z(uα)。
与序列方法相反,概率场方法将满足原始数据条件化的任务和协方差再现的任务分开,前者通过ccdf F(u,z|(n))来完成,后者通过概率值P(ι)(u)来完成。
该法的主要优点是计算速度快:
a.F(u,z|(n))的条件只是n个原始数据,只须计算一次并存储起来。
b.由于概率场的实现{p(ι)(u),u∈A,ι=1,…,L}是无条件的,各种快速的模拟算法都可以用,例如谱方法,无条件fratals,或简单的随机滑动平均等。
c.每个P实现就可用于从已算出的ccdf中抽取Z(u)的实现。
四、高斯场的多重截断
该法可用高斯实现(例如用序列高斯模拟)的多重截断对离散型RF进行模拟。
具体计算分成如下几个步骤:
(1)这里所研究的对象为离散型的RF Z(u),u∈ G,它可取k个不同的状态s1,…,sk。原始数据为z(uα)=z(uα1,uα2,uα3),α=1,2,…,n,它的取值为上述k个不同状态中的一个。
(2)根据问题的实际背景调整各状态的顺序,使之能够反映实际状态的接触关系,假定现有的状态顺序即为调整后的结果。根据原始数据,计算各状态所占有的比例p1,p2,…,pk,作为Z(u)取各状态的概率的近似。将这些比例转化为累积概率。
地质勘探三维可视化技术及系统开发
由此形成正态分布的截断值。
0=t0≤t1≤…≤tk=1
有时要考虑与空间位置有关的截断值ti(u), i=1,…,K,u∈ G。
图7-17 概率场模拟计算流程
(3)造一个平稳的高斯RF Y(u)~N(0,1),u∈G。它的相关系数函数ρ(h)的选择应保证在该高斯函数经过截断处理后,得到的理论交叉指示变异函数模型与原始数据的实验交叉指示变异函数相容。
以此为基础,求Y(u)的非条件模拟yi(u)(可用转向带法),要同时算出网络节点在原始数据点处的模拟结果。
(4)运用克立格方法,将原始数据转化为Y(u)的条件值,以保证将Y(u)的条件模拟结果截断处理后,条件化到已知数据。
(5)在前两步的基础上,求Y(u)的条件模拟(例如用误差模拟方法)。
(6)将上述条件模拟结果ycs(u)按下述方法作多重截断处理:
地质勘探三维可视化技术及系统开发
由此得到原来离散RFZ(u)的模拟结果:
地质勘探三维可视化技术及系统开发
图7-18 高斯场的多重截断计算流程
⑨ ikanalyzer 可以做词性标注吗
可以的