简单数据结构之线性表【五千字详解】

数组

STL中的可变数组vector

vector是一个不定长的数组,它还在内部封装了一些常用操作,例如:

1》 vector< int>a :声明一个vector,实际上它完整的写法是vector< int>a(n,i),表示该可变数组最开始有n个元素,全部初始化为 i。如果将其省略,则会默认为 0。尖括号内表示数据类型,也可换成其他类型。
2》 a.push_back(x) : 将元素x插入到数组尾部。
3》 a.pop_back() : 删除尾部最后一个元素。
4》 a.size() : 返回数组的长度。
5》 a.resize(n,m) : 将数组调整为 n ,多出的部分删除,新增的部分初始化为 m,m省略时默认为 0。

访问该数组时像访问普通数组一样用方括号,使用时需要头文件#include< vector>,此处不再赘述。

二维可变数组

与普通数组类似,可变数组也可以进行嵌套,例如vector< int>a[N]就是由N个可变数组组成的二维数组,只不过第一维大小固定,第二维不固定,当然也会有vector<vector< int> >这样的二维都不定长的数组,道理我们都懂,那它们到底适用于那些题目呢?下面我们通过一道题来看看它的作用:

例题:寄包柜

题目描述
超市里有 n(n≤10^5 ) 个寄包柜。每个寄包柜格子数量不一,第 i个寄包柜有ai(ai≤10^5) 个格子,不过我们并不知道各个ai的值。对于每个寄包柜,格子编号从 1 开始,一直到 ai 。现在有 q(q≤10^5 ) 次操作: 1.若输入 1 i j k:在第 i 个柜子的第 j 个格子存入物品 k(0≤k≤10^9)。当 k=0 时说明清空该格子。
2.若输入 2 i j:查询第 i个柜子的第 j 个格子中的物品是什么,保证查询的柜子有存过东西。
已知超市里共计不会超过10^7个寄包格子,ai是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。
输入格式:
第一行 2 个整数 n 和 q,寄包柜个数和询问次数。 接下来 q 个整数,表示一次操作。
输出格式:
对于查询操作时,输出答案。

分析:显然,需要建立一个二维数组分别记录柜子号和格子号,但根据本题的数据,建立10^5 ✖10^5 的int 数组肯定会超出内存限制,这时候就要用上vector了,我们先定义一个二维的vector<vector< int> >a(n+1),此时这个数组第二维有n+1个元素,代表1到n个柜子,而柜子里面的格子数,我们可以视情况而定,当数量不够时用resize调整,这样就避免了内存超出限制。参考代码:

#include<iostream>
#include<vector>
using namespace std;
int n,q;
int main()
{
	cin>>n>>q;
	vector<vector<int> >a(n+1);
	while(q--)
	{
		int x,i,j,k;
		cin>>x;
		if(x==1)
		{
			cin>>i>>j>>k;
			if(a[i].size()<j+1)
			{
				a[i].resize(j+1);	
			}
			a[i][j]=k;
		}
		else
		{
			cin>>i>>j;
			cout<<a[i][j]<<endl;
		}
	}
	return 0;
}

STL中的栈stack

所谓栈,就是符合“后进先出”规则的数据结构,头文件< stack>,主要有以下操作:

1》 stack< int>a : 建立一个栈a,其内部元素是int。
2》 a.push(x) : 将元素x压进栈。
3》 a.pop() : 将栈顶元素弹出。
4》 a.top() : 查询栈顶元素。
5》 a.size() : 查询元素个数。
6》 a.empty() : 查询栈是否为空。

例题:括号匹配

题目描述

