程序员都应该精通的六种算法,你会了吗

程序员都应该精通的六种算法,你会了吗,第1张

对于一名优秀的程序员来说,面对一个项目的需求的时候,一定会在脑海里浮现出最适合解决这个问题的方法是什么,选对了算法,就会起到事半功倍的效果,反之,则可能会使程序运行效率低下,还容易出bug。因此,熟悉掌握常用的算法,是对于一个优秀程序员最基本的要求。

那么,常用的算法都有哪些呢?一般来讲,在我们日常工作中涉及到的算法,通常分为以下几个类型:分治、贪心、迭代、枚举、回溯、动态规划。下面我们来一一介绍这几种算法。

一、分治算法

分治算法,顾名思义,是将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治算法一般分为三个部分:分解问题、解决问题、合并解。

分治算法适用于那些问题的规模缩小到一定程度就可以解决、并且各子问题之间相互独立,求出来的解可以合并为该问题的解的情况。

典型例子比如求解一个无序数组中的最大值,即可以采用分治算法,示例如下:

def pidAndConquer(arr,leftIndex,rightIndex):

if(rightIndex==leftIndex+1 || rightIndex==leftIndex){

return Mathmax(arr[leftIndex],arr[rightIndex]);

}

int mid=(leftIndex+rightIndex)/2;

int leftMax=pidAndConquer(arr,leftIndex,mid);

int rightMax=pidAndConquer(arr,mid,rightIndex);

return Mathmax(leftMax,rightMax);

二、贪心算法

贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。

贪心算法的基本思路是把问题分成若干个子问题,然后对每个子问题求解,得到子问题的局部最优解,最后再把子问题的最优解合并成原问题的一个解。这里要注意一点就是贪心算法得到的不一定是全局最优解。这一缺陷导致了贪心算法的适用范围较少,更大的用途在于平衡算法效率和最终结果应用,类似于:反正就走这么多步,肯定给你一个值,至于是不是最优的,那我就管不了了。就好像去菜市场买几样菜,可以经过反复比价之后再买,或者是看到有卖的不管三七二十一先买了,总之最终结果是菜能买回来,但搞不好多花了几块钱。

典型例子比如部分背包问题:有n个物体,第i个物体的重量为Wi,价值为Vi,在总重量不超过C的情况下让总价值尽量高。每一个物体可以只取走一部分,价值和重量按比例计算。

贪心策略就是,每次都先拿性价比高的,判断不超过C。

三、迭代算法

迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程。迭代算法是用计算机解决问题的一种基本方法,它利用计算机运算速度快、适合做重复性 *** 作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。最终得到问题的结果。

迭代算法适用于那些每步输入参数变量一定,前值可以作为下一步输入参数的问题。

典型例子比如说,用迭代算法计算斐波那契数列。

四、枚举算法

枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。枚举法的本质就是从所有候选答案中去搜索正确地解。

枚举算法适用于候选答案数量一定的情况。

典型例子包括鸡钱问题,有公鸡5,母鸡3,三小鸡1,求m钱n鸡的所有可能解。可以采用一个三重循环将所有情况枚举出来。代码如下:

五、回溯算法

回溯算法是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

典型例子是8皇后算法。在8 8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问一共有多少种摆法。

回溯法是求解皇后问题最经典的方法。算法的思想在于如果一个皇后选定了位置,那么下一个皇后的位置便被限制住了,下一个皇后需要一直找直到找到安全位置,如果没有找到,那么便要回溯到上一个皇后,那么上一个皇后的位置就要改变,这样一直递归直到所有的情况都被举出。

六、动态规划算法

动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

动态规划算法适用于当某阶段状态给定以后,在这阶段以后的过程的发展不受这段以前各段状态的影响,即无后效性的问题。

典型例子比如说背包问题,给定背包容量及物品重量和价值,要求背包装的物品价值最大。

分治,顾名思义,分而治之;把一个父运算,分解成几个子运算,常见算法如归并排序。用函数T来表示运算的时间的话,父运算T(n)=T(n/k)+C。

具体的也记不太清楚了,如果是我做这题,我会用归并把数组排序,排完序,最大最小自然就出来了。

标准归并算法C++描述,具体思想网上有很多资料自己去看

/归并算法/

#include<iostream>

typedef int keytype;

void merge(keytype ,int,int,int);

void merge_sort(keytype ,int,int);

int main()

{

using namespace std;

keytype a[11]={1,5,8,4,6,3,8,1,3,8,2};

merge_sort(a,0,9);

for(int i=0;i<11;++i)

cout<<a[i]<<" ";

return 0;

}

void merge_sort(keytype a,int low,int up)

