算法设计与分析-算法效率分析基础-蛮力法

蛮力法(brute force method,也称穷举法或枚举法),是一种简单直接地解决问题的方法 。用蛮力法设计的算法其时间性能往往是最低的,典型的指数时间算法一般都是通过蛮力穷举得到的。

通常来说,蛮力法是最容易应用的方法。

例如,对于给定的整数a和非负整数n,计算的值,最直接最简单的想法就是把1和a相乘,再与a乘n-1次。

依次处理所有元素是蛮力法的关键,应用蛮力法**首先要确定穷举的范围,其次为了避免陷入重复试探,**应保证处理过的元素不再被处理。

由于蛮力法需要依次穷举待处理的元素,因此,用蛮力法设计的算法其时间性能往往是最低的。但是,基于以下原因,蛮力法也是一种重要的算法设计技术。

(1)理论上,蛮力法可以解决可计算领域的各种问题。对于一些基本的问题,例如求一个序列的最大元素,计算n个数的和等,蛮力法是一种常用的算法设计技术。

(2)蛮力法经常用来解决一些较小规模的问题。如果问题的输入规模不大,用蛮力法设计的算法其时间是可以接受的,此时,设计一个更高效算法的代价是不值得的。

(3)对于一些重要的问题(如排序、查找、串匹配),蛮力法可以设计些合理的算法,这些算法具有实用价值,而且不受问题规模的限制。

(4)**蛮力法可以作为某类问题时间性能的下界,**来衡量同样问题的其他算法是否具有更高的效率。

选择排序和冒泡排序

选择排序:

选择排序开始的时候,我们扫描整个列表,找到它的最小元素,然后和第一个元素换,将最小元素放到它在有序表中的最终位置上。然后我们从第二个元素开始扫描列表,找到最后n−1个元素中的最小元素,再和第二个元素交换位置,把第二小的元素放在它的最终位置上。一般来说,在对该列表做第i遍扫描的时候(i的值0⋯n−2),该算法在最后n−i个元素中寻找最小元素,然后拿它和交换。

复制代码
SelectionSort(A[0..n-1]) //该算法用选择排序对给定的数组排序 
//输入:一个可排序数组A[0..n-1] 
//输出:升序排列的数组A[0..n-1] 
for i←0 to n-2 do 
    min←i 
    for j←i+1 to n-1 do 
        if A[j]<A[min] 
            min←j 
    swap A[i] and A[min]

效率分析:对于比较次数 ,依赖于数组长度,则有

如果是求交换次数,则

假如给定初始数据:(118,101,105,127,112)

一次排序:101,118,105,127,112

二次排序:101,105,118,127,112

三次排序:101,105,112,127,118

四次排序:101,105,112,118,127

冒泡排序:

比较表中的相邻元素,如果它们是逆序的话就交换它们的位置。重复多次以后,最终,最大的元素就"沉到"列表的最后一个位置。第二遍操作将第二大的元素沉下去。这样一直做,直到n−1遍以后,该列表就排好序了。

复制代码
BubbleSort(A[O.n-1]) //该算法用冒泡排序对数组A[0..n-1]进行排序 
//输入:一个可排序数组A[0..n-1] 
//输出:非降序排列的数组A[0..n-1] 
for i←0 to n-2 do 
    for j←0 to n-2-i do 
        if A[j+1]<A[j] 
            swap A[j] and A[j+1]

效率分析:对于比较次数

当情况最糟糕的时候交换次数S(n)此时和比较次数是一致的

对 3,9,-1,10,20排序:

第一轮排序:

(1)3,9,-1,10,20 ----3跟9比较,不交换

(2)3,-1,9,10,20 ----9比 -1大,所以9跟 -1交换

(3)3,-1,9,10,20 ----9跟10比较,不交换

(4)3,-1,9,10,20 ----10跟20比较,不交换

第一轮过后,将20这个最大的元素固定到了最后的位置

第二轮排序:

因为20的位置已经固定,所以只对前4个进行排序即可:

(1)-1,3,9,10,20 ----3比 -1大,进行交换

(2)-1,3,9,10,20 ----3跟9比较,不交换

(3)-1,3,9,10,20 ----9跟10比较,不交换

第二轮过后,将第二大的元素固定到了倒数第二个位置

第三轮排序:

10和20的位置已经确定,只需对前三个进行排序

(1)-1,3,9,10,20 ----3和-1比较,不交换

(2)-1,3,9,10,20 ----3和9比较,不交换