给定若干字符串,每个字符串由(、)、[、 ] 、{ 、}组成,如果所有的括号都能匹配得上,说明这个字符串合法,否则为非法。
例如:字符串“ [()] ” 合法 ,字符串“[ { ]”非法。
若合法输出Yes 非法输出No。

分析:
将字符串遍历,遇到匹配的就消去,我们用栈来实现这个操作,从左向右遍历,若匹配不但不压入还要弹出栈顶,若不匹配就压入,最后判断栈是否为空即可。

#include<iostream>
#include<string>
#include<stack>
using namespace std;
string str;
stack<char>a;
int n;
char find(char x)
{
	if(x==')') return '(';
	if(x==']') return '[';
	if(x=='}') return '{';
	return '\0';
}
int main()
{
	cin>>n;
	getline(cin,str);
	while(n--)
	{
		while(!a.empty()) a.pop();
		getline(cin,str);
		for(int i=0;i<str.size();i++)
		{
			if(a.empty()) 
			{
				a.push(str[i]);
				continue;
			}
			if(a.top()==find(str[i]) )
			{
				a.pop();
			 } 
			else a.push(str[i]);
		}
		if(a.empty()) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
		
	}
	return 0;
}

例题:后缀表达式

题目描述
所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

如:3*(5–2)+7对应的后缀表达式为:3.5.2.-*7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。

分析:
首先我们读入字符串然后遍历,对于数字我们需要将它存起来,对于运算符,我们需要取出刚放入的两个数字进行相应运算,显而易见,这里我们用栈会更好,本题的坑点在于:题目中只给出了个位数的运算,实际上数据中存在多位数。如果能意识到这点的话应该能顺利做出来。代码如下:

#include<iostream>
#include<stack>
#include<string>
using namespace std;
stack<int>a;
int res;
int check(int x,int y,char z)
{
	if(z=='*') return x*y;
	if(z=='+') return x+y;
	if(z=='-') return x-y;
	if(z=='/') return x/y;
}
int main()
{
	string str;
	cin>>str;
	for(int i=0;i<str.size();i++)
	{
		if(str[i]>='0'&&str[i]<='9')
		{
			res=res*10+str[i]-'0';
		}
		if(str[i]=='.')
		{
			a.push(res);
			res=0;
		 } 
		if(str[i]=='@') break;
		if(str[i]=='+'||str[i]=='*'||str[i]=='/'||str[i]=='-') 
		{
			int num1,num2;
			num1=a.top();a.pop();
			num2=a.top();a.pop();
			a.push(check(num2,num1,str[i]));
		}
	}
	cout<<a.top();
	return 0;
} 

用数组实现栈

用 tt 表示栈顶元素。将tt 初始化为 0,表示没有元素。
1》 push操作 :将栈顶所在位置往后移动一格,放入x。即stk[++tt] = x
2》 pop 操作: 将tt 往前移动一格。即 tt--
3》empty 操作:如果tt 大于 0 则栈非空,若等于 0 则栈空。
4》query : 返回栈顶元素。stk[tt]

#include<iostream>
using namespace std;
const int N=10000;
int stk[N];
int tt,res,x;
void push(int x)
{   
    cin>>x;
    stk[++tt]=x;
}
void pop(int x)
{
    tt--;
}
void empty()
{
    cout<<(tt==0?"YES":"NO")<<endl;
}
void query()
{
    res=stk[tt];
    cout<<res<<endl;
}
int main()
{
    string t;
    while(cin>>t)
    {   
        //push x,pop,empty,query 
        if(t=="push")   push(x);
        else if(t=="pop")    pop(x);
        else if(t=="empty")   empty();
        else if(t=="query")  query();
    }
           
    return 0;
}

队列

STL中的队列queue

与栈不同,队列是一种“先进先出”的线性表,限制在一端删除,另一端插入,头文件为< queue>,主要操作有:

1. queue< int>a : 建立一个队列。
2. a.push(x) : 将x插入队尾。
3. a.pop() : 删除队首。
4. a.front() : 查询队首。
5. a.back() : 查询队尾。
6. a.size() : 查询元素个数。
7. a.empty() : 查询队列是否为空。

例题:约瑟夫问题

题目描述
n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 1 开始报数,数到 m的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式 输入两个整数 n,m。

输出格式 输出一行 n 个整数,按顺序输出每个出圈人的编号。

当数据有先进先出的性质时,就可以考虑使用队列,仔细考虑本题,我们会发现出列的人都站到了队尾,这样一直循环,直到数到m的人出圈,又重新开始操作,完全符合队列的性质,利用队列模拟题意即可:

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

queue<int>a;
int n,m;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		a.push(i);
	}
	while(!a.empty())
	{
		for(int i=1;i<=m;i++)
		{
			if(i==m) 
			{	
				cout<<a.front()<<' ';
				a.pop();
				continue;
			}
			a.push(a.front());
			a.pop();
		}	
	}
	return 0;
}

例题:机器翻译

题目描述:

小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。
这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。

假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过
M−1,软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。

假设一篇英语文章的长度为 N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。

分析:
分析题意,当内存占满时,存入新单词、清空旧单词,可不就是先进先出的队列嘛,只需要额外设置一个标记数组记录队列中的单词(本题中单词用数字代替),队内的设置为1,队外的时设置为0即可。代码如下:

#include<iostream>
#include<queue>
using namespace std;
const int N=1e3+10;
int check[N],a[N];
int n,m,res,x;
queue<int>q;
int main()
{
	cin>>m>>n;
	for(int i=0;i<n;i++)
	{
		cin>>x;
		if(!check[x])
		{
			if(q.size()>=m)
			{

				check[q.front()]=0;
				q.pop();				
			}
			check[x]=1;
			q.push(x);
			res++;	
		}
	}
	cout<<res;
	return 0;
}

