Python-数据结构与算法(六、链表)

保证一周更两篇吧,以此来督促自己好好的学习!代码的很多地方我都给予了详细的解释,帮助理解。好了,干就完了~加油!
声明:本python数据结构与算法是imooc上liuyubobobo老师java数据结构的python改写,并添加了一些自己的理解和新的东西,liuyubobobo老师真的是一位很棒的老师!超级喜欢他~
如有错误,还请小伙伴们不吝指出,一起学习~
No fears, No distractions.

链表(LinkList)

数据存储在“节点(Node)”中,
节点包含的内容:数据和下一个节点的指针
链表额增删改查的时间复杂度全部是O(n)级别,但是在链表头的操作的时间复杂度是O(1)的
优点:真正的动态,不需要考虑容量的问题
缺点:丧失了随机访问的能力,因为每一个节点通过next指针穿插起来,不是连续存储的

和数组对比

数组最好用于索引有语意的情况。例如scores[2]
是线性结构
数组最大的优点:支持快速查询

链表不适合用于索引有语意的情况
不是线性结构
链表最大的优点:真正的动态,不浪费存储空间

实现

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data:             2018-09-27
# Python version:   3.6

class Node:
    def __init__(self, elem_=None, next_=None):
        """
        节点类构造函数,注意节点是链表的成员,一个链表可以有很多个节点
        :param elem_: 节点所带的元素,默认为None
        :param next_: 指向下一个节点的标签(在python中叫做标签)
        """
        self.elem = elem_
        self.next = next_  		# 连接下一个节点的标签

    def printNode(self):
        """打印Node"""
        print(self.elem, end='  ')  # 就打印一下元素的值


class LinkedList:
    def __init__(self):
        """
        链表构造函数
        """
        self._dummyhead = Node()        # 虚拟头结点,作用巨大,把他当成不属于链表的哨兵节点就好
        # 如果没有dummyhead,在链表头部插入与删除操作将要特殊对待,因为找不到待操作节点的前一个节点
        # 而有了虚拟头结点后,就不存在这种情况了
        self._size = 0       # 容量

    def getSize(self):
        """
        获得链表中节点的个数
        :return: 节点个数
        """
        return self._size

    def isEmpty(self):
        """
        判断链表是否为空
        :return: bool值,空为True
        """
        return self.getSize() == 0

    def add(self, index, elem):
        """
        普适性的插入功能
        时间复杂度:O(n)
        :param index: 要插入的位置(注意index对于用户来说也是从零开始的,这里我没做更改)
        :param elem: 待插入的元素
        """
        if index < 0 or index > self._size:     # 有效性检查
            raise Exception('Add failed. Illegal index')
        prev = self._dummyhead      # 从虚拟头结点开始,注意虚拟头结点不属于链表内的节点,当做哨兵节点来看就好了
        for i in range(index):      # 往后撸,直到待操作节点的前一个节点
            prev = prev.next
        prev.next = Node(elem, prev.next)
        # 先看等式右边,创建了一个节点对象,携带的元素是elem,指向的元素就是index处的节点,即
        # 现在有一个新的节点指向了index处的节点
        # 并将它赋给index节点处的前一个节点的next,是的prev的下一个节点就是这个新节点,完成拼接操作
        # 可以分解成三句话:  temp = Node(elem); temp.next = prev.next; prev.next = temp
        # 画个图就很好理解啦
        self._size += 1 # 维护self._size

    def addFirst(self, elem):
        """
        将elem插入到链表头部
        时间复杂度:O(1)
        :param elem: 要插入的元素
        """
        self.add(0, elem)       # 直接点用self.add

    def addLast(self, elem):
        """
        链表尾部插入元素elem
        时间复杂度:O(n)
        :param elem: 待插入的元素
        """
        self.add(self._size, elem)      # 调用self.add

    def remove(self, index):
        """
        删除第index位置的节点
        时间复杂度:O(n)
        :param index: 相应的位置,注意从零开始
        :return: 被删除节点的elem成员变量
        """
        if index < 0 or index >= self.getSize():    # index合法性检查
            raise Exception('Remove falied. Illegal index')
        pre = self._dummyhead       # 同样的,要找到待删除的前一个节点,所以从dummyhead开始
        for i in range(index):      # 往后撸index个节点
            pre = pre.next
        retNode = pre.next          # 此时到达待删除节点的前一个节点,并用retNode对待删除节点进行标记,方便返回elem
        pre.next = retNode.next     # pre的next直接跨过待删除节点直接指向待删除节点的next,画个图就很好理解了
        retNode.next = None         # 待删除节点的next设为None,让它完全从链表中脱离,使得其被自动回收
        self._size -= 1             # 维护self._size
        return retNode.elem         # 返回被删除节点的elem成员变量

    def removeFirst(self):
        """
        删除第一个节点(index=0)
        时间复杂度:O(1)
        :return: 第一个节点的elem成员变量
        """
        return self.remove(0)   # 直接调用self.remove方法

    def removeLast(self):
        """
        删除最后一个节点(index=self._size-1)
        时间复杂度:O(n)
        :return: 最后一个节点的elem成员变量
        """
        return self.remove(self.getSize() - 1)		# 直接调用self.remove方法

    def get(self, index):
        """
        获得链表第index位置的值
        时间复杂度:O(n)
        :param index: 可以理解成索引,但并不是索引!
        :return: 第index位置的值
        """
        if index < 0 or index >= self.getSize():    # 合法性检查
            raise Exception('Get failed.index is Valid, index require 0<=index<=self._size-1')
        cur = self._dummyhead.next      # 初始化为第一个有效节点
        for i in range(index):          # 执行index次
            cur = cur.next              # 往后撸,一直到第index位置
        return cur.elem

    def getFirst(self):
        """
        获取链表第一个节点的值
        时间复杂度:O(1)
        :return: 第一个节点的elem
        """
        return self.get(0)      # 调用self.add方法

    def getLast(self):
        """
        获取链表最后一个节点的值
        时间复杂度:O(n)
        :return: 最后一个节点的elem
        """
        return self.get(self.getSize() - 1)		# 调用self.add方法

    def set(self, index, e):
        """
        把链表中第index位置的节点的elem设置成e
        时间复杂度:O(n)
        :param index: 链表中要操作的节点的位置
        :param e:  将要设为的值
        """
        if index < 0 or index >= self.getSize():        # 合法性检查
            raise Exception('Set failed.index is Valid, index require 0<=index<=self._size-1')
        cur = self._dummyhead.next  # 从第一个元素开始,也就是dummyhead的下一个节点
        for i in range(index):      # 往后撸,直到要操作的节点的位置
            cur = cur.next
        cur.elem = e        # 设置成e即可

    def contains(self, e):
        """
        判断链表的节点的elem中是否存在e
        时间复杂度:O(n)
        由于并不存在索引,所以只能从头开始找,一直找。。。如果到尾还没找到就是不存在
        :param e: 要判断的值
        :return: bool值,存在为True
        """
        cur = self._dummyhead.next  # 将cur设置成第一个节点
        while cur != None:      # 只要cur有效,注意这个链表的最后一个节点一定是None,因为dummyhead初始化时next就是None,这个
            # 通过链表的这些方法只会往后移动,一直处于最末尾
            if cur.elem == e:   # 如果相等就返回True
                return True
            cur = cur.next      # 否则就往后撸
        return False            # 到头了还没找到就返回False

    def printLinkedList(self):
        """对链表进行打印操作"""
        cur = self._dummyhead.next
        print('表头:', end=' ')
        while cur != None:
            cur.printNode()
            cur = cur.next
        print('\nSize: %d' % self.getSize())