第三轮过后,将第三大的元素位置确定

第四轮排序:

只对前两个元素进行排序

(1)-1,3,9,10,20 ----3和-1比较,不交换

值得一提的是,当某一次循环中,数组不存在任何交换操作的时候,就可以视为数组有序,从而剪枝,这也是优化冒泡排序的方法

顺序查找和蛮力字符串匹配

顺序查找:

简单地将给定列表中的连续元素和给定的查找键进行比较,直到遇到一个匹配的元素(成功查找),或者在遇到匹配元素前就遍历了整个列表(失败查找)。如果我们把查找键添加到列表的末尾,那么查找就一定会成功,所以不必在算法的每次循环时都检查是否到达了表的末尾。

复制代码
SequentialSearch2(A[O..n], K) //顺序查找的算法实现,它用了查找键来作限位器 
//输入:一个n个元素的数组A和一个查找键K 
//输出:第一个值等于 K的元素的位置,如果找不到这样的元素,返回-1 
A[n]←K 
i←0 
while A[i]≠K do // while i<n and A[i]≠K do 
    i←i+1 
if i<n return i 
else return -1

在原版算法中,每一次while循环都会进行两次比较(越界和查询),改进后只进行一次比较。虽然只是减少了一次比较,但对于算法的执行效率仍有不少提升。

蛮力字符串匹配:

给定一个n个字符组成的串(文本),一个m个字符的串(模式),从文本中寻找匹配模式的子串。更精确地说,我们求的是i,也即文本中第一个匹配子串最左元素的下标,使得

复制代码
BruteForceStringMatch(T[0..n-1],P[0..m-1]) // 该算法实现了蛮力字符串匹配 
// 输入:一个n个字符的数组T[0..n-1],代表一段文本 
// 一个m个字符的数组P[0..m-1],代表一个模式 
// 输出:如果查找成功,返回文本的第一个匹配子串中第一个字符的位置, 
// 否则返回-1 
for i←0 to n-m do 
    j←0 
    while j<m and P[j]=T[i+j] do 
        j←j+1 
        if j=m return i 
return -1

效率分析:

比较次数C(m,n)同时依赖文本的规模和模式的规模,在最差情况下需要移动n−m+1次模式串且每次移动都遍历模式,

例子如下:

最近对问题和凸包问题:

最近对问题:

要求在一个包含n个点的集合中,找出距离最近的两个点。任意给定两点,它们之间的欧几里得距离表示为

复制代码
BruteForceClosestPoints(p) // 使用蛮力算法求平面中距离最近的两点 
// 输入:一个n(n>2)个点的列表p,p1=(x1,y1),...,pn=(xn,yn) 
// 输出:两个最近点的距离 
d←∞ 
for i←1 to n-1 do 
    for j←i+1 to n do 
        d←min(d,sqrt((xi-xj)^2+(yi-yj)^2)) //sqrt是平方根函数 
return d

效率分析:平方根操作的规律依赖于点集的规模,即,算法中,平方根操作非常耗时,而对于比较两点间距离这个要求来说,即便不开根号也是能够实现的,因此直接比较平方就更有效率:

凸包问题:

定义对于平面上的一个点集合(有限的或无限的),如果以集合中任意两点p和q为端点的线段都属于该集合,我们说这个集合是凸的。现在可以介绍凸包的概念了。对于平面上n个点的集合,它的凸包就是包含所有这些点(或者在内部,或者在边界上)的最小凸多边形。

定理:

任意包含n>2个点(不共线的点)的集合S的凸包是以S中的某些点为顶点的凸多边形(如果所有的点都位于一条直线上,多边形退化为一条线段,但它的两个端点仍然包含在S中),这些顶点被称为"极点"。

由于线段构成了凸包的边界 ,对于一个n个点集合中的两个点 ,当且仅当该集合中的其他点都位于穿过这两点的直线的同一边时,它们的连线是该集合凸包边界的一部分。对每一对点都做一遍检验之后,满足条件的线段构成了该凸包的边界。

对于一条直线ax+by=c(其中 ),它将一个平面分为两个半平面,其中一个半平面中的点都满足ax+by>c,另一个半平面中的点都满足ax+by<c。

复制代码
ConvexSet(p) // 使用蛮力法求解凸包的极点 
// 输入:一个n(n>2)个点的列表p,p1=(x1,y1),...,pn=(xn,yn) 
// 输出:一个由极点构成的集合q 

