大二上 数据结构与算法 课堂模板算法 20241225

数据结构与算法

      笔记主体部分来源于戚俊涵先生,在此表示感谢!

1-基本数据结构

2-分治策略

3-堆

4-排序

5-选择 & 树

6-搜索树 & 散列表 & 并查集

6.1-搜索树

6.2-散列表

unordered_map 是 C++ 标准模板库(STL)中的一个关联容器,它存储键值对(Key-Value Pair),并且基于哈希表实现。与 map 不同,unordered_map 不保证元素的顺序,但通常能提供更快的查找速度。下面我将结合简单例子为你讲解 unordered_map 的使用。

1. 包含头文件

要使用 unordered_map,首先需要包含对应的头文件:

#include <unordered_map>

2. 声明和初始化

unordered_map 可以存储任意类型的键和值,但键必须是唯一的。下面是一个简单的声明和初始化的例子:

#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    // 声明一个unordered_map,键为string,值为int
    unordered_map<string, int> ageMap;

    // 插入键值对
    ageMap["Alice"] = 30;
    ageMap["Bob"] = 25;
    ageMap["Charlie"] = 35;

    // 使用insert方法插入键值对
    ageMap.insert(make_pair("David", 40));

    // 初始化时直接插入多个键值对
    unordered_map<string, int> anotherMap = {
        {"Eve", 28},
        {"Frank", 22}
    };

    return 0;
}

3. 查找元素

可以使用 find 方法查找特定键的元素,如果找到,返回指向该元素的迭代器;如果未找到,返回指向 unordered_map 结尾的迭代器。也可以直接使用下标运算符 [] 访问元素,如果键不存在,会自动插入一个默认值的键值对。

#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> ageMap = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    // 使用find方法查找
    auto it = ageMap.find("Bob");
    if (it != ageMap.end()) {
        cout << "Bob's age is " << it->second << endl;
    } else {
        cout << "Bob not found" << endl;
    }

    // 使用下标运算符访问
    cout << "Alice's age is " << ageMap["Alice"] << endl;

    // 如果键不存在,会插入一个默认值的键值对
    cout << "New person's age is " << ageMap["New person"] << endl; // 默认值为0

    return 0;
}

4. 遍历元素

可以使用范围基于的 for 循环(C++11 及以后版本)或迭代器来遍历 unordered_map 中的所有元素。

#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> ageMap = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    // 使用范围基于的for循环遍历
    for (const auto& pair : ageMap) {
        cout << pair.first << " is " << pair.second << " years old" << endl;
    }

    // 使用迭代器遍历
    for (auto it = ageMap.begin(); it != ageMap.end(); ++it) {
        cout << it->first << " is " << it->second << " years old" << endl;
    }

    return 0;
}

5. 删除元素

可以使用 erase 方法删除特定键的元素,也可以删除迭代器指向的元素。

#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> ageMap = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    // 删除特定键的元素
    ageMap.erase("Bob");

    // 使用迭代器删除元素
    auto it = ageMap.find("Charlie");
    if (it != ageMap.end()) {
        ageMap.erase(it);
    }

    // 遍历剩余元素
    for (const auto& pair : ageMap) {
        cout << pair.first << " is " << pair.second << " years old" << endl;
    }

    return 0;
}

6. 统计元素数量

可以使用 size 方法获取 unordered_map 中的元素数量,使用 empty 方法判断是否为空。

#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> ageMap = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    cout << "Number of elements: " << ageMap.size() << endl;

    ageMap.clear(); // 清空unordered_map

    if (ageMap.empty()) {
        cout << "The map is empty" << endl;
    }

    return 0;
}

总结

unordered_map 是一个非常有用的容器,适用于需要快速查找、插入和删除元素的场景。通过上述例子,你可以看到如何声明、初始化、查找、遍历、删除和统计 unordered_map 中的元素。

6.3-并查集

int find(int x) //查找父节点
{
    if(pre[x] == x) return x;     
    return pre[x] = find(pre[x]);
}

void join(int x,int y) //合并
{
    int fx=find(x), fy=find(y);
    if(fx != fy)
        pre[fx]=fy;
}

7-图

7.1-BFS(邻接表)

vector<vector<int>> graph; // 图的邻接表表示
vector<int> color; // 访问标记数组

