双链表学习

本文详细介绍了双向链表的数据结构,包括节点定义、初始化、插入和删除操作,并提供了C语言和C++代码示例,以及针对题目827的解决方案,展示了如何处理链表的动态插入和删除操作。
摘要由CSDN通过智能技术生成

双链表

由来

我们在单链表中,有了next指针,这就使得我们要查找下一结点的时间复杂度为O(1)。可是如果我们要查找的是上一结点的话,那最坏的时间复杂度就是O(n)了,因为我们每次都要从头开始遍历查找。为了克服单向性这一缺点,我们的老科学家们,设计出了双向链表。双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

/*线性表的双向链表存储结构*/    
typedef struct DulNode    {        
    ElemType data;
    //数据域
    struct DuLNode *prior;      
    /*直接前驱指针*/        
    struct DuLNode *next;       
    /*直接后继指针*/    
    } DulNode, *DuLinkList;
​
    p->next->prior = p = p->prior->next;
    //由于这是双向链表,那么对于链表中的某一个结点p,它的后继的前驱是谁?当然还是它自己。它的前驱的后继自然也是它自己

初始化

v代指value,是数据域

用数组实现双,链表为了实现初始化,v=0节点的右指针指向v=1的节点,v=1的结点的左指针指向v=0的节点,代码实现如下

// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
​
// 初始化
void init()
{
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}

插入

我们现在假设存储元素e的结点为s,要实现将结点s插入到结点p和p -> next之间需要下面几步,如图3-14-5所示。

// 在节点a的右边插入一个数x
void insert(int a, int x)
{
    e[idx] = x;//将值放到a+1的位置
    l[idx] = a, r[idx] = r[a];//先让x的左指针连接a结点,让x的右指针连接a之后的节点
    l[r[a]] = idx, r[a] = idx ++ ;//连接完成需要把新增的值长度加上
}

删除

若要删除结点p,只需要下面两步骤,如图3-14-6所示。

// 删除节点a
void remove(int a)
{
    l[r[a]] = l[a];//把a结点的后继指针的左指针指向a节点前驱
    r[l[a]] = r[a];//把a节点的前驱结点的右指针指向a结点后继
}

题目827. 双链表

实现一个双链表,双链表初始为空,支持 55 种操作:

  1. 在最左侧插入一个数;

  2. 在最右侧插入一个数;

  3. 将第 k 个插入的数删除;

  4. 在第 k 个插入的数左侧插入一个数;

  5. 在第 k 个插入的数右侧插入一个数

现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n 个插入的数。

输入格式

第一行包含整数 M,表示操作次数。

接下来 M行,每行包含一个操作命令,操作命令可能为以下几种:

  1. L x,表示在链表的最左端插入数 x。

  2. R x,表示在链表的最右端插入数 x。

  3. D k,表示将第 k个插入的数删除。

  4. IL k x,表示在第 k 个插入的数左侧插入一个数。

  5. IR k x,表示在第 k个插入的数右侧插入一个数。

输出格式

共一行,将整个链表从左到右输出。

数据范围

1≤M≤10000 所有操作保证合法。

输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2
输出样例:
8 7 7 3 2 9

解法

1、c语言

#include <stdio.h>
#include <string.h>

#define N 100010

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

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

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

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

int main(void) {
    scanf("%d\n", &m);
    // scanf("%d", &m);
    // getchar(); // To consume the newline character after the integer input 就是换行,两句加起来和上面一样

    init();
    
    while (m--) {
        char op[3];
        scanf("%s", op);
    
        int k, x;
        if (strcmp(op, "R") == 0) {
            scanf("%d", &x);
            add(l[1], x);
        } else if (strcmp(op, "L") == 0) {
            scanf("%d", &x);
            add(0, x);
        } else if (strcmp(op, "D") == 0) {
            scanf("%d", &k);
            remove_node(k + 1);
        } else if (strcmp(op, "IL") == 0) {
            scanf("%d%d", &k, &x);
            add(l[k + 1], x);
        } else {
            scanf("%d%d", &k, &x);
            add(k + 1, x);
        }
    }
    
    for (int i = r[0]; i != 1; i = r[i]) {
        printf("%d ", e[i]);
    }
    
    return 0;

}

