我们有的时候需要利用数据结构来存储有线性关系的数值,这就有了线性表。
本篇分上,中,下。
上篇主要介绍一维数组。
顺序表
用一组连续的地址来存储的线性表,叫做顺序表。
顺便一提下一篇我要写的线性表(中)——栈与队列中的栈与队列也是顺序表。
一维数组
在我看来,一维数组应该是最简单的线性结构了。而且有了一维数组,许多线性表使用C++自带的STL模板要么就是时间复杂度太高,要么就是使用太麻烦,用数组模拟就简单的多。
当然,大家也可以看《信息学奥赛一本通(C++版)》的第76~86页来自行学习。
一、一维数组的定义
类型标识符 数组名[常量表达式]
例如:
int a[101]; //此处定义合法
int res[x]; //此处定义非法,因为x不是常量
const int maxn=100001;
char s[maxn];//此处定义也合法,因为maxn为常量
long long d=1001;
bool sam[d];//此处依然合法,原因同上
struct data{
int vec,val;
}k[100000001];//此处定义非法,因为空间复杂度为O(10^9),会MLE
二、一维数组的引用
数组名[下标]
与上面差不多,但有一个要求:
0
≤
i
<
n
0≤i<n
0≤i<n其中i是下标,n是数组长度。
三、一维数组的初始化
数组名[常量表达式]={值1,值2,值3…}
例如:
int a[5]={1,2,3,4,5}
PS:下标从0开始。
另外还要介绍一个函数memset。
memset(a,b,sizeof(a));
作用是将a数组填充为b。
好的,那来道例题吧。
洛谷P1428 小鱼比可爱
其实我们只需要通过遍历此鱼前有几个不如自己可爱的就行了,模拟+暴力就能过
上AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[101];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);//输入第i个数
int ans=0;//初始化ans
for(int j=1;j<i;j++)if(a[j]<a[i])ans++;//统计前面比鱼i不可爱的鱼数
printf("%d ",ans);
}
}
字符串
说白了,就是字符数组,当然STL模板里也有自带的字符串类型string,使用习惯因人而异,反正我是喜欢用string的。
当然,大家也可以看《信息学奥赛一本通(C++版)》的第94~108页来自行学习(习题超多,超全,可以做做)。
字符数组的使用
定义,引用,初始化如一维数组。
但是字符数组有一些小便利。
一、输入
流输入(最慢,但是最万能,当然这个也是有优化的指令的):
cin>>s;
格式化输入:
scanf("%s",s);
PS:这里之所以没有‘&’,是因为数组名可当地址用这里写的其实相当于是以下程序的省略:
scanf("%s",s+0);
或
scanf("%s",&s[0]);
至于有关这一方面的内容,以后也抽个时间写写。
gets语句(专用于字符数组):
gets(s);
PS:以上3种方法的下标均从0开始,其实了解指针以后,你们会知道一种利用scanf从任意下标开始读入的方法:
scanf("%s",s+i);
或
scanf("%s",&s[i]);
i为开始输入的下标。
二、输出
流输出:
cout<<s;
格式化输出:
printf("%s",s);
puts语句(字符数组专用):
puts(s);
三、字符数组模拟字符串处理函数:
函数格式 | 函数功能 |
---|---|
strcat(s1,s2) | 将s2接到s1后面,返回s1 |
strncat(s1,s2,n) | 将s2前n个字符接到s1后面,返回s1 |
strcpy(s1,s2) | 将s2复制到s1,返回s1 |
strncpy(s1,s2,n) | 将s2前n个字符复制到s1,返回s1 |
strcmp(s1,s2) | 将s2接到s1后面,返回s1 |
strncmp(s1,s2,n) | 比较s1,s2:大于返回正整数,等于返回0,小于返回负整数 |
strlen(s) | 计算s的长度,结尾’\0’不算 |
strlwr(s) | 将s里的大写字母换成小写字母 |
strupr(s) | 将s里的小写字母换成大写字母 |
这些函数包括填充数组的memset都在cstring库里话说万能库stdc++.h用着不香吗
STL模板字符串的使用
支持像一维数组那样下标引用。
一、定义
string s
二、输入
流输入:
cin>>s;
getline输入(string专用):
getline(cin,s);//读到换行后停止输入
getline(cin,s,c);//读到字符c后停止输入
PS:输出只能流输出。
三、string处理函数
函数格式 | 函数功能 |
---|---|
s.length()或s.size() | s的长度 |
s . at(i) | 将s2前n个字符接到s1后面,返回s1 |
s.push_back( c ) | 在s的末尾添加字符或字符串变量c,等价于s+=c |
s.append(n,c) | 在s的末尾添加n个字符c |
s1.insert(n,s2) | 将s2插入到s1的第n个字符后面 |
s1.replace(i,j,s2) | 将s1从i开始的j个字符替换成s2 |
s.erase(n) | 将s从n开始的字符全部删除 |
s.erase(i,j) | 将s从i开始的j个字符删除 |
s.substr() | 返回s |
s.substr(n) | 返回s从n开始的所有内容 |
s.substr(i,j) | s从i开始的连续j个字符 |
s1.find(s2) | 在s1里面查找s2,返回第一次找到的位置 |
s1.find(s2,n) | 在s1里面从位置n开始查找s2,返回第一次找到的位置 |
s1.rfind(s2) | 在s1里面右边查找s2,返回第一次找到的位置 |
s1.rfind(s2,n) | 在s1里面从右边开始数的位置n开始查找s2,返回第一次找到的位置 |
s.c_str() | 返回内容为s的字符数组 |
s.copy() | 返回内容为s的字符数组到已有的字符数组里 |
PS:以上内容都在string库里。大小比较可直接使用数值比较的符号,赋值可直接使用“=”,在字符串前添加用“+”:
s=c+s;//c为字符或字符串变量
四、字符串流
字符串流可实现字符串流与各个类型的转换。
定义:
stringstream s;
利用流转换:
stringstream ss;
int i;
string s;
ss<<i;
ss>>s;
当然,你假如用同一个流来进行操作,时不时的要用ss.clear()
来清除状态信息。
反复读写数据,会造成大量消耗了,于是我们需要用ss.str()
来清理一下缓存。
现在我们再来看看字符串的例题。
P5733 【深基6.例1】自动修正
这道题自然就有了手写和懒人两种方式。
懒人方法倒是用到了字符数组。怎么做呢?利用strupr
.
本机测完全没问题,但是上洛谷就CE了。
献上CE代码:
#include<bits/stdc++.h>
using namespace std;
char s[101];
int main(){
gets(s);
strupr(s);
puts(s);
return 0;
}
手写的AC代码根本连字符数组都不需要,直接边读边写。
当然,你要是为了扯上字符串读完再写也成
其实手写很暴力的:就是判断是不是小写,是小写就转大写输出,不是就直接输出。
上AC代码:
#include<bits/stdc++.h>
using namespace std;
char c;
int main(){
if(scanf("%c",&c)!=EOF){//假如还有输入就继续输,Ctrl+Z结束输入
if(c>='a'&&c<='z')c+='A'-'a';//判断并转换
printf("%c",c);//输出
main();//调用主程序,不想这么写的上面打while循环
}
return 0;
}
vector
这是一个不用设定长度的数组,当你加入时,会对数组长度自行扩大。访问如普通数组,下标从0开始。
一、定义
vector<int> a;
其中的<>中可填任何类型,包括自定义结构体。
二、vector处理函数
函数格式 | 函数功能 |
---|---|
a.clear() | 清空a但不释放内存 |
a.push_back(x) | 在a中加入x |
a.size() | a的长度 |
for(auto x:a) | 利用x遍历a(C++11新功能) |
sort(a.begin(),a.end(),cmp) | 根据cmp将a排序 |
a.insert(a.begin()+i,x) | 在下标为i的位置插入x |
a.erase(a.begin()+i,a.begin()+j) | 将a从i开始到j结束全部删除 |
PS:以上函数均在vector库,并且insert和erase时间复杂度均为O(n),不推荐使用。
好了,那我们再来看一道例题。
P3156 询问学号
其实vector的主要应用还是在图论上,上面的这一道题,普通数组也能过。
其实就是依次读入,然后访问下标,但要注意,vector的下标从0开始,使用vector的话访问时要减1.
普通数组AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,x,a[20000001];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);//读入
for(int i=1;i<=m;i++){
scanf("%d",&x);
printf("%d\n",a[x]);//询问
}
}
vectorAC代码:
#include<bits/stdc++.h>
using namespace std;
vector<int> a;
int n,m,x;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&x);//读入
a.push_back(x);//加入a
}
for(int i=1;i<=m;i++){
scanf("%d",&x);
printf("%d\n",a[x-1]);//询问
}
}
好的,那我们的线性表(上)——普通数组就完结了,敬请期待线性表(中)——栈与队列!