基础篇(三)Node类数据结构的包接口开发

前言

本文以Node类数据结构(链表结构,singky linked node/doubly linked node)为例,围绕如何利用Python实现包接口以及抽象集合的开发展开介绍。本文面向于对OOP编程有一定基础的朋友,建议您在阅读本文章前参考学习文章 <面向对象编程>,如果您对【接口函数、模块、包、库 】等概念仍模糊,文章 Python数据结构之基础概念讲解(一)已给出了具象化的详细解释。

注:本系列文章参考自书籍 Fundamentals of Python Data Structure ,博客内容上如有错误,还请大家指出🙏


引入

链表结构包括单链表结构(singky linked node)和双链表结构(doubly linked node),相较于数组中数据项的连续存储方式,链表结构中数据项的逻辑顺序和内存中的顺序解耦,也就是说,要访问链表结构中的数据项只需通过一个给定项的地址和位置的链接,从堆栈空间内申请的指针即可寻址找到该数据项。这种内存的映射关系,我们称之为非连续性的内存(noncontiguous memory)

链表结构的基本单元是节点(node)👇

  • 单链表的节点包括了一个数据项(node.data)和到结构中的下一个节点的一个链接(node.next),单链表结构的用户通过沿着一个外部的头链接(head link)来访问第一个项。
  • 双链表的节点包括了==一个数据项(node.data)、到结构中的下一个节点的一个链接(node.next)、到结构中前一个节点的一个链接(node.previous),双链表结构的用户同样可以通过沿着一个外部的头链接(head link)来访问第一个项。
  • 头节点初始化为空,即未存储任何数据项;在链表结构中的最后一个节点没有下一个的指向,因而被设置为空链接(empty link)

在这里插入图片描述

👉一览自顶向下的设计流程:

Node类
LinkedBag
BagInterface
AbstractCollection

👉层级目录如下:


>DATA_STRUCTURES
	>arrays.py
	
	>node.py
	>node_test.py
	>testnode.py
		
	>linkedbag.py

	>baginterface.py
	>abstractcollection.py

	>testset.py
	

硬件资源/配置环境: MacBookPro12,1/VS Code


一、Array 类的准备

请确保在您的Python数据结构学习目录下已创建好arrays.py,因为在完成Node类的设计过程中需要用到Array类实例。👇

""" 
File: arrays.py

An array is like a list, but the client can use only [], len, iter and str 

To instantiate, use <variable> = Array(<capacity>, <optional fill value>)

The fill value is None by default 

"""

class Array(object):
    """ Represents an array """
    def __init__(self, capacity, fillValue = None):
        """ Capacity is the static size of the array. 
        fillValue is placed at each position """
        self._items = list()
        for count in range(capacity):
            self._items.append(fillValue)

    def __len__(self):
        """ -> The capacity of the array """
        return len(self._items)

    def __str__(self):
        """ -> The string representation of the array """
        return str(self._items)

    def __iter__(self):
        """ Support traversal with a for loop """
        return iter(self._items)
    
    def __getitem__(self, index):
        """ Subscript operator for access at index """
        return self._items[index]
    
    def __setitem__(self, index, newItem):
        """ Subscript operator for replacement at index """
        self._items[index] = newItem

使用命令 python <文件名> ,确保该文件在IDE中正常运行👇

在这里插入图片描述


二、单链表节点类的实现

接下来考虑我们可以用什么样的数据类型将链表结构表示出来:C++和C语言中有指针的定义、允许用户在不连续的内存映射关系中找到链表结构中的数据项,FORTRAN语言虽然没有指针的概念,但其可以通过两个数组分别存储数据项及其在内存中的物理地址以达到目的;而在Python中我们可以使用类的概念定义一个链表结构。

根据单链表结构特点,在node.py文件中定义一个单链表节点类如下,并创建三个节点(头节点、含有数据项A的节点、含有数据项B的节点):

"""
File: node.py

Node classes for one-way linked structures linked structures.
"""

class Node(object):

    def __init__(self, data, next = None):
        """Instantiates a Node with default next of None"""
        self.data = data
        self.next = next

# Just an empty link
node1 = None

# A node containing data and an empty link
node2 = Node("A", None)

# A node containing data and a link to node2
node3 = Node("B", node2)

创建一个node_test.py文件以测试👇

""" 
File: node_test.py

使用 Node 类创建一个单链表结构,并输出其内容
"""

from node import Node
from arrays import Array

head = None

# Add five nodes to the beginning of the linked structure
for count in range(1, 6):
    head = Node(count, head)
# Print the contents of the structure
while head != None:
    print(head.data)
    head = head.next        # 当显示数据时,按照与插入相反的顺序输出直至head指向None,最后节点被回收
    

