一.STL-vector
1.含义:
本质上是一个变长数组,可以根据其中装的元素的数量,动态分配内存空间
2.头文件:
#include<vector>
3.定义一个type类型的变长数组
vector<type>数组名;
4.常用操作
①往末尾追加元素e
a.pish_back(e);//无返回值,第一个元素在a[0]
②引用随机位置idx元素
a[idx];
③获取元素数量
a.size();
④定义a并分配空间
vector<int> a(n);//定义a,同时分配n个大小,即a[0]~a[n-1]
a[n-1]=n-1;
5.定义结构体
struct node{int x,y;};
vector<node> a;
------>定义了一个结构体数组a
a.push_back((node){1,2});//添加了一个元素
a[0].x;//结果为1
a[0].y;//结果为2
6.定义二维数组
vector<vector<int>> a;
二.链表的基本概念
链表是一种线性表。
线性表中,除了第一个元素没有前驱,最后一个元素没有后继以外,其他每一个元素都有唯一的前驱和后继。
1.链表和数组的区别
链表和数组都属于线性表,区别是:
数组在计算机中是连续 分配内存的;
链表在计算机中是不连续分配内存的。
2.链表的实现方式
单向链表中的每一个元素包含3部分信息:
(1)地址:在内存中的位置
(2)数据域:存放数据
(3)地址域:存放后继的地址
(地址相当于变量在计算机内存里的"门牌号码")
3.头指针
链表中第一个元素没有前驱,我们使用一个头指针指向它的位置,头指针是一个专门存储地址的变量。
4.空地址
链表中最后一个元素没有后继,我们在它的地址域放一个空地址,NULL。
三、链表的特点
链表由于在内存中不是连续存储的,所以:
- 不支持随机访问第i个元素。每次查询某个元素需要遍历整个链表,时间复杂度为O(n)。
- 插入和删除元素不需要移动后面的元素,只需要修改一些元素的地址域,时间复杂度为0(1)。但是查找到插入和删除的位置需要花时间。
1.插入操作

