数组模拟的理由:
效率问题
struct Node//动态链表
{
int val;
Node *next;
};
new Node(); //此操作非常慢
一、 使用数组模拟单链表(用的最多的是邻接表,邻接表多用来存储树与图)
head->[结点0,值为3] ->[结点1,值为5] ->[结点2,值为7] ->[结点3,值为9] ->[空结点-1]
每个结点拥有两个属性,分别是结点值和下一个结点地址(这里就是下一个结点的结点号),用数组表示即为e[N]与ne[N].
上图的各结点即可写作:
e[0] = 3 e[1] = 5 e[2] = 7 e[3] = 9
ne[0]= 1 ne[1] = 2 ne[2] = 3 ne[3] = -1
1. 在头结点后插入新结点
让head指向新结点,新结点指向原来head指向的结点。
2. 将新结点插入k结点之后
让k结点指向新结点,新结点指向原来k指向的结点。
3. 将k结点之后的结点删除
让k结点指向要删除结点之后的结点即可
//单链表
#include<iostream>
using namespace std;
const int N = 100010;
//head表示头结点的下标
//e[i]表示结点i的值
//ne[i]表示结点i的next指针下标是多少
//idx存储我们当前用到的下标
int head, e[N], ne[N], idx;
//初始化
void init()
{
head = -1;
idx = 0;
}
//将x插到头结点
void add_to_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]];//ne[k]是要删除的结点的下标,ne[ne[k]]是要删除结点所指向的结点
}
int main()
{
int m;
cin >> m;
init();
while(m --)
{
int k, x;
char op;
cin >> op;
if(op == 'H')//在头结点插入新结点x
{
cin >> x;
add_to_head(x);
}
else if(op == 'D')//删除k结点之后的结点
{
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] << ' ';
cout << endl;
return 0;
}
二、 使用数组模拟双链表(用于优化某些问题)
和单链表比较类似,但它的每个结点有两个指向,所以会形成一个闭环,故不存在头尾结点之说。
将结点号为0的结点作为头结点,结点号为1的点作为尾结点。
由左端点指向右的箭头数组为r,由右端点指向左的箭头数组为l。
1. 在k结点之后插入新结点
首先让新结点的左箭头指向k结点,右箭头指向k之后的结点;接着让k结点的右箭头指向新结点,k之后的结点的左箭头指向新结点。
如果要在k之前插入新结点,就调用add[ l[k], x ].
2. 删除k结点
让k结点前面的结点的右箭头指向k之后的结点,k之后的结点的左箭头指向k之前的结点。
//双链表
#include<iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
//初始化
void init()
{
//0表示左端点,1表示右端点
r[0] = 1;
l[1] = 0;
idx = 2;
}
//在结点k之后插入x
void add(int k, int x)
{
e[idx] = x;
r[idx] = r[k];//k结点原来的右箭头指向新结点
l[idx] = k;
r[k] = idx;
l[r[k]] = idx;
}
//删除第k个点
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}