蓝桥杯:C++贪心算法、字符串函数、朴素模式匹配算法、KMP算法

news2024/12/26 11:06:22

贪心算法

贪心(Greedy)算法的原理很容易理解:把整个问题分解成多个步骤,在每个步骤都选取当前步骤的最优方案,直到所有步骤结束;每个步骤都不考虑对后续步骤的影响,在后续步骤中也不再回头改变前面的选择。

贪心算法虽然简单,但它有广泛的应用。例如图论中的最小生成树(Minimal Spanning Tree,MST)算法、单源最短路径算法(Dijkstra)都是贪心算法的典型应用。

贪心算法的主要问题是不一定能得到最优解,因为局部最优并不总是能导致全局最优,而竞赛题基本都是求全局最优解的。

一个问题是否能用贪心算法求解,有时很容易判断,有时不那么容易判断。例如常见的最少硬币支付问题,能否用贪心算法求解取决于硬币的面值。

最少硬币支付问题:

假设有3种面值的硬币,分别是1元、2元、5元,数量不限;需要支付M元,问怎么支付,才能使硬币数量最少?

用贪心算法求解,第一步先用面值最大的5元硬币,第二步用面值第二大的2元硬币,最后用面值最小的1元硬币。在这个解决方案中,硬币数量总数是最少的,使用贪心算法得到的结果是全局最优的。但是如果是其他面值的硬币,则使用贪心算法就不一定能得到全局最优解。

例如,假设硬币面值有5种,分别是1元、2元、4元、5元、6元。要支付M = 9元,如果用贪心算法,则答案是6 + 2 + 1,需要3个硬币,而全局最优解是5 + 4,只需要两个硬币。

DP不是本文的讲解范围,这里只是拓展一下解题技巧。

贪心算法没有固定的算法框架,关键是如何选择贪心策略。贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。贪心题是蓝桥杯大赛的常见题型。有的贪心题考验参赛人员的思维能力,有的贪心题结合了其他算法,贪心题可能很难。下面通过一些例题介绍一下贪心算法的应用。

例题1.翻硬币

先分析翻动的具体操作。

(1)只有一个硬币不同。例如位置a的硬币不同,那么翻动它时,会改变与它相邻的硬币b,现在变成了硬币b不同,回到了“只有一个硬币不同”的情况。也就是说,如果只有一个硬币不同,无法求解。

(2)有两个硬币不同。这两个硬币位于任意两个位置,从左边的不同硬币开始翻动,一直翻动到右边的不同硬币,结束。

(3)有3个硬币不同。左边两个不同硬币,可以用操作(2)完成翻动;但是最后一个硬币需要使用操作(1),无法完成。

总结这些操作,得到以下结论。

(1)有解的条件。初始字符串s和目标字符串t必定有偶数个字符不同。

(2)贪心操作。从头开始遍历字符串,遇到不同的字符就翻动,直到最后一个字符。下面证明这个贪心操作是局部最优也是全局最优。

代码:

#include<bits/stdc++.h>
using namespace std;
int main() {
	string s,t;
	cin >> s >> t;
	int ans=0;
	for(int i=0; i<s.length(); i++)
		if(s[i] != t[i]) {
			// s[i]   = (s[i]  =='*'? 'o': '*');      //多余,我们求的是最小操作步数
			s[i+1] = (s[i+1]== '*'? 'o': '*');      //翻下一个就行了
			ans++;
		}
	cout << ans;
}

字符串函数

求解字符串的题目,最好使用系统提供的库函数。本节将介绍C++常用的字符串函数:

代码(讲解在注释中):

