《洛谷深入浅出进阶篇》简单数据结构

本篇文章内容如下,请耐心观看,将持续更新。

  • 简单数组
  • 简单栈
  • 简单队列
  • 简单链表
  • 简单二叉树
  • 简单集合
  • 图的基本概念
  • 二叉堆
  • 线段树
  • 树状数组与字典树
  • 线段树进阶

简单数组:

  • STL可变数组 vector

" 我们首先要知道这个容器有什么特性,然后它是咋创建的、然后要知道这个东西最常见的功能,访问,查找,删除,修改,添加……是如何实现的。再接着,我们尽可能了解一些这个容器的常见函数的使用,还要知道它的时间复杂度。那么这个容器,你就算大概了解了。"

vector 是一个“ 可变长度 ” 数组。 一般是数组,定义的时候需要同时定义长度。

有些时候,我们不知道应该定义多长,或者定义过长会出现浪费的情况

那么我们就希望 有一个弹性的数组,需要用多少就有多长。

vector 就是一个这样的东西

 “ 建立一个可变长度数组v,内部元素类型是int , 该可变数组最开始有 n 个元素, 每个元素被初始化为 m 。如果省略m,那么这个可变数组有n个元素,每个元素初始化为0.”

vector<int>  v(n,m)

"我们也可以省略上面的 (n,m),此时的 v 长度就是0。 “

vector<int > v

“并且,v容器内的元素还可以是其他的数据类型。” 

vector<string>  v;
vector<char> v;

对于vector 数组的 访问 或 编辑 ,我们可以像普通数组一样使用方括号进行索引(也就是说下标这个概念是存在于vector中的),比如 v [1 0 ] ,就是访问下标为10的元素。

不过需要注意的是: 如果使用下标进行访问,那么需要注意vector 的长度是不是足够,否则就会造成越界。

其实,因为vector 是可变长的,所以如果空间可能会不够的话,我们可以实时地给它增长长度。

就可以用 push_back ( )  , 或者  resize ( ) 函数,进行增长。

然后要想知道这个数组的长度的话,我们就可以用 size()函数。

vector <int > v ;1、v.push_back(a);
2、v.resize(n,m);
3、v.size();

1、v.push_back(a) 指的是 向容器里添入一个元素,添入成功后,自然v的长度也就变长了  。

2、v.resize(n,m)指的是  “重新调整数组长度为n”  ,如果当前vector 的长度小于n ,那么他的长度在原来的基础上就会增加到n,而新增加的元素,就会被初始化为m。

倘若vector 的长度 大于n ,那么就会删除多余的部分。

3、v.size() 指的是 " 返回v数组的长度"。


来一道简单的例题,上手实践一下这个新容器吧?

P3156 【深基15.例1】询问学号 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3156

#include<iostream>
#include<vector>
using namespace std;
int main()
{vector<int> arr;int n,m;cin >> n>>m;for (int i = 0; i < n; i++){int temp;cin >> temp;arr.push_back(temp);}for (int i = 0; i < m; i++){int temp;cin >> temp;cout << arr[temp-1];if(i!=m-1)cout<<endl;}
}

看到这里,我们想想还有什么没讲?

  • 上面说了增删改查的第一种形式,就是使用数组下标,那么还有没有另一种方式呢?
  • 上面介绍了一维vector , 我们都知道数组也有二维,那么vector 有没有二维呢?
  • vector 还有其他有用的函数吗? 这些函数对比常规的静态数组有什么优势呢?

接下来的内容我们就围绕这些问题来讲述:

增删改查的第二种形式: 迭代器。

vector <int > v;
vector <int > :: iterator it;
vector <char > :: iterator ii; 

定义一个迭代器 i t,这个迭代器只能指向 vector<int >。

定义一个迭代器 i i ,这个迭代器只能指向 vector<char >。

由上面的代码,我们足以看出一些东西了。

首先 迭代器一定是有对象的(悲,我没有 ),并且这个迭代器 是为一类容器服务的,不是为了某一个具体的容器。所以我们后续可能会有一些方法,使得这个迭代器变成某个具体容器的专门迭代器。

其次,迭代器的定义也太特么长了吧!! (说不定存在某些东西可以让我们简写)

最后,迭代器肯定和一般的int,char 这种数据类型不一样,它肯定是一种特殊的数据类型,比如指针这种。所以使用它的时候,我们不能以看待常见的数据类型的眼光去看它……。

相信,再给一段代码,这段代码代表,遍历容器内所有的元素,你一定能解决上面的问题吧!喵