数组模拟队列

用 head 表示队首元素,tail 表示队尾元素,将 tail 初始化为 -1,表示没有元素。
1》 push操作 :将队尾所在位置往后移动一格,放入x。即que[++tail] = x
2》 pop 操作: 将 head 往后移动一格。即head++
3》empty 操作:如果 head大于 tail 则队列非空,否则队列为空。
4》query :返回队首元素。que[head]

#include<iostream>
using namespace std;
const int N=100001;
int que[N];
int head,x,res;
int tail=-1;
void push(int x)
{   
    cin>>x;
    que[++tail]=x;
}
void pop()
{
    head++;
}
void empty()
{
    cout<<(head>tail?"YES":"NO")<<endl;
}
void query()
{
    res=que[head];
    cout<<res<<endl;
}
int main()
{
    string t;
    while(cin>>t)
    {   
        //push x,pop,empty,query 
        if(t=="push")   push(x);
        else if(t=="pop")    pop();
        else if(t=="empty")   empty();
        else if(t=="query")  query();
    }
           
    return 0;
}

链表

在存储一大波数据时,我们常常会用到数组,但是数组在某些情况下往往不够灵活,例如在一串排好顺序的数中再插入一个数,那么我们就需要把这个数之后的所有数依次往后移动一位,这样的操作是很耗费时间的,这个时候就体现出了链表的重要性。

STL中的list链表

链表同样可以用 list 简化操作,需要使用< list>头文件,支持以下常用方法:

1. list< int>a : 定义 int 类型的链表 a
2. list< int>a(arr,arr+3) : 从数组 arr 中的前 3个元素作为链表 a 的初始值。
3. a.size() : 返回链表节点数量。
4. list< int>::iterator it : 定义名为 it 的迭代器。
5. a.begin() ,a.end() : 链表开始和末尾的迭代器。
6. it++ , it-- : 迭代器指向前一个和后一个元素 。
7. a.push_front( x ) , a,push_back( x ) : 在链表开始和末尾的迭代器指针 。
8. a.insert( it, x ) : 在迭代器 it 的前面插入元素 x 。
9. a.pop_front( ) , a.pop_back( ) : 删除链表开头或者末尾 。
10. a.erase( it ) : 删除迭代器所在元素。
11. for( it=a.begin(); it!=a.end() ; it++ ) : 遍历链表。

PS:end()返回的不是指向最后一个元素的迭代器,而是指向最后一个元素后面的位置的迭代器,所以在遍历迭代器的过程中也不会遍历到end()

数组模拟链表

单链表模板

初始化操作:

const int N=1e5+10;
int n[N],ne[N];
int idx,k,x; //  idx 为当前用到的点
void init()
{
    ne[0]=N-1; // head为 0,tail为 N-1
    idx=1;
}

删除操作:

void remove(int k)
{
    ne[k]=ne[ne[k]]; // ne[k] 指向下一个 ne[] 指向的数
}

在第k个插入的数后插入一个数x:

void add(int k,int x)
{
    n[idx]=x;
    ne[idx]=ne[k]; // ne[idx] 指向原来 ne[k] 指向的数
    ne[k]=idx;
    idx++;
}
向头结点添加为:add(0,x)

输出整条链表:

for(int i=ne[0];i!=N-1;i=ne[i]) cout<<n[i]<<' ';

数组模拟双链表

双链表模板

初始化操作:

const int N = 1e5+10;
int e[N], l[N], r[N], idx;
void init()
{
    l[N-1]=0;
    r[0]=N-1;
    idx=1;
}
其中数组 e 存放的是该结点的值,l 和 r 分别表示该节点左右相邻的节点
idx 为现在用到的节点

向第k个插入的数右边插入一个数x:

void add_r(int k,int x)
{
    e[idx]=x;
    l[idx]=k;
    r[idx]=r[k];
    l[r[k]]=idx;    
    r[k]=idx;
    idx++;
}
那么如何向左边插入一个数呢?
向第k个数的左边插入一个数等价于向第k个数左边的数的右边插入一个数
只需将 k 改为 l[k] 即可
同理向头结点和尾结点添加分别为:add_r(0,x)add_r(l[N-1],x)

删除第k个插入的数:

void remove(int k)
{
    l[r[k]]=l[k];
    r[l[k]]=r[k];
}

输出整条链表:

for(int i=r[0];i!=N-1;i=r[i])  cout<<e[i]<<" ";
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值