#include<bits/stdc++.h>
using namespace std;
int main() {
	string str ="123456789abcdefghiaklmn";
	for(int i=0; i<10; i++)   cout<<str[i]<< " ";       //把str看成一个字符串数组
	cout << endl;
	//find()函数
	cout<<"123的位置:"<<str.find("123")<<endl;     //输出:123的位置:   0
	cout<<"34在str[2]到str[n-1]中的位置:"<<str.find("34",2)<<endl;	//输出:34在str[2]到str[n-1]中的位置:   2
	cout<<"ab在str[0]到str[12]中的位置:"<<str.rfind("ab",12)<<endl;
	//输出:ab在str[0]到str[12]中的位置:    9
	//substr()函数
	cout<<"str[3]及以后的子串:"<<str.substr(3)<<endl;
	//输出:str[3]及以后的子串:456789abcdefghiaklmn
	cout<<"从str[2]开始的4个字符:"<<str.substr(2,4)<<endl;      //若小于限制长度,则报错
	//输出:从str[2]开始的4个字符:3456
	//find()函数
	str.replace(str.find("a"), 5, "@#"); //用str替换指定字符串从起始位置pos开始长度为len的字符 string& replace (size_t pos, size_t len, const string& str); 
	cout<<str<<endl;           //输出:123456789@#fghiaklmn
	//insert()函数
	str.insert(2, "***");
	cout<<"从2号位置插入: "<<str<<endl;    //输出:从2号位置插入:12***3456789@#fghiaklmn
	//添加字符串:append()函数
	str.append("$$$");
	cout<<"在字符串str后面添加字符串: "<<str<<endl;     //输出:在字符串str后面添加字符串:12***3456789@#fghiaklmn$$$
	//求字符串长度
	cout<<str.size()<<endl;//26
	cout<<str.length()<<endl;//26
	//交换字符串:swap()函数
	string str1="aaa",str2="bbb";
	swap(str1, str2);
	cout<<str1<< " " <<str2<<endl;//bbb aaa
	//字符串比较:compare()函数,若相等,则输出0;若不等,则输出1
	cout<<str1.compare(str2)<<endl;  //1
	if(str1==str2) cout <<"==";      //直接比较也行
	if(str1!=str2) cout <<"!=";      //!=
	return 0;
}

例题1.标题统计

分析:用string定义变量,一个一个地读入字符串,字符串之间是用空格分开的。

代码:

#include<bits/stdc++.h>
using namespace std;
int main() {
	string s;
	int ans=0;
	while(cin >> s)   ans += s.size();   
	cout << ans;
	return 0;
}

朴素模式匹配算法

模式匹配(Pattern Matching)问题:在一篇长度为n的文本S中,查找某个长度为m的关键词P,称S为母串,P为模式串。

P可能出现多次,都需要找到。例如在S = abcxyz123bqrst12dg123gdsa中查找P=123,P出现了两次。

最优的模式匹配算法复杂度是什么?由于至少需要检索文本S的n个字符和关键词P的m个字符,所以复杂度至少是O(m + n)。

最简单的是朴素模式匹配算法,这是一种暴力法,从S的第1个字符开始,逐个匹配P的每个字符,如果发现不同,就从S的下一个字符重新开始匹配。

例如S =abcxyz123,P = 123。第1轮匹配:比较S[0]~S[2]= abc和P[0]~P[2]= 123。发现第1个字符就不同,即P[0]≠S[0],这种情况称为“失配”,后面的P[1]、P[2]就不用比较了,如图9.1所示。

第2轮匹配:S往后移一个字符,比较S[1]~S[3]= bcx和P[0]~P[2]=123。发现P的第1个字符与S的第2个字符不同,即P[0]≠S[1],后面的P[1]、P[2]就不用比较了,如图9.2所示。

继续匹配,直到第7轮匹配,终于在S中完全匹配了一个P,如图9.3所示。

一共比较了6 + 3 = 9次:前6轮每次只比较了P的第1个字符,第7轮比较了P的3个字符。

这个例子比较特殊,因为P和S的字符基本都不一样。在每轮匹配时,往往第1个字符就不同,用不着继续匹配P后面的字符。计算复杂度差不多是O(n),这已经是字符串匹配能达到的最优复杂度了。所以,如果字符串S、P符合这样的特征,暴力法是很好的选择。

但是如果情况很“恶劣”,例如P的前m−1个都容易找到匹配,只有最后一个不匹配,那么复杂度就退化成O(nm)。例如S =aaaaaaaab,P =aab。

第1轮匹配:比较S[0]~S[2]=aaa和P[0]~P[2]=aab,前两个字符相同,第3个字符不同,即S[2]≠P[2],共比较了3次。i是指向S的指针,j是指向P的指针,在i=2、j=2处失配,如图9.4所示。

第2轮匹配:让i回溯到1,j回溯到0,重新开始比较S[1]~S[3]= aaa和P[0]~P[2]=aab,如图9.5所示。发现S[3]≠P[2],共比较了3次,在i=3、j=2处失配。