c++

#include<iostream>

using namespace std;

const int N = 1e5 + 10;

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

//! 初始化
void init()
{
    l[1] = 0, r[0] = 1;//* 初始化 第一个点的右边是 1   第二个点的左边是 0
    idx = 2;//! idx 此时已经用掉两个点了
}

//* 在第 K 个点右边插入一个 X 
void add(int k, int x)
{
    e[idx] = x;
    l[idx] = k;
    r[idx] = r[k]; 
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}
//*删除第 k个 点
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main(void)
{
    ios::sync_with_stdio(false);
    cin >> m;



init();

while(m--)
{
    string op;
    cin >> op;
    int k, x;
    if(op=="R")
    {
        cin >> x;
        add(l[1], x); //! 最右边插入 只要在  指向 1的 那个点的右边插入就可以了,遗忘可以向上找双链表初始化的代码,有注释
    }
    else if(op=="L")//! 最左边插入就是 在指向 0的数的左边插入
    {
        cin >> x;
        add(0, x);
    }
    else if(op=="D")
    {
        cin >> k;
        remove(k + 1);
    }
    else if(op=="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;

}

补充知识

13.5.1 strcpy函数

strcpy(字符串复制)函数在<string.h>中的原型如下:

char *strcpy(char *s1, const char *s2);

strcpy函数把字符串s2复制给字符串s1。(准确地讲,应该说成是“strcpy函数把s2指向的字符串复制到s1指向的数组中”。)也就是说,strcpy函数把s2中的字符复制到s1中直到遇到s2中的第一个空字符为止(该空字符也需要复制)。strcpy函数返回s1(即指向目标字符串的指针)。这一过程不会改变s2指向的字符串,因此将其声明为const。 strcpy函数的存在弥补了不能使用赋值运算符复制字符串的不足。例如,假设我们想把字符串"abcd"存储到str2中,不能使用下面的赋值:

str2 = "abcd";      /*** WRONG ***/*

这是因为str2是数组名,不能出现在赋值运算的左侧。但是,这时可以调用 strcpy函数:

strcpy(str2, "abcd");   /* str2 now contains "abcd" */

类似地,不能直接把str2赋值给str1,但是可以调用strcpy:

strcpy(str1, str2);    /* str1 now contains "abcd" */*

大多数情况下我们会忽略strcpy函数的返回值,但有时候strcpy函数调用是一个更大的表达式的一部分,这时其返回值就比较有用了。例如,可以把一系列strcpy函数调用连起来:

strcpy(str1, strcpy(str2, "abcd"));•  /* both str1 and str2 now contain "abcd" */

警告: 在strcpy(str1, str2)的调用中,strcpy函数无法检查str2指向的字符串的大小是否真的适合str1指向的数组。假设str1指向的字符串长度为,如果str2指向的字符串中的字符数不超过,那么复制操作可以完成。但是,如果str2指向更长的字符串,那么结果就无法预料了。(因为strcpy函数会一直复制到第一个空字符为止,所以它会越过str1指向的数组的边界继续复制。) 尽管执行会慢一点,但是调用strncpy函数(➤23.6节)仍是一种更安全的复制字符串的方法。strncpy类似于strcpy,但它还有第三个参数可以用于限制所复制的字符数。为了将str2复制到str1,可以使用如下的strncpy调用:

strncpy(str1, str2, sizeof(str1));

只要str1足够装下存储在str2中的字符串(包括空字符),复制就能正确完成。当然,strncpy本身也不是没有风险。如果str2中存储的字符串的长度大于str1数组的长度,strncpy会导致str1中的字符串没有终止的空字符。下面是一种更安全的用法:

strncpy(str1, str2, sizeof(str1) - 1);
str1[sizeof(str1)-1] = '\0';

第二条语句确保str1总是以空字符结束,即使strncpy没能从str2中复制到空字符。

参考

C语言程序设计:现代方法(第2版)-K.N.King-微信读书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值