算法设计技巧论述.docx
- 文档编号:2425975
- 上传时间:2023-05-03
- 格式:DOCX
- 页数:27
- 大小:124.10KB
算法设计技巧论述.docx
《算法设计技巧论述.docx》由会员分享,可在线阅读,更多相关《算法设计技巧论述.docx(27页珍藏版)》请在冰点文库上搜索。
算法设计技巧论述
算法设计技巧论述
摘要:
本文对几种经典算法设计技巧进行了分析和论述。
包括贪婪算法、分治算法、动态规划、随机化算法以及回溯算法,系统的介绍了他们的原理、特点和算法设计的步骤,并对每一种算法进行了举例分析,对算法设计的发展趋势进行了展望。
关键词:
贪婪算法;分治算法;动态规划;随机化算法;回溯算法
1前言
随着科学技术的不断发展,算法设计变得越来越复杂,算法设计的研究受到人们越来越多的重视。
特别是计算机技术的广泛应用,更促进了算法设计相关理论的研究与发展。
2贪婪算法
2.1基本概念
概念:
贪婪算法(又称贪心算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。
也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
2.2算法思想及算法实现步骤
2.2.1算法思想
从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。
当达到某算法中的某一步不能再继续前进时,算法停止。
2.2.2实现该算法的步骤:
1、从问题的某一初始解出发;
2、while能朝给定总目标前进一步do
3、求出可行解的一个解元素;
4、由所有解元素组合成问题的一个可行解;
2.3贪婪算法的基本要素
对于一个具体的问题,怎么知道是否可用贪心算法解此问题,以及能否得到问题的最优解呢?
这个问题很难给予肯定的回答。
但是,从许多可以用贪心算法求解的问题中看到这类问题一般具有2个重要的性质:
贪心选择性质和最优子结构性质。
2.3.1贪心选择性质
所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
动态规划算法通常以自底向上的方式解决子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。
2.3.2最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
2.4贪婪算法的不足及优势
2.4.1贪婪算法的不足:
1、不能保证求得的最后解是最佳的;
2、不能用来求最大或最小解问题;
3、只能求满足某些约束条件的可行解的范围。
2.4.2贪婪算法的优势
贪婪算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪婪算法与其他算法相比具有一定的速度优势。
如果一个问题可以同时用几种方法解决,贪婪算法应该是最好的选择之一。
2.5例题分析
2.5.1背包问题
题目要求:
有一个背包,背包容量是M=150.有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品ABCDEFG
重量35306050401025
价值10403050354030
算法分析:
目标函数:
最大
约束条件是装入的物品总重量不超过背包容量,既
1、根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
2、每次选取挑选所占重量最小的物品装入是否能得到最优解?
3、每次选取单位重量价值最大的物品,成为解决本题的策略。
贪心算法是很常见的算法之一,这是由于它简单易行,构造贪心策略简单。
但是,它需要证明后才能真正运用到题目的算法中。
一般来说,贪心算法的证明围绕着整个问题的最优解一定是由在贪心策略中存在的子问题的最优解得来的。
对于本例题中的3种贪心策略,都无法成立,即无法被证明,解释如下:
1、贪心策略:
选取价值最大者。
反例:
W=30
物品ABC
重量281212
价值302020
根据策略,首先选取物品A,接下来就无法选取了,可是,选取B、C则更好。
2、贪心策略:
选取重量最小。
它的反例与第一种策略的反例差不多。
3、贪心策略:
选取单位重量价值最大的物品。
反例:
W=30
物品:
ABC
重量:
282010
价值:
282010
根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
比如,求最小生成树的Prim算法和Krushal算法都是漂亮的贪心算法。
2.5.2均分纸牌问题
题目要求:
有N对纸牌,编号分别为1,2,...,n。
每堆上有若干张,但纸牌总数必为n的倍数,可以在任一堆上取若干张纸牌,然后移动。
纸牌的规则为:
在编号为1上取纸牌,只能移动到编号为2的堆上;在编号为n的堆上取得纸牌,
只能移动到编号为n-1的堆上;其他堆上取得纸牌,可以移动到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如:
n=4,4堆纸牌分别为:
①9②8③17④6移动三次可以达到目的:
从③取4张牌放到④,再从③取三张放到②,取一张放到①。
算法分析:
设a[i]为第I堆纸牌的张数(0<=I<=n),v为均分后每堆纸牌的张数,s为最小移动次数。
我们用贪心算法,按照从左到右的顺序移动纸牌。
如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种情况移动:
1、若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆;
2、若a[i] 为了设计的方便,我们把这两种情况统一看作是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v;a[I+1]=a[I+1]+a[i]-v在从第I+1堆取出纸牌补充第I堆的过程中可能回出现第I+1堆的纸牌小于零的情况。 贪婪算法均分纸牌流程图 如n=3,三堆指派数为1227,这时v=10,为了使第一堆为10,要从第二堆移9张到第一堆,而第二堆只有2张可以移,这是不是意味着刚才使用贪心法是错误的呢? 我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张,第二堆剩下-7张,在从第三堆移动17张到第二堆,刚好三堆纸牌都是10,最后结果是对的,我们在移动过程中,只是改变了移动的顺序,而移动次数不便,因此此题使用贪心法可行的。 3分治算法 3.1基本概念 概念: 分治算法是指把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题······直到最后子问题可以简单的直接求解,原问题即子问题的合并。 3.2算法思想及算法实现步骤 3.2.1分治算法的基本思想 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。 求出子问题的解,就可得到原问题的解。 3.2.2分治法解题的一般步骤 分治法解题的一般步骤: 1、分解,将要解决的问题划分成若干规模较小的同类问题; 2、求解,当子问题划分得足够小时,用较简单的方法解决; 3、合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。 3.2.3算法设计模式 它的一般的算法设计模式如下: Divide-and-conquer(P) 1、if|P|≤n0 2、thenreturn(ADHOc(P)) 3、将P分解为较小的子问题P1,P2,...,Pk 4、fori←1tok 5、doyi←Divide-and-conquer(Pi)△递归解决Pi 6、T←MERGE(y1,y2,...,yk)△合并子问题 7、return(T) 其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。 ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。 因此,当P的规模不超过n0时,直接用算法ADHOc(P)求解。 算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1,P2,...,Pk的相应的解y1,y2,...,yk合并为P的解。 3.3分治算法的分治策略及复杂性分析 3.3.1分治算法的分治策略 当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。 对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。 如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。 这就是分治策略的基本思想。 根据分治法的分割原则,原问题应该分为多少个子问题才较适宜? 各个子问题的规模应该怎样才为适当? 这些问题很难予以肯定的回答。 但人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。 换句话说,将一个问题分成大小相等的k个子问题的处理方法是行之有效的。 许多问题可以取k=2。 这种使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。 分治法的合并步骤是算法的关键所在。 有些问题的合并方法比较明显,有些问题合并方法比较复杂,或者是有多种合并方案,或者是合并方案不明显,究竟应该怎样合并,没有统一的模式,需要具体问题具体分析。 3.3.2分治算法的复杂性分析 从分治法的一般设计模式可以看出,用它设计出的程序一般是一个递归过程。 因此,分治法的计算效率通常可以用递归方程来进行分析。 为方便起见,设分解阈值n0=1,且算法ADHOC解规模为1的问题耗费1个单位时间。 又设分治法将规模为n的问题分成k个规模为n/m的子问题去解,而且,将原问题分解为k个子问题以及用算法MERGE将k个子问题的解合并为原问题的解需用f(n)个单位时间。 如果用T(n)表示该分治法Divide-and-conquer(P)解规模为|P|=n的问题P所需的计算时间,则有: 1、T (1)=1T(n)=kT(n/m)+f(n) 用算法的复杂性中递归方程解的渐进阶的解法介绍的解递归方程的迭代法,可以求得 (1)的解: logm(n-1) 2、T(n)=n^(logmK)+∑(k^j)*f(n/(m^j))j=0 注意,递归方程 (1)及其解 (2)只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由等于m的方幂时T(n)的值可以估计T(n)的增长速度。 通常, 我们可以假定T(n)是单调上升的,从而当mi≤nn0 由于我们关心的一般是最坏情况下的计算时间复杂度的上界,所以用等于号(=)还是小于或等于号(≤)是没有本质区别的分治法的几种变形。 3.4例题分析 3.4.1找出伪币 题目要求: 给你一个装有16个硬币的袋子。 16个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。 你的任务是找出这个伪造的硬币。 为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。 解决方案一: 比较硬币1与硬币2的重量。 假如硬币1比硬币2轻,则硬币1是伪造的;假如硬币2比硬币1轻,则硬币2是伪造的。 这样就完成了任务。 假如两硬币重量相等,则比较硬币3和硬币4。 同样,假如有一个硬币轻一些,则寻找伪币的任务完成。 假如两硬币重量相等,则继续比较硬币5和硬币6。 按照这种方式,可以最多通过8次比较来判断伪币的存在并找出这一伪币。 解决方案二: 利用分而治之方法。 假如把16硬币的例子看成一个大的问题。 第一步,把这一问题分成两个小问题。 随机选择8个硬币作为第一组称为A组,剩下的8个硬币作为第二组称为B组。 这样,就把16个硬币的问题分成两个8硬币的问题来解决。 第二步,判断A和B组中是否有伪币。 可以利用仪器来比较A组硬币和B组硬币的重量。 假如两组硬币重量相等,则可以判断伪币不存在。 假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。 最后,在第三步中,用第二步的结果得出原先16个硬币问题的答案。 若仅仅判断硬币是否存在,则第三步非常简单。 无论A组还是B组中有伪币,都可以推断这16个硬币中存在伪币。 因此,仅仅通过一次重量的比较,就可以判断伪币是否存在。 现在假设需要识别出这一伪币。 把两个或三个硬币的情况作为不可再分的小问题。 注意如果只有一个硬币,那么不能判断出它是否就是伪币。 在一个小问题中,通过将一个硬币分别与其他两个硬币比较,最多比较两次就可以找到伪币。 这样,16硬币的问题就被分为两个8硬币(A组和B组)的问题。 通过比较这两组硬币的重量,可以判断伪币是否存在。 如果没有伪币,则算法终止。 否则,继续划分这两组硬币来寻找伪币。 假设B是轻的那一组,因此再把它分成两组,每组有4个硬币。 称其中一组为B1,另一组为B2。 比较这两组,肯定有一组轻一些。 如果B1轻,则伪币在B1中,再将B1又分成两组,每组有两个硬币,称其中一组为B1a,另一组为B1b。 比较这两组,可以得到一个较轻的组。 由于这个组只有两个硬币,因此不必再细分。 比较组中两个硬币的重量,可以立即知道哪一个硬币轻一些。 较轻的硬币就是所要找的伪币。 分治算法解决伪币流程图 3.4.2金块问题 题目要求: 有一个老板有一袋金块。 每个月将有两名雇员会因其优异的表现分别被奖励一个金块。 按规矩,排名第一的雇员将得到袋中最重的金块,排名第二的雇员将得到袋中最轻的金块。 根据这种方式,除非有新的金块加入袋中,否则第一名雇员所得到的金块总是比第二名雇员所得到的金块重。 如果有新的金块周期性的加入袋中,则每个月都必须找出最轻和最重的金块。 假设有一台比较重量的仪器,我们希望用最少的比较次数找出最轻和最重的金块。 解决方案一: 假设袋中有n个金块。 可以用函数Max思想通过n-1次比较找到最重的金块。 找到最重的金块后,可以从余下的n-1个金块中用类似的方法通过n-2次比较找出最轻的金块。 这样,比较的总次数为2n-3。 解决方案二: 分而治之法。 当n很小时,比如说,n≤2,识别出最重和最轻的金块,一次比较就足够了。 当n较大时(n>2),第一步,把这袋金块平分成两个小袋A和B。 第二步,分别找出在A和B中最重和最轻的金块。 设A中最重和最轻的金块分别为HA与LA,以此类推,B中最重和最轻的金块分别为HB和LB。 第三步,通过比较HA和HB,可以找到所有金块中最重的;通过比较LA和LB,可以找到所有金块中最轻的。 在第二步中,若n>2,则递归地应用分而治之方法。 分治算法查找金块 假设n=8。 这个袋子被平分为各有4个金块的两个袋子A和B。 为了在A中找出最重和最轻的金块,A中的4个金块被分成两组A1和A2。 每一组有两个金块,可以用一次比较在A中找出较重的金块HA1和较轻的金块LA1。 经过另外一次比较,又能找出HA2和LA2。 现在通过比较HA1和HA2,能找出HA;通过LA1和LA2的比较找出LA。 这样,通过4次比较可以找到HA和LA。 同样需要另外4次比较来确定HB和LB。 通过比较HA和HB(LA和LB),就能找出所有金块中最重和最轻的。 因此,当n=8时,这种分而治之的方法需要10次比较。 设c(n)为使用分而治之方法所需要的比较次数。 为了简便,假设n是2的幂。 当n=2时,c(n)=1。 对于较大的n,c(n)=2c(n/2)+2。 当n是2的幂时,使用迭代方法可知c(n)=3n/2-2。 在本例中,使用分而治之方法比逐个比较的方法少用了25%的比较次数。 4动态规划 4.1基本概念 概念: 动态规划是指,每次决策依赖于当前状态,又随即引起状态的转移。 一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策问题的过程就称为动态规划。 4.2动态规划的基本思想及算法实现步骤 4.2.1动态规划的基本思想 动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。 与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。 如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。 我们可以用一个表来记录所有已解的子问题的答案。 不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。 这就是动态规划法的基本思路。 具体的动态规划算法多种多样,但它们具有相同的填表格式。 4.2.2动态规划实现基本步骤 动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。 这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。 如图所示。 动态规划的设计都有着一定的模式,一般要经历以下几个步骤。 初始状态→│决策1│→│决策2│→…→│决策n│→结束状态 图1动态规划决策过程示意图 1、划分阶段: 按照问题的时间或空间特征,把问题分为若干个阶段。 在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。 2、确定状态和状态变量: 将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。 当然,状态的选择要满足无后效性。 3、确定决策并写出状态转移方程: 因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。 所以如果确定了决策,状态转移方程也就可写出。 但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。 4、寻找边界条件: 给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。 一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。 实际应用中可以按以下几个简化的步骤进行设计: 1、分析最优解的性质,并刻画其结构特征。 2、递归的定义最优解。 3、以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值 4、根据计算最优值时得到的信息,构造问题的最优解 4.3算法的适用情况及算法实现的说明 4.3.1适用情况 能采用动态规划求解的问题的一般要具有3个性质: 1、最优化原理: 如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。 2、无后效性: 即某阶段状态一旦确定,就不受这个状态以后决策的影响。 也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。 3、有重叠子问题: 即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。 (该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。 4.3.2算法实现的说明 动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。 使用动态规划求解问题,最重要的就是确定动态规划三要素: 1、问题的阶段 2、每个阶段的状态 3、从前一个阶段转化到后一个阶段之间的递推关系。 递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。 确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。 f(n,m)=max{f(n-1,m),f(n-1,m-w[n])+P(n,m)} 4.4动态规划的优越性与缺点 4.4.1动态规划的优越性 1、能够得到全局最优解。 由于约束条件确定的约束集合往往很复杂,即使指标函数较简单,用非线性规划方法也很难求出全局最优解。 而动态规划方法把全过程化为一系列结构相似的子问题,每个子间题的变量个数大大减少,约束集合也简单得多,易于得到全局最优解。 特别是对于约束集合、状态转移和指标函数不能用分析形式给出的优化问题,可以对每个子过程用枚举法求解,而约束条件越多,决策的搜索范围越小,求解也越容易。 对于这类问题,动态规划通常是求全局最优解的唯一方法。 2、可以得到一族最优解。 与非线性规划只能得到全过程的一个最优解不同,动态规划得到的是全过程及所有后部子过程的各个状态的一族最优解。 有些实际问题需要这样的解族,即使不需要,它们在分析最优策略和最优值对于状态的稳定性时也是很有用的。 当最优策略由于某些原因不能实现时,这样的解族可以用来寻找次优策略。 3、能够利用经验提高求解效率。 如果实际问题本身就是动态的,由于动态规划方法反映了过程逐段演变的前后联系和动态特征,在计算中可以利用实际知识和经验提高求解率。 比如在策略迭代法中,实际经验能够帮助选择较好的初始策略,提高收敛速度。 4.4.2动态规划的主要缺点 1、没有统一的标准模型,也没有构造模型的通用方法,甚至还没有判断一个问题能否构造动态规划模型的具体准则(大部分情况只能够凭经验判断是否适用动态规划)。 这样就只能对每类问题进行具体分析,构造具体的模型。 对于较复杂的问题在选择状态、决策、确定状态转移规律等方面需要丰富的想象力和灵活的技巧性,这就带来了应用上的局限性。 2、用数值方法求解时存在维数灾(curse of dimensionality)。 若一维状态变量有m个取值,那么对于n维问题,状态xk就有mn个值,对于每个状态值都要计算、存储函数fk(xk),对于n稍大(即使n=3)的实际问题的计算往往是不现实的。 目前还没有克服维数灾的有效的一般方法。 4.5例题分析 [经典例题]背包问题 题目要求: 从n个物品中选取装入背包的物品,每件物品i的重量为wi,价值为pi。 求使物品价值最高的选取方法。 题目分析: 初看这类问题,第一个想到的会是贪心,但是贪心法却无法保证一定能得到最优解,看以下实例: 贪心准则1: 从剩余的物品中,选出可以装入背包的价值最大的物品。 利用这种规则,价值最大的物品首先被装入(假设有足够容量),然后是下一个价值最大的物品,如此继续下去。 这种策略不能保证得到最优解。 例如,考虑n=2,w=[100,10,10],p=[20,15,15],c=105。 (c为背包容量,n为物品数量)当利用价值贪婪准则时,获得的解为x=[1,0,0],这种方案的总价值为20。 而最优解为[0,1,1],其总价值为30。 贪心准则2: 从剩下的物品中选择可装入背包的重量最小的物品。 虽然这种规则对于前面的例子能产生最优解,但在一般情况下则不一定能得到最优解。 考虑n=2 ,w=[10,20], p=[5,100], c=25。 当利用重量贪婪策略时,获得的解为x =[1,0], 比最优解[0,1]要差。 贪心准则3: 价值密度pi/wi 贪婪算法,这种选择准则为: 从剩余
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 算法 设计 技巧 论述