void BFS(int start)
{
	queue<int> q; // BFS队列
	q.push(start); // 将起始节点加入队列
	color[start] = 1; // 标记起始节点为已访问
	while (!q.empty()) 
	{
		int current = q.front(); // 取出队列的第一个元素
		q.pop(); // 将该元素从队列中移除
		color[current] = 2;
		// 遍历当前节点的所有邻接节点
		for (auto &neighbour:graph[current]) 
		{
			color[neighbour] = 1;
			q.push(neighbour);
		}
	}
}

7.2-DFS(邻接表)

vector<vector<int>> graph; // 图的邻接表表示
vector<int> color; // 访问标记数组

void DFS(int node)
{
	color[node] = 1;
    for (auto &neighbour : graph[node])
    {
        if (color[neighbour]==0)
        {
            DFS(neighbour);
        }
    }
    color[node] = 2;
}

8-深度优先算法&生成树

  • DAG:有向无环图
  • (S)CC:(强)连通图
  • MST:最小生成树

8.1-Topological Sort(DAG)

//拓扑序:DAG中的所有顶点的线性序列,且满足每个顶点出现且只出现一次;若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面
int V; //顶点个数
vector<vector<int>> graph;
vector<int> indegree;
queue<int> q; // 维护一个入度为0的顶点的集合

bool topological_sort()
{
    for(int i=0; i<V; ++i)
        if(indegree[i] == 0)
            q.push(i);         // 将所有入度为0的顶点入队

    int count = 0;             // 计数,记录当前已经输出的顶点数 
    while(!q.empty())
    {
        int current = q.front();      // 从队列中取出一个顶点
        q.pop();
        cout << current << " ";      // 输出该顶点
        count++;
        // 将所有current指向的顶点的入度减1,并将入度减为0的顶点入栈
        for(auto &node:graph[current])
        {
        	indegree[node]--;
        	if(indegree[node]==0)
        	q.push(node);
        }
    }
    if(count < V)
        return false;           // 没有输出全部顶点,有向图中有回路
    else
        return true;            // 拓扑排序成功
}

8.2-Minimum Spanning Trees

8.2.1-Kruskal算法

int V; 			    //顶点个数
int parent[MAX];    //定义parent数组用来判断边与边是否形成环路

struct Edge
{
	int u,v,w;
};
vector<Edge> edges;

bool cmp(Edge a,Edge b)
{
	return a.w < b.w;
}

int Find(int x)
{
   return parent[x]==x ? x : parent[x] = find(parent[x]);
}
 
void Kruskal()
{
	sort(edges.begin(),edges.end(),cmp); //把图中的所有边按代价从小到大排序
	for(int i=0;i<V;i++) 				 //把图中的n个顶点看成独立的n棵树组成的森林
		parent[i] = i;
	for(int i=0;i<edges.size();i++)
	{
		int u=edges[i].u,v=edges[i].v;
		if(Find(u)!=Find(v)) //如果Find(u)=Find(v),则形成环路
			//将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
			parent[Find(u)] = Find(v);
	}
}

8.2.2-Prim算法

#define inf INT_MAX
int n; //顶点个数
int res; //结果

vector<vector<int>> ma(n+1,vector<int>(n+1,inf)); //邻接矩阵,存边权,初始化为正无穷
vector<int> dist(n+1,inf);					      //dist[]储存到生成树的距离
vector<bool> book(n+1,false);					  //用book数组记录某个点是否加入到生成树中

