6.2 链表
首先还是一个问题,什么叫做链表呢?
我们已经学过了数组,和很多的容器,不过可以知道的是,无论选用什么样的容器,里面存储的元素都是按照下标顺序,连续地存储在开辟的空间里。
链表,在这里又可称为线性表的链式存储,是一种非顺序的存储结构。每个数据元素在存储的同时,要配备一个指针,用于指向它的直接后继元素,即每一个数据元素都指向下一个数据元素(最后一个指向NULL(空))
每个元素本身由两部分组成:
1.本身的信息,称为数据域
2.指向后继的指针(下标),称为指针域
这两部分信息组成数据元素的存储结构,称之为“结点”。n个结点通过指针域相互链接,组成一个链表。
每个结点只包含一个指针域,生成的链表,我们即可称为线性链表或者单链表。
头结点:有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点。
首元结点:链表中第一个元素所在的结点,它是头结点后边的第一个结点。
头结点:永远指向链表中第一个结点的位置(如果链表有头结点,头指针指向头结点;否则,头指针指向首元结点)。
数组比较省空间,并且在查询一个元素的时候更加优势,但是删除一个元素,或者添加一个元素时的时间复杂度为o(n),链表的话插入数据和删除数据就比较快,不过查询的时候比较麻烦,空间开销也比较大。
6-4 破损的键盘 (UVA 11988)
你有一个破损的键盘(哎我知道它坏了,我就是不换,就是玩)。键盘上的所有键都是可以正常工作的,但有时Home键或者End键会自动按下。但是你不知道发生了这个情况,你在专心打稿子,甚至显示器都没有开(有眼睛我不用,就是玩)。你的任务就是在打开显示之前计算出这段悲剧文本。
输入一个长度不超过100000个字母,下划线,字符“[”和“]”,其中“[”表示Home键,“]”表示End键,输出对应的文本。
例如:
输入:This_is_a_[Beiju]_text
输出:BeijuThis_is_a__text
分析:不会有人不知道编辑文本时,Home和End是干嘛用的吧?我这里解释一下,当你进行文本编辑时,Home会将光标移动到文本的最开始,同理,End会将光标移动到最后。
最简单的想法就是用一个数组去保存这个文本,pos表示光标位置。然后Home表示在文本前进行插入。(将后面的字符全部右移)
但这样做会超时,因为前面说了,数组插入元素的复杂度为o(n),极端情况下,250000的a和[交替出现,操作次数会超过6*1012次。
这里使用我们刚刚学的链表:
准备条件:
int next[250000];//next表示指针域,这里的next[i]表示s的第i个字符
//同时next[i]通过不断地迭代,从0到1到2最后到文本的结束
int last=0,pos=0;//last表示当前文本的末尾光标,pos表示当前光标所在位置
char s[250000];//这道题的数据量比较大,为了节省时间考虑用char数组
遇到方括号的两种情况:
if (s[i]=='[') pos=0;//home,光标移至队首
else if (s[i]=']') pos=last;//end,光标移至队尾
接下来我自己写的时候,写下来这三行代码:
next[pos]=i+1;//指针里存入地址
if (pos==last) last=i+1;//update最后一个字符的光标位置
pos=i+1;//移动光标
表示更新pos和last的值,并且存入当前字符数据的指针,但结果始终不对。后来发现链表最为关键的一点在于,指针不是像数组一样连续排序的,而是通过上一个指针去指向下一个结点的数据,然后通过类似于映射的方法,得到下一个结点的指针,再去指向下一个结点·······
也就是说这里缺少了指针的互相连接:
//建立上一个pos到这一个pos的联系
next[i+1]=next[pos];
就这两行代码,我看了一个下午都看不明白它的实际意义,然后我把i用后面的next[pos]代替:
next[next[pos]]=next[pos]
这一下就很清晰了。
完整代码如下:
#include<cstring>
#include<iostream>
using namespace std;
int next[250000];//next表示指针域,这里的next[i]表示s的第i个字符
//同时next[i]通过不断地迭代,从0到1到2最后到文本的结束
int last=0,pos=0;