""" 将一个填满的数组中的项转移到一个单链表结构中,这个操作保证输出时保持这些项的顺序不变,这与上述的靠近头节点插入相反 """
a = Array(5)
print("{0:=^20}".format("原来的数组"))
for i in range(len(a)):
    a[i] = i + 1
    print("%d " % a[i], end="")
print("\n")
print("{0:=^20}".format("转移后的数组"))
head = None
i = 0
while i < len(a):
    head = Node(a[i], head)
    i += 1
    print(head.data, end=" ")

输出为:

在这里插入图片描述


现在来实现基于单链表数据结构的常见操作:

  • 取长度
  • 遍历搜索数据项
  • 插入(头插法、尾插法、在任何位置插入)
  • 删除(从任意位置删除)
  • 以字符串的形式输出

testnode.py中实现上述功能 👇

"""
File: testnode.py

Add some operation to test the Node class.
"""

from node import Node

def length(head):
    """Returns the number of items in the linked structure
    referred to by head."""
    probe = head
    count = 0
    while probe != None:
        count += 1
        probe = probe.next
    return count
    
def search(head, targetItem):
    """Search for the item in the linked structure. 
    If found, print(pos), otherwise not found"""
    probe = head
    count = 0
    while probe != None and targetItem != probe.data:
        probe = probe.next
        count += 1
    if probe != None:
        if probe.data == targetItem:
            print("Find it! At position【%d】 of the linked structure\n" % count)
    else:
        print("【%d】is not in the linked structure\n" % targetItem)

def insert(index, newItem, head):
    """Inserts newItem at position is the linked structure
    referred to by head.  Returns a reference to the new
    structure."""
    if index <= 0:
        # newItem goes at the head
        head = Node(newItem, head)
    else:
        # Search for node at position index - 1 or the last position
        probe = head
        while index > 1 and probe.next != None:   
            probe = probe.next
            index -= 1
        # Insert new node after node at position index - 1 
        # or last position
        probe.next = Node(newItem, probe.next)
    return head

def pop(index, head):
    """Removes the item at index from the linked structure
    referred to by head and returns the tuple (head, item)
    Precondition: 0 <= index < length(head)"""
    if index < 0 or index >= length(head):
        raise IndexError("Index out of bounds")
    # Assumes that the linked structure has at least one item
    if index == 0:
        removedItem = head.data
        head = head.next
    else:
        # Search for node at position index - 1 or 
        # the next to last position
        probe = head
        while index > 1 and probe.next.next != None:
            probe = probe.next
            index -= 1
        removedItem = probe.next.data
        probe.next = probe.next.next
    return (head, removedItem)


def printStructure(head):
    """Prints the items in the structure referred to by head."""
    probe = head
    while probe != None:
        print(probe.data, end = " ")
        probe = probe.next
    print()


def main():
    """Tests modifications."""
    head = None

    head = insert(0, "1", head)
    print("After inserting a node at pos【0】:", end = " ")
    printStructure(head)

    (head, item) = pop(0, head)
    print("A node.data at pos【0】:", item, end = " ")
    printStructure(head)

    # Add five nodes to the beginning of the linked structure
    for val in range(1, 6):
        head = Node(val, head)
    printStructure(head)
    print(length(head))
    search(head, 8)
    
    (head, item) = pop(0, head)
    print("【5 4 3 2 1 1】->", item, end = " ")
    printStructure(head)
    
    (head, item) = pop(length(head) - 1, head)
    print("Expect 【1 4 3 2】:", item, end = " ")
    printStructure(head)

    (head, item) = pop(1, head)
    print("Expect 【3 4 2】:", item, end = " ")
    printStructure(head)

    pop(4, head)        # raise ERROR
    

if __name__ == "__main__": 
	main()

输出为:

在这里插入图片描述


三、双链表节点类的实现

根据双链表结构特点,在node.py文件中定义一个双链表节点类如下:

"""
File: node.py

Node classes for one-way linked structures and two-way
linked structures.
"""

class Node(object):

    def __init__(self, data, next = None):
        """Instantiates a Node with default next of None"""
        self.data = data
        self.next = next

class TwoWayNode(Node):

    def __init__(self, data, previous = None, next = None):
        Node.__init__(self, data, next)
        self.previous = previous

# Just an empty link
node1 = None

# A node containing data and an empty link
node2 = Node("A", None)

# A node containing data and a link to node2
node3 = Node("B", node2)

因为我们在testnode.py中定义的方法统一适用于链表结构,所以只需在其中添加一个创建双链表结构的方法即可套用之前设计的功能函数👇

"""
File: testnode.py

Add a makeTwoWay function.

Tests the Node class.
"""

from node import Node, TwoWayNode

def length(head):
    """Returns the number of items in the linked structure
    referred to by head."""
    probe = head
    count = 0
    while probe != None:
        count += 1
        probe = probe.next
    return count
 
