算法与数学问题的微妙联系

一.引言

算法的学习往往与数学问题的求解有着千丝万缕的联系。
数学问题的求解方式往往不是唯一的,算法的求解往往也是有套路的,
这里选几道比较经典的题目供大家参考。

1.质数
质数又称素数,指的是在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。值得注意的是,每一个数都可以分解成质数的乘积。

题目描述
给定一个数字 n,求小于 n 的质数的个数。
输入输出样例
输入一个整数,输出也是一个整数,表示小于输入数的质数的个数。
Input: 10
Output: 4
在这个样例中,小于 10 的质数只有 [2,3,5,7]。

题解
埃拉托斯特尼筛法(Sieve of Eratosthenes,简称埃氏筛法)是非常常用的,判断一个整数是
否是质数的方法。并且它可以在判断一个整数 n 时,同时判断所小于 n 的整数,因此非常适合这
道题。其原理也十分易懂:从 1 到 n 遍历,假设当前遍历到 m,则把所有小于 n 的、且是 m 的倍
数的整数标为和数;遍历完成后,没有被标为和数的数字即为质数。

int countPrimes(int n) {
	if (n <= 2) return 0;
	vector<bool> prime(n, true);
	int count = n - 2; // 去掉不是质数的1
	for (int i = 2; i <= n; ++i) {
	if (prime[i]) {
	for (int j = 2 * i; j < n; j += i) {
	if (prime[j]) {
	prime[j] = false;
	--count;
	}
	}
	}
	}
	return count;
}

利用质数的一些性质,我们可以进一步优化该算法。

int countPrimes(int n) {
	if (n <= 2) return 0;
	vector<bool> prime(n, true);
	int i = 3, sqrtn = sqrt(n), count = n / 2; // 偶数一定不是质数
	while (i <= sqrtn) { // 最小质因子一定小于等于开方数
	for (int j = i * i; j < n; j += 2 * i) { // 避免偶数和重复遍历
	if (prime[j]) {
	--count;
	prime[j] = false;
	}
	}
	do {
	i += 2;
	} while (i <= sqrtn && !prime[i]); // 避免偶数和重复遍历
	}
	return count;
}

2.数字处理
题目描述
给定一个十进制整数,求它在七进制下的表示。
输入输出样例
输入一个整数,输出一个字符串,表示其七进制。
Input: 100
Output: “202”
在这个样例中,100 的七进制表示法来源于 101 = 2 * 49 + 0 * 7 + 2 * 1。

题解
进制转换类型的题,通常是利用除法和取模(mod)来进行计算,同时也要注意一些细节,如
负数和零。如果输出是数字类型而非字符串,则也需要考虑是否会超出整数上下界。

string convertToBase7(int num) {
	if (num == 0) return "0";
	bool is_negative = num < 0;
	if (is_negative) num = -num;
	string ans;
	while (num) {
	int a = num / 7, b = num % 7;
	ans = to_string(b) + ans;
	num = a;
	}
	return is_negative? "-" + ans: ans;
	}

这里再聊一聊贪心算法可以用来求解我们日常生活中的一些问题,如活动安排等

贪心算法并不总能求得问题的整体最优解。但对于活动安排问题,贪心算法greedySelector却总能求得的整体最优解,即它最终所确定的相容活动集合A的规模最大。
算法greedySelector的效率极高。当输入的活动已按结束
时间的非减序排列,算法只需O(n)的时间安排n个活动,使最多的活
动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以
用O(nlogn)的时间重排。

证明: 设E={1,2,…n}活动集合(数学归纳推出矛盾)
由于E中活动安排结束时间的非减序排列,活动1具有最早的完成时间。
1)证明活动安排问题的最优解以贪心选择开始,包含活动1
设 是所给活动安排问题的一个最优解,A中活动按结束时间非减排序,A中
第一个活动是活动k
a.若k=1则得证
b.若k>1,则设 。由于f1≤fk,且A中活动是相容的,故B中
活动也是相容的。由于A与B中活动数目相同,故A与B都是最优的活动安排。因
此总存在以贪心选择开始的最优活动安排 。

2)证明活动安排问题的最优解与贪心选择解一致
原问题简化为对E中所有活动与活动1相容的活动进行活动安排的子问题。
若A是原问题的最优解,则A’=A-{1}是活动安排问题E’={i∈E,si≥f1}的最优解。若能
找到E’的一个解B’, B’包含比A’更多的活动,则将活动1加入B’,将产生E的一个解B,
它包含比A更多的活动,这与A是原问题的最优解矛盾,因此A’是活动安排问题E’={i∈E,
si≥f1}的最优解。因此每一步所做出的贪心选择都将问题简化为一个更小的与原问题具有相同
形式的子问题。以此对贪心算法选择次数用数学归纳法,则知贪心算法将产生原问题的最优
解。

哈希表
哈希表,又称散列表,使用 O(n) 空间复杂度存储数据,通过哈希函数映射位置,从而实现
近似 O(1) 时间复杂度的插入、查找、删除等操作。
C++ 中的哈希集合为 unordered_set,可以查找元素是否在集合中。如果需要同时存储键
和值,则需要用 unordered_map,可以用来统计频率,记录内容等等。如果元素有穷,并且范围
不大,那么可以用一个固定大小的数组来存储或统计元素。例如我们需要统计一个字符串中所有
字母的出现次数,则可以用一个长度为 26 的数组来进行统计,其哈希函数即为字母在字母表的
位置,这样空间复杂度就可以降低为常数。
一个简单的哈希表的实现如下。

	template <typename T>
	class HashTable {
	private:
	vector<list<T>> hash_table;
	// 哈希函数
	int myhash(const T & obj) const {
	return hash(obj, hash_table.size());
	}
	public:
	// size最好是质数
	HashTable(int size=31) {
	hash_table.reserve(size);
	hash_table.resize(size);
	}
	~HashTable() {}
	// 查找哈希表是否存在该值
	bool contains(const T & obj) {
	int hash_value = myhash(obj);
	const list<T> & slot = hash_table[hash_value];
	std::list<T>::const_iterator it = slot.cbegin();
	for (; it != slot.cend() && *it != obj; ++it);
	return it != slot.cend();
	}
	// 插入值
	bool insert(const T & obj) {
	if (contains(obj)) {
	return false;
	}
	int hash_value = myhash(obj);
	std::list<T> & slot = hash_table[hash_value];
	slot.push_front(obj);
	return true;
	}
	// 删除值
	bool remove(const T & obj) {
	list<T> & slot = hash_table[myhash(obj)];
	auto it = find(slot.begin(), slot.end(), obj);
	if (it == slot.end()) {
	return false;
	}
	slot.erase(it);
	return true;
	}
	};
	// 一个简单的对整数实现的哈希函数
	int hash(const int & key, const int &tableSize) {
	return key % tableSize;
	}

如果需要大小关系的维持,且插入查找并不过于频繁,则可以使用有序的 set/map 来代替unordered_set/unordered_map。

本文由个人查阅和总结而成,转载请注明!
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_46612124

点个星星支持一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值