q←() // 初始化空集合,用于存储凸包极点
j←1 // 第二个点的索引(和i配对的点)
for i←1 to n // 遍历每个点pi(作为直线的第一个端点)
    while j<=n and j≠i // 遍历每个点pj(作为直线的第二个端点,j≠i避免自己和自己配对)
        s←0 // s记录"除pi、pj外的第一个点"相对于直线pi-pj的位置符号
        flag←1 // 标记:是否是第一个判断的点(k≠i,j)
        // 计算直线pi-pj的一般式 ax + by + c = 0 的系数
        a←yj - yi   // 直线pi-pj的一般式系数a = yj - yi
        b←xi - xj   // 直线pi-pj的一般式系数b = xi - xj
        c←xi*yj - xj*yi // 直线pi-pj的一般式系数c = xi*yj - xj*yi
        
        k←1 // 遍历所有其他点pk(判断pk在直线pi-pj的哪一侧)
        while k<=n and k≠i and k≠j // 跳过pi和pj本身,检查所有其他点
            if flag=1 // 第一次判断点pk(还没确定参考符号)
                // sign()是符号函数:返回1(正)、-1(负)、0(在直线上)
                s←sign(a*xk + b*yk - c) // 记录第一个点的位置符号
                flag←0 // 标记:已确定参考符号
            else 
                // 检查当前点pk的符号是否和s不一致,且pk不在直线上
                if s≠sign(a*xk + b*yk - c) and sign(a*xk + b*yk - c)≠0 
                    break // 有點在直線另一側 → 直線pi-pj不是凸包邊,跳出循环
            
            k←k+1 // 检查下一个点
        
        // 循环结束后:如果k>n,说明所有点都在直线pi-pj的同一侧(或直线上)
        if k>n    
            add pi to q // pi是凸包极点,加入集合
            add pj to q // pj是凸包极点,加入集合
        
        j←j+1 // 换下一个pj和pi配对
    
return q // 返回所有凸包极点

效率分析:比较次数依赖于点集数量的规模,因此对于n个点组成条直线,需要比较剩下的n-2个点,也就是

穷举查找

旅行商问题:

要求找出一条n个给定的城市间的最短路径,使我们在回到出发的城市之前,对每个城市都只访问一次。这个问题可以很方便地用加权图来建模,也就是说,用图的顶点代表城市,用边的权重表示城市间的距离。这样该问题就可以表述为求一个图的最短哈密顿回路问题。很容易看出来,哈密顿回路也可以定义为n+1个相邻顶点​ ​ 、​ ​ 、⋯、​ ​ 、​ ​ 的一个序列,其中序列的第一个顶点和最后一个顶点是相同的,而其他n−1个顶点都是互不相同的。不失一般性的前提下,可以假设所有的回路都开始和结束于相同的特定顶点。因此,可以通过生成n-1个中间城市的组合来得到所有的旅行线路,计算这些线路的长度,然后求得最短的线路。

效率分析:

n−1个顶点的所有组合共有个(单方向),需要计算同样次数的权重和。

背包问题:

这是计算机科学中另一个著名的问题。给定n个重量为​ 、​ 、⋯、​ 价值为​ 、​ 、⋯、 的物品和一个承重为W的背包,求这些物品中一个最有价值的子集,并且要能够装到背包中。用穷举法解决这一问题需列举出所有物品的组合(物品的子集),分别计算他们的总重量和总价值并进行判断。

效率分析:

n个元素构成集合的子集个数为 (每个元素有取和不取两种状态),需要计算同样次数的总重量和总价值。

NP问题

NP问题指的是目前没有已知的效率可以用多项式来表示的算法(Non-Polynomial)。旅行商问题和背包问题就是其中的两个经典例子,一些方法可以在优于指数级的效率下解决这些问题。

分配问题:

有n个任务需要分配给n个人执行,一个任务对应一个人。对于每一对i,j=1,2,⋯,n来说,将第j个任务分配给第i个人的成本是,该问题要找出总成本最小的分配方案。

我们可以用一个n维元组来描述分配问题的一个可能的解,其中第i个分量(i=1,⋯,n)表示的是在第i行中选择的列号(也就是说,给第i个人分配的任务号)。

效率分析:n个元素构成的排列有n!个,需要计算同样次数的成本并进行比较。

深度优先查找和广度优先查找

深度优先查找

