python数据结构与算法学习笔记(一)

好久没有写了,今天来开个新坑。最近在看数据结构与算法,因为比较熟悉python语言,就选择了它的python语言版本来学习,现在就记录一下。

无序列表的实现

我们需要构造一个链表来实现无序列表。链表,顾名思义是前后两项之间有连接的数据结构,它不受限与连续的内存空间,而是通过每一个结点的指针域来找到它的下一个结点(后继),因此链表的增删十分方便,更改指针域指针的指向即可,而不用调整大量数据的存储位置。

构造结点类

首先需要构造链表中的基本元素:结点(Node)

class Node:
    def __init__(self,initdata):
        self.data = initdata
        self.next = None
    def getData(self):
        return self.data
    def getNext(self):
        return self.next
    def setData(self,newdata):
        self.data = newdata
    def setNext(self,newnext):
        self.next = newnext

每个结点包括两个组成部分,数据(data)和指向它的后继的指针(next)。

构造无序列表类

接着就是无序列表(UnorderedList)本体的构造

class UnorderedList:
    def __init__(self):
        self.head = None

在构造链表时,头指针(head)是必须的,它指向链表中的第一个结点,如果是空链表就为None。有时,为了操作的统一,链表还会有头结点,头指针指向它,它的后继是链表的第一个结点,而它的数据域是无意义的,可以存放链表长度等公共信息。一个链表头指针必须有但头结点可以没有。

定义方法

接下来我们来定义无序列表的各种方法。

  • isEmpty

判断无序列表是否为空

def isEmpty(self):
    return self.head == None
  • add

向无序列表中增加元素

def add(self,item):
    temp = Node(item)
    temp.setNext(self.head)
    self.head = temp

由于是无序列表,新增结点的位置无关紧要,并且链表本身维护了一个头指针,指向链表头部,所以直接在链表头部新增结点最简单。

注意这里必须先设定新增结点的后继为第一个结点,再更改头指针的指向,否则会导致链表其他结点丢失,无法访问。

  • size

返回无序列表的元素个数

def size(self):
    current = self.head
    count = 0
    while current != None:
        count += 1
        current = current.getNext()
    return count

最后一个结点的next是None,所以后继为None时表示已经到达列表尾部,循环结束。

  • search

查询无序列表中是否有指定元素

def search(self,item):
    current = self.head
    found = False
    while current != None and not found:
        if current.getData() == item:
            found = True
        else:
            current = current.getNext()
    return found
  • remove

移除无序列表中的指定元素

def remove(self,item):
    current = self.head
    previous = None
    found = False
    while not found:
        if current.getData() == item:
            found = True
        else:
            previous = current
            current = current.getNext()
    if previous == None:
        self.head = current.getNext()
    else:
        previous.setNext(current.getNext())

删除操作需要找到指定的元素,然后把它的前驱和它的后继连接起来,由于链表不能通过当前结点得到它的前驱,所以这里设置了一个previous变量,用来存储当前结点的前驱。

循环结束时如果previous仍然时None,说明我们要找的元素就在第一个位置,此时不能把它的前驱(previous)和后继(getNexTt)相连,因为此时previous是NoneType,并不是结点类。此时我们只要把链表头指针的指向设置为当前结点的后继即可。

这里我们假定指定的元素在无序列表中存在。

  • index

查询指定元素在无序列表中的索引

def index(self,item):
    i = 0
    current = self.head
    while current != None:            
        if current.getData() == item:
            break
        else:
            current = current.getNext()
            i+=1
    return i

这里依然假定指定的元素在无序列表中存在。

  • insert

在无序列表的指定位置插入指定元素

def insert(self,pos,item):
    temp = Node(item)
    i = 0
    current = self.head
    if pos == 0:
        self.add(item)
    else:
        while current != None:
            if i+1 == pos:
                temp.setNext(current.getNext())
                current.setNext(temp)
                break
            else:
                current = current.getNext()
                i += 1

这里通常的做法是和remove操作一样,设置一个previous,在找到指定位置后,把temp设为当前结点的前驱,previous的后继。也可以像我这样,做个小改动,把pos为0的情况单独处理,定位之后把temp设为当前结点的后继的前驱,当前结点的后继。注意这时需要向后多判断一个位置,即if i+1 == pos:

这里假定指定的位置不会超出无序列表的大小。

  • append

在无序列表尾部添加元素

通常的做法是以后继为None为终止条件循环找到链表尾,把新增结点设为尾部结点的后继。这样时间复杂度为O(n)

一种偷懒的做法是通过size()方法得到整个无序列表的长度,在通过insert()方法,以长度减1为参数在列表尾部插入新增元素,但这样更加耗时,因为有两个时间复杂度为O(n)的操作。

但是如果在无序列表类中多维护一个尾结点的属性,同时小改动一下add方法,就会简单很多。

class UnorderedList:
    def __init__(self):
        self.head = None
        self.rear = None

    def add(self,item):
        temp = Node(item)
        if self.rear == None:
            self.rear = temp
        temp.setNext(self.head)
        self.head = temp

    def append(self,item):
        temp = Node(item)
        if self.rear == None:
            self.add(item)
        else:
            self.rear.setNext(temp)
            self.rear = temp

这时append方法的时间复杂度仅为O(1)

rear为None表示列表为空,此时add方法需要更新rear属性的值,并且append操作等价于add操作。

同时需要注意在append方法中设定rear的后继和更新rear的顺序,顺序相反会导致添加结点失败。

  • pop

从无序列表中删除并返回指定位置的元素,如果不指定位置,则删除并返回最后一个元素

def pop(self,pos = -1):
    default = False
    if pos == -1:
        pos = self.size()-1
        default = True
    i = 0
    current = self.head
    previous = None
    while current != None and i<pos:
        previous = current
        current = current.getNext()
        i += 1
    if previous == None:
        self.head = current.getNext()
    else:
        previous.setNext(current.getNext())
    if default:
        self.rear = previous
    return current.getData()

和删除操作一样,这里需要新增一个previous变量来记录当前结点的前驱。定位后previous仍然为None时说明当前时链表头部,更改头指针的指向即可。

这里可以体现出python动态语言的方便之处,如果是其他静态语言,我们需要定义两个函数pop(self)pop(self,pos)用来实现两个不同情况下的操作。这里我们只需要将pos参数设置默认为-1来表示不指定位置在尾部操作的情况,因为指定的位置不可能是-1。

注意不指定位置参数在尾部操作时需要更新尾结点rear属性。

这样我们就基本实现了无序列表的构造。但是python内置的list并不是这样的,从方法的时间复杂度上可以看出,我们的pop()方法是O(n),但python内置list的pop()方法是O(1)

posted @ 2020-09-13 18:04  Baoding_LIU  阅读(207)  评论(0)    收藏  举报