目录
链表的概念
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
1.单链表
下面为单链表的示意简图
每一个节点node存储自身的val值和下一个节点node的地址,整体结构为单链表。
当我们知道第一节节点之后就可以通过节点保存的地址来得到单链表的其他节点
所以节点类的定义语法为
class Node{
int val; // 当前节点保存的val值
Node.next; //下一个节点的地址
}
单链表的定义语法为
class SingleLinkedlist{
Node head; // 头节点
int size; // 链表保存的节点个数
}
1)单链表的插入
头插法 addFirst(int val)
当链表为空时直接使newnode=head;再让 size++; 即可
当单链表不为空时,先使 newnode.next = head; 再让 head = newnode; 最后 size++;即可完成头插法。
在链表index位置插入值为val的元素 add(int index, int val)
add(1,10) :在索引为1的位置插入一个10的元素
如图所示,找到索引为1的元素后,执行1,2的先后顺序再让size++即可完成插入,
问题1:先1再2顺序可否进行改变?
不可以,可以试想一下如果是先1后2的顺序,进行2是node1.next已经为newnode,会丢失后面链表的数据。
问题2:如何找到node1元素?
让链表从头节点head的位置往后走index-1步恰好就走到node1的位置,
但是如果用头节点head向后走 ,会丢失找到node1之前的元素,所以我们需重新定义一个prev节点代替head向后走index-1步。prev就称为前驱节点。
问题3:头节点没有前驱节点,若index=0该如何插入?
调用之前写的头插法就行 addFirst(val)
示例代码:
尾插法 addLast(int val)
调用add方法使index=size;
2)单链表的查询
是否包含指定值的结点 boolean contains(int val)
从头遍历链表 ,如果出现节点值与val相等,返回ture,反之返回false;
查询索引为index的元素值 get(int index)
从头开始向后走index步,取当前的节点val值返回。
查询第一个值为val的节点索引 getByValue(int val)
从头开始遍历链表,直到节点值与val相等,定义整型变量index用来记录索引。如果有与val值相等的节点 返回index,否则返回-1(索引值不可能为负)。
3)单链表的修改
修改索引为index位置结点值为newval set(int index , int newval)
从头遍历链表向后走index步找到指定索引位置,修改值为newval。
4)单链表的删除
删除索引为index位置的元素 ,返回值为删除的元素 remove(int index)
和插入元素一样需要找前驱节点,定义节点prev,让prev从头走index-1步,在进行上述删除操作,头节点没有前驱节点,所以需要特殊处理。
删除第一个值为val的元素 removeValueOnce(int val)
遍历链表找到待删除元素的前驱prev,进行删除操作;
头节点依然需要特殊处理。
删除链表所有值为val的元素 removeAllValue(int val)
先判断头结点的值是否为待删除元素
如果头节点是待删除元素,删除头节点之后判断新头节点是否是待删除的元素,如果是继续删除头节点直至头节点不是待删除元素;
头节点不是待删除的元素,就遍历链表找待删除元素的前驱,进行删除操作;
这里需要注意,如果待删除元素之后的元素依然是待删除元素,所以需要再次判断当前索引的元素是否为待删除元素,确认不是带删除元素再使prev向后进行遍历链表
虚拟头节点dummyHead
相信大家都看到上面单链表的添加与删除操作,找前驱的时候都需要考虑头节点没有前驱的情况,
如果可以引入一个虚拟头节点,这样所有的节点都有前驱,大大的降低了我们编程难度
头插法
删除链表中所有值为val的节点
2.双向链表
通过双向链表与单向链表的节点示意图,可以发现双向链表还保存了一个上一个节点的地址;
既然每一个节点都保存了前后两个节点的地址,那么当我们拿到双向链表的任意一个节点,就可以知道该链表的全部节点(单链表只能从前向后遍历)。
双向链表节点类的定义语法为
class DoubleNode{
DoubleNode prev ;//前驱节点地址
int val;//当前节点值
DoubleNode next;//后驱节点地址
}
双向链表定义语法
class DoubleLinedkList{
int size;//有效节点个数
DoubleNode head;//当前链表头节点
DoubleNode tail;//当前链表尾节点
}
1)双向链表的插入
头插法
当链表为空时,插入的节点即使链表的头节点也是尾节点;
当链表不为空时,要用当前头节点的前驱保存新节点地址,再让新节点的后驱保存头节点地址,做后再让新节点为链表头节点 ;
尾插法
当链表为空时,新节点既为头节点也为尾节点;
当链表不为空,让当前链表的尾节点保存新节点的地址,新节点前驱节点保存当前链表尾节点地址,最后让新节点为当前链表的尾节点;
在链表index位置插入新元素val;
找到插入位置的前驱prev,进行下面四步操作,prev.next =prev;必须最后一步执行,其余三步顺序无所谓
找前驱这个方法经常会用到,我们在类中写一个找前驱的方法(node),这样就可以重复利用这样的方法;根据index位置来决定是从前向后遍历还是从头向前遍历;
index>size/2 从前先后 ; index<size/从后向前;这样可以减少遍历次数
找前驱方法
add():任意位置插入示例代码
2)双向链表的删除操作
分治思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
删除操作比较复杂,我们就可以采用上述的分支思想;先处理前驱节点,不管后继节点;等前驱部分全部处理完毕再单独处理后继节点。
上面是删除node 节点方法,先处理前驱节点在处理后继节点;
删除index位置的元素
删除链表中出现的第一个值为val的元素
删除链表中所有值为val的元素
有了上述的删除指定节点方法与找前驱的方法,删除操作的代码就十分好写了。