def search(head, targetItem):
    """Search for the item in the linked structure. 
    If found, print(pos), otherwise not found"""
    probe = head
    count = 0
    while probe != None and targetItem != probe.data:
        probe = probe.next
        count += 1
    if probe != None:
        if probe.data == targetItem:
            print("Find it! At position【%d】 of the linked structure\n" % count)
    else:
        print("【%d】is not in the linked structure\n" % targetItem)
   
def insert(index, newItem, head):
    """Inserts newItem at position is the linked structure
    referred to by head.  Returns a reference to the new
    structure."""
    if index <= 0:
        # newItem goes at the head
        head = Node(newItem, head)
    else:
        # Search for node at position index - 1 or the last position
        probe = head
        while index > 1 and probe.next != None:   
            probe = probe.next
            index -= 1
        # Insert new node after node at position index - 1 
        # or last position
        probe.next = Node(newItem, probe.next)
    return head

def pop(index, head):
    """Removes the item at index from the linked structure
    referred to by head and returns the tuple (head, item)
    Precondition: 0 <= index < length(head)"""
    if index < 0 or index >= length(head):
        raise IndexError("Index out of bounds")
    # Assumes that the linked structure has at least one item
    if index == 0:
        removedItem = head.data
        head = head.next
    else:
        # Search for node at position index - 1 or 
        # the next to last position
        probe = head
        while index > 1 and probe.next.next != None:
            probe = probe.next
            index -= 1
        removedItem = probe.next.data
        probe.next = probe.next.next
    return (head, removedItem)

def makeTwoWay(head):
    """Creates and returns a doubly linked structure that
    contains the items in the structure referred to by head."""
    if head is None:
        # Empty structure
        return None
    else:
        # Set the first node
        twoWayHead = TwoWayNode(head.data)
        twoWayProbe = twoWayHead
        probe = head
        # Set remaining nodes, if any
        while probe.next != None:
            newNode = TwoWayNode(probe.next.data, twoWayProbe)
            twoWayProbe.next = newNode
            twoWayProbe = newNode
            probe = probe.next
        return twoWayHead

def printStructure(head):
    """Prints the items in the structure referred to by head."""
    probe = head
    while probe != None:
        print(probe.data, end = " ")
        probe = probe.next
    print()


def main():
    """Tests modifications."""
    head = None

    # Add five nodes to the beginning of the linked structure
    for count in range(1, 6):
        head = Node(count, head)
    
    print("5 4 3 2 1:", end = " ")
    printStructure(head)
    
    print("5 4 3 2 1:", end = " ")
    twoWayHead = makeTwoWay(head)
    printStructure(twoWayHead)


if __name__ == "__main__": 
	main()


四、LinkedBag 类的实现

对照ArrayBag类的定义,将基于Node类的链表结构基本操作打包至文件linkedbag.py中。如下所示:

"""
File: linkedbag.py

Add clone method to linkedbag.(compare to the arraybag)
"""

from node import Node

class LinkedBag(object):
    """A link-based bag implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self.items = None
        self.size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

    # Accessor methods
    def isEmpty(self):
        """Returns True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    
    def __len__(self):
        """-Returns the number of items in self."""
        return self.size

    def __str__(self):
        """Returns the string representation of self."""
        return "{" + ", ".join(map(str, self)) + "}"

    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = self.items
        while not cursor is None:
            yield cursor.data
            cursor = cursor.next

    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        result = LinkedBag(self)
        for item in other:
            result.add(item)
        return result

    def clone(self):
        """Returns a copy of self."""
        return LinkedBag(self)

    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other) or \
           len(self) != len(other):
            return False
        for item in self:
            if not item in other:
                return False
        return True

    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        self.size = 0
        self.items = None

    def add(self, item):
        """Adds item to self."""
        self.items = Node(item, self.items)
        self.size += 1

    def remove(self, item):
        """Precondition: item is in self.
        Raises: KeyError if item in not in self.
        Postcondition: item is removed from self."""
        # Check precondition and raise if necessary
        if not item in self:
            raise KeyError(str(item) + " not in bag")
        # Search for the node containing the target item
        # probe will point to the target node, and trailer
        # will point to the one before it, if it exists
        probe = self.items
        trailer = None
        for targetItem in self:
            if targetItem == item:
                break
            trailer = probe
            probe = probe.next
        # Unhook the node to be deleted, either the first one or one
        # thereafter
        if probe == self.items:
            self.items = self.items.next
        else:
            trailer.next = probe.next
        # Decrement logical size
        self.size -= 1
        

👉现在我们在文件linkedbag_test.py中对LinkedBag类进行测试,如下:
注:我为了省事,就将arraybag_test里的代码搬了过来,改成了LinkedBag类进行测试,测试的类方法不全、仅供参考。读者可自行编写代码测试

"""
File: linkedbag_test.py

"""

from linkedbag import LinkedBag