……

……

第7轮匹配:在S中完全匹配了一个P,如图9.6所示。

这7轮匹配共比较7×3 = 21次,远远超过上面例子中的9次。有没有更好的方法?后面介绍的KMP算法是在任何情况下都稳定、高效的算法。   

这里先把朴素模式匹配算法的代码放在这参考:

int ViolentMatch(string text, string pattern)		//text是文本串,pattern是关键字
{
	int tLen = text.size();      //获取长度 
	int pLen = pattern.size();
 
	int i = 0;
	int j = 0;
	while (i < tLen && j < pLen)    //开始匹配 
	{
		if (text[i] == pattern[j])
		{
			//如果当前字符匹配成功(即text[i] == pattern[j]),匹配下一个字符
			i++;
			j++;
		}
		else					//如果当前字符匹配失败,pattern从头开始匹配
		{
			i = i - j + 1;
			j = 0;
		}
	}
	
	if (j == pLen)				//当pattern全部匹配成功,返回匹配位置
		return i - j;         
	else
		return -1;
}

KMP算法

对于模式匹配,目前所学的最简单的是BF算法,即偏向于“暴力”匹配的方法。另外一种就是较为复杂KMP算法了。而俩者的区别在于:BF算法是时间复杂度相对高的,KMP则可以理解为用空间换时间。

KMP算法: KMP只需要将j值模式串中j的位置回溯到next[j]位,而免除了前面不需要的匹配,以此来换取时间。
相比一个一个比较,而KMP则在此基础上更加的简便了。

现在一步一步模拟一下KMP匹配的过程:

现在要在text中找temp:

string text = "ABAABAABBABAAABAABBABAAB";
string temp = "ABAABBABAAB";

next[] = {0,0,1,1,2,0,1,2,3,4,5}      //next数组已经给出

再给你一个公式:
移动位数 =  已匹配值   -    部分匹配值 

(说人话:移动位数 =  已匹配值   -    最大公共元素长度)     ---在匹配的过程中就会明白该怎么移动

1.第一步匹配与暴力没有区别,找到第一个都是A的点开始匹配:

可以看到,我们匹配到第6位的时候,匹配失败了,我们直接中断本次匹配
然后移动       已匹配值   -    最大公共元素长度 
我们匹配了5位,对应的next[5]=2,那么我们就应该移动 5 - 2 =3位。

很好,我们现在又可以进行匹配了,匹配到第11位时,又匹配失败了,我们同样中断本次匹配并进行移动操作。如图:

继续套公式,我们需要移动   已匹配值   -    最大公共元素长度
我们匹配了10位,对应的next[10] = 4,那么我们移动10-4=6位。

如法炮制,我们继续进行匹配,匹配到第5位的时候又出错了,继续进行处理,移动    已匹配值   -    最大公共元素长度
我们匹配了4位,对应next[4] = 1,我们需要移动4 - 1 = 3 位。如图:

这次只匹配到了1位,移动已匹配值   -    最大公共元素长度,这次匹配了1位,对应next[1] = 0,我们要移动 1 - 0 = 1 位。

总结:在kmp算法中最重要的部分就是next[] 数组。它存放的是我们当前位置下的最大公共元素长度。

以待匹配文本"ABCDABD"来举例:

前缀后缀就是:把字符串的子串分解出来,从前到后(前缀),从后到前(后缀)的一个排列
!注意:(前缀得不到最后一位,后缀得不到最前一位)。

然后我们找出前缀和后缀中相同的子排列,将它们的长度保存到字符串子串的长度下的下标中去。

下面是实现代码:

#include <bits/stdc++.h>
using namespace std;

//在关键字中记录与前缀匹配的组合,记录在next数组
void make_next(string pattern, int *next)
{
    int q, k;               //q是匹配的位置         k是记录前缀开始的地方
    int m = pattern.size(); //关键字的长度

    next[0] = 0;
    for (q = 1, k = 0; q < m; q++)
    { 
        while (k > 0 && pattern[q] != pattern[k])
        { 
            k = next[k - 1];
        }

        if (pattern[q] == pattern[k])
        {
            k++;
        }
        next[q] = k; //用于记录关键字中与前缀匹配的组合的位置
    }
}