vector<int > v;
vector<int > :: iterator it;
for( it = v.begin() ; it != v.end() ; it++ ){cout << *it<<' ' ;
}

” 如果你是第一次看见, 你可能会说, 卧槽,这一坨东西是啥,为什么我每个字母都看得懂,连起来我就不知道是啥了 啊 呜呜呜 “ 。

别急别急,我们细细分析, 在上面我们说到,it 迭代器是为一类容器服务的(比如容器内部是int的元素算一类 )   但是为啥  it 能用来遍历 v容器(一个具体的容器)啊?   噢!

因为我们让 it 等于了  v.begin() 这个东西。 所以it 就暂时为 v容器服务了。

我们还能知道,因为it是迭代器类型, 一个东西能直接给它赋值, 那么这俩玩意,一定是互通的,或者说,这俩是同一种类型 。  

哎哟卧槽,这岂不是说

v.begin( ) 应该就是 专属于 v的东西(因为它开头已经被打上了标记v

并且,v.begin( ) (我们可以看出他应该是个函数) 这个函数的返回值必然是 一个 迭代器类型的东西!!!。

bingo !!恭喜你少年,你猜对了!!。

那么 v.end () 肯定跟 v.begin ( ) 是差不多的东西辣。

结合他俩的英文单词,再加上我说这串代码是遍历容器v,所以所以所以!!!

v.begin ( )  返回的是一个指向 v容器开头的迭代器,

v.end( ) 返回的是指向v容器末尾的迭代器。

我们的临时工 it ,从v容器的开头,遍历到v容器的结尾, 这特么可不就是遍历吗!!!!

但是,你可能还有一个疑问,这和我们常规的数组遍历不一样啊,咋回事啊?

for ( int i=0 ;i< v.size() ;i ++ ) {cout << v[i]<<' ';
}vector<int > :: iterator it;for ( it = v.begin() ;it!= v.end(); it++ ) cout<< *it<<' ';
}

(为啥一个 是 i<v.size()  , 一个是 it != v.end( ) )

(为啥一个是 cout<< i  , 一个是cout<< *it  )

等等,华生,你发现了盲点!!!cout后面的内容,居然是输出 *it , 那么岂不是说明,这个迭代器,在某些层面上和指针是一样的!!!

嗷嗷嗷嗷嗷,你彻底懂了, 所以 it 不能写成  it< v.end() ,为啥,这特么是地址啊,只有等于或不等于的情况,没有大于小于的情况。

那么也就明白了, it ++ 是什么意思, 也就是指向下一个元素 地址。总之,这一方面,就是指针的内容。

好好好 , 你已经彻底懂了迭代器是啥,也知道begin,end 这俩函数是啥,你已经掌握一大半了。

你也许会问,为啥,it != v.end() , end 难道不是最后一个元素吗?  

其实。。  end不是指最后一个元素,它指的是最后一个元素的再后面一个位置,这个位置是未知的,所以千万要小心,不能输出 *end。

然后,就剩最后一个问题了,二维的vector是咋样的呢?

首先,我先不告诉你二维的vector 是啥样的。我们不妨来想象一下吧!

对于一个这样的数组:

int a[10][10] ; 

我们都知道它可以表示成:

{ ………………} ,

{………………},

…………

{………………},

一共有是10 层,每一层都有 10个元素。

也就是说,如果这个数组是二维的,那么它的一维下标代表有多少层,二维下标代表这一层有多少个数字。(等等,我们为啥说是数字呢?因为,这个数组的数据类型是 int )

好,那么我们参照二维普通数组的概念。

那么可变二维vector 我们可以猜测: 它的第一维表示有多少层,这个层数是不定长的,二维表示每一层有多少元素,这个元素也是不定个数的。

不等层数,每一层不定元素的初始化: 

vector< vector<int > >  v;

定层数,但是每一层元素不固定的初始化:(最常用)

vector< vector<int > >  v(5);

定层数,定元素的初始化:

vector< vector<int >  >  v (5,vector<int>(4,0) );

那么我们由彼及此,由于二维vector 的 第二维也是一个vector ,那么 v是一个vector , v[i] 自然也是一个vector咯。

v[ 1 ].push_back(b), 就代表在第一层,添加一个元素;

看到这里你可能会想:那第一种不定层数,不定元素的 二维vector 应该怎么加入层数呢?

我们观察它的初始化,不难发现,最外层的vector 的元素的数据类型 也是 vector,所以要想增加层数,则我们只要push_back( )  vector类型就行了。


现在你已经知道 了二维vector , 那么就来做一道题巩固一下吧!!!

