提到链表一般指的是单链表,这种链表由节点组成,节点包括存放数据的数据域和指向下一个节点的指针域。这样的链表有两个特点:

  • 头指针head永远指向第一个节点(头指针本身不是节点)
  • 最后一个节点的指针永远指向空

因此,首先需要定义节点类,该类包括两个数据成员,即数据data和指向下一个节点的指针pt。在创建一个新节点时,其数据应赋值为空,指针应指向空,因此在初始化函数传入了默认值。

class Node:
    def __init__(self, data=None, pt=None):
        self.data = data
        self.pt = pt

然后,建立链表类及其相关的操作,第一个问题就是链表该如何初始化,也就是一个不包含任何节点的空链表应该是怎样的?

答:空链表只需要一个头指针,将该指针指向空;为了方便创建length变量记录链表的长度,初始值为0。

class LinkedList:
    # 链表初始化
    def __init__(self):
        self.length = 0
        self.head = None
    # 返回链表长度的方法
    def list_length(self):
        return self.length

链表的插入

在链表的头部插入

“巧妇难为无米之炊”,不管在哪插首先得有节点;然后才是把节点链接入链表。因此头插可以分三步进行:

  1. 创建新节点,并将待插入的数据存入新节点
  2. 将新节点的指针指向头结点
  3. 将头指针指向新节点

注意:步骤2和步骤3不可以调换顺序,头指针存着头节点的地址,如果先将头指针指向了新节点,相当于头节点就“失联”了,就没办法将新节点链接到头结点了。

# Insert at beginning
def insert_at_begin(self, data):
    new_node = Node(data, )  # step1
    new_node.pt = self.head  # step2
    self.head = new_node  # step3
        
    self.length += 1

显然,当链表为空,该方法仍然有效,其时间复杂度为O(1)。

在链表的中间插入

第一步仍然是创建新节点并存入数据,假设考虑在插入位置pos后面进行插入,则必须先找到指向插入位置节点的指针,然后将新节点和插入位置节点的下一个节点链接起来,再把插入位置节点和新节点链接起来。因此,中间插应分四部进行:

  1. 创建新节点,并将待插入的数据存入新节点
  2. 找到指向插入位置节点的指针
  3. 新节点的指针指向插入位置节点的下一个节点
  4. 插入位置节点指针指向新节点
# Insert at middle   
def insert_at_mid(self, data, pos):
    new_node = Node(data, )  # step1
    current = self.head  # step2
    for i in range(pos-1):
        current = current.pt
    new_node.pt = current.pt  # step3
    current.pt = new_node  # step4
  • 为什么循环pos-1次?因为从第一个节点到第pos个节点向前移动pos-1次
  • 是空链表能运行吗?不能,因为此时循环将不执行,current指向空,不存在current.pt
  • 时间复杂度多少?O(n)

为了使得方法在空表时也能执行,进一步修改:

# Insert at middle
def insert_at_mid(self, data, pos):
    if pos < 0 or pos > self.length:
        print("Error: please input another pos")
    elif pos == 0:
        self.insert_at_begin(data)
    else:
        new_node = Node(data, )  # step1
        current = self.head  # step2
        for i in range(pos-1):
            current = current.pt
        new_node.pt = current.pt  # step3
        current.pt = new_node  # step4
        self.length += 1

在链表的尾部插入

第一步仍然是创建新节点并存入数据,然后是找到指向尾节点的指针,尾节点的指针指向新节点。因此,尾插分三步进行:

  1. 创建新节点,并将待插入的数据存入新节点
  2. 找到指向尾节点的指针
  3. 尾节点的指针指向新节点

注意:新节点默认指针指向空

# Insert at the end
def insert_at_end(self, data):
    new_node = Node(data, )  # step1
    current = self.head  # step2
    for i in range(self.length-1):
        current = current.pt
    current.pt = new_node  # step3
    self.length += 1

同样地,时间复杂度为O(n)。当链表为空时,方法失效,因此进一步改进:

# Insert at the end
def insert_at_end(self, data):
    if self.length == 0:
        self.insert_at_begin(data)
    else:
        new_node = Node(data, )  # step1
        current = self.head  # step2
        for i in range(self.length-1):
            current = current.pt
        current.pt = new_node  # step3
        self.length += 1

链表的删除

删除操作和插入操作本质上是类似的,因此不再考虑特殊情况而只考虑一般情况,重点掌握逻辑。

在链表头部删除

python由于不需要自己手动释放内存,因此步骤相对其它语言相对简单。

其它语言的流程:

  1. 新建temp变量指向头指针指向的节点
  2. 头指针指向下一个节点
  3. delete temp释放内存

Python流程:

  1. 头指针指向下一个节点

实现

def delete_from_begin(self):
    self.head = self.head.pt  # step1
    self.length -= 1

在链表中间删除

其它语言的流程:

  1. 找到待删除节点的前一个节点的指针
  2. 新建temp变量存储待删除节点的指针
  3. 将待删除节点的前一个节点的指针指向待删除节点的下一个节点
  4. 释放待删除节点的内存

Python流程:

  1. 找到待删除节点的前一个节点的指针
  2. 将待删除节点的前一个节点的指针指向待删除节点的下一个节点

实现

def delete_from_mid(self, pos):
    current = self.head  # step1
    for i in range(pos-2):
        current = current.pt
    current.pt = current.pt.pt  # step2
    
    self.length -= 1

在链表尾部删除

其它语言流程:

  1. 找到指向待删除节点的前一个节点的指针
  2. 新建temp变量存储指向待删除节点的指针
  3. 将待删除节点的前一个节点指针指向空
  4. 释放待删除节点的内存

Python流程:

  1. 找到指向待删除节点的前一个节点的指针
  2. 将待删除节点的前一个节点指向空

实现

def delete_from_end(self):
    current = self.head  # step1
    for i in range(self.length-2):
        current = current.pt
    current.pt = None  # step2
    
    self.length -= 1

链表的清空

同样,因为python可以自动收集内存,因此链表的清空极为简单,流程如下:

  1. 头指针指向空

实现

def clear(self):
    self.head = None  # step1
    
    self.length = 0