小白学算法(9)双链表(静态数组模拟)

165 阅读4分钟

双链表

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情

前面介绍了 为什么要用静态数组模拟链表,那么有单链表就会有双链表,而双链表的构造仅增加一个左边指针

1.模板介绍

// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

2.模板解析

1)初始化

让我们想一下结构体构造双链表的方法

typedef struct DNode{               
    ElemType data;                  // 数据域
    struct DNode *pre,*next;        // 前驱和后继指针
}DNode,*DLinklist;

所以静态数组构造就需要三个数组

1.数据域数组

2.前驱指针数组

3.后继指针数组

int e[N],l[N],r[N],idx; //我们需要知道此时节点的位置,就用idx变量进行节点的统计

接下来就是初始化链表,数组与结构体链表不同的就是构造方法,前者是在固定的数组中,后者地址可以是不连续的。

所以我们需要确定好头指针和尾指针,并以此基础进行构造链表

void init()
{
    //1 为尾部,0为头
    //头-->尾
    //0-->1
    l[1]=0,r[0]=1,idx=2;
}

2)插入操作

链表版

bool add(DNode *p,DNode *s){
    if(p==NULL || s==NULL)      // 非法参数
        return false;
    s->next = p->next;
    if(p->next!=NULL)       // 如果p结点有后继结点
        p->next->prior=s;  // 将后继结点的前驱指针指向新结点
    s->prior = p;
    p->next = s;
    
    return true;
}

在a节点右边插入一个x,尾插法和头插法就包括在内

void add(int a,int x)
{
    //首先存入数据域
    e[idx]=x;
    //l[idx]=a 新增节点的左指针指向a
    l[idx]=a;
    //新增节点的右指针指向原本a右指针指向的节点
    r[idx]=r[a];
    //那么a右指针指向的节点的左指针就不指向a了,而是指向当前节点
    l[r[a]]=idx;
    //那么a节点的右指针就指向当前节点了
    r[a]=idx;
    //最后++ 进入下一个节点
    idx++;
}

image-20221216100005182

3)删除操作

链表版

bool remove(DNode *p){
    if(p==NULL) return false;   // 判断p结点是否存在
    DNode *q = p->next;         // 找到p结点的后继结点
    if(q==NULL) return false;   // 判断p的后继节点是否存在(q为要删除的结点)
    p->next = q->next;
    if(q->next!=NULL)           // 判断q结点是否是链表中的最后一个结点
        q->next->pre = p;
    free(q)
        
    return true;
}
void remove(int a)
{
    //a的下一个节点的左指针不指向a了,指向a的上一个节点
    l[r[a]]=l[a];
    //同上
    r[l[a]]=r[a];
}

image-20221216095958037

3.实操演练

827. 双链表 - AcWing题库

  1. 在最左侧插入一个数;(在头节点右边插个数,头节点位置为0,add(0,x))
  1. 在最右侧插入一个数(在尾节点左边插个数,尾节点位置为1,左边一个节点的右边插个数add(l[1],x)
  2. 将第 k 个插入的数删除;
  3. 在第 k 个插入的数左侧插入一个数;
  4. 在第 k 个插入的数右侧插入一个数
#include<iostream>
using namespace std;
const int N=1e6+10;
int e[N],l[N],r[N],idx;
void init()
{
    ...
}
//节点a右边插入一个x
void add(int a,int x)
{
   ...
}
//删除a节点
void remove(int a)
{
    ...
}
int main()
{
    int M;
    cin>>M;
    init();
    while(M--)
    {
        string temp;
        cin>>temp;
        int x,k;
        //右侧插入数
        if(temp=="R")
        {
            cin>>x;
            add(l[1],x);
          //最左侧添加数
        }else if(temp=="L")
        {
            cin>>x;
            add(0,x);
        }else if(temp=="D")
        {
            cin>>k;
            //因为有个头部所以+1
            remove(k+1);
            
        }else if(temp=="IL")
        {
            cin>>k>>x;
            add(l[k+1],x);
        }else
        {
            cin>>k>>x;
            add(k+1,x);
        }
    }
    for(int i=r[0];i!=1;i=r[i])
    {
        cout<<e[i]<<" ";
    }
    return 0;
}