传送门:P3613 【深基15.例2】寄包柜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3613

题解:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
using namespace std;int n, q, i, j, k, ops;
int main() {cin >> n >> q;vector< vector<int> > v(n+10);while (q--) {cin >> ops;if (ops == 1) {cin >> i >> j>>k;if (v[i].size() <= j) {	v[i].resize(j + 1);}v[i][j]=k;}else {cin >> i >> j;cout << v[i][j] << endl;}}
}

现在你已经大致了解了 简单vector 的用法。下面我们来介绍一些好用的vector 函数,这些函数可以给我们解题带来极大的遍历。(待更新中……)


简单栈:

“ 啥是栈啊? ”

你可以把它想象成一个深坑。  嗯。。没了,这就是栈。

你任意在这个坑里面放一些元素(元素不能融合!)

你能想到的任何对这些元素的操作,都是栈操作。(不要杠我

栈的最常见的操作就是,压入,取出。

你可以想象,我们将一些物品用1~n标号,逐个放入这个坑中。我们想将1取出,那么必然要将1上面的物品取出,要想取出1上面的物品,那就要取出1上面的上面的物品…………

直到这个物品上方为空(即没有任何物体)

我们可以发现,这样的坑,有个明显的特点” 后进先出,先进后出 “,”只能从一端进行增删的操作“。

于是,聪明的数学家们,将满足上面性质的东西,称作栈结构。

所以啊,我们便不必局限于是不是一个坑了。任何满足这些性质的东西,我们都可以将它抽象成一个栈结构。

对于一个栈结构,我们一般会对其进行以下的操作:

stack < int > s;s. push(a)  将a压入栈中s.pop  () 弹出栈顶元素s.top    ()查询栈顶元素s.size   ()查询栈内元素个数s.empty ()查询栈是否为空

我们再用一个例子来描述一下栈的简单操作流程:

假设现在有 1,2,3 三个数 , 我们依次将其压入栈,然后压入之后,再一个一个取出来。

stack <int > s;
s.push(1);
s.push(2);
s.push(3);cout<<s.top()<<endl;
s.pop();cout<<s.top()<<endl;
s.pop();cout<<s.top()<<endl;
s.pop()输出结果: 3 2 1 

在我们初步了解了栈的基本操作之后,我们就应该试图手写一个栈。

ps:(栈虽然直接用stl方便,但是如果我们不打开 O2优化的话,就会有一点慢。

在一些非常需要追求运行速度的情况下,往往需要自己手写栈。)

手写栈:

#include<iostream>
using namespace std;const int MAXN = 1e5;
int stack[MAXN];
int p = 0;   //栈顶指针void push(int x) {if (p >= MAXN) {cout << "Stack overflow" << endl;return;}p++;stack[p] = x;return;
}void pop() {if (p == 0) {cout << "Stack is empty";return;}p--;
}int top() {if (p == 0) {cout << "Stack is empty";return 0;}return stack[p];
}int size() {if (p == 0) {cout << "Stack is empty";return 0;}return p;
}bool empty() {if (p == 0)return true;else return false;
}

好啦,现在你已经大概知道了栈是如何工作的。

那么我们就做几道题来巩固一下吧。

例题1、

Parentheses Balance - UVA 673 - Virtual Judgeicon-default.png?t=N7T8https://vjudge.net/problem/UVA-673

输入一个包含“()”和“[]”的括号序列,判断是否合法。 具体规则:

  1. 空串合法;
  2. 如果A和B合法,那么AB合法;
  3. 如果A合法(A)和[A]都合法

输入输出样例:
输入 #1复制

3
([])
(([()])))
([()[]()])()

输出 #1复制

Yes
No
Yes
#include<bits/stdc++.h>
using namespace std;
char reverse(char ch){if(ch==')')return '(';if(ch==']')return'[';else return ' ';
}int main(){stack<char> s;int sum;cin>>sum;cin.ignore();string t;while(sum--){while(!s.empty())s.pop();getline(cin,t);for(int i=0;i<t.size();i++){if(s.empty()){s.push(t[i]);continue;}if(reverse(t[i])==s.top())s.pop();else s.push(t[i]);}if(s.empty())cout<<"Yes"<<endl;else cout<<"No"<<endl;}
}