三、测试

import linkedlist       # Linkedlist写在这个py文件中
import numpy as np
np.random.seed(7)

test = linkedlist.LinkedList()
print(test.getSize())
print(test.isEmpty())
test.addFirst(6)
for i in range(13):
    test.addLast(np.random.randint(11))
test.printLinkedList()
test.add(10, 'annihilation7')
test.printLinkedList()
print(test.getSize())
print(test.get(2))
print(test.getLast())
print(test.getFirst())
test.set(0, 30)
test.printLinkedList()
print(test.contains(13))
print(test.remove(8))
test.printLinkedList()
print(test.removeFirst())
test.printLinkedList()
print(test.removeLast())
test.printLinkedList()

四、输出

0
True
表头: 6  4  9  6  3  3  7  7  9  7  8  9  10  10  
Size: 14
表头: 6  4  9  6  3  3  7  7  9  7  annihilation7  8  9  10  10  
Size: 15
15
9
10
6
表头: 30  4  9  6  3  3  7  7  9  7  annihilation7  8  9  10  10  
Size: 15
False
9
表头: 30  4  9  6  3  3  7  7  7  annihilation7  8  9  10  10  
Size: 14
30
表头: 4  9  6  3  3  7  7  7  annihilation7  8  9  10  10  
Size: 13
10
表头: 4  9  6  3  3  7  7  7  annihilation7  8  9  10  
Size: 12

五、典型习题

习题(leetcode-203)

Remove all elements from a linked list of integers that have value val.
Example:
Input: 1->2->6->3->4->5->6, val = 6
Output: 1->2->3->4->5
就是删除链表中值为val的所有节点。

解决方案

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        dummyhead = ListNode(-1)        # 创建虚拟头结点,此时就不需要针链表头的删除激进行特殊的处理了,非常方便
        dummyhead.next = head

        pre = dummyhead
        while pre.next:				# pre下一个Node有效
            if pre.next.val == val:
                pre.next = pre.next.next        # 等于val就略过下一个节点
            else:
                pre = pre.next      # 否则往后撸
        return dummyhead.next       # 注意dummyhead.next才是链表头

六、总结

链表的所有操作只有在表头的操作其时间复杂度才是O(1)的,正好与数组相反,数组只有在尾部的操作的时间复杂度才O(1)的。
链表和数组既然在功能上如此相近,在性能上谁优谁劣?请看后面的章节吧O(∩_∩)O

若有还可以改进、优化的地方,还请小伙伴们批评指正!

猜你喜欢

转载自blog.csdn.net/Annihilation7/article/details/82875275
今日推荐