{

int q=0;

if(low<up)

{

q=(low+up)/2;

merge_sort(a,low,q);

merge_sort(a,q+1,up);

merge(a,low,q,up);

}

}

void merge(keytype a,int low,int mid,int up)

{

int len1=mid-low+1;

int len2=up-mid;

keytype L=new keytype[len1+1];//栈内存效率更高

keytype R=new keytype[len2+1];

for(int i=0;i<len1;++i)

L[i]=a[low+i];

for(int i=0;i<len2;++i)

R[i]=a[mid+i+1];

L[len1]=R[len2]=32767;

int i=0,j=0;

for(int k=low;k<=up;++k)

{

if(L[i]<=R[j])

{

a[k]=L[i];

++i;

}

else

{

a[k]=R[j];

++j;

}

}

delete[] L;

delete[] R;

}

递归的把任务分解成二个子任务,merge整合函数复杂度是O(n),每次分解成两部分分别排序之后调用整合函数,很容易知道复杂度是O(n),所以算法T(n)=T(n/2)+O(n),复杂度为O(nlogn)

随机增量法较为简单,遵循增量法的一贯思路,即按照随机的顺序依次插入点集中的点,在整个过程中都要 维护并更新一个与当前点集对应的Delaunay三角剖分。考虑插入vi点的情况,由于前面插入所有的点v1,v2,,vi-1构成的DT(v1, v2,,vi-1)已经是Delaunay三角剖分,只需要考虑插入vi点后引起的变化,并作调整使得DT(v1,v2,,vi-1) U vi成为新的Delaunay三角剖分DT(v1,v2,,vi)。

(插入调整过程:首先确定vi落在哪个三角形中(或边上),然后将vi与三角形三个顶点连接起来构成三个三角形(或与共边的两个三角形的对顶点连接起来构 成四个三角形),由于新生成的边以及原来的边可能不是或不再是Delaunay边,故进行边翻转来调整使之都成为Delaunay边,从而得出DT (v1,v2,,vi)。)

其Pseudocode如下:

Algorithm IncrementalDelaunay(V)

Input: 由n个点组成的二维点集V

Output: Delaunay三角剖分DT

1add a appropriate triangle boudingbox to contain V ( such as: we can use triangle abc, a=(0, 3M), b=(-3M,-3M), c=(3M, 0), M=Max({|x1|,|x2|,|x3|,} U {|y1|,|y2|,|y3|,}))

2initialize DT(a,b,c) as triangle abc

3for i <- 1 to n

do (Insert(DT(a,b,c,v1,v2,,vi-1), vi))

4remove the boundingbox and relative triangle which cotains any vertex of triangle abc from DT(a,b,c,v1,v2,,vn) and return DT(v1,v2,,vn)

Algorithm Insert(DT(a,b,c,v1,v2,,vi-1), vi)

1find the triangle vavbvc which contains vi // FindTriangle()

2if (vi located at the interior of vavbvc)

3 then add triangle vavbvi, vbvcvi and vcvavi into DT // UpdateDT()

FlipTest(DT, va, vb, vi)

FlipTest(DT, vb, vc, vi)

FlipTest(DT, vc, va, vi)

4else if (vi located at one edge (Eg edge vavb) of vavbvc)

5 then add triangle vavivc, vivbvc, vavdvi and vivdvb into DT (here, d is the third vertex of triangle which contains edge vavb) // UpdateDT()

FlipTest(DT, va, vd, vi)

FlipTest(DT, vc, va, vi)

FlipTest(DT, vd, vb, vi)

FlipTest(DT, vb, vc, vi)

6return DT(a,b,c,v1,v2,,vi)

Algorithm FlipTest(DT(a,b,c,v1,v2,,vi), va, vb, vi)

1find the third vertex (vd) of triangle which contains edge vavb // FindThirdVertex()

2if(vi is in circumcircle of abd) // InCircle()

3 then remove edge vavb, add new edge vivd into DT // UpdateDT()

FlipTest(DT, va, vd, vi)

FlipTest(DT, vd, vb, vi)

复杂度分析

问题的规模为点集中的点的总个数n(没有重合的点),循环内的基本的 *** 作有:

1寻找插入点所在的三角形(FindTriangle())

2测试点是否在外接圆内(InCircle())

3更新三角网(UpdateDT())

4寻找共测试边的第三顶点(FindThirdVertex())

考虑最坏情况:

时间复杂度T = T(addboudingbox()) + Sum(T(insert(i), i=1,2,,n)) + T(removeboundingbox)

因为addboudingbox()和removeboundingbox不随n变化,是个常量。T(addboudingbox()) = O(1), T(removeboundingbox()) = O(1)

T = Sum(T(insert(i), i=1,2,,n)) + O(1) + O(1)

