【算法】算法基础入门学习笔记

*持续更新中……

暂时不准备作为一个知识点总结,仅是作为过算法书对于部分有趣内容的随笔

以及跟着b站灵茶山艾府大佬过算法的一些茅塞顿开

《算法图解(第2版)》阿迪蒂亚·Y.巴尔加瓦   著   比较推荐,还挺不错的还没过完

二分查找

循环不变量(红蓝染色法)

跟着灵神被开闭区间讲蒙了可能我数学确实不太好)

贴一下ai,觉得这个更好理解:

在编程中,循环不变量是一个在循环运行过程中始终保持不变的条件。不管循环执行多少次,这个条件总是成立的。

循环不变量的作用就像是一个“安全检查点”。它能帮助我们确保程序在每次循环的时候都按照我们期望的方式运行。如果某个地方出了问题,我们可以通过检查这个“安全点”来发现问题。

例子:

假设你有一个任务:在一个排好序的数字列表里找到一个特定的数字。比如,你要在   [1, 3, 5, 7, 9]   里找到数字   5  。

我们可以用二分查找来解决这个问题。二分查找的思路是每次取中间的数字来比较,如果中间的数字比目标数字小,就去右边找;如果比目标数字大,就去左边找。直到找到为止。现在,我们来定义一个循环不变量:在每次循环的时候,目标数字(如果存在的话)一定在我们当前查找的范围内。

用这个规则,我们来逐步分析:

第一步:初始化

• 我们从整个列表开始:  [1, 3, 5, 7, 9]  。

• 左边界是   left = 0  ,右边界是   right = 4  。

• 中间数字是   mid = (0 + 4) / 2 = 2  ,对应的数字是   5  。

标红的地方尽量不要用(left+right)/2这种方法,尽量养成采用  left+(right-left)/2  的方法计算的习惯,否则可能会超范围。

意为:起始点加上区间内一半的值即得答案。

第二步:比较

• 我们发现中间的数字   5   等于目标数字   5  ,找到了!如果没找到怎么办?假设我们要找的数字是   6  ,

那么:1. 第一次比较后,发现中间的数字是   5  ,比   6   小。

2. 根据循环不变量,目标数字(如果存在的话)一定在右边的部分,所以更新左边界为   mid + 1  。

3. 现在范围变成了   [7, 9]  ,继续查找。4. 如果最终范围为空(  left > right  ),说明目标数字不存在。

灵神视频中讲的34题为例。

设左指针为L,右指针为R。

最后时刻的左指针和右指针是可以交换位置的,即L在R的右侧,R在L的左侧。

保证L-1和R+1时刻指向的是小于target的部分和大于等于target的部分从而找到第一个位置:R+1

此时只找到一个值。所以要变换条件。比如说找到最后一个位置,可以选取找(target+1)元素的第一个位置,下标-1即得答案。

然后还要时刻注意下标,判断是否会出现超出数组范围。例如a[-1],或者直接越界。

最后得出答案。

学明白了回来再贴一次:

实际上三种写法:闭区间,半闭半开,开区间不变的都是思路,变的只是 边界 和 你得到的最终结果的转化方式。

比如说闭区间最后所指的可能不是答案,所指的left-1才是答案。

写闭区间尤其要注意数组下标的访问。问就是WA了

//闭区间:
left=mid+1;
right=mid-1;

//左闭右开:
left=mid+1;
right=mid;

//开区间
left=mid;
right=mid;

滑动窗口

跟双指针有点像,只要控制好left和right就行了

滑动窗口就是控制left和right步步移动,如果将left和right同等然后再算那时间复杂度就高了

哈希表

(c中map)学了stl觉得这个有点像

(python中字典)用于存储用户信息很方便

为避免最糟情况可以采用  链表数组  的结构存储

(web开发应用-缓存)将url存储在哈希表中,用户再次请求的时候用于判断是否已经有缓存

二叉树

霍夫曼编码

(文本压缩算法的基础)压缩字节

采用自定义的编码方式,把每个字符及其频率看成一个节点,将频率最小的两个节点合并成一个新节点(最小的父节点)        由此,可缩短出现频率高的字符的编码以节省大量的存储空间

图论

邻接表

Dijkstra算法

