双链表
由来
我们在单链表中,有了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 种操作:
-
在最左侧插入一个数;
-
在最右侧插入一个数;
-
将第 k 个插入的数删除;
-
在第 k 个插入的数左侧插入一个数;
-
在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M行,每行包含一个操作命令,操作命令可能为以下几种:
-
L x
,表示在链表的最左端插入数 x。 -
R x
,表示在链表的最右端插入数 x。 -
D k
,表示将第 k个插入的数删除。 -
IL k x
,表示在第 k 个插入的数左侧插入一个数。 -
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中复制到空字符。