//kmp算法
int kmp(string text, string pattern, int *next)
{
    int n = text.size();    //文本串的长度
    int m = pattern.size(); //关键字的长度

    make_next(pattern, next); //在关键字中记录与前缀匹配的组合,记录在next数组

    int i, q;
    for (i = 0, q = 0; i < n; i++)
    { //i --> text, q --> pattern
        while (q > 0 && pattern[q] != text[i])
        {                    
            q = next[q - 1]; 
        }                    

        if (pattern[q] == text[i])
        { 
            q++;
        }

        if (q == m)
        {                     
            return i - q + 1; 
        }
    }
    return -1; //整个文本串找完都没有找到字符串,返回-1
}

int main()
{
    string text = "ABAABAABBABAAABAABBABAAB";
    string temp = "ABAABBABAAB";
    int len = temp.size();
    int arr[len+1];
    make_next(temp, arr);
    
    cout << kmp(text, temp, arr);//13

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1457603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

折叠式隐形纱窗原理

如果出现上轨与下轨不同步&#xff0c;可分析是否是某些绳子被拉长导致的。 以上图现象为例&#xff0c;可调整折叠纱窗一侧上部分的4跟组织线长度。从而解决上轨与下轨拉动不同步的问题。

【天衍系列 01】深入理解Flink的 FileSource 组件:实现大规模数据文件处理

文章目录 01 基本概念02 工作原理03 数据流实现04 项目实战4.1 项目结构4.2 maven依赖4.3 StreamFormat读取文件数据4.4 BulkFormat读取文件数据4.5 使用小结 05 数据源比较06 总结 01 基本概念 Apache Flink 是一个流式处理框架&#xff0c;被广泛应用于大数据领域的实时数据…

报表开发工具DevExpress .NET Reporting v23.2亮点 - 支持智能标签

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reporting控件日前正式发布了v23.2…

STM32学习·HAL库·STM32CubeMX系列学习(安装和使用)

目录 ​编辑 1. STM32CubeMX简介 2. STM32CubeMX安装 2.1 STM32CubeMX软件获取 2.1.1 获取Java软件 2.1.2 STM32CubeMX软件获取 2.2 搭建Java运行环境 2.3 安装STM32CubeMX软件 2.4 下载和关联STM32cube固件包 1. STM32CubeMX简介 STM32CubeMX 是 ST 微电子公…

Go 是否有三元运算符?Rust 和 Python 是怎么做的?

嗨&#xff0c;大家好&#xff01;本文是系列文章 Go 技巧第十四篇&#xff0c;系列文章查看&#xff1a;Go 语言技巧。 今天来聊聊在 Go 语言中是否支持三元运算符。这个问题很简单&#xff0c;没有。 首先&#xff0c;什么是三元运算符&#xff1f; 在其他一些编程语言中&a…

The Sandbox NFT 概览与数据分析

作者&#xff1a;stellafootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;The Sandbox NFT Collection Dashboard Sandbox NFT 系列包括独特的体素资产和 LAND 地块&#xff0c;使所有者能够在 The Sandbox 元宇宙中构建、玩虚拟体验并从中获…

【VSCode】设置 一键生成vue模板 的快捷入口

问题 每次写一个组件的时候&#xff0c;都需要去手敲默认结构或者是复制粘贴&#xff0c;十分的麻烦&#xff01; 解决办法 文件 > 首选项 > 用户代码片段 > vue.json 配置vue模板 其中prefix是用来触发代码段的内容&#xff0c;即模版的快捷入口&#xff1b;body里…

红帽认证——步入优质职场的第一步

在当今数字化时代&#xff0c;掌握先进的技术和技能是开启成功职业生涯的关键。红帽认证课程将为你提供这样的机会&#xff0c;帮助你成为一名具备实际操作能力的专业人士。Redhat&#xff0c;红帽公司是全球知名的开源技术厂家&#xff0c;领先的开源解决方案供应商。Linux有很…

Python Flask高级编程之RESTFul API前后端分离(学习笔记)

Flask-RESTful是一个强大的Python库&#xff0c;用于构建RESTful APIs。它建立在Flask框架之上&#xff0c;提供了一套简单易用的工具&#xff0c;可以帮助你快速地创建API接口。Flask-RESTful遵循REST原则&#xff0c;支持常见的HTTP请求方法&#xff0c;如GET、POST、PUT和DE…

Datawhale零基础入门金融风控Task1 赛题理解

Task1 赛题理解 Tip:本次新人赛是Datawhale与天池联合发起的0基础入门系列赛事第四场 —— 零基础入门金融风控之贷款违约预测挑战赛。 赛题以金融风控中的个人信贷为背景&#xff0c;要求选手根据贷款申请人的数据信息预测其是否有违约的可能&#xff0c;以此判断是否通过此项…

Office2019安装冲突解决方法 ErrorCode 30182-392

问题描述 挂载安装Office 2019安装镜像后直接安装会出现如下的错误&#xff1a; 问题原因在于Office 365与Offfice2019版本号相同&#xff08;均为16.0&#xff09;官方页面-各Office版本号 解决办法 解决方法就是利用官方部署工具进行安装&#xff0c;绕过版本冲突问题 …

ansible剧本中的角色

1 roles角色 1.1 roles角色的作用&#xff1f; 可以把playbook剧本里的各个play看作为一个角色&#xff0c;将各个角色打的tasks任务、vars变量、template模版和copy、script模块使用的相关文件等内容放置在指定角色的目录里统一管理&#xff0c;在需要的时候可在playbook中使…

从可靠性的角度理解 tcp

可靠性是 tcp 最大的特点。常见的用户层协议&#xff0c;比如 http, ftp, ssh, telnet 均是使用的 tcp 协议。可靠性&#xff0c;即从用户的角度来看是可靠的&#xff0c;只要用户调用系统调用返回成功之后&#xff0c;tcp 协议栈保证将报文发送到对端。引起不可靠的表现主要有…

【conda环境 安装 tensorflow2.2】 解决方案

1.检查anaconda安装&#xff1a;在cmd输入 conda --version 2.检测已经安装的环境&#xff1a;conda info --envs 3.新建一个python3.5的环境&#xff0c;tensorflow&#xff1a; ###conda create -n xxx python3.5 xxx为虚拟环境名 ###conda create -n xxx python3.6 xxx为虚拟…

【求职】搜狗2016 C++笔试题

1.关于重载和多态正确的是&#xff1f; A.如果父类和子类都有相同的方法,参数个数不同,将子类对象赋给父类后,由于子类继承于父类,所以使用父类指针调用父类方法时,实际调用的是子类的方法; B.选项全部都不正确 C.重载和多态在C面向对象编程中经常用到的方法,都只在实现子类…

使用智能电销机器人,拓客效果更佳!

现在很多的企业做销售都离不开电话营销&#xff0c;它是一种能够直接帮助企业获取更多利润的营销模式&#xff0c;目前被各大行业所采用。 znyx222 了解探讨 电话营销是一个压力很大的职业&#xff0c;新员工培养难度大、老员工又不好维护&#xff0c;会有情绪问题出现等&…

Redis篇----第七篇

系列文章目录 文章目录 系列文章目录前言一、Redis 的回收策略(淘汰策略)?二、为什么 edis 需要把所有数据放到内存中?三、Redis 的同步机制了解么?四、Pipeline 有什么好处,为什么要用 pipeline?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍…

Java 21 新特性的扫盲级别初体验

一、前言 JDK 21 于 2023 年 9 月发布&#xff0c;作为目前讨论热度最高的JDK&#xff0c;虽然大家都开玩笑说你发任你发&#xff0c;我用Java8&#xff0c;但是作为一个Javaer&#xff0c;对JDK21的新特性还是要有所了解的。 以下是 JDK 21 的新功能列表&#xff1a; 虚拟线…

初阶数据结构之---导论,算法时间复杂度和空间复杂度(C语言)

说在整个初阶数据结构开头 数据结构其实也学了挺长时间了&#xff0c;说着是要刷题所以才没怎么去写关于数据结构方面的内容。数据结构作为计算机中及其重要的一环&#xff0c;如果不趁着假期系统整理一下着实可惜&#xff0c;我这里构想的是将初阶数据结构和高阶数据结构&…

Servlet原理学习

一、网站架构和Servlet技术体系架构 1.网站架构 现在的网站架构分为 B/S架构和C/S的架构两种。 这种“B/S”结构有很多好处&#xff0c;维护和升级方式更简单&#xff0c;客户端是浏览器&#xff0c;基本不需要维护&#xff0c;只需要维护升级服务器端就可以&#xff0c; C/S结…