试水天梯赛L2的时候突然被狠狠迷住了,遂学之  搞懂原理了来试水讲一下

 就是存储上一节点的最短路及其前驱。然后进行筛选比较并重新更新每个节点的最优路径和对应的前驱。

优先队列和小顶堆:

(?)实际上如果你不会也可以找到最短路,因为优先队列的使用只是为了优化这个算法

如何优化?

用dist[N]结合优先队列,可以动态维护每个节点的最短距离。

dist[N]存储着每个节点的最短距离,每次发现最短路就更新一遍。

而优先队列的存在,则是为了快速找到当前距离最小的节点,从而实现在时间上对于该算法的优化。

比如说起点为0,dist[0]=0,我们先将(dist[0],0)存入优先队列中。

此时优先队列pq不为空,while(pq.empty()!=)成立,循环开始。

弹出(0,0),根据graph[0]去探寻起点城市0的邻居v。随后判断dist[v]和dist[0]+distance孰大孰小,动态更新pq,并将更新后的路径的数据入队。( 比如(dist[1],1),(dist[2],2),(dist[3],3) )

随后根据优先队列,它会弹出目前距离上一节点城市最小的目标城市的数据,根据这个“最小”,你可以快速更新出最短路。

剪枝操作1:if(u==d) break;

当目标城市u的下标等于终点城市d的下标的时候,实际上我们已经遍历了所有比终点城市离起点城市更近的节点,所以此时的dist[u]已经是最小值了,再去进行下面的判断已经是没有意义的了,所以直接剪枝。

剪枝操作2:if (current_dist > dist[u]) continue;

为什么会出现 current_dist > dist[u]?

原因:优先队列中的“重复条目”

当某个节点 u 的最短距离 dist[u] 被多次更新时,优先队列中可能会存在 同一个节点 u 的多个不同距离的条目。

例如:

第一次发现 u 的距离为 5,将 (5, u) 加入队列。

之后又发现 u 的更短距离 3,将 (3, u) 加入队列。

此时队列中同时存在 (3, u) 和 (5, u)。

当 (5, u) 被弹出时:实际此时 dist[u] 已经在先前被更新为 3(更小的值)。

current_dist(=5)比 dist[u](=3)大,说明这是一个“过时”的条目,不用进行下面的判断,直接剪枝跳过。

关于多条最短路问题:

为什么在发现 等长路径 时是 count_leastroad[v] += count_leastroad[u],而不是简单的 count_leastroad[v]++?很简单的数学问题但是犯蠢了

1. count_leastroad[u] 代表什么?

count_leastroad[u] 表示 从起点 s 到节点 u 的最短路径数量

如果 u 有 多条最短路径 到达,count_leastroad[u] 会存储这个数量。

2. 为什么是 += count_leastroad[u] 而不是 ++?

假设:

节点 u 有 3 条最短路径 到达(即 count_leastroad[u] = 3)。

现在发现 u → v 是一条新的最短路径(dist[v] == dist[u] + w)。

你的思路(count_leastroad[v]++):

只增加 1,相当于认为 u → v 只贡献 1 条新路径。

但实际上,u 本身有 3 条最短路径,所以 u → v 会贡献 3 条新路径(每条 s→u 的路径都可以延伸为 s→u→v)。

正确做法(count_leastroad[v] += count_leastroad[u]):

这样能正确累加所有可能的路径组合。

或者你按照count_leastroad[v]++ 的方法存然后最后相乘(?)

这里贴一个不错的题和ai写的该题模版:

天梯赛L2-紧急救援

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <algorithm>

using namespace std;

const int INF = INT_MAX;

