• 算法设计与分析期末复习不挂科


    算法的基本概念

    算法概念

    通俗讲:算法是解决问题的一种方法或一个过程
    严格讲:算法是解某一特定问题的一组有穷规则的集合
    且满足以下性质:

    • 有限性:算法在执行有限步之后必须终止
    • 确定性:算法的每一个步骤都有精确的定义,要执行的每一个动作都是清晰的,无歧义的
    • 输入:一个算法有0个或多个输入,他是由外部提供的,作为算法执行前的初始值或初始状态
    • 输出:一个算法有一个或多个输出,这些输出与输入有着特定的关系,实际上是输入的某种函数
    • 能行性:指的是算法中有待实现的运算都是基本运算,原则上可以由人们用纸笔在有限时间里精确的完成

    算法的时间复杂性分析

    有几种方法可以用来分析算法的时间复杂性,如循环次数的统计,基本操作频率的统计,计算步的统计等

    • 循环次数的统计:计算多项式O(n),排序(冒泡排序) O(n^2),选手竞技淘汰比赛O(n),洗牌O(nlogn)
    • 基本操作频率统计:赋值操作,比较操作,四则运算操作,这些都可以看做基本操作,选取的基本操作执行的频率必须和算法中的任何其他操作一样多
      例如冒泡中的比较操作,可以看成基本操作: 归并排序中的赋值操作O(n),收割白菜O(n)
    • 计算步的统计: 统计算法中所有部分的时间花费 计算步和问题规模n无关,我们可以把一个语句的执行看成一个计算步,或者把连续200个乘法操作看做一个计算步,不能把n次操作看做一个计算步! 可以将 flag = (a+b+c==n)&&(5*a+3*b+c/3==n)&&(c%3==0);看做一个计算步,或者a=b;看做计算步,然后用conut统计次数!

    最好情况/最坏情况和平均情况分析

    有些算法因为不同输入实例,运行时间可能差别输入规模n的某个阶

    • 最好情况: 例如排序算法排升序,输入实例已经是升序,冒泡,插排最好时间复杂性为O(n)
    • 最坏情况:例如排升序,输入实例是降序,冒泡,插排最坏时间复杂度为O(n^2)
    • 平均情况: 算法运行时间选取算法所有可能输入的平均运行时间

    渐进复杂性

    算法的输入规模和运行时间的阶
    在这里插入图片描述

    通俗点讲就是保留最大阶,就是我们要求的渐进复杂性
    在这里插入图片描述

    渐进记号

    运行时间的上界O记号
    在这里插入图片描述
    运行时间的下界
    在这里插入图片描述
    运行时间的准确界
    在这里插入图片描述
    在这里插入图片描述

    递归方程解的渐进阶求法

    在这里插入图片描述
    在这里插入图片描述

    合并排序

    • 算法思想:
      先将元素划分多组,直到不能划分为止,然后进行区间的合并,合并成有序序列,直到合并成一整个有序序列!
    • 代码
    void merge_sort(int a[],int l,int r,int tmp[])
    {
    	if(l>=r) return;
    	int mid = l+r>>1;
    	//递归区间拆分
    	merge_sort(a,l,mid);
    	merge_sort(a,mid+1,r);
    	//进行区间合并
    	int i = l,j = mid+1,k = 0;
    	while(i<=mid&&j<=r)
    	{
    		if(a[i]<a[j]) tmp[k++] = a[i++];
    		else tmp[k++] = a[j++];
    	}
    	while(i<=mid) tmp[k++] = a[i++];
    	while(j<=r) tmp[k++] = a[j++];
    	//拷贝回原数组
    	for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    合并过程:
    eg: 8 5 3 9 11 6 4 1 10 7 2

    在这里插入图片描述
    时间复杂度:O(nlogn)
    空间复杂度:O(n)

    递归和分治法

    递归

    • 基于归纳的递归算法的思想方法

    对于一个规模为n的问题P(n),归纳法的思想如下:

    • 基础步:a1是问题P(1)的解
    • 归纳步:对所有的k,1,若ak是问题P(k)的解,则p(ak)是问题 P(k+1)的解.其中,p(ak)是对ak的某种运算或处理.这里可以将p()看成递归函数!

    分治

    分治算法思想

    在求解一个输入规模为n,而n的取值有很大的问题时,直接求解往往非常困难.这时,可以先分析问题本身所具有的某些特性,然后从这些特性出发,选择某些适当的设计策略来求解.
    例如:把n个输入划分成k个子集,分别对这些子集进行求解,再把所有得到的解组合起来,从而得到整个问题的解,这样,有时可以收到很好的效果.这种方法就是所谓的分治法

    eg 最大最小问题:

    输入:整数数组a,数组的起始边界和结束边界lr
    输出:最大运算l,最小元素e_min

    void maxmin(int a[],int &e_max,int &e_min,int l,int r)
    {
    	if(r-l<=1) //元素少直接求解
    	{	
    		if(a[l]<a[r])
    		{
    			e_max = max(a[r],e_max);
    			e_min = min(a[l],e_min);
    		}
    		else
    		{
    			e_max = max(a[l],e_max);
    			e_min = min(a[r],e_min);
    		}
    		return;
    	}
    	int mid,x1,y1,x2,y2;
    	mid = (l+r)>>1; //划分成2个子区间
    	maxmin(a,x1,y1,l,mid); //求2个子区间的最大最小值
    	maxmin(a,x2,y2,mid+1,r);
    	e_max = max(x1,x2); //合并原数组的最大最小值
    	e_min = min(y1,y2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    求解过程如下图:

    在这里插入图片描述
    时间复杂度:O(n)
    空间复杂度:O(logn) 递归深度

    分治法设计步骤

    • 划分步:将输入的问题划分成k个子问题
    • 治理步:当问题规模大于某个预定义的阀值n0时,治理步由k个递归调用组成
    • 组合步:这一步把各个子问题组合起来

    上面3个步骤对应归并排序:
    划分步-> 划分子区间
    治理步 -> 对区间合并
    组合步 -> 拷贝数组

    快速排序

    算法思想:
    把一个序列划分成2个子序列,使得第一个序列的元素都小于第二个序列的所有元素,不断进行划分直到最后构成n个序列,每个序列只有一个元素.这时他们就是有序序列了!

    划分步:划分序列
    治理步:选择一个基准位置,将区间进行划分,使左边的元素小于基准值,右边元素大于基准值
    组合步:这里划分成n个区间后,就已经有序了,也就是整个序列的解

    //按枢纽元素划分序列 采用快慢指针
    int split(int A[], int low, int high)
    {
    	int k, i = low;
    	int x = A[low];
    	//这里的i指向待划分区间的第一个元素,k指向第二个元素!
    	//这里的k一直往后遍历
    	for (k = low + 1; k < high; k++)
    	{
    		if (A[k] <= x) //这里我们排升序,所以当 A[k]大于x的时候,说明这个位置,符号升序,我们不进入if语句,k指针继续往后
    		{
    			//进入了if说明 A[k]比我们选择的基准值x小,应该放在基准值的左边,所以让指针i++,如果k和i不是指向同一个元素就进行交换
    			i++;
    			if (i != k)
    				swap(A[i], A[k]);
    		}
    	}
    	//完成了上述交换后,我们在将基准值和i指向的元素交换,这样也就完成了一次划分
    	swap(A[low], A[i]);
    }
    void quick_sort(int A[], int low, int high)
    {
    	int k;
    	if (low < high) //有区间未划分就会进入if进行划分
    	{
    		k = split(A, low, high); //这里返回的k位置,这个元素已经有序,也就是完成了一次划分!
    		quick_sort(A, low, k - 1);//对 k左边区间进行划分
    		quick_sort(A, k + 1, high);//对k右边区间进行划分
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    算法执行过程
    第一次划分:
    在这里插入图片描述

    在这里插入图片描述
    时间复杂度最坏情况: O(n)每次划分都划分成空区间和另一个区间,解决方案基准元素3数取中!
    平均复杂度: O(nlogn)

    贪婪法

    • 设计思想:

    贪婪法通常用来解决具有最大值或者最小值优化问题.他犹如登山一样,一步步向前推进,从某一个初始状态出发,根据当前局部的而不是全局的最优决策,以满足约束方程为条件,以使得目标函数的值增加的最快或最慢为准则,选择一个能够最快到达要求的输入元素,以便尽快的构成问题的可行解

    适合贪婪法求解的问题满足下面2个重要性质:

    • 选择性质

    是指所求问题的全局最优解,可以通过一系列局部最优选择来达到.每进行一次选择,就得到一个局部的解,并把所求的问题简化为一个规模更小的类似子问题

    • 最优子结构性质

    是指一个问题的最优解中包含子问题的最优解

    背包问题

    这里的背包问题物体可分解,就不介绍了,就是将性价比最高的物品放入到背包中,直到背包放不下此时性价比最高的物品,那就将物品进行分解,然后计算总价值!

    单源最短路径问题

    Dijkstra狄斯喹诺算法解最短路径

    视频链接这是求最短路径的一种算法
    算法思想:
    多个节点多个连接的边组成的图,我们需要求一个节点到另一个节点的最短路径
    我们可以先从第一个节点出发求临近最短的路径的节点,然后确定路径后,再到最短路径节点处重复之前操作,如果当前节点的路径值比之前路径值小就更新路径值,否则不变,直到到达目标节点

    在这里插入图片描述
    书上例题:

    在这里插入图片描述
    时间复杂度O(n^2) 空间复杂度: O(n)

    最小花费生成树问题

    最小花费生成树问题应用场景: 例如用图的顶点代表城市,顶点与顶点之间的边代表城市之间的道路或通信线路,用边的权代表道路的长度或通信路线的费用,最小花费生成树问题,就表示城市之间最短的道路或费用最少的通信线路问题.

    最小生成树概念:
    在这里插入图片描述

    Kruskal克鲁斯卡尔算法

    算法思想俗称避环法

    开始时将图的所有节点都作为孤立顶点,每个顶点都构成了一颗只有根节点的书,构成了森林T.
    然后将所有边按照权值排升序,每次从边集中选择最小权值边,然后加入到到T中,并且不会使当前T构成回路,就加入,否则就丢弃该边,重复上述操作,直到把n-1条边加入后就生成了最小花费生成树
    在这里插入图片描述
    时间复杂度: 完全图O(n^2logn) 平面图 O(nlogn) 空间复杂度: O(n)

    Prim普利姆算法

    算法思想:

    该算法有点类似狄斯喹诺算法
    先从起点位置开始,然后加入与起点相连的最短权值的点,然后将当前树看成一个整体,再加入与当前树相连权值最小的顶点,依次进行直到所有顶点都加入

    在这里插入图片描述
    适用于顶点少,边数多的情况
    时间复杂度:O(n^2) 空间复杂度:O(n)

    霍夫曼编码问题

    在这里插入图片描述
    通俗点讲哈夫曼树就是一个带权的二叉树,相同的叶节点可以构造出不同哈夫曼二叉树

    前缀码和最优二叉树

    最优二叉树,就是刚刚哈夫曼树构造WPL为最小
    也就是权值越大的越靠近根节点,权值越小的越远离根节点
    前缀码就是将哈夫曼树二叉树的每条边编码,左边为0,右边为1进行
    到叶子节点,有唯一的前缀码,并且前缀码唯一,任意节点的前缀码不会是另一叶子节点的前缀码
    在这里插入图片描述
    解题步骤

    • 构造最优二叉树
    • 编码
    • 得出每个叶节点的前缀码

    在这里插入图片描述
    哈夫曼算法时间复杂度:O(nlogn)

    动态规划

    动态规划思想方法

    动态规划的最优决策原理:

    对于具有n个输入的最优解问题,它们的活动过程往往划分为若干个阶段,每一个阶段的决策依赖于前一个阶段的状态,由决策所采取的动作使状态发送转移,成为下一阶段的决策依据.

    多段树的最短路径问题

    多段树的决策过程

    多段树的最短路径问题,是求源点s到达收点t的最小花费通路
    决策的第一阶段,确定图中第k-1段的所有顶点到达收点t的花费最小通路
    决策的第二阶段,确定图中第k-2段所有节点到达收点t的花费的最小通路

    详细过程如图所示
    在这里插入图片描述
    在这里插入图片描述

    0/1背包问题

    0/1背包问题中,物体或者被装入背包,或者不装入背包,只有这2种选择,不能分割物体!

    在这里插入图片描述
    代码实现:

    #include
    
    using namespace std;
    
    const int MAXN = 1005;
    int v[MAXN];    // 体积
    int w[MAXN];    // 价值 
    int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 
    
    int main() 
    {
        int n, m;   
        cin >> n >> m;
        for(int i = 1; i <= n; i++) 
            cin >> v[i] >> w[i];
    
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
                if(j < v[i]) 
                    f[i][j] = f[i - 1][j];
                // 能装,需进行决策是否选择第i个物品
                else    
                    f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
            } 
         }          
        cout << f[n][m] << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    验证结果

    在这里插入图片描述

    回溯

    回溯法的思想方法

    问题的解空间和状态空间树

    状态空间树的动态搜索

    回溯法的效率问题

    分支于限界

    分支与限界法的基本思想TSP 图 8.9

    随机算法

    随机算法的类型和特点

  • 相关阅读:
    计算机组成原理 | 中央处理器
    MySQL高级篇
    使用tkinter 实现一个猜数字游戏
    iptables、firewalld防火墙详解
    【VMware vCenter】连接和使用vCenter Server嵌入式vPostgres数据库。
    哈希表相关知识
    大数据实训项目(小麦种子)-02、实训项目整体功能介绍与演示
    【Linux】网络编程套接字Scoket:UDP网络编程
    小米root以及面具的使用
    计算机网络总结笔记
  • 原文地址:https://blog.csdn.net/weixin_52345071/article/details/127988288