算法(三)数据结构

一、栈和队列

1.栈

栈是一种特殊的线性表,它只能在一端进行插入和删除,是一种先进后出的顺序;
在C++的标准库中,引用头文件可以直接使用栈,栈的常用操作有:
(1)push():向栈内压入一个成员
(2)popp():向栈顶弹出一个成员,意味着栈中没有这个成员了
(3)empty():如果栈为空返回true,否则返回false;
(4)top():取栈顶元素
(5)size():返回栈内元素的大小
#include<bits/stdc++.h>

#include<stack>
using namespace std;
stack<int> stk;
int main(){
	for(int i = 0; i < 16; i++){
		stk.push(i);
	}
	cout << stk.size()<<endl;
	while(!stk.empty()){
		stk.pop();
	}
	cout << stk.size() << endl; 
}

2.队列

队列是一种先进先出的线性表,它只允许在表的一端进行插入,在另一端进行删除,允许插入的一端叫做队尾,删除的一端叫做队首。
在C++标准库中可以引用头文件直接使用队列。
它的常用操作:
(1)push(x),将x接到队列的末尾
(2)pop(),弹出队列的第一个元素,并不会返回被弹出元素的值
(3)front(),取队头
(4)back(),取队尾
(5)empty(),判空
(6)size(),访问队列中元素个数

3.优先队列

优先队列为元素赋予了优先级,在优先队列中的数据是按照优先级高低的顺序排序,一般优先级最高的先被去除。默认的int类型的优先级队列实际上是按照从大到小的顺序排列,先出队的为队列中最大的数。
格式声明:priority_queueque,意味着声明了一个名叫que的优先队列,更多情况下我们是自定义优先级的,一般采用结构体
最常见的有以下几种:

#include<bits/stdc++.h>
#include<queue>
using namespace std;
struct Node
{
    int x,y;
    Node(int x,int y):x(x),y(y){}
    friend bool operator <(const Node &a,const Node &b){
        return a.x < b.x;
    }
};
int main(){
    priority_queue<int>que1;
    priority_queue<Node>que2;
    priority_queue<int,vector<int>,greater<int> >que3;
    priority_queue<int,vector<int>,less<int> >que4;
 
    que1.push(1);que3.push(1);que4.push(1);
    que1.push(4);que3.push(4);que4.push(4);
    que1.push(7);que3.push(7);que4.push(7);
    que1.push(5);que3.push(5);que4.push(5);
    que2.push(Node(1,4));
    que2.push(Node(7,2));
    que2.push(Node(2,5));
    que2.push(Node(5,3));
 
    cout <<"que1:";
    while (!que1.empty())
    {
        /* code */
        cout <<que1.top();
    }
    cout <<"que2:";
    while (!que2.empty())
    {
        /* code */
        cout <<que2.top();
    }
    cout <<"que3:";
    while (!que3.empty())
    {
        /* code */
        cout <<que3.top();
    }
    cout <<"que4:";
    while (!que1.empty())
    {
        /* code */
        cout <<que1.top();
    }
     
 
}

二、并查集

主要用于处理一些不相交集合的合并问题,一些常见的用途有求连通子图、求最小生成树的Kruskal算法和求最近公共祖先(LCA)等
并查集的基本操作

1初始化
假如有编号为1,2,3,…,n的n个元素,我们用一个数组fa[]来存储每个元素的父节点。一开始,我们将它们的父节点设为自己

int init(int n){
	for(int i = 1; i <= n; i++){
		fa[i] = i;
	}
}

2.查询
找到i的祖先直接返回
分为路径压缩和路径未压缩
在这里插入图片描述
法一、未进行路径压缩

 int find(int i){ //未进行路径压缩
     if(fa[i] == i){
         return i;
     }else{
         return find(fa[i]);
     }
 }

法二:优化可以在查找时达到O(1)

