链表:用链式存储实现的线性表。
链式存储:在内存中,不仅存储当前元素信息,还存储前继或后继节点的地址。通过地址实现元素与元素之间的关系。
单链表:动态实现:通过new动态的申请链表中的节点,通过delete来释放节点。静态实现:两个足够大的数组,一个数组elem存储数据,充当数据域,另一个数组next 存储下一个元素的下标,充当指针域。
模板:int e[N],ne[N],h,id;
int main()
{
return 0;
}
链表的头插操作:id++,标记新节点的位置,同时存储新节点的值:e[id] = x;
再修改新节点的指针域,让其指向哨兵位的下一个位置:ne[id] = ne[h];
最后修改哨兵位的指针域,让其指向新节点:ne[h] = id;
头插
void push_front(int x)
{
id++;
a[id] = x;
//先让新节点指向头节点的下一个位置
//然后让头节点指向新来的节点
ne[id] = ne[h];
ne[h] = id;
}
遍历链表
void push_front(int x)
{
for(int i = ne[h]; i ; i = ne[i])
{
//e[i] 就是当前元素
cout << e[i] << " ";
}
cout << endl;
}
按值查找:
方法一:遍历整个链表,一个一个对比
int find(int x)
{
for(int i = 1; i <= 5; i++)
{
if(e[i] = x)
return i;
}
cout << find(1) << endl;
cout << find(2) << endl;
cout << find(3) << endl;
return 0;
}
方法二:重新创建一个数组mp,标记,每一个元素存储的下标位置,此时mp[i]表示:i这个元素,存储的位置。缺点:就是不能有重复的数
void push_front(int x)
{
id++;
e[id] = x;
mp[x] = id; 标记x存储的位置
//先让新节点指向头节点的下一个位置
//然后让头节点指向新来的节点
ne[id] = ne[h];
ne[h] = id;
}
在任意位置插入元素:在任意位置为p的元素后面,插入新的元素x
先id++ ,标记新节点的位置,同时存储新节点的值:e[id] = x
然后 修改新节点的指针域,让其指向p的下一个位置:ne[id] = ne[p]
最后 修改p位置的指针域,让其指向新节点:ne[p] = id
void insert(int p,int x)
{
id++;
e[id] = x;
mp[x] = id;
ne[x] = ne[p];
ne[p] = id;
}
int main()
{
insert(i,10);
insert(2,100);
return 0;
}
删除任意位置之后的元素:删除存储位置为p后面的元素
方法:直接让p指向下一个元素的下一个元素即可 ne[p] = ne[ne[p]]; 此方法的缺点是:删除链表中的最后一个元素后面的元素时:ne[p] = ne[ne[p]] 会找到哨兵位的下一个结点,此时再去赋值就会让链表成环,破坏了实际的结构。
void erease(int p)
{
if(ne[p]) 当p不是最后一个元素的时候
{
mp[e[ne[p]]] = 0;
ne[p] = ne[ne[p]];
}
}
遗留问题:单链表为什莫不实现尾插、尾删、删除任意位置等操作:能实现,但是没有必要,因为时间复杂度较高。
双向链表:静态实现:创建三个做够大的数组:第一个数组存储数据,第2个数组存储前一个元素的存储下标,第三个元素存储下一个元素的存储下标
const int N = 1e5 + 10;
int e[N],ne[N],pre[N],id,h;
int mian()
{
return 0;
}
头插:将x插入哨兵位与y的中间,先修改新节点x的左右指针,然后修改结点y的左指针,最后修改哨兵位的右指针
先将id++,标记新节点存储的位置,把新的元素存储下来:e[id] = x;
修改新节点的前驱指针,让其指向哨兵位: pre[id] = h;
修改新节点的后继指针,让其指向哨兵位的下一个位置: ne[id] = ne[h];
修改y结点的前驱指针,让其指向新的结点: pre[ne[h]] = id;
修改哨兵位的后继指针,让其指向新的结点: ne[h] = id;
遍历链表:直接无视prev数组,与单链表的遍历方式一致
void print()
{
for(int i = ne[h];i;i = ne[i])
{
cout << e[i] << " ";
}
cout << endl << endl;
}
int main()
{
for(int i = 1; i <= 5; i++)
{
push_front(i);
print();
}
return 0;
}
按值查找:直接使用mp数组优化了
int find(int x)
{
return mp[x];
}
int main()
{
for(int i = 1;i <= 5; i++)
{
push_front(i);
print();
}
cout << find(3) << endl;
cout << fint(5) << endl;
return 0;
}
在任意位置之后插入元素:在存储位置为p的元素后面,插入新的元素,操作与头插操作类似,注意最后修改p的后继指针即可
先id++,标记新结点存储的位置,把新的元素存储下来:e[id] = x;
修改新结点的前驱指针,让其指向p位置:pre[id] = p;
修改新结点的后继指针,让其指向p位置的下一个位置:ne[id] = ne[p];
修改p的下一位置的前驱指针,让其指向新的结点:pre[ne[p]] = id;
修改p的后继指针,让其指向新的结点:ne[p] = id;
在任意位置之前插入元素:在存储位置为p的元素前面,插入新的元素x。注意要最后修改p的前驱指针
id++,标记新结点存储的位置,把新的元素存储下来:e[id] = x;
修改新结点的前驱指针,让其指向p的前一个位置:pre[id] = pre[p]
修改新结点的后继指针,让其指向p位置:ne[id] = p;
修改p前一个位置的后继指针,让其指向新的结点:ne[pre[p]] = id;
修改p的前驱指针,让其指向新的结点:pre[p] = id;
删除任意位置的元素:删除存储位置为p的元素
让p的前驱结点的后继指针指向p的后继结点:pre[pre[p]] = ne[p];
让p的后继结点的前驱指针指向p的前驱结点:pre[ne[p]] = pre[p];
循环链表:就是单项链表的一种特殊情况