例题2、后缀表达式 - 洛谷1449​​​​​​icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1449

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
using namespace std;stack<int >  S;
void todo(char ch,int first,int second) {if (ch == '*')S.push(first * second);else if (ch == '+')S.push(first + second);else if (ch == '-')S.push(second - first);else if (ch == '/')S.push(second / first);return;
}
int main() {string s;getline(cin, s);int first, second;int t = 0;for (int i = 0; i < s.size(); i++) {while (isdigit(s[i])) {t = t * 10 + s[i] - '0';i++;}if (s[i] == '.') {S.push(t);t = 0;continue;}else if (s[i] == '@')break;else {first = S.top();S.pop();second = S.top();S.pop();todo(s[i], first, second);}}cout << S.top();
}

简单队列:

这玩意,我们需要解释吗?啥是队列,超市买东西排队这就是队列。一端付完钱直接走人,叫队头。

一端挑完东西,加入队列,叫队尾。

所以,一端进入一端出去,先进先出就是队列。

如果我们看到一道题目满足这样的性质,我们就可以用队列来模拟。

那么,对于这样的队列,我们有啥操作呢?其实和栈是差不多了。

  • queue< int > q ;
  • 入队 (加入队尾) q.push()
  • 出队   (从队首出队)q.pop()
  • 查询队首 q. front()
  • 查询队尾  q.back()
  • 查询元素个数  q. size()
  • 是否为空  q.empty () 

这样光说,肯定是枯燥的,那么我们就列举一个生活中常见的例子吧。

超市排队:(编写一个程序模拟收银过程)

