为什么要用数组模拟
众所周知,链表是通过指针将所有的结点相连接,以此来达到快速插入和删除的数据结构。但是在
很多时候,特别是在学校、书本里面,大多都是用的结构体,而使用结构体的劣势也显而易见:前戏长,运算慢。在我们平时做算法题还是练习的时候,我们推荐使用数组模拟。
当然,队列、栈、二叉树 这些也可以用数组模拟。
结构体实现链表
// 定义结构体
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
// 在单链表中取得第i个元素
Status getElem L(LinkList L,int i,ElemType &e) {
// L为带头结点的单链表指针。
// 当第i个元素存在时,其赋给e并返回,否则返回Error。
p = L->next, j=1; // 初始化,p指向第一个结点,j为计数器。
while(p && j<i) { // 从头结点开始查找,知道p指向第i个结点或 p 为空
p = p->next ;
++j;
}
if(!p ||j>i) return ERROR; // 第i个元素不存在
e = p-> data; // 取第i个元素并返回
return e;
}
// 在单链表中第i个位置之前插入元素e
Status ListInsert L(LinkList &L,int i, ElemType e) { // & 返回哪个参数
p = L , j=0;
while(p && j < i-1) { // 寻找第i-1个结点
p = p-> next;
++j;
}
if(!p || j>i-1) return ERROR; // i小于1 或者 i大于表长+1(健壮性处理)
s = (LinkList)malloc (sizeof LNode);
s->data = e;
s->next = p->next;
p -> next = s;
// 指针操作,注意前后顺序
}
// 在单链表中删除第i个元素 并返回删除的值为e
Status ListDelete L(LinkList &L,int i,ElemType &e) {
p = L,j=0; // 初始化
while(p->next && j<i-1){
p = p->next ;
++j;
}
if(!(p->next) || j>i-1) return ERROR;
q = p->next ;
p->next = q->next; // 删除并释放结点(q)
e = q->data;
free(q);
// 注意操作顺序
}
数组实现单链表
定义变量: idx,head , h[ ], ne[ ] , e[ ] 。
idx:链表的下标。 在进行删除,插入操作时作为辅助元素帮助操作。
head:链表头结点。
ne[]:指向当前元素位置的下一个位置。
e[]:保存当前节点的值。
定义基本方法:init_list() 、 insert_head() 、add() 、 remove() 。
// 对链表进行初始化。
void init_list(){
idx = 0; // 令当前下标0 (c++全局变量初试为0)
head = -1; // 链表的头节点要指向-1
}
//将x插入到头节点上
void insert_head(int x) {
e[idx] = x; //第一步,先将值放进去
ne[idx] = head; //head作为一个指针指向上一个头结点,现在ne[idx] = head 相当于 新添加的元素
// 指向上一个更新的头结点。
head = idx; //更新head结点进行迭代。
idx ++; //指针向下移一位。
}
//将x插入到下标为k的点的后面
void add(int k, int x){
e[idx] = x;//先将元素插进去
ne[idx] = ne[k];//让元素x配套的指针,指向它要占位的元素的下一个位置
ne[k] = idx;//让原来元素的指针指向自己
idx ++;//将idx向后挪
}
//将下标是k的结点后面的点删除
void remove(int k){
ne[k] = ne[ne[k]]; //让k的指针指向k下一个结点的下一个结点 。
}
值得注意的是 ,在数组模拟链表这几个基本方法中,需尤为注意操作的顺序。例如:在 add() 方法中,如果把
ne[idx] = ne[k] 和 ne[k] = idx 互换位置 ,那结果就不一样了, 因为在其之前, ne[k] 的位置已经不一样了 ,这个时候再去指向 ne[k] 就错了。
例题示范
借鉴 Acwing 里面一道模板题
思路:定义好基本变量后 ,使用数组模拟链表或者使用c++自带的STL(之前在洛谷看到过一句话,很深刻:我们通过努力去尝试AC的每一道题,不是目标,而是训练。所以说学习这种东西真的是多多益善 0.0) 。然后写好各自方法体。
AC代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int head, e[N],h[N],ne[N], idx;
int n;
void init(){
head = -1;
idx = 0;
}
// 头结点插值
void inset_head(int x){
e[idx] = x;
ne[idx] = head;
head = idx;
idx ++ ;
}
// 将x插入到下标是k的结点的后面
void add(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]];
}
int main(){
cin >> n;
init();
while (n--){
int k, x;
char op;
cin >> op;
if (op == 'H'){
cin >> x;
inset_head(x);
}
else if (op == 'D'){
cin >> k;
if (!k) head = ne[head];
remove(k - 1);
}
else{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] <<" ";
return 0;
}