链表中,已经元素p,在后面插入一个元素t,要先确定p后面的元素地址,可以表示为p->next,接下来进行两步操作
- 把t指向p->next,
- 把p指向t。
代码表示为:
t->next=p->next;
p->next=t;
【注意】:两步的顺序不能颠倒!
2.删除操作
链表中,假设我们要在a和b之间的结点删除,需要将a的地址域赋值为p的地址域。
通常还跟着一步删除结点p,释放结点p的内存空间。
3.双向链表
有的链表中,每个元素不仅有存储后继的地址域,还有存储其前驱的地址域。这样的链表叫双向链表。
双向链表中的节点p,它有4条线,llink表示前驱,rlink表示后继,那么可以用代码表示为:
(1)p->llink
(2)p->rlink
(3)p->llink->rlink
(4)p->rlink->llink
4.循环链表
有的链表中,最后一个元素的后继不是NULL,而是链表的第一个元素。这样的链表叫循环链表。
四、代码实现
1.头文件与定义链表
- 头文件:
#include<list>
- 定义:
list<数据类型> 链表名;
- 例子:
list<int> L1;//定义一个int类型的链表L1
list<double> L2;//定义一个double类型的链表L2
2.求链表长度与遍历链表
定义一个链表l:list<int> l;
1.求链表长度:l.size();
2.遍历链表:通过地址遍历
- 方法1:for循环
cpp
list<int> ::iterator it;//定义一个迭代器it,it表示地址
for(it=l.begin();it!=l.end();it++){
cout<<*it<<" ";//输出这个地址中的数据
}
或者
for(auto it=l.begin();it!=l.end();it++){
cout<<*it<<" ";
}
- 方法2:while循环
cpp
list<int> l;
list<int> ::iterator it=l.begin();
//当迭代器没有达到链表尾部,迭代器往后移动
while(it!=l.end()){
out<<*it<<" ";//输出这个地址中的数据
it++;
}
3.删除值为e的元素
cpp
①找到值为e的元素
list<int>l;
list<int>::iterator it=l.begin();
while(it!=l.end()&&*it!=e){
it++;
}
②删除元素
l.erase(it);//函数会返回指向被删除元素后一个元素的迭代器
4、插入元素
cpp
①向尾部插入新元素e:l.push_back(e);
②向元素x前插入一个e
先找到x的位置
list<int>l;
list<int>::iterator it=l.begin();
while(it!=l.end()&&*it!=x){
it++;
}
在插入
l.insert(it,e);//函数返回一个指向新元素e的迭代器
5、简记
线性表 前驱和后继唯一
第一个元素无前驱 最后一个元素无后继 数组地址连续
cpp
#include <bits/stdc++.h>
using namespace std;
int a[10];
int main() {
for(int i=0; i<=9; i++) {
cout << &a[i] << ' ';//取a[i]存储的地址
}
return 0;
}
重要性质
链表不连续分配内存 1. 需要掌握第一个元素的地址(头指针) 2. 每个元素除了本身的元素值(数据域),还需要下一个元素的地址(地址域) 3. 最后一个元素的下一个地址为空(NULL)
特点
-
访问元素要遍历整个链表,时间复杂度O(n)
-
插入和删除元素都是O(1) 其他链表 双向链表:地址域有两个,存储前驱和后继的地址域 循环链表:最后一个元素的地址域不是NULL,而是第一个元素的地址
STL-list
cpp
#include <list>
list<int> a;//定义链表
#include <bits/stdc++.h>
using namespace std;
list<int> a;
int n, t;
int main() {
cin >> n;
while(n--) {
cin >> t;
a.push_back(t);
}
cout << a.size() << endl;
for(auto it=a.begin(); it!=a.end(); it++) {
cout << *it << ' ';
}
return 0;
}
插入函数 a.insert(it, e);//在it前插入e 该函数会返回指向新元素e的迭代器 it = a.insert(it, e);
删除函数 a.erase(it);//删除it指向的元素 该函数会返回指向删除元素后一个元素的迭代器 it = a.erase(it);
五、课堂题代码
5035 多次删除链表里的元素
cpp
#include<iostream>
#include<list>
using namespace std;
int n,t;
list<int> l;
list<int>::iterator it;
int main(){
cin>>n;
while(n--){//输入n个数存入链表
cin>>t;
l.push_back(t);
}
//输入若干数,删除这些数
while(cin>>t){
//删除链表中值为t的元素
//借助迭代器it遍历链表,得到数据域为t时,进行删除
for(it=l.begin();it!=l.end();it++){
if(*it == t){
l.erase(it);
break;
}
}
}
//输出剩下的元素
for(it=l.begin();it!=l.end();it++){
cout<<*it<<' ';
}
return 0;
}
5038 删除指定的倒数第k个元素
cpp
#include<iostream>
#include<list>
using namespace std;
/*
倒数第k个数
正数:第n-k+1个数
遍历链表的时候计数,计数达到k,删除
指定元素不存在则不删除:k<1或者k>n
*/
int n,k,t;
int cnt;
list<int> l;
list<int>::iterator it;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>t;
cnt++;
if(cnt==n-k+1) continue;
l.push_back(t);
}
//遍历链表
for(it=l.begin();it!=l.end();it++){
cout<<*it<<' ';
}
return 0;
}
六、数组模拟链表
cpp
数组模拟链表 以空间换时间
方法一:结构体
struct NODE {
int val;//数值域
int r;//地址域
} a[105];
int main() {
int h = 1;//头指针
a[1] = {10, 3};
a[3] = {15, 6};
a[6] = {12, -1};
for(int it=h; it!=-1; it=a[it].r) {
cout << a[it].val << ' ';
}
return 0;
}
方法二:两个数组(推荐)
int val[105];//数值域
int r[105];//地址域
//int l[105];如果是双向链表还需前驱的地址域 也就是left
int main() {
int h = 1;//头指针
val[1] = 10;
val[3] = 15;
val[6] = 12;
r[1] = 3;
r[3] = 6;
r[6] = -1;
for(int it=h; it!=-1; it=r[it]) {
cout << val[it] << ' ';
}
return 0;
}
插入和删除元素
int h = 1;//头指针
val[1] = 10;
val[3] = 15;
val[6] = 12;
r[1] = 3;
r[3] = 6;
r[6] = -1;
//在15和12之间插入13
val[4] = 13;
r[4] = r[3];
r[3] = 4;
//删除13
r[3] = r[r[3]];