超市排队模拟器:int queue[MAXN]; // 用数组模拟队列,MAXN 表示队伍一次性能加入的人数
int head=0; // 队头指针
int tail=0; // 队尾指针有人挑完东西,加入正在付款的队伍:
void push(int x){if( tail>MAXN )  cout<<" Queue overflow "<<endl;else {queue[tail]=x;tail++;}
}队伍最前面的人已经买完了东西,现在要走人
void pop(){if(head==tail){cout<<"Queue is empty"<<endl;}else{head++;}
}查询队伍最前面的人是谁
int front(){if(head==tail){cout<<"Queue is empty"<<endl;}else{return queue[head];}查询队伍最后面的人是谁
int back(){if(head=tail){cout<<"Queue is empty"<<endl;}else{return queue[tail-1];}查询队伍人数:
int size(){return tail-head;}
查询队伍是否为空:
bool empty(){
if(head==tail)return 1;
else return 0;
}

现在,你应该对队列的操作熟悉了吧?那下面来写一道经典的习题吧!!

P1996 约瑟夫问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1996ps(这道题可以优化成一个队列的写法,就是每次数到的数字整除m,先输出,再删除,,否则,先将数字加入队尾,然后再删除。

退出循环的条件就是这个队列为空

#include<bits/stdc++.h>
using namespace std;queue<int > id;
queue<int > temp;
queue<int > out;
//先将所有的人加入到id队列
// 将id队列的人一个一个出队,出到 temp临时队列
// 每次当数到的数字能够整除m,此时的人出队,进入到out队列。
// 然后循环完了一遍,将temp队列重新赋值到id队列,再将temp队列清空
// 结束条件就是out队列的数量大于等于 n
int main() {int n, m;cin >> n >> m;for (int i = 1; i <= n; i++)id.push(i);int count = 1;while (out.size()<=n) {if (out.size() >= n)break;int len = id.size()+count-1;for (; count <= len; count++) {if (count % m == 0)out.push(id.front());else temp.push(id.front());id.pop();}while (!temp.empty()) {id.push(temp.front());temp.pop();}
}while (!out.empty()) {cout << out.front() << ' ';out.pop();}
}


简单链表 :

啥是链表?链表是一种和上面我们讲过的栈,队列,数组 相似的线性的,储存元素排列顺序的表。

让我们再接触链表之前,先和之前一样从生活实际开始模拟。

假设n名同学排成一排,解散后,现在要求每个人重新排成原来的样子,但是没有人知道原来是怎么排的,好在每个同学都记得自己后面的第一个人是谁。利用这些信息,你能还原出初始的队列吗?

假如有4名同学编号从1~4,他们后面的同学分别是:4,3,0,2 (0代表后面无人)

(已经知道1号同学站在第一位)

不难写出代码:

int Next[MAXN];
for(int i=1;i<=MAXN;i++){cin>>Next[i];
}
for(int i=1;i!=0;i=Next[i]){cout<<i<<' ' ;
}

从这个问题,我们不难知道链表具有怎样的性质:

如果你知道每个元素的前面后面是谁,那么你就可以恢复整个表的顺序。

也就是说,链表,其实就是一种储存了每个元素前驱和后继的表。

如此,我们就得到了链表的重要特性,利用这个特性,我们可以做什么呢?

下面通过另一个问题来回答这个问题。

有n名同学正在排队,但是来了一位恶霸(y号)插队到了x号的后面,其余的同学顺序不变,求,插队之后,队伍是什么样的顺序?

int Next[MAXN];
void insert(int x,int y) {int temp = Next[x]; // 先储存x的后继Next[x] = y; // x的后继为yNext[y] = temp; // y的后继是原来x 的后继
}

通过这样的方式,我们就可以快速地实现插入这一步骤了,即时数据量再大也不怕捏。

那么再来思考一下这个:

假如,x同学的后面一个同学忍不住,先离开了,那么现在的队列应该是什么顺序呢?

void restore(int x) {Next[x] = Next[Next[x]];
}

通过以上的两种方法,我们可以只要耗费O(1)的时间复杂度就完成一次维护。

这是单链表的使用方法,但其实上,我们有很多类型的链表需要学习:(目前只了解单双链表即可)

  • 单链表(每一个结点记录自己的后继,只能单向移动)
  • 双链表(每一个结点记录自己的前驱和后继,可以双向移动)
  • 循环单链表(尾部的后继是头部)
  • 循环双链表
  • 块状链表
  • 跳表

老规矩下面进行一个排队的模拟(假设队伍里最开始只有编号1这一个人):

这是排队的程序应该有的功能

  • ins_back(x,y); 将元素y插入到x的后面
  • ins_bcak(x,y); 将元素y插入到x的前面
  • ask_back(x); 询问x的后继是谁
  • ask_front(x);  询问x的前驱是谁
  • del(x); 从列表中删除元素,不改变其他元素的先后顺序。

这题,我们就可以用双向链表来维护:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
using namespace std;
const int MAXN = 1e6 + 7;
struct node {int pre,nex,val; // 前驱,后继,值
}a[MAXN] = {0};
int n, pls;//相对位置
int flag[MAXN]; //标记数组,标记是否存在
int index1[MAXN];//用来记录每个结点编号void ins_pre(int x, int y) {//首先找到x的结点编号int now = pls++; //新增一个编号给y用a[pls].nex = now; // y的后缀等于 x的结点编号nowa[pls].val = y; // y的值等于y。a[pls].pre = a[now].pre; // y的前驱是x原来的前驱a[a[now].pre].nex = pls; // x原来的前驱的后继变成y的编号 plsa[now].pre = pls; // x的前驱是pls。index1[y] = pls;
}//将y元素插在x 的后面
void ins_back(int x, int y) {int now = index1[x];pls++;a[pls].pre = now; // 先把y的前驱改成xa[pls].nex = a[now].nex;//再把y的后继改成x的后继a[pls].val = y;//再把y的值附上a[a[now].nex].pre = pls;//然后改变x的后继的前驱a[now].nex = pls;//最后改变x的后继index1[y] = pls;
}//询问x的后继的值
int ask_back(int x) {return a[a[index1[x]].nex].val;// 先查询x的结点编号,然后返回x的后继的val,这里跳步了直接return
}
//查询x 的前驱的值
int ask_pre(int x) {return a[a[index1[x]].pre].val;
}//从链表中删除元素
void del(int x) {int now = index1[x]; // 找到x的结点编号a[a[now].pre].nex = a[now].nex;// 将x前驱的后继改成x的后继a[a[now].nex].pre = a[now].pre;// 将x后继的前驱改成x的前驱
}int main() {a[0].val = 0;a[0].pre = 0;a[0].nex = 0;index1[0] = 0;ins_back(0,1);
}

(如果数据过大,index1可能存不下那么多,那么可以用hash或map来优化)

相信看完前面,你对链表也已经有了一些理解,下面来几道简单的例题练练手吧!

例题1:

P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1160

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 7;
struct node {int pre,nex,val; // 前驱,后继,值
}a[MAXN] = {0};
int n, pls;//相对位置
int flag[MAXN]; //标记数组,标记是否存在
int index1[MAXN];//用来记录每个结点编号void ins_left(int x, int y) {//把y插入到x的左边int now =index1[x];pls++;a[pls].val = y;a[pls].nex = now;a[pls].pre = a[now].pre;a[a[now].pre].nex=pls;a[now].pre = pls;index1[y] = pls;}
void ins_right(int x, int y) {int now = index1[x];pls++;a[pls].val = y;a[pls].pre = now;a[pls].nex = a[now].nex;a[a[now].nex].pre = pls;a[now].nex = pls;index1[y] = pls;}
void del(int x) {int now = index1[x];int Nex = a[now].nex;int Pre = a[now].pre;a[Nex].pre = Pre;a[Pre].nex = Nex;index1[x] = 0;
}
int main() {cin >> n;a[0].val = 0;a[0].pre = 0;a[0].nex = 0;ins_right(0, 1);for (int i = 2; i <= n; i++) {int k, p;cin >> k >> p;if (p == 0) {ins_left(k, i);}else ins_right(k, i);}int m;cin >> m;while (m--) {int temp;cin >> temp;if (index1[temp])del(temp);}int now = a[0].nex;while (now) {cout << a[now].val<<' ';now = a[now].nex;}
}

ps(虽然我没写注释,但是这道题和我们上面的模拟排队是几乎一样的,所以看懂一个就行 了。)

例题2:

P1996 约瑟夫问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1996

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
using namespace std;
const int MAXN = 1e6 + 7;
struct node {int pre,nex,val; // 前驱,后继,值node(int _pre=0, int _nex=0 , int _val=0 ) {pre = _pre, nex = _nex, val = _val;}
}a[MAXN] ;
int n, pls;//相对位置
int flag[MAXN]; //标记数组,标记是否存在
int index1[MAXN];//用来记录每个结点编号void ins_pre(int x, int y) {//首先找到x的结点编号int now = index1[x];pls++; //新增一个编号给y用a[pls] = node(a[now].pre, now, y);a[a[now].pre].nex = pls; // x原来的前驱的后继变成y的编号 plsa[now].pre = pls; // x的前驱是pls。index1[y] = pls;
}//将y元素插在x 的后面
void ins_back(int x, int y) {int now = index1[x];pls++;a[pls] = node(a[now].nex, now, y);a[a[now].nex].pre = pls;//然后改变x的后继的前驱a[now].nex = pls;//最后改变x的后继index1[y] = pls;
}//询问x的后继的值
int ask_back(int x) {return a[a[index1[x]].nex].val;// 先查询x的结点编号,然后返回x的后继的val,这里跳步了直接return
}
//查询x 的前驱的值
int ask_pre(int x) {return a[a[index1[x]].pre].val;
}//从链表中删除元素
void del(int x) {int now = index1[x]; // 找到x的结点编号int Nex = a[now].nex;int Pre = a[now].pre;a[Nex].pre = Pre;a[Pre].nex = Nex;index1[x] = 0;
}int main() {int n;cin >> n;for (int i = 1; i <= n; i++) {index1[i] = i;a[i] = node(i - 1, i + 1, i);}
//搞一个循环双链表。然后循环的次数大一点,一定是可以删除干净的。
//其实循环单链表好像也可以。a[1] = node(n, 2, 1);a[n] = node(n - 1, 1, n);int m;cin >> m;int now = index1[1];while (n--) {for (int i = 1; i <= m; i++) {if (i == m) {cout << now << ' ';del(now);}now = a[now].nex;}}}

好啦,上面是手写链表的过程,那么接下来我就介绍一下链表(stl):list

下面给出链表的常用函数:

定义一个int类型的链表

list<int > a; 

我们可以用这样的方式来给链表初始化。

int arr[5] = {1,2,3} ; list<int > a(arr.arr+3 ) ;

返回链表的节点数量:

a.size();

定义一个迭代器:

list<int >:: iterator it ;

链表的开头,和末尾( 返回的是迭代器)

a.begin() ,  a.end() ;

在链表的开头或者末尾插入元素x

a.push_front (x) 

a.push_back(x) ;

在链表某一位置的前面插入元素x:

a.insert( it,x ) ;  it 表示这个位置的迭代器

在链表开头或结尾删除元素

a.pop_front () ,  a.pop_bcak();

删除链表某一位置的元素

a.erase(it)  it表示这一位置的迭代器

遍历整个链表

for( it=a.begin() ; it!=a.end() ; it++) 

有了上面的那些函数,我们就可以实现如下功能

  • ins_back(x,y); 将元素y插入到x的后面
  • ins_bcak(x,y); 将元素y插入到x的前面
  • ask_back(x); 询问x的后继是谁
  • ask_front(x);  询问x的前驱是谁
  • del(x); 从列表中删除元素,不改变其他元素的先后顺序。
const int MAXN = 1e6 + 7;
list<int > a;
list<int >::iterator index1[MAXN]; // 迭代器数组,用来代替findvoid ins_front(int x, int y) {  //y插入x的前面auto it = index1[x];a.insert(it, y);index1[y] = --it; // y的迭代器就是it的前一个位置
}
void ins_back(int x, int y) { //yc插入 x的后面auto it = index1[x];it++;a.insert(it, y);index1[y] = --it;
}
void del(int x) {  //删除xif (index1[x] == a.end())return;auto it = index1[x];a.erase(it);index1[x] = a.end();
}
int ask_front(int x){auto it=index1[x];return *(--it);
}
int ask_back(int x){auto it=index1[x];return *(++it);
}
void print() {for (auto it = a.begin(); it != a.end(); it++) {cout << *it<<' ';}
}


P1996 约瑟夫问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1996

#include<bit/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 7;
list<int > a;
int main() {int n, m;cin >> n >> m;for (int i = 1; i <= n; i++) {a.push_back(i);}list<int >::iterator it, now;it = a.begin();int count = 0;while (!a.empty()) {count++;now=it;if (++it == a.end())it = a.begin();if (count % m == 0) {cout << *now << ' ';a.erase(now);}}
}

 P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1160

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;
const int MAXN = 1e6 + 7;
list<int > a;
list<int >::iterator index1[MAXN];void ins_front(int x, int y) {auto it = index1[x];a.insert(it, y);index1[y] = --it; // y的迭代器就是it的前一个位置
}
void ins_back(int x, int y) {auto it = index1[x];it++;a.insert(it, y);index1[y] = --it;
}
void del(int x) {if (index1[x] == a.end())return;auto it = index1[x];a.erase(it);index1[x] = a.end();
}
void print() {for (auto it = a.begin(); it != a.end(); it++) {cout << *it<<' ';}
}
int main() {int n;cin >> n;a.push_back(1);index1[1] = a.begin();for (int i = 2; i <= n; i++) {int k, p;cin >> k >> p;if (p == 0)ins_front(k, i);else ins_back(k,i);}int m;cin >> m;while (m--) {int temp;cin >> temp;del(temp);}print();
}

简单二叉树:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228891.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java多线程编程学习

1 线程的概念 多线程是指同一个程序同时存在多个“执行体”&#xff0c;它们可以同时工作 1.1 进程的概念 一次程序的每一次运行都叫做进程&#xff08;一个进程可以包含多个线程 1.2 线程的概念 多线程是指一个程序中多段代码同时并发进行 1.3 主线程的概念 JavaMain中的线程就…

Python语言学习笔记之十(字符串处理)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 字符串处理&#xff1a;以实现字符串的分割、替换、格式化、大小写转换&#xff0c;Python字符串处理是指对Py…

人工智能知识蒸馏:模型精简与迁移的奇妙之旅

导言 在人工智能领域&#xff0c;知识蒸馏作为一种模型精简和知识迁移的技术手段&#xff0c;逐渐成为提高模型效能和适应更广泛场景的关键工具。知识蒸馏是近年来在深度学习领域备受瞩目的技术&#xff0c;其通过将大模型的知识传递给小模型&#xff0c;旨在实现模型的轻量化与…

docker-compose Install gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

交友网站的设计与实现(源码+数据库+论文+开题报告+说明文档)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

TSINGSEE青犀基于opencv的安全帽/反光衣/工作服AI检测算法自动识别及应用

安全帽/反光衣/工作服自动识别检测算法可以通过opencvyolo网络对现场画面中人员穿戴着装进行实时分析检测&#xff0c;判断人员是否穿着反光衣/安全帽。在应用场景中&#xff0c;安全帽/反光衣/工作服检测应用十分重要&#xff0c;通过对人员的规范着装进行实时监测与预警&…

CloudFlare 优选ip 和 优选域名的获取方法

1.CloudFlare优选IP网站:【链接直达】 2.CloudFlare 优选IP工具&#xff1a;【开源软件】 3.CloudFlare 优选域名&#xff1a;【GitHub开源工具】 4.CF优选域名推荐&#xff1a; time.cloudflare.com shopify.com time.is icook.hk icook.tw ip.sb japan.com malaysia.com rus…

YOLOV8:最新探测物体状态综合指南

YLOLV8是最新的以YOO为基础的物体检测模型,提供最先进的性能。 利用前几部《yolo》,yolov8更快速更准确&#xff0c;同时为训练模式提供统一的框架 物体检测实例分割影像分类 到编写本文件时,许多功能还没有被添加到超催化剂YLOLV8存储库中。这包括训练有素的模型的一整套导…

Axure元件的介绍使用以及登录界面和个人简历的绘制

目录 一、Axure元件介绍 1.1 简介 1.2 特点 1.3 元件操作 二、基本元件的使用 2.1 矩形和圆形 2.2 图片 2.2.1 图片元件特点 2.2.2 具体操作 2.3 占位符 2.3.1 使用规范方法举例 2.4 文本元件 2.4.1 图示 2.5 热区 2.5.1 图示 2.5.2 热区辅助页面排版 2.6 线段…

Redis7--基础篇8(集群cluster)

1. 集群&#xff08;cluster&#xff09;介绍 由于数据量过大&#xff0c;单个Master复制集难以承担&#xff0c;因此需要对多个复制集进行集群&#xff0c;形成水平扩展每个复制集只负责存储整个数据集 的一部分&#xff0c;这就是Redis的集群&#xff0c;其作用是提供在多个…

2023人物专访【中国新时代艺坛楷模】徐大龙盆景艺术专题报道

随着社会经济的发展和人们审美观念的改变&#xff0c;苏派盆景艺术以其精湛的技艺和独特的艺术魅力&#xff0c;又成为了中外盆景界的一大亮点。今天来聊聊江南盆景艺术专家徐大龙先生。 徐大龙&#xff0c;江苏常州人&#xff0c;「复兴之路&#xff0c;强国有我」50位爱国奉献…

web(HTML之表单练习)

使用HTML实现该界面&#xff1a; 要求如下&#xff1a; 用户名为文本框&#xff0c;名称为 UserName&#xff0c;长度为 15&#xff0c;最大字符数为 20。 密码为密码框&#xff0c;名称为 UserPass&#xff0c;长度为 15&#xff0c;最大字符数为 20。 性别为两个单选按钮&a…

Web安全漏洞分析—文件包含

在当今数字化时代&#xff0c;随着Web应用程序的广泛应用&#xff0c;网络安全问题愈加凸显。其中&#xff0c;文件包含漏洞作为一种常见但危险的安全隐患&#xff0c;为恶意攻击者提供了可乘之机。在这篇博客中&#xff0c;我们将深入探讨文件包含漏洞的本质、攻击手法以及应对…

PMP项目管理 - 沟通管理

系列文章目录 PMP项目管理 - 质量管理 PMP项目管理 - 采购管理 PMP项目管理 - 资源管理 PMP项目管理 - 风险管理 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in…

LVS-DR部署

目录 LVS的工作模式及其工作过程 NAT模式&#xff08;VS-NAT&#xff09; 直接路由模式&#xff08;VS-DR&#xff09; IP隧道模式&#xff08;VS-TUN&#xff09; DR模式 LVS负载均衡群集的分析及特点 数据包流向分析 DR 模式的特点 LVS-DR部署实例 LVS-DR模式部署流…

2023最新最全【PostMan】安装教程

为了验证接口能否被正常访问&#xff0c;我们常常需要使用测试工具&#xff0c;来对数据接口进行检测。 好处&#xff1a;接口测试工具能让我们在不写任何代码的情况下&#xff0c;对接口进行调用和调试。 下载并安装PostMan 首先&#xff0c;下载并安装PostMan&#xff0c;请…

吊炸天,腾讯开源的一站式微服务解决方案

项目介绍 Spring Cloud Tencent 是腾讯开源的一站式微服务解决方案。 Spring Cloud Tencent 实现了Spring Cloud 标准微服务 SPI&#xff0c;开发者可以基于 Spring Cloud Tencent 快速开发 Spring Cloud 云原生分布式应用。 Spring Cloud Tencent 的核心依托腾讯开源的一站式…

Git-瑞吉外卖

什么是GIt? 分布式版本控制工具&#xff0c;用来管理源代码文件。分布式主要体现在两种仓库&#xff08;本地仓库、远程仓库&#xff09;。 git的作用&#xff1f; 代码回溯、版本切换&#xff08;切换不同框架&#xff09;、多人协作、远程备份 基本命令&…

SpringBoot零基础入门到项目实战——学习路线规划与目录结构

文章目录 第一部分&#xff1a;Spring Boot基础第二部分&#xff1a;Web开发与RESTful API第三部分&#xff1a;数据访问与持久化第四部分&#xff1a;安全与身份验证第五部分&#xff1a;高级主题第六部分&#xff1a;测试总结与扩展实战项目练习 &#x1f389;欢迎来到Spring…

Base64编码解码(文本与文件)

目录 1.什么是base64 2.在java中使用base64 3.在js中使用base64 1.什么是base64 base64编码就是将字符串以每3个比特&#xff08;bit&#xff09;的字节子序列拆分为4个6比特&#xff08;bit&#xff09;的字节子序列&#xff08;这个6比特是有效字节&#xff0c;最左边两个…