int find(int i){ //采用路径压缩
    if(fa[i] == i){
        return i;
    }else{
        fa[i] = find(fa[i]); //进行路径压缩
        return fa[i]; //返回父结点
    }
}

3.合并

void unionn(int i,int j){
    int i_fa = find(i); //找到i的祖先;
    int j_fa = find(j); //找到j的祖先;
    fa[i_fa] = j_fa; //将i的祖先指向j的祖先
}

在这里插入图片描述
如(4,5)的合并,将4的父亲指向5
并查集完整代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
int fa[MAXN];
void init(int n){
    for(int i = 1; i <=n; i++){
        fa[i] = i;
    }
}
// int find(int i){ //未进行路径压缩
//     if(fa[i] == i){
//         return i;
//     }else{
//         return find(fa[i]);
//     }
// }
int find(int i){ //采用路径压缩
    if(fa[i] == i){
        return i;
    }else{
        fa[i] = find(fa[i]); //进行路径压缩
        return fa[i]; //返回父结点
    }
}
void unionn(int i,int j){
    int i_fa = find(i);
    int j_fa = find(j);
    fa[i_fa] = j_fa;
}
 
 
int main(){}

优化解决栈溢出
1.堆栈溢出一般是由什么原因导致的?
1).函数调用层次太深。
函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
2).动态申请空间使用之后没有释放。
由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。
3).数组访问越界。
C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。
4).指针非法访问。
指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。
2.优化:
使用以上优化算法虽然能加快查询时间,但是当数据很大时,有可能会有栈溢出的情况,于是提出一种优化查询的算法这种写法避免了回溯过程中返回值的问题,先找到父亲节点,在沿着路径进行一一修改。
合并操作也很简单。⭐⭐⭐

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
int fa[MAXN];
void init(int n){
    for(int i = 1; i <= n; i++){
        fa[i] = i;
    }
}
int find(int x){
    int y = x;
    while(y != fa[y]){
        y = fa[y];
    }
    while(x != fa[x]){
        int px = fa[x];
        fa[x] = y;
        x = px;
    }
    return  y;
}
void unionn(int a,int b){
    fa[b] = a;
}
 
 
int main(){
 
}

例题

现在有若干家族关系,给出了一些亲戚关系,如Marry和Tom是亲戚,Tom和Ben是亲戚等。从这些信息中,你可以推导出Marry和Ben是亲戚。请写一个程序,对于我们的关于亲戚关系的提问,一最快速度给出答案。

输入格式:

第一部分是一M,N开始,N为人数(1<=N<=20000),这些人的编号为1,2,3,…,N。下面有M行(1<=M<=1000000),每行有两个数a,b,表示a和b是亲戚; 第二部分是以Q开始。以下Q行有Q个询问(1<=Q<=1000000),每行为c,表示询问c和d是否为亲戚。

输出格式:

对于询问c,d,输出一行:若c,d为亲戚,则输出"YES"否则输出"NO";

样例输入:
10 7
2 4
5 7
1 3
8 9
1 2
5 6
2 3
3
3 4
7 10
8 9
样例输出:
YES
NO
YES

#include<bits/stdc++.h>
#include<stack>
using namespace std;
stack<int> stk;
int fa[20001];
int init(int n){
	for(int i = 1; i <= n; i++){
		fa[i] = i;
	}
}
int find(int i){
	if(fa[i] == i){
		return i;
	}else{
		return fa[i] = find(fa[i]);
	}
}
int unionn(int x,int y){
	int x_index = find(x);
	int y_index = find(y);
	fa[x_index] = y_index;
}
int main(){
	int m, n;
	cin >> n >> m;init(n);
	while(m--){
		int a,b;
		cin >> a >> b;
		unionn(a,b);
	}
	int q;
	cin >> q;
	while(q--){
		int a,b;
		cin >> a >> b;
		if(fa[a] == fa[b]){
			cout << "YES" << endl;
		}else{
			cout << "NO" << endl;
		}
	}
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

释怀°Believe

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值