考虑插入第i个的点的时间:

T(insert(i)) = T(FindTriangle(i)) + T(UpdateDT(i)) + KT(FlipTest(i)) (K为常数)

故T = Sum(T(FindTriangle(i)), i=1,2,,n) + Sum(T(UpdateTD(i)), i=1,2,,n) + KSum(T(FlipTest(i)), i=1,2,,n)

挨个考虑:

FindTriangle(i)是要找出包含第i个点的三角形,由欧拉公式知道,平面图的面数F是O(n), n为顶点数,故此时总的三角形数是O(i)的。所以问题相当于在O(i)个记录中查找目标记录,如果不借助特殊的数据结构,按照一般顺序查找,需要O (i)的时间。

T(FindTriangle(i)) = O(i),故:Sum(T(FindTriangle(i)), i=1,2,,n) = O(nn)

UpdateTD(i)是更新三角网数据结构,插入和删除三角形到当前的三角网是个常量 *** 作,因为已经知道插入和删除的位置。

T(UpdateDT(i)) = O(1),故:Sum(T(UpdateTD(i)), i=1,2,,n) = O(n)

FlipTest(i)比较复杂些,是个递归过程。细分为:T(FlipTest(i)) = T(FindThirdVertex(i)) + T(InCircle(i)) + T(UpdateDT(i)) + 2T(FlipTest(j))。(这里,用j来区分不同的深度)

因为InCircle(i)是测试点是否在外接圆内,是个常量 *** 作,不随点数变化而变化。故T(InCircle(i)) = O(1), 又T(UpdateDT(i) = O(1)(见上)

FindThirdVertex(i)是查找目标点,在O(i)个记录中查找目标记录,如果不借助特殊的数据结构,按照一般顺序查找需要O(i)的时间。T(FindThirdVertex(i)) = O(i)

剩下的是递归调用FlipTest的过程,不过还好,因为FlipTest的递归深度只有常数级(设为K)。正比于点在三角网中的度数(degree)。故FlipTest(i)最多调用的次数为22,,2 = K',还是常数。

故T(FlipTest(i)) = O(i) + O(1) + O(1) + K'(O(i) + O(1) + O(1)) = O(i) + O(1) + O(1)

Sum(T(FlipTest(i)), i=1,2,,n) = O(nn) + O(n) + O(n)

综上,最坏情况下算法总时间复杂度 T = O(nn) + O(n) + K(O(nn) + O(n) + O(n)) + O(1) + O(1) = O(nn)

其中,关键的 *** 作是FindTriangle()和FindThirdVertex(),都是nn次的。

在网上很多资料说随机增量法是O(nlogn)的,但是分析下来,却是O(nn)的。后来看到别人的实 现,原来是用的别的数据结构来存储三角网,减少了FindTriangle()和FindThirdVertex()的复杂度。使得某次查找三角形和共边 三角形的第三顶点能在O(logn),而非O(n)的时间内实现。这样,总的查找的时间为 O(log1)+O(log2)+,+O(logn) = O(nlogn)。程序=算法+数据结构,看来一点没错。

比如说用DAG,Quad-edge等,都能达到O(nlogn)的复杂度。

分治法(Divide and Conquer)

据说是现在实际表现最好的。

#include<iostream>// 引入错误

using namespace std;

int MIN,MAX;

double Min,Max;

template<typename T>

void minmax (T A[10],int low,int high,T &min,T &max)

{

    T max1,min1,max2,min2;

    if(high == low) // 增加相等时候判断

    {

        min = A[high];

        max = A[high];

    }

    else if (high-low==1)// 具体 *** 作错误

    {

        min = A[high] < A[low]  A[high]:A[low];

        max = A[high] > A[low]  A[high]:A[low];

    }

    else

    {   int mid;

        mid=(low+high)/2;

        minmax (A,low,mid,min1,max1);

        minmax (A,mid+1,high,min2,max2);

        min = min1 < min2  min1:min2;//  *** 作错误与 *** 作遗漏

        max = max1 > max2  max1:max2;

    }

}

int main()

{

    int A1[8]={1,2,5,6,8,10,32,56};

    minmax (A1,0,7,MIN,MAX);

    cout<<"min="<<MIN<<"   max="<<MAX<<endl;

    double A2[8]={11,22,32,46,53,102,935,836};

    minmax (A2,0,7,Min,Max);

    cout<<"min="<<Min<<"   max="<<Max<<endl;

    return 0;

}

修改代码如上,楼主有些语法用错了,然后就是逻辑判断考虑不周全。

运行结果如下:

邮局选址问题

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zz/9405611.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-28
下一篇 2023-04-28

发表评论

登录后才能评论

评论列表(0条)

保存