void prim()
{
	dist[1] = 0;
    book[1] = true;
    for(int i = 2; i <= n; i++)
    	dist[i] = min(dist[i],ma[1][i]);//用点1去更新dist[]
    for(int i = 2; i <= n; i++)
    {
        int temp = inf;//初始化距离
        int t = -1;    //接下来去寻找离生成树点集最近的点加入到集合中,用t记录这个点的下标。
        for(int j = 2 ; j <= n; j++)
        {
            if(!book[j] && dist[j]<temp)
            {
                temp = dist[j];
                t = j;
            }
        }
        //如果t==-1,意味着在集合V找不到边连向集合S,生成树构建失败,将res赋值正无穷表示构建失败
        if(t==-1)
        {
        	res = INF; 
        	return;
        }
        book[t] = true;//如果找到了这个点,就把它加入集合S
        res += dist[t];//加上这个点到集合S的距离
        for(int j = 2 ; j <= n ; j++)
        	dist[j] = min(dist[j],ma[t][j]);//用新加入的点更新dist[]
}

8.2.3-Borůvka算法

int N,M;//N:点个数,M:边条数
struct Edge
{
	int u,v,w;
}
vector<Edge> edges;
vector<bool> visited(M+1,0);
vector<int> parent(N+1,0),dist(M+1,0),e(M+1,0);

int find(int x)
{
    return parent[x] == x ? x : parent[x] = find(parent[x]);
}

bool cmp(Edge a,Edge b)
{
	return a.w < b.w;
}

int boruvka()
{
	int cnt = 0,sum = 0;
	for(int i = 1;i <= N; i++)
		parent[i] = i;
	while (1)
	{
		int cnt_tmp=0;
		memset(dist,inf,sizeof(dist));                      //初始化dist数组 
		for (int i = 1; i <= M; i++)                        //每个边都计算一下dist
		{
			int f1 = find(edges[i].u),f2 = find(edges[i].v);//用并查集找到根 
			if (f1 == f2 || visited[i])                     //如果一棵树或者拓展过这个边 
				continue;
			cnt_tmp++;                                      //记录一下看下有没有边可以拓展 
			if (edges[i].w < d[f1])
				dist[f1] = edges[i].w,e[f1] = i;            //更新,e数组表示dist数组对应的边 
			if (edges[i].w < d[f2])
				dist[f2] = edges[i].w,e[f2] = i;
		}
		if (cnt_tmp == 0 || cnt == n - 1)
			break;
		for (int i = 1; i <= N; i++)                         //扫描每棵树 
		{
			int f1 = find(edges[e[i]].u),f2 = find(edges[e[i]].v);
			if(dist[i] == inf || f1 == f2 || visited[e[i]])
				continue;
			visited[e[i]] = 1;                              //加入最小生成树集合 
			parent[f1] = f2;
			sum += edges[e[i]].w;
			cnt++;
		}
	}
}

9-贪心

10-最短路

10.1-Floyd算法

1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i][j]=d,d表示该路的长度;否则G[i][j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i][j]表示从Vi到Vj需要经过的点,初始化D[i][j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i][j] = min( G[i][j], G[i][k]+G[k][j] ),如果G[i][j]的值变小,则D[i][j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。
比如,要寻找从V5到V1的路径。根据D,假如D(5,1)=3则说明从V5到V1经过V3,路径为{V5,V3,V1},如果D(5,3)=3,说明V5与V3直接相连,如果D(3,1)=1,说明V3与V1直接相连。

int N; //顶点个数
vector<vector<int>> ma;//邻接矩阵,存储i到j的边权

void Floyd()
{
	for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            for (int k = 0; k < N; k++)
            {
                if (ma[j][k] > ma[j][i] + ma[i][k]) //若j到k间存在更短的通路,则更新
                    ma[j][k] = ma[j][i] + ma[i][k];
            }
        }
    }
}

10.2-Dijkstra算法

#define inf INT_MAX
template <typename T>
using min_priority_queue = priority_queue<T, vector<T>, greater<T>>;

class node
{
public:
    int dist = inf;
    vector<pair<node *, int>> edges;
    bool operator>(node *&n) { return dist > n->dist; }
};

min_priority_queue<node *> Q;

void Dijkstra(vector<node *> &nodes, int s)
{
    nodes[s]->dist = 0;
    Q.push(nodes[s]);
    while (!Q.empty())
    {
        node *u = Q.top();
        Q.pop();
        for (auto tmp : u->edges)
        {
            node *v = tmp.first;
            if (v->dist > u->dist + tmp.second)
            {
                v->dist = u->dist + tmp.second;
                Q.push(v);
            }
        }
    }
}

10.3-Bellman-Ford算法

//用于判断图中是否存在负权环
#define inf INT_MAX
int N,M;//N:顶点个数,M:边条数
struct Edge
{
    int u, v, w;
};
vector<Edge> edges(M+1);
vector<int> dist(N+1,inf);

bool Bellman_Ford()
{
	for (int i = 1; i <= N; i++) //将dist数组更新一次,存储i节点所在环的权
    {
        for (int j = 1; j <= M; j++)
        {
            int u = edges[j].u, v = edges[j].v, w = edges[j].w;
            if (dist[v] > dist[u] + w)
                dist[v] = dist[u] + w;
        }
    }
    for (int i = 1; i <= M; i++) //再次更新dist数组,若权值改变,则图中存在负权环
    {
        if (dis[edges[i].v] > dis[edges[i].u] + edges[i].w)
            return 1;
    }
    return 0;
}

