数组模拟双链表(每周一类)

        继上次的模拟单链表之后,我们要学习一下比起模拟单链表的,模拟双链表,逻辑思路稍微复杂一点,但是基本思路和单链表是类似的。

        先讲一下上节课的作业,活动 - AcWing

        

        代码奉上

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;
int idx = 0, head, e[N], ne[N];

void hinsert(int x){
    e[idx] = x;
    ne[idx] = head;
    head = idx;
    idx ++;
}

void init(){
    head = -1;
    idx = 0;
}

void iinsert(int k, int x){
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++;
}

void delate(int k){
    ne[k] = ne[ne[k]];
}

int main(){
    int n;
    int x, k;
    char op;
    init();
    cin >> n;
    for(int i = 0; i < n ; i ++ ){
        cin >> op;
        if(op == 'H'){
            cin >> x;
            hinsert(x);
        }
        else if(op == 'D'){
            cin >> x;
            if(x == 0) head = ne[head];
            else delate(x - 1);
        }
        else if(op == 'I'){
            cin >> k >> x;
            iinsert(k - 1, x);
        }
    }
    for(int i = head; i != -1; i = ne[i]) cout << e[i] << " ";
    return 0;
}

        老规矩,还是开始我们的代码处理程序

        第一步,阅读题目,很显然,就是实现单链表的创建,头插入,任意位置插入,删除这些操作;现学现卖,我们需要用数组来模拟链表,这个时候我们对于每一步操作就要记起上次课画的那些图(这个至关重要),只有我们脑海中知道底层的逻辑是什么样子,我们才能更好地用代码来实现(退一步,能让我们更好地记住模版)。

        第二步,用计算机语言来将之前的想法来实现刚才脑中的图,这个时候就需要head:头结点,e[N]:赋值的数组,ne[N]:指针数组,随后就需要用严谨的代码来实现,具体见上节课

        第三步,用算法来进行优化,显然,这道题不太需要

        第四步,debug,这个需要凭经验自行操作

        其实思路和函数和上节课讲的都是基本一致的,但是不同的题总归还是有各自的特点,这道题要注意的点有两个,一是要注意字符的输入,对于每一种字符对应不同的操作,比如头插入,任意插入,删除操作,二是要注意这里题中的第一个结点是默认从1开始的,我们之前给的代码模版是默认第一个结点是从0开始的,所以当输入第k个点时,要操作它前一个点,即第k-1个点。不同的题要求不一样,我们要注意,不能死记模版。

        正课!!!

       我们这节课将演示模拟双链表,先将代码奉上

int e[N], l[N], r[N];
int idx;

void init(){
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}

void hinsert(int x){
    e[idx] = x;
    l[idx] = 0;
    r[idx] = r[0];
    l[r[0]] = idx;
    r[0] = idx ++;
}

void tinsert(int x){
    e[idx] = x;
    r[idx] = 1;
    l[idx] = l[1];
    r[l[1]] = idx;
    l[1] = idx ++;
}

void delet(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

void linsert(int k, int x){
    e[idx] = x;
    r[idx] = k;
    l[idx] = l[k];
    r[l[k]] = idx;
    l[k] = idx ++ ;
}

void rinsert(int k, int x){
    e[idx] = x;
    l[idx] = k;
    r[idx] = r[k];
    l[r[k]] = idx;
    r[k] = idx ++ ;
}

        首先,我们要先理清两个概念:1.我们将第一个点,即下标为0的点视为最左侧的点,将第二个点,即下标为1的点视为最右侧的点。2.双链表和上节课的单链表其实很相似,最不同点有两个,一个是端点有两个,就是最左侧的点和最右侧的点,另一个就是指针数组,也就是类似于上节课的ne[N],变成了两个指针数组,l[N]和r[N]这两个区别。

        e[N]还是代表元素的值,idx表示目前操作的是第几个数(单调递增的)。

        之后我们开始讲每一个函数,从init()开始,这是一个创建双链表的函数,我们将r[0]=1,意思就是最左侧的点的右侧指向最右侧的点,l[1]=0,意思是最右侧的点的左侧指向最左侧的点。idx = 2,这里和之前的单链表不同,由于0和1被占用,所以从第三个点开始,所以idx从2开始。注意这里2的意思不是第二个点,也不是下标是2的点,而是第三个点的意思,(0,1,2)这么数数,所以是2。

        hinsert()表示头插,也就是最左侧插入,tinsert()表示尾插,也就是最右侧插入。这两个代码的实现逻辑是一样的,是对称的,所以拿在一起讲。

        我们在做这种插入的时候谨记一个顺序,一个原则就都可以自己轻松地写出来了。

        一个顺序是:先赋值,再创建,最后删除。意思就是我们插入的时候要先将值赋给新的点idx中。再创建这个点与其他点的指针关系,最后再删除之前的指针关系。这个三个顺序保证大体的先后顺序。

        一个原则是:最后删除之前需要使用的指针。意思就是在删除的时候,如果某个指针在之前的操作中有使用的需要,那么就最后删除它。这个原则保证了删除的先后顺序。

        其实所有的链表的插入操作,不论是真的指针链表,还是数组模拟链表,不论是单链表还是双链表的插入,根本的原则都是这个,如果不按照这个原则操作,那么大概率会出错。这两个规律在之后做图的操作中都是基本,所以一定要理解并谨记。

        

        遵从一个顺序,先赋值,e[idx]=x,就是将x插入第idx个数中。之后创建联系,l[idx]=0,就是将idx的左面指向0,也就是将idx左面指向最左边,r[idx]=r[0],就是将idx的右面指向0的右面,创建有一个规律,那就是创建的都是由新的数idx指向的

        之后删除联系,这里要用到“一个原则”,l[r[0]]=idx,就是将从原来0右侧的元素指向0的指针指向idx,r[0]=idx,就是将0的右指针指向idx。

        (假设我们先删除r[0],语句是r[0]=idx,那么我们之后删除l[r[0]]的时候就没有办法找到l[r[0]]了,这是因为r[0]已经改变。这时候我们就只能用l[r[idx]]=idx,效果和之前没改变r[0]的l[r[0]]是一样的,当然这个语句的成功也是建立在“一个顺序”正确的前提下,但是这样的逻辑我们坚决不推荐,因为有正确的容易走的路我们尽量走容易走的路,不要曲线救国。

      

        尾部插入也如图所示,不过尾部也就是最右侧插入先是 r[l[1]]=idx ,也就是先改r指针,最左侧插入是先改l指针,这里大家自行体会一下。

        之后的linsert左插入和rinsert右插入本质也都是一样的逻辑,这里就不过多赘述了。

        删除元素也和单链表一样,就是通过指针,跳过需要被删除的元素。

        我们这节课最重要的内容就是前面黑体的“一个顺序”和“一个原则”,这两个规律体现了指针插入的统一规律。希望大家重点理解

        最后留一下作业 活动 - AcWing 这是一道双链表的题,来试试吧(doge)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值