void solve() {
    int N, M, S, D;
    cin >> N >> M >> S >> D;
    vector<int> rescue(N);
    for (int i = 0; i < N; ++i) {
        cin >> rescue[i];
    }
    
    vector<vector<pair<int, int>>> graph(N);
    for (int i = 0; i < M; ++i) {
        int c1, c2, length;
        cin >> c1 >> c2 >> length;
        graph[c1].emplace_back(c2, length);
        graph[c2].emplace_back(c1, length);
    }
    
    vector<int> dist(N, INF);
    vector<int> num_paths(N, 0);
    vector<int> max_rescue(N, 0);
    vector<int> pre(N, -1);
    
    dist[S] = 0;
    num_paths[S] = 1;
    max_rescue[S] = rescue[S];
    
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.emplace(0, S);
    
    while (!pq.empty()) {
        auto [current_dist, u] = pq.top();
        pq.pop();
        
        if (u == D) {
            break;
        }
        
        if (current_dist > dist[u]) {
            continue;
        }
        
        for (auto [v, length] : graph[u]) {
            if (dist[v] > dist[u] + length) {
                dist[v] = dist[u] + length;
                num_paths[v] = num_paths[u];
                max_rescue[v] = max_rescue[u] + rescue[v];
                pre[v] = u;
                pq.emplace(dist[v], v);
            } else if (dist[v] == dist[u] + length) {
                num_paths[v] += num_paths[u];
                if (max_rescue[v] < max_rescue[u] + rescue[v]) {
                    max_rescue[v] = max_rescue[u] + rescue[v];
                    pre[v] = u;
                }
            }
        }
    }
    
    vector<int> path;
    int current = D;
    while (current != -1) {
        path.push_back(current);
        current = pre[current];
    }
    reverse(path.begin(), path.end());
    
    cout << num_paths[D] << " " << max_rescue[D] << endl;
    for (size_t i = 0; i < path.size(); ++i) {
        if (i != 0) {
            cout << " ";
        }
        cout << path[i];
    }
    cout << endl;
}

int main() {
    solve();
    return 0;
}

见到一个二分+BFS的解法,等我学一下bfs再来trytry

自己写的Dijkstra:

#include<iostream>
#include<vector>
#include<utility>//二元组的头文件
#include<queue>//优先队列头文件
using namespace std;
int main()
{
	int n, m, s, d;
	cin >> n >> m >> s >> d;
	vector<int> rescue;
	for (int i = 0;i < n;i++)
	{
		int a;
		cin >> a;
		rescue.emplace_back(a);
	}
	vector<vector<pair<int, int>>> road;//按出发城市为下标存储到达城市和路径长度
	road.resize(n);
	for (int i = 0;i < m;i++)
	{
		int a, b,c;
		cin >> a >> b >> c;
		road[a].emplace_back(b, c);
		road[b].emplace_back(a, c);//无向图
	}
	vector<int> min_dist(n,503);//500为路径最大长度
	vector<int> pre_Index(n, s);
	vector<int> count_leastroad(n, 0);
	vector<int> max_rescuers(n, 0);
	priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>>pq;

	//初始化出发点
	min_dist[s] = 0;
	max_rescuers[s] = rescue[s];
	count_leastroad[s] = 1;
	pq.emplace(0, s);

	//n个出发点,i就是出发点
	while (!pq.empty())//队列不为空
	{
		auto[current_dist, destination] = pq.top();
		pq.pop(); 
		//cout << "iii     " << "(" << current_dist << "," << destination << ")" << endl;
		if (destination == d)
			break;	
		if (current_dist > min_dist[destination])
			continue;
		for (auto[i,dist]:road[destination])
		{
			
			if (min_dist[i] >= (min_dist[destination] + dist))
			{
				//原路径不是最优路径,需被更新
				//需要把记录最短路径数量的对应元素重置为1
				if (min_dist[i] == (min_dist[destination] + dist))
				{
					count_leastroad[i] += count_leastroad[destination];
					if (max_rescuers[i] > (rescue[i] + max_rescuers[destination]))//比较救援队数量,决定是否更新最优解
						continue;
					else
						min_dist[i] = min_dist[destination] + dist;
				}
				else
				{
					count_leastroad[i] = count_leastroad[destination];
					min_dist[i] = min_dist[destination] + dist;
					pq.emplace(min_dist[i], i);
				}
				//更新最优解
				pre_Index[i] = destination;
				//cout << min_dist[i] << endl;
				max_rescuers[i] = rescue[i] + max_rescuers[destination];
			}
		}
	}
	int index=d;
	while (index != s)
		index = pre_Index[index];
	cout << count_leastroad[d] <<" " << max_rescuers[d] << endl;
	cout << s<<" ";
	vector<int> inddex;
	index = d;
	while (index != s)
	{
		inddex.emplace_back(index);
		index = pre_Index[index];
	}
	for (int p = inddex.size() - 1;p >= 0;p--)
	{
		if (p != 0)
			cout << inddex[p] << " ";
		else
			cout << inddex[p];
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值