11-动态规划

11.1钢材切割问题

一种解决该问题的dp算法如下:

//对于一段长度为n的绳子,有一个数组vectorvalue,value[i]代表长度为i的绳子的价值.你可以任意分割这个绳子,求出使得切分后的绳子价值最高的分法.

include <bits/stdc++.h>

using namespace std;
int main(){
vectorvalue{0,1,5,8,9};//不会编数据,这里就是PPT上的数据,所以只有五个.
vectordp(5,0);//dp[i]代表长度为i的绳子切割后的最大的价值
//dp可以看作是一种自下而上的思考方式,计算出最优子结构,然后把最优子结构结合起来,就会成为整体最优.
//因而,很多dp的计算就是,先求出基础情况,如dp[0],dp[i][0],然后用0求1,用1求2,用n-1求n.
//这个求的方程,我们叫做动态转移方程,这里指的就是dp[i]=max(value[j]+dp[i-j],0<=j<=i)
dp[0]=0;
for(int i=1;i<value.size();i++){
int maxValue=value[i];
for(int j=0;j<=i;j++)
maxValue=max(maxValue,value[j]+dp[i-j]);
dp[i]=maxValue;
}
cout<<dp[value.size()-1];
}

11.2最小编辑距离

//最小编辑距离
//这个挺经典的.leetcode72,或者去看【ACM 金牌选手教你动态规划的本质。力扣 No.72 编辑距离,真·动画教编程,适合语言初学者或编程新人。】 https://www.bilibili.com/video/BV1FJ4m1M7RJ/?share_source=copy_web&vd_source=3ce6749836b98dddde11ab8e5277b8ea

include <bits/stdc++.h>

using namespace std;
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector>dp(word1.size()+1,vector(word2.size()+1,0));
for(int i=0;i<=word1.size();i++)
dp[i][0]=i;
for(int i=0;i<=word2.size();i++)
dp[0][i]=i;
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]!=word2[j-1]){
int minIndex=min(dp[i-1][j],dp[i][j-1]);
dp[i][j]=min(minIndex,dp[i-1][j-1])+1;
} else
dp[i][j]=dp[i-1][j-1];
}
}
return dp[word1.size()][word2.size()];
}
};

11.3矩阵相乘问题

include <bits/stdc++.h>

using namespace std;
//对于给定的n个矩阵进行相乘,求花销最小的结合方式.
//意思是:比方说矩阵A1,是q1q2的长和宽,A2是q2q3的长和宽,A3是q3q4的长和宽,每次相乘(例如An-1An)的花销是qn-1qnqn+1,矩阵的乘法满足结合律,这里的计算就是,如何通过结合律,求出最小的开销.
int main(){
vectorcost{10,100,5,50};
int n=cost.size()-1;
vector<vector>dp(n,vector(n,0));//dp[i][j]的意思是求出AiAi+1...Aj的最小花费.
for(int i=0;i<n;i++)
dp[i][i]=0;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
dp[i][j]=cost[i]
cost[i+1]*cost[i+2];
}
}//dp基础状态的标记

//接下来就是要求动态转移方程
//应该去想的是,求出Ai*Ai+1*...*Aj的最小花费,就是求Ai*Ai+1*...*Ak的花费+Ak+1*Ak+2*...*Aj的花费+cost[i]*cost[k+1]*cost[j+1]的最小花费(为什么?就是把一个比较大的乘法在k处拆分成两个乘法.)
//所以就可以求出动态转移方程.
//按照我们自下而上,先求最优子结构的角度来考虑,首先要计算的,肯定是Ai*Ai+1的最小花费,然后再求每三个一结合的最小花费,再求多个的.
//因而dp的求的顺序如下:
for (int l=1;l<n;l++) {
    for(int i=0;i+l<n;i++){
        int minCost=INT_MAX;
        for(int k=i;k<i+l;k++)
            minCost=min(minCost,dp[i][k]+dp[k+1][i+l]+cost[i]*cost[k+1]*cost[i+l+1]);
        dp[i][i+l]=minCost;
    }
}
for(int i=0;i<n;i++){
    for(int j=i;j<n;j++)
        cout<<dp[i][j]<<' ';
    cout<<endl;
}

}

posted @ 2024-12-25 21:30  陆舟LandBoat  阅读(50)  评论(0)    收藏  举报