C++使用数组模拟单链表、双链表、栈和队列
在算法题目中,直接使用STL库的容器可能会因常数过大或内存分配导致超时。利用静态数组结合下标来模拟指针,是提升执行效率的常用手段。本文将逐一解析如何通过数组模拟实现单链表、双链表、栈以及队列,并结合单调栈和滑动窗口等典型场景提供完整代码示例。
一、数组模拟实现单链表
第1步:理解单链表的数组存储结构
传统结构体实现链表中包含数据域和指针域。在数组模拟中,我们通常定义两个数组:e[N]用于存储节点的值,ne[N]用于存储该节点的下一个节点的下标。此外还需一个head变量指向头结点的下标,以及idx记录当前已使用的最大下标。
第2步:完成初始化和核心操作
初始化时,将头指针head置为-1表示空表,idx从0开始计数。核心操作包括:
- 头插法 (InsertHead):新节点的值存入
e[idx],其next指向当前的head,更新head为新下标,最后idx自增。 - k节点后插入 (Insert):新节点指向原k的next,再将k的next指向新节点。
- 删除k节点 (remove):直接将k的next指向k的next的next即可跳过k节点。
const int N = 100010;
int head, e[N], ne[N], idx;
void Init() {
head = -1;
idx = 0;
}
// 向链表头插入x
void InsertHead(int x) {
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
// 在第k个插入的数后面插入x
void Insert(int k, int x) {
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
// 删除第k个插入的数后面的数
void remove(int k) {
ne[k] = ne[ne[k]];
}
第3步:解决单链表综合例题
通过维护一系列操作命令(插入、删除、输出),验证上述逻辑的正确性。注意题目中的“第k个插入的数”是依据输入顺序的索引,而非链表中当前的位置,因此在处理D(删除)和I(插入)指令时需对输入的k值进行减1处理以适配0基索引。
二、数组模拟实现双链表
第1步:确立左右指针数组模型
为了支持双向遍历,需要增加一个l[N]数组存储左指针下标。r[N]数组存储右指针下标。初始化时设置r[0]=1, l[1]=0,即虚拟的头尾哨兵节点,真正的节点从idx=2开始。
第2步:简化插入与删除逻辑
只维护一个通用函数Insert(k, x)实现在k节点右侧插入。这样在左侧插入只需调用Insert(l[k+1], x)。删除操作同样简单,断开相邻节点的互相引用即可。
int e[N], l[N], r[N], idx;
void Init() {
// 0是左端点,1是右端点
r[0] = 1;
l[1] = 0;
idx = 2;
}
// 在下标为k的右边插入一个数
void Insert(int k, int x) {
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
idx++;
}
// 删除下标为k的点
void remove(int k) {
r[l[k]] = r[k];
l[r[k]] = l[k];
}
第3步:处理双链表复杂操作
在实际编程题中,需根据命令映射到正确的参数。例如在最左端插入(L)对应Insert(0, x),在最右端插入(R)对应Insert(l[1], x)。由于内部索引与题目描述的第k个插入数存在偏移,遍历时应忽略哨兵节点,从r[0]出发直到到达1停止。
三、数组模拟实现栈
第1步:掌握单调栈的基本操作
栈的实现极为精简,仅用stack[]存数据,top记录栈顶位置(初始通常为0)。元素入栈为stack[top++],出栈为--top,访问栈顶元素为stack[top-1]。
第2步:应用单调栈求解最近小值
利用栈存储满足特定性质的元素(如递增序列)。在处理数值x时,不断弹出栈顶大于等于x的元素,此时栈顶即为左边第一个比它小的数。若栈为空则输出-1。这种方法可以将O(N²)暴力解法优化至O(N)。
const int N = 100010;
int stack[N], top = 0;
int main() {
int n;
scanf("%d", &n);
while(n--) {
int x;
scanf("%d", &x);
while(top && stack[top-1] >= x)
top--;
if(!top) printf("-1 ");
else printf("%d ", stack[top-1]);
stack[top++] = x;
}
return 0;
}
四、数组模拟实现队列
第1步:区分普通队列与单调队列
普通队列用head和tail指针分别指示队首和队尾的下一位位置。判断非空条件为head < tail。但在处理滑动窗口类问题时,通常需要维护区间的最值,这引入了单调队列的概念。
第2步:实现滑动窗口最值的单调队列
单调队列要求队列内元素保持单调性。遍历数组时:
- 剔除过期元素:如果队首元素的下标距离当前位置超过窗口大小k,则
head++。 - 保持单调:如果要入队的值小于队尾,则在求最小值队列中弹出队尾;反之求最大值。
- 记录答案:当遍历次数达到k-1后,队首始终是当前窗口的最优解。
// 寻找滑动窗口最小值
for(int i = 0; i < n; i++) {
if(head < tail && i - k + 1 > q[head]) head++; // 判断对头是否滑出窗口
while(head < tail && a[q[tail-1]] >= a[i]) tail--; // 保证单调性
q[tail++] = i; // 加入队尾
if(i >= k - 1) printf("%d ", a[q[head]]); // 输出对头最小值
}
printf("\n");
同理,寻找最大值时将比较符号由>=改为<=即可。以上方法均能显著降低时间与空间复杂度,非常适合应对高频率的数据结构查询场景。
总结
通过数组下标模拟指针是C++算法编写中的重要技巧,它不仅规避了动态分配的开销,也让内存管理更为直观。以上就是C++数组模拟实现单链表、双链表、栈和队列的详细内容,更多关于C++算法优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