a = LinkedBag([1, 2, 3, 4])
b = a       # 实际上是深拷贝,即b是a在的视图,对a的修改都会映射到b中
c = LinkedBag([1, 2, 3, 4])  # Python在堆中另外申请了一个内存空间以存储对象c
print("Setting items when class being created: ", a)          

a.__init__([22, 33, 44])    # 初始化赋值后会覆盖之前在创建对象时传入的列表,即恢复了出厂设置
print("\nAfter Initialing: ", a)

a.__add__(b)    # 该方法不会直接将b中的值添加到a中,该类方法只起到验证可以将b对象添加到a对象的作用
print("\n\'b __Add__ a?\' Now a: ", a)
print("\nCan b Add into a? Now a: ", a.__add__(b))

print("\na Equals To b: ", a.__eq__(b))
print("\na Equals To c: ", a.__eq__(c))

a.add(4)
print("\nAfter adding【4】to a: ", a)
print("\nRemove an item not existing: \n", c.remove(5))

输出为:

在这里插入图片描述


五、抽象集合类的实现

Ok,至此,暂停一下关于链表数据结构的包开发,结合先前在文章 基础篇(一)Array类数据结构的包接口开发 中实现的arraybag.py和抽象出来适用于所有包的baginterface.py,我们来总结出一个适用于所有数据类型的抽象集合及其操作,在文件abstractcollection.py中完成 AbstractCollection类 的创建:

"""
File: abstractcollection.py

"""

class AbstractCollection(object):
    """An abstract collection implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self.size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

    # Accessor methods
    def isEmpty(self):
        """Returns True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    
    def __len__(self):
        """Returns the number of items in self."""
        return self.size

    def __str__(self):
        """Returns the string representation of self."""
        return "[" + ", ".join(map(str, self)) + "]"

    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        result = type(self)(self)
        for item in other:
            result.add(item)
        return result

    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other) or \
           len(self) != len(other):
            return False
        otherIter = iter(other)
        for item in self:
            if item != next(otherIter):
                return False
        return True

    def count(self, item):
        """Returns the number of instances of item in self."""
        total = 0
        for nextItem in self:
            if nextItem == item:
                total += 1
        return total


六、制作测试包

在文件testset.py中添加如下所示的代码:

"""
File: testset.py

A tester program for set implementations.
"""

from arrayset import ArraySet
from linkedbag import LinkedBag

def test(setType):
    """Expects a set type as an argument and runs some tests
    on objects of that type."""
    lyst = [2013, 61, 1973]
    print("The list of items added is:", lyst)
    s1 = setType(lyst)
    print("Expect 3:", len(s1))
    print("Expect the set's string:", s1)
    print("Expect True:", 2013 in s1)
    print("Expect False:", 2012 in s1)
    print("Expect the items on separate lines:")
    for item in s1:
        print(item)
    s1.clear()
    print("Expect {}:", s1)
    s1.add(25)
    s1.remove(25)
    print("Expect {}:", s1)
    s1 = setType(lyst)
    s2 = setType(s1)
    print("Expect True:", s1 == s2)
    print("Expect False:", s1 is s2)
    print("Expect one of each item:", s1 + s2)
    for item in lyst:
        s1.remove(item)
    print("Expect {}:", s1)

# test(ArraySet)
test(LinkedBag)

输出为:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据结构与算法是计算机科学和软件工程领域中非常重要的基础知识。数据结构是指组织和存储数据的方式,而算法则是解决问题的一系列步骤。在这里,我将简要介绍数据结构与算法的基础知识。 1. 数组(Array):是一种线性数据结构,可以存储相同型的元素。数组的特点是可以通过索引快速访问元素。 2. 链表(Linked List):也是一种线性数据结构,不同于数组,链表的元素在内存中可以不连续存储,每个元素含一个指向下一个元素的指针。 3. 栈(Stack):是一种后进先出(LIFO)的数据结构,只能在栈的一端进行插入和删除操作。 4. 队列(Queue):是一种先进先出(FIFO)的数据结构,只能在队列的一端进行插入操作,在另一端进行删除操作。 5. 树(Tree):是一种非线性数据结构,由节点和边组成。树的一个节点可以有多个子节点。 6. 图(Graph):也是一种非线性数据结构,由节点和边组成。不同于树,图中的节点之间可以有多个连接。 7. 排序算法:常见的排序算法括冒泡排序、选择排序、插入排序、快速排序、归并排序等,它们用于将一组元素按照特定的顺序进行排列。 8. 查找算法:常见的查找算法括线性查找、二分查找等,它们用于在一组元素中查找特定的值。 以上只是数据结构与算法的基础知识,还有许多其他重要的概念和算法,如哈希表、堆、图算法等。掌握数据结构与算法的基础知识可以帮助我们更好地理解和解决实际的计算机问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Larissa857

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值