一、数组模拟链表
1、单链表
每个节点包含两个元素:值、下个指针的地址,因此可以用两个数组 e[N]
(值)和 ne[N]
(下个节点的地址)进行模拟。 注意:这里的地址指的是数组的下标。 代码(内含链表的基础操作)
# include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10 ;
int head, e[ N] , ne[ N] , idx;
void Link_init ( ) {
head = - 1 ;
idx = 0 ;
}
void add_to_head ( int x) {
e[ idx] = x; ne[ idx] = head; head = idx++ ;
}
void remove ( int k) {
ne[ k] = ne[ ne[ k] ] ;
}
void add ( int k, int x) {
e[ idx] = x; ne[ idx] = ne[ k] ; ne[ k] = idx++ ;
}
void print_link ( ) {
cout << "value: " ;
for ( int i = head; i != - 1 ; i = ne[ i] ) cout << e[ i] << "->" ;
cout << endl;
}
2、双链表
每个节点包含三个元素:值、上个指针的地址、下个指针的地址,因此可以用三个数组 e[N]
(值)、 l[N]
(上个节点的地址)和 r[N]
(下个节点的地址)进行模拟。 注意:这里的地址指的是数组的下标。 代码(内含链表的基础操作)
# include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10 ;
int l[ N] , r[ N] , e[ N] , idx;
void Link_init ( ) {
r[ 0 ] = 1 ;
l[ 1 ] = 0 ;
idx = 2 ;
}
void insert ( int k, int x) {
e[ idx] = x;
l[ idx] = k;
r[ idx] = r[ k] ;
l[ r[ k] ] = idx;
r[ k] = idx;
idx++ ;
}
void remove ( int k) {
r[ l[ k] ] = r[ k] ;
l[ r[ k] ] = l[ k] ;
}
void print_link ( ) {
cout << "value: " ;
for ( int i = r[ 0 ] ; i != 1 ; i = r[ i] ) cout << e[ i] << "->" ;
cout << endl;
}
二、数组模拟栈
栈的性质是:先入后出,后入先出。因此可以理解成无头的队列,弹出元素和添加元素都只能从尾部进行。因此可以用一个数组 stk[N]
(值)和一个整数 tt
(尾部位置)来模拟。 注意:初始默认 tt = 0
。这样方便判断栈是否为空,这样的话数组第零位元素不存值。这个不固定,依习惯而定。 代码(内含链表的基础操作)
# include <iostream>
using namespace std;
const int N = 1e5 + 10 ;
int stk[ N] , tt;
stk[ ++ tt] = x;
tt-- ;
if ( tt > 0 ) { not empty}
else { empty}
stk[ tt] ;
扩展:单调栈
例题:AcWing 830. 单调栈 思路:题目要求寻找每一个数左边第一个比它小的数,我们可以发现一个性质是『第 i
个数左边大于等于它的数不可能满足题意,因此可以删除,这样的话无论第 i + 1
个数是多少,第 i
个数都是最接近它的且是它左边最小的数』,这样就得到了一个单调递增的单调序列,且只有尾部,因此可以用数组模拟栈的方式实现,即为单调栈。 代码
# include <iostream>
using namespace std;
const int N = 100010 ;
int stk[ N] , tt;
int main ( )
{
int n;
cin >> n;
while ( n -- )
{
int x;
scanf ( "%d" , & x) ;
while ( tt && stk[ tt] >= x) tt -- ;
if ( ! tt) printf ( "-1 " ) ;
else printf ( "%d " , stk[ tt] ) ;
stk[ ++ tt] = x;
}
return 0 ;
}
三、数组模拟队列
队列的性质是:先入先出,后入后出。弹出元素从头部弹出,添加元素从尾部插入。因此可以用一个数组 q[N]
(值)和两个整数 hh
(头部位置)和 tt
(尾部位置)来模拟。 注意:初始默认 hh = 0, tt = -1
。这样方便判断队列是否为空。这个不固定,依习惯而定。 代码(内含链表的基础操作)
# include <iostream>
using namespace std;
const int N = 1e5 + 10 ;
int q[ N] , hh, tt = - 1 ;
q[ ++ tt] = x;
hh++ ;
if ( hh <= tt) { not empty}
else { empty}
q[ hh] ;
q[ tt] ;
扩展:单调队列
例题:AcWing 154. 滑动窗口 思路:题目要求寻找每次滑动窗口中最大最小值。我们以寻找最小值为例,可以发现一个性质『滑动窗口中大于等于滑动窗口右边的数时,当滑动窗口往后移动时,它们不可能满足题意,因此可以删除』,这样就得到了一个单调递增的单调序列,且有头有尾,因此可以用数组模拟队列的方式实现,即为单调队列。寻找活动窗口中最大值与寻找最小值类似,这里就不赘述了。 代码
# include <iostream>
using namespace std;
const int N = 1000010 ;
int a[ N] , q[ N] ;
int main ( )
{
int n, k;
scanf ( "%d%d" , & n, & k) ;
for ( int i = 0 ; i < n; i ++ ) scanf ( "%d" , & a[ i] ) ;
int hh = 0 , tt = - 1 ;
for ( int i = 0 ; i < n; i ++ )
{
if ( hh <= tt && i - k + 1 > q[ hh] ) hh ++ ;
while ( hh <= tt && a[ q[ tt] ] >= a[ i] ) tt -- ;
q[ ++ tt] = i;
if ( i >= k - 1 ) printf ( "%d " , a[ q[ hh] ] ) ;
}
puts ( "" ) ;
hh = 0 , tt = - 1 ;
for ( int i = 0 ; i < n; i ++ )
{
if ( hh <= tt && i - k + 1 > q[ hh] ) hh ++ ;
while ( hh <= tt && a[ q[ tt] ] <= a[ i] ) tt -- ;
q[ ++ tt] = i;
if ( i >= k - 1 ) printf ( "%d " , a[ q[ hh] ] ) ;
}
puts ( "" ) ;
return 0 ;
}