链表
之前秋招参加了几家公司的面试,大多问到了基础,数据结构基本都问到了,想了想还是好好复习一下数据结构,现在就从链表开始吧。
链表毋庸置疑,和顺序表是两大基础了,之前在知乎也写过顺序表的博客,以后搬过来。
先看看顺序表结构,顺序表无非就是像数组一样,可以通过下标去访问元素,但是可以设计成比数组更灵活的东西,你可以设计成不断往里面添加元素。还有一些基本操作了,增删改查,但是有些人发现,顺序表去增删改查真的超级费劲,要在插入点之后的每个元素后移,删除元素时每个元素又要前移,当涉及到百万级的顺序表,肯定是处理时间很长的,这样就要采取另一种数据结构了,链表。
链表和数组不一样,不需要连续的地址空间,但是不需要连续的地址空间又怎么让元素之间联系起来呢,在c和c++里面可以给每个元素弄一个指针,这个指针指向的类型就是一个节点,从而这个指针就将一个个元素串起来了,这里用串不合适,因为并不是连续的,而是随机的地址空间联系起来,这样说起来应该更准确。而在java里面就是引用联系元素了,引用可以想象成A和B是同一个东西,共享同一块地址。java里面没有了许多繁杂的概念,指针操作是真的麻烦。这里我就不用c或者c++演示了,直接用java代码。
一些基本操作我就不多说,增删改查,还有面试的时候(当然我没有被问到,别人被问到过),判断是否成环,或者查倒数第几个元素(再没有尾节点的情况下)等等一些相关的链表操作,我都来一一列举.
链表就是除了包含了数据之外还有一个后继,指向下一个节点。
需要注意的是,链表有很多种,单向链表,双向链表,循环链表等等。
以下我就以单链表为例了。
首先就是增删改查
增加我写了两个,一个是添加L型的数据,还有就是添加节点
其他的我相信看代码就可以理解了。
node<L> newnode=new node<>();
newnode.data=e;
tail.next=newnode;
tail=newnode;
size++;
}
public void addElem(node<L> e) {
tail.next=e;
tail=e;
size++;
}
public void del_index(int index) {
node<L> link=head;
for (int i=0;i<index-1;i++) {
link=link.next;
}
link.next=link.next.next;
}
public void change(int index,L e) {
node<L> datae=find(index);
datae.data=e;
}
public node find(int index) {
node<L> link=head;
int i=0;
while(link.next!=null) {
link=link.next;
if(index==i) {return link;}
i++;
}
return null;
}
public void display() {
node<L> link=head.next;
while(link!=null) {System.out.println(link.data); link=link.next;}
}
public void insert(list<L> list_inversion,L e,int index) {
node<L> nodea=head;
int i=0;
while(i<index-1) {
nodea=nodea.next;
i++;
}
node<L> newnode=new node();
newnode.data=e;
newnode.next=nodea.next;
nodea.next=newnode;
}
上面还有一个输出链表的操作,这也是基本操作,我也不多说。
链表的倒置,这里链表的倒置有两种了
简单的就是双向链表的倒置,这个很简单的呗,就是从末节点往前驱找啦。
单链表的话就可能有点麻烦了。就是从第二个节点开始,一个个往头结点后面移动,就像这样:
原链表:head->1->2->3->4->5->6->7
第一次移动:head->2->1->3->4->5->6->7
第二次移动:head->3->2->1->4->5->6->7
…
最终倒置:head->7->6->5->4->3->2->1
就是这样一步步实现,相信大家都可以的。
public void inversion() {
node<L> nodeA,nodeB;
nodeA=head.next;
nodeB=nodeA.next;
while(nodeB.next!=null) {
nodeA.next=nodeB.next;
nodeB.next=head.next;
head.next=nodeB;
nodeB=nodeA.next;
}
nodeB.next=head.next;
head.next=nodeB;
nodeA.next=null;
}
交换节点:
这个是我晕晕呵呵的时候写的,可能想法有点傻哈哈哈,就是找到交换节点的前驱然后交换了。
public void swap(list<L> link,node a,node b) {
//找到前驱
node<L> apre=null,bpre=null;
node<L> flag=link.head;
while(flag!=null&&(apre==null||bpre==null)) {
if(flag.next==a) {apre=flag;}
if(flag.next==b) {bpre=flag;}
flag=flag.next;
}
//a 和 b的前驱分别为apre和 bpre
if(a.next==b) {
a.next=b.next;
b.next=a;
apre.next=b;
}else if(b.next==a) {
b.next=a.next;
a.next=b;
bpre.next=a;
}else {
node c=b.next;
b.next=a.next;
a.next=c;
bpre.next=a;
apre.next=b;
}
}
查找倒数第几个:这个前提是不知道链表长度的情况下。如果是双链表就没有出这个题目的意义了。
首先我们来看看,倒数第n个与最后一个节点的联系,是不是和它相差一定的距离,倒数第3个,和最后一个节点相差两个,倒数第7个,和最后一个节点相差6个,这样就可想而知了,倒数第n个节点,和最后一个节点相差n-1个节点。我们只要从head开始找出相差同样距离的节点,然后以同样的速度往尾节点移动,当后面的节点到达尾节点了,前面那个节点就是我们要找的。
代码如下:
public void check(list<L> li,int last_index) {
//设置两个指针差
if(li.head==li.tail) {System.out.println("没有元素"); return;}
node<L> first=li.head.next;
node<L> seconde=first;//此时first是倒数第1个
for (int i=1;i<last_index;i++) {
if(seconde.next!=null) {
seconde=seconde.next;
}else {
System.out.println("不存在"+last_index+"倒数!");
return;
}
}
while(seconde!=li.tail) {
first=first.next;
seconde=seconde.next;
}
System.out.println(first.data);
}
判断是否成环:
这个我们可以想一下初中的相遇问题,在一个操场上,当一个人跑另一个人追,当跑的那个人很快的时候,是不是可以再次追上他,就形成了一个环了。
同样的道理,在链表里面,我们只要用两个移动节点,一个速度快于另一个,这样如果是一个有环的链表的话,快的节点总会追上它,并且到达同一个节点的,这样的话,就可以判断是否成环了。
public boolean judge_circle(list<L> li) {
//指针是否追上另一个
node<L> first=li.head;
node<L> second=li.head;
while(true) {
if(second.next!=null) {
if(second.next==first) return true;
}else return false;
if(second.next.next!=null) {
if(second.next.next==first) return true;
second=second.next.next;
}else return false;
first=first.next;
}
}
当然还有一些其他操作,面试的时候大家都有可能会碰到,当然数据结构知识一部分,还有很多的其他方面的,以防万一,还是理解理解复习复习比较好。
另外我还写了一个双向链表。增加了前驱和后继。
node类:
package bothway_linklist;
public class node {
node pre;
int data;
node next;
}
linklist类:
package bothway_linklist;
//双向链表
public class linklist {
node head;
node tail;
public linklist() {
head=new node();
tail=head;
}
public void add(int e) {//尾插
node newnode=new node();
newnode.data=e;
tail.next=newnode;
newnode.pre=tail;
tail=newnode;
}
public void display_x() {
node link=head;
while(link.next!=null) {
link=link.next;
System.out.println(link.data);
}
}
public void display_t() {
node link=tail;
while(link!=head) {
System.out.println(link.data);
link=link.pre;
}
}
/*
*
*
*
*/
//双向链表增删改查随机插入
//实现链表合并
public static void main(String[] args) {
linklist list=new linklist();
list.add(1);
list.add(2);
list.add(3);
list.add(34);
list.add(53);
list.add(32);
list.add(65);
list.add(3432);
list.display_x();
System.out.println();
list.display_t();
}
}