复制代码
DFS(G) 
//实现给定图的深度优先查找遍历 
//输入:图G=<V,E> 
//输出:图G的顶点,按照被DFS遍历第一次访问到的先后次序,用连续的整数标记 将V中的每个顶点标记为0,表示还"未访问" 
    count←0 
    for each vertex v in V do 
        if v is marked with 0 
            dfs (v) 


dfs(v) 
//递归访问所有和v相连接的未访问顶点,然后按照全局变量count的值 
//根据遇到它们的先后顺序,给它们赋上相应的数字 
    count←count+1; 
    mark v with count 
    for each vertex w in V adjacent to v do 
        if w is marked with 0 
            dfs(w)

DFS重要的基本应用包括检查图的连通性和无环性,除此之外还可以用来求关节点(如果从图中移走一个节点和所有它附带的边之后,图被分为若干个不相交的部分,我们说这样的节点是图的关节点)。

连通性:DFS在访问了所有和初始顶点有路径相连的顶点之后就会停下来,所以我们可以这样检查判断:从任意一个节点开始DFS遍历,在该算法停下来以后,检查一下是否所有的顶点都被访问过了。如果都访问过了,那么这个图是连通的;否则,它是不连通的。再推广一步,我们可以用DFS来找到一个图的连通分量(一次 DFS = 一个连通分量,多次 DFS = 所有连通分量)。

无环性:如果要检查一个图中是否包含回路,我们可以利用图的DFS森林形式的表示法。如果DFS森林不包含回边,这个图显然是无回路的。如果从某些节点u到它的祖先v之间有一条回边,则该图有一个回路,这个回路是由DFS森林中从u到v的路径上的一系列树向边以及从u到v的回边构成的,当遍历时遇到已经访问的节点且该节点不为当前节点的父节点(直接前驱)的时候存在回路(遍历时需要标记当前节点的前一个结点)。

效率分析:所有顶点和所有边均被访问一次,对邻接矩阵来说,时间效率属于Θ( );对邻接表来说,时间效率属于Θ(∣V∣+∣E∣)。

广度优先查找:

复制代码
BFS(G) 
// 实现给定图的广度优先查找遍历 
// 输入:图G=<V,E> 
// 输出:图G的顶点,按照被BFS遍历访问到的先后次序,用连续的整数标记 将V中的每个顶点标记为0,表示还"未访问" 
count←0 
for each vertex vin Vdo 
    if v is marked with 0 
        bfs(v) 


bfs(v) 
// 访问所有和v相连接的未访问顶点,然后按照全局变量count的值 
// 根据访问它们的先后顺序,给它们赋上相应的数字 
count←count+1; 
mark v with count and initialize a queue with v 
while the queue is not empty do 
    for each vertex w in V adjacent to the front vertex do 
        if w is marked with 0 
            count←count+1; 
            mark w with count 
            add w to the queue 
    remove the front vertex from the queue

可以用BFS来检查图的连通性和无环性,做法在本质上和DFS是一样的。BFS不能解决关节点问题,但可以用来处理一些DFS无法处理的情况。

例如,BFS可以用来求两个给定顶点间边的数量最少的路径(最短路径)。

效率分析:

所有顶点和所有边均被访问一次,对邻接矩阵来说,时间效率属于Θ( );对邻接表来说,时间效率属于Θ(∣V∣+∣E∣)。

相关推荐
祁同伟.2 小时前
【算法】优选 · 双指针
c++·算法·容器·stl
菜菜小狗的学习笔记2 小时前
数据结构(一)数组和链表
数据结构
项目申报小狂人2 小时前
基于迁移学习与丢弃法的神经网络算法在无人机失移动目标搜索中的应用,含代码
神经网络·算法·迁移学习
stolentime2 小时前
洛谷P15652 [省选联考 2026] 排列游戏 / perm题解
c++·算法·交互·洛谷·联合省选2026
仰泳的熊猫2 小时前
题目1834:蓝桥杯2016年第七届真题-路径之谜
数据结构·c++·算法·蓝桥杯·深度优先·图论
Darkwanderor2 小时前
数据结构——单调栈和单调队列
数据结构·c++·单调栈·单调队列
机器学习之心2 小时前
198种组合算法+优化SVR支持向量机回归+SHAP分析+新数据预测!机器学习可解释分析,强烈安利,粉丝必备!
算法·shap分析·新数据预测·优化svr支持向量机回归
自信150413057592 小时前
数据结构之队列的实现
c语言·数据结构·算法·链表
宵时待雨2 小时前
C++笔记归纳8:stack & queue
开发语言·数据结构·c++·笔记·算法