本文由GarfieldTheOldCat原创,转载请标明dekkyandlappy-CSDN博客
最近忙着学校的各种毕业事项,鸽了几天,昨天参加完毕业典礼之后开始赶进度,以后争取一天一至二更。
今天进入链表部分了
基础
链表在之前数据结构的学习中接触过,是一种需要部分自行定义的数据结构。链表通过指针将不同的节点串联到一起。对于单链表,每个节点由两部分构成,其中一部分用于存储数据,另一部分用于存储下一个节点的指针。第一个节点称为头节点head,通过访问head,可以遍历整个链表;最后一个节点指向空(对java而言是null,对python而言是None)。
除此之外,还有双链表(每个节点由三部分构成,包括数据和指向前后节点的两个指针)和循环链表(首尾相连)。
与数组不同,链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。链表适合数据量经常发生变化(增加或删除),但是查询比较少的场合。
单表定义:
java
package Data_structure;
public class ListNode{
public int val;
public ListNode next;
public ListNode() {}
public ListNode(int val) { this.val = val; }
public ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
python(不用类名初始化)
class ListNode:
def __init__(self,val=None,next=None):
self.val=val
self.next=next
移除链表元素
链表元素是通过指针相连接的,因此,如果想要删除某个元素,只要将指向此元素的指针指向此元素的下一个元素即可,即前一个元素previous的next=previous的next的next。
这个过程看着比较简单,但是有一下两个难点:
- 如果需要移除的元素为链表的第一个元素(头节点),此时不存在上一个元素;
- 如果需要移除的元素为链表最后一个元素,此时下个元素为null
为了解决这些问题,有两种方法:
在原链表上直接操作:先检查头节点是否要删除,若要删除则head=head.next,再遍历剩下的链表,完成删除操作,到最后一个元素时停止遍历。
虚拟头节点:引入一个虚拟的头节点Dummyhead,使其next为原始的head,直接遍历剩下的链表,完成删除操作,到最后一个元素时停止遍历。注意:此方法返回的是虚拟头节点Dummyhead的next
lc23:203. 移除链表元素
思路和刚才介绍的一样
首先看直接操作的实现:
java版
// 不使用虚拟头节点
public ListNode removeElements_0(ListNode head, int val) {
// 处理head节点
while(head !=null && head.val==val){
head=head.next;
}
if (head == null) return head;
ListNode current=head;
if (current.next ==null) return head;
while(current.next.next != null){
ListNode next =current.next;
if (next.val==val) current.next=current.next.next;
else current=current.next;
}
if (current.next.val ==val) current.next=null;
return head;
}
这个是我自己按照思路自己写的代码,仍然比较啰嗦,按照题解进行了精简:
// 参考
public ListNode removeElements_1(ListNode head, int val) {
while(head!=null && head.val==val){
head = head.next;
}
ListNode curr = head;
while(curr!=null){
while(curr.next!=null && curr.next.val == val){
curr.next = curr.next.next;
}
curr = curr.next;
}
return head;
}
精简之后的代码不需要对尾节点做额外的处理。
python版:
# 原始链表
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
while head is not None and head.val == val:
head=head.next
current=head
while current is not None:
while current.next is not None and current.next.val ==val:
current.next=current.next.next
current=current.next
return head
python在判断节点是否为空时可以不用==,而用 is None/is not None
再实现使用虚拟头节点的方法:
Java版
// 虚拟头节点
public ListNode removeElements(ListNode head, int val) {
ListNode DummyHead=new ListNode();
DummyHead.next=head;
ListNode current=DummyHead;
while(current !=null){
while(current.next != null && current.next.val ==val){
current.next =current.next.next;
}
current=current.next;
}
return DummyHead.next;
}
python版:
python第一次实现报错:
class Solution(object):
# 虚拟头节点,错误
def removeElements_0(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
Dummyhead=ListNode(None,head)
current=Dummyhead
while current.next is not None:
if current.next.val == val:
current.next=current.next.next
current=current.next
return Dummyhead.next
检查发现遗漏了对current是否为None的判断:
# 虚拟头节点
def removeElements_1(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
Dummyhead=ListNode(None,head)
current=Dummyhead
while current is not None:
while current.next is not None and current.next.val ==val:
current.next=current.next.next
current=current.next
return Dummyhead.next
改正后就可以了。
设计链表
包括链表的增,删,返回下标等功能的实现。
lc707:707. 设计链表
先上java:
package integer_array_round1.day3;
import Data_structure.ListNode;
class MyLinkedList_v1 {
private ListNode Dummyhead;
public MyLinkedList_v1() {
this.Dummyhead = new ListNode();
this.Dummyhead.next=null;
}
public int get(int index) {
int pos=-1;
ListNode current=this.Dummyhead;
while(current != null){
if( pos ==index) return current.val;
pos++;
current=current.next;
}
return -1;
}
public void addAtHead(int val) {
ListNode head_adder=new ListNode(val,this.Dummyhead.next);
ListNode current=this.Dummyhead;
this.Dummyhead.next=head_adder;
}
public void addAtTail(int val) {
ListNode tail_adder=new ListNode(val,null);
ListNode current=this.Dummyhead;
while(current.next != null) current=current.next;
current.next=tail_adder;
}
public void addAtIndex(int index, int val) {
ListNode index_adder=new ListNode(val);
int pos=-1;
ListNode current=this.Dummyhead;
while(current != null){
if( pos ==index-1){
index_adder.next=current.next;
current.next=index_adder;
return;
}
pos++;
current=current.next;
}
return;
}
public void deleteAtIndex(int index) {
int pos=-1;
ListNode current=this.Dummyhead;
while(current != null){
if( pos ==index-1){
if(current.next != null){
current.next=current.next.next;
return;
}
}
pos++;
current=current.next;
}
return;
}
}
观察题解时发现addAtHead和addAtTail都可以由addAtIndex实现,在python版本里完整了这种方法:
from Data_structure.ListNode import ListNode
class MyLinkedList(object):
def __init__(self):
self.DummyHead = ListNode()
self.size = 0
def get(self, index):
"""
:type index: int
:rtype: int
"""
if index < 0 or index >= self.size:
return -1
else:
current = self.DummyHead
count = 0
while count <= index:
current = current.next
count = count + 1
return current.val
def addAtHead(self, val):
"""
:type val: int
:rtype: None
"""
return self.addAtIndex(0, val)
def addAtTail(self, val):
"""
:type val: int
:rtype: None
"""
return self.addAtIndex(self.size, val)
def addAtIndex(self, index, val):
"""
:type index: int
:type val: int
:rtype: None
"""
adder = ListNode(val=val)
if index < 0 or index > self.size:
return
else:
current = self.DummyHead
count = 0
while count < index:
current = current.next
count = count + 1
if index == self.size:
current.next = adder
adder.next = None
else:
adder.next = current.next
current.next = adder
self.size = self.size + 1
return
def deleteAtIndex(self, index):
"""
:type index: int
:rtype: None
"""
if index < 0 or index >= self.size:
return
else:
current = self.DummyHead
count = 0
while count < index:
current = current.next
count = count + 1
if index == self.size:
current.next = None
else:
current.next = current.next.next
self.size = self.size - 1
return
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
TODO:这题第一次写错了很多个地方,之后有空再试试;另外,也可以试试不用虚拟头节点的实现。
翻转链表
分为两种做法:
首先是双指针(实际需要三个指针):初始current指针指向head,previous和temp均置为空。流程如下:
- temp置为current的next
- current的next指向previous
- previous置为current
- current置为temp
- 重复以上步骤,直到current为空,此时previous为新的头节点
注意:3和4的顺序不可以颠倒,否则无法正常移动。
另一种做法是递归,大体逻辑和双指针类似,不过循环改为递归实现。
lc206:206. 反转链表
首先是双指针:
java
第一个版本报错:Error - Found cycle in the ListNode...
// 双指针法,错误Error - Found cycle in the ListNode...
public ListNode reverseList_0(ListNode head) {
ListNode current=head;
ListNode previous = new ListNode();
previous.next=current;
ListNode temp=head;
while (current != null){
temp=current.next;
current.next=previous;
previous=previous.next;
current=temp;
}
return previous;
}
检查发现,current.next = previous;一行与previous.next=current;构成了循环,对其修改如下:
// 双指针法,错误
public ListNode reverseList_1(ListNode head) {
ListNode current=head;
ListNode previous = new ListNode();
ListNode temp=head;
while (current != null){
temp=current.next;
current.next=previous;
previous=current;
current=temp;
}
return previous;
}
去除了previous.next=current;但是仍然报错,检查发现在于previous的定义上:
我们需要的previous初始的null是指previous初始为空指针,而我的语句导致previous初始时为一个空的ListNode对象而不是空指针。
最终改正如下:
// 双指针法,
public ListNode reverseList_2(ListNode head) {
ListNode current=head;
ListNode previous = null;
ListNode temp=head;
while (current != null){
temp=current.next;
current.next=previous;
previous=current;
current=temp;
}
return previous;
}
python版本:
# 双指针
def reverseList_0(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
current = head
previous = None # None和 ListNode()区别?
while current is not None:
temp = current.next
current.next = previous
previous = current
current = temp
return previous
搞懂了双指针之后,实现递归就很容易了。
java:
// 递归
// 主函数
public ListNode reverseList(ListNode head) {
return reverse(null,head);
}
// 递归函数
public ListNode reverse_0(ListNode previous, ListNode current){
if(current == null) return previous;
else{
ListNode temp = current.next;
current.next=previous;
previous=current;
current=temp;
return reverse_0(previous,current);
}
}
对递归部分的写法进行优化:
// 递归函数,写法优化
public ListNode reverse(ListNode previous, ListNode current){
if(current == null) return previous;
else{
ListNode temp = current.next;
current.next=previous;
return reverse(current,temp);
}
}
python
# 递归
# 递归函数
def reverse(self, previous, current):
if current is None: return previous
temp = current.next
current.next = previous
return self.reverse(current, temp)
# 主函数
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
return self.reverse(None, head)
注意写递归时不要忘记写递归中止条件!
本文由GarfieldTheOldCat原创,转载请标明
本文由GarfieldTheOldCat原创,转载请标明
总结
今天学习了链表这一种数据结构,其增删简单,遍历复杂,适合经常修改而较少遍历的数据
移除链表元素要注意头节点和尾节点
反转链表使用双指针或递归
Todo
lc707第一次写有错,有空复习一下,再试试别的写法
再想想ListNode previous = null;和ListNode previous = new ListNode();的区别。
本文由GarfieldTheOldCat原创,转载请标明dekkyandlappy-CSDN博客