python数据结构学习笔记(七)

  • 7 Linked Lists

    • 7.1 singly linked list
    • 7.2 circular linked lists
    • 7.4 the positional list ADT
    • 7.5 sorting a positional list
    • 7.6 链表与数组实现的序列比较

7 Linked Lists

7.1 singly linked list

单向链表,就是一系列的结点组成一个线性集合。里面每个结点存储有一个元素的引用,还会指向链表的下一个元素. 最后一个元素指向None.

链表的第一个和最后一个结点称为链表的头部和尾部。从头部开始不断访问下一个next可以遍历这个链表。为了能访问 链表,必须保证有头部的引用。

链表的一个特点使可以很方便的在头部和尾部增加新的结点。add_first在头部增加一个新结点,并更新头部指针。

add_first(L, e):
    newest = Node(e)
    newest.next = L.head
    L.head = newest
    L.size = L.size + 1

在尾部增加结点并更新tail

add_last(L, e):
    newest = Node(e)
    newest.next = None
    L.tail.next = newest
    L.tail = newest
    L.size = L.size + 1

之前使用list实现的stack和queue在插入和删除操作上有摊还O(1)的时间,但是在个别插入上可能时间比较长。 使用链表来实现则可以所有操作都达到O(1)的时间。

class Empty(Exception):
    pass

class LinkedStack:
    class _Node:
        """lightweight, nonpublic class for storing a singly linked node."""
        __slots__ = ‘_element‘, ‘_next‘

        def __init__(self, element, next):
            self._element = element
            self._next = next

    def __init__(self):
        self._head = None
        self._size = 0

    def __len__(self):
        return self._size

    def is_empty(self):
        return self._size == 0

    def push(self, e):
        self._head = self._Node(e, self._head)
        self._size += 1

    def top(self):
        if self.is_empty():
            raise Empty(‘stack is empty‘)
        return self._head._element

    def pop(self):
        if self.is_empty():
            raise Empty(‘stack is empty‘)
        answer = self._head._element
        self._head = self._head._next
        self._size -= 1
        return answer

链表实现队列的情况类似,需要注意的是队列需要维护多一个成员_tail,因为队列可以在末尾插入新的元素。 在空的队列插入第一个元素的时候要注意同时还要修改_head来指向这第一个元素。同时如果删除一个元素刚好 队列为空的时候也需要注意,修改_tail来指向None.

7.2 circular linked lists

将链表最后一个元素的next指向head,就可以形成一个环。

有时候在队列中我们取出第一个元素,进行操作后又要放回队列的末尾,实现不断循环的重复操作,这时候 就可以使用循环链表来实现队列。

队列只需要维护一个tail引用,需要head的时候访问tail.next即可。而需要取出队列头部的一个元素放到尾部的时候, 不需要真正的改变数据结构,只要将tail = tail.next就等于将第一个元素放到尾部。

下面用循环链表模拟一个循环队列:

class CircularQueue:
    class _Node:
        """lightweight, nonpublic class for storing a singly linked node."""
        __slots__ = ‘_element‘, ‘_next‘

        def __init__(self, element, next):
            self._element = element
            self._next = next

    def __init__(self):
        self._tail = None
        self._size = 0

    def __len__(self):
        return self._size

    def is_empty(self):
        return self._size == 0

    def first(self):
        if self.is_empty():
            raise Empty(‘queue is empty‘)
        return head._element

    def dequeue(self):
        if self.is_empty():
            raise Empty(‘queue is empty‘)
        oldhead = self._tail._next
        if self._size == 1:
            self._tail == None
        else:
            self._tail._next = oldhead._next
        self._size -= 1
        return oldhead._element

    def enqueue(self, e):
        newest self._Node(e, None)
        if self.is_empty():
            newest._next = newest
        else:
            newest._next = self._tail._next
            self._tail._next = newest
        self._tail = newest
        self._size += 1

    def rotate(self):
        if self._size > 0:
            self._tail = self._tail._next

和普通队列一样,需要注意的是往空的队列中插入第一个元素和删除最后一个元素的情况。

7.4 the positional list ADT

前面的队列只是支持在头部或者尾部增删元素,但是不能随便在任意位置进行操作。对于链表来说,使用下标访问 并不是那么容易,因为很难通过一个下标来访问一个结点,只能从头部或者尾部开始逐个移动。

如果用一个结点的引用表示他的位置,则可以在特定位置前面或者后面插入元素,或者删除某个特定结点。

下面重新抽象出一种队列,可以根据引用来访问特定位置的结点。定义positional list ADT。

前面的队列中执行插入等是直接在提供给用户对结点的操作,如果用户给的参数结点出错则可能造成无法预测的结果。 所以现在要想办法将这些对结点的操作封装起来,然后提供给用户一些高层次的借口,比如在某个位置上的操作。

定义位置position为新的数据类型,p.element()返回这个位置上存储的元素。然后之前list的操作中直接返回元素的都 改为只是返回一个position位置。 对于一个list L:

  • L.first(): 返回第一个元素的position,如果L为空则返回None.
  • L.last()
  • L.before(p): 返回position p前一个位置的position,如果p是第一个位置则返回None.
  • L.after(p)
  • L.is_empty()
  • len(L)
  • iter(L): 返回一个遍历elements的迭代器,如可以使用for e in L: process e等
  • L.add_first(e): 参数e是要插入的element,返回新元素的position
  • L.add_last(e)
  • L.add_before(p, e): 同样返回position
  • L.add_after(p, e)
  • L.replace(p, e): 返回原来的element
  • L.delete(p): 返回被删除的element

现在用position把元素封装起来,要访问第一个元素可以通过L.first().element()

遍历整个list的方法:

cursor = data.first()
while cursor is not None:
    print cursor.element()
    cursor = data.after(cursor)

因为cursor等于最后一个position的时候,after(cursor)返回None,所以用来作为判断结束的条件。

为了判断两个position是否相等,给position加上__eq____ne__两个方法。

  • 验证position是否合法

为了方便判断一个postion是否属于一个list,在position中维护一个包含这个position的list的引用,还有 这个position指向的那个结点的引用。_validate函数接受一个position参数,判断该position是否属于当前list, 属于则返回对应的结点node.

_make_position函数则通过一个结点得到对应的position

class Empty(Exception):
    pass

class _DoublyLinkedBase:
    class _Node:
        __slots__ = ‘_element‘, ‘_prev‘, ‘_next‘

        def __init__(self, element, prev, next):
            self._element = element
            self._prev = prev
            self._next = next

    def __init__(self):
        self._header = self._Node(None, None, None)
        self._trailer = self._Node(None, None, None)
        self._header._next = self._trailer
        self._trailer._prev = self._header
        self._size = 0

    def __len__(self):
        return self._size

    def is_empty(self):
        return self._size == 0

    def _insert_between(self, e, predecessor, successor):
        newest = self._Node(e, predecessor, successor)
        predecessor._next = newest
        successor._prev = newest
        self._size += 1
        return newest

    def _delete_node(self, node):
        predecessor = node._prev
        successor = node._next
        predecessor._next = successor
        successor._prev = predecessor
        self._size -= 1
        element = node._element
        node._prev = node._next = node._element = None
        return element

class PositionalList(_DoublyLinkedBase):
    """a sequential container of elements allowing positional access."""

    class Position:
        def __init__(self, container, node):
            self._container = container
            self._node = node

        def element(self):
            return self._node._element

        def __eq__(self, other):
            return type(other) is type(self) and other._node is self._node

        def __ne__(self, other):
            return not (self == other)

    def _validate(self, p):
        if not isinstance(p, self.Position):
            raise TypeError(‘p must be proper Position type‘)
        if p._container is not self:
            raise ValueError(‘p does not belong to this container‘)
        if p._node._next is None:
            raise ValueError(‘p is no longer valid‘)
        return p._node

    def _make_position(self, node):
        if node is self._header or node is self._trailer:
            return None
        else:
            return self.Position(self, node)

    def first(self):
        return self._make_position(self._header._next)

    def last(self):
        return self._make_position(self._trailer._prev)

    def before(self, p):
        node = self._validate(p)
        return self._make_position(node._prev)

    def after(self, p):
        node = self._validate(p)
        return self._make_position(node._next)

    def __iter__(self):
        cursor = self.first()
        while cursor is not None:
            yield cursor.element()
            cursor = self.after(cursor)

    def _insert_between(self, e, predecessor, successor):
        node = _DoublyLinkedBase._insert_between(self, e, predecessor, successor)
        return self._make_position(node)

    def add_first(self, e):
        return self._insert_between(e, self._header, self._header._next)

    def add_last(self, e):
        return self._insert_between(e, self._trailer._prev, self._trailer)

    def add_before(self, p, e):
        original = self._validate(p)
        return self._insert_between(e, original._prev, original)

    def add_after(self, p, e):
        original = self._validate(p)
        return self._insert_between(e, original, original._next)

    def delete(self, p):
        original = self._validate(p)
        return self._delete_node(original)  # inherited method returns element

    def replace(self, p, e):
        """
        :param p, e: replace the element at position p with e

        :return: return the element formerly at position p.
        """
        original = self._validate(p)
        old_value = original._element
        original._element = e
        return old_value

7.5 sorting a positional list

下面要使用插入排序的算法来对positional list排序。回顾插入排序的算法,从左边第一个元素开始设置一个标志 marker,表示包括marker左边的都是已经有序的。然后每次将marker右边一个元素加进来,逐个和marker前面的元素 比较,找到合适的位置并将其插入,直到整个list都是有序的。每次取marker右边的一个元素为pivot,然后用一个变量 walk从marker开始向左遍历,直到walk的前一个元素的值小于等于pivot的值(从小到大排序),最后将pivot插入到 walk和前一个元素之间。

def insertion_sort(L):
    if len(L) > 1:
        marker = L.first()
        while marker != L.last():
            pivot = L.after(marker)
            value = pivot.element()
            if value > marker.element():  # pivot is already sorted.
                marker = pivot
            else:
                walk = marker
                while walk != L.first() and L.before(walk).element() > value:
                    walk = L.before(walk)
                L.delete(pivot)
                L.add_before(walk, value)

7.6 链表与数组实现的序列比较

数组序列的优势

  • 数组可以在O(1)的时间实现下标访问,相比这下链表就不能实现下标访问,而访问任意一个元素需要从头开始遍历查找。
  • 实际操作中对某个位置的操作可能比链表要快,因为只要改变该位置存储内容就行,而链表通常还要更新相邻的指针
  • 使用的内存更少,最坏的情况是数组刚刚扩展的时候内存利用率只有一半,但是链表由于每个元素要多存一个或者两个指针而需要两到三倍的空间。

链表序列优势

  • 链表的增删查改执行时间是O(1),相比之下数组有些操作只是摊还时间为O(1)
  • 支持任意位置O(1)时间的插入删除操作,只要给出相对位置的引用。而数组删除某个位置的元素则比较慢,需要将后面的元素都向前移动一个位置。
时间: 2024-10-29 19:11:26

python数据结构学习笔记(七)的相关文章

python数据结构学习笔记(九)

Priority Queues 9.1 ADT 9.2 implementing a priority queue 用无序的list来实现 用有序的列表来实现优先队列 9.3 heaps heap数据结构 使用堆来实现优先队列 基于数组实现的完全二叉树 使用最小优先队列来进行排序 adaptable priority queue Locators Priority Queues 9.1 ADT P.add(k, v) P.min(): return (k, v) P.remove_min():

python数据结构学习笔记(五)

5 Array-Based Sequence 5.2.1 referential arrays 5.2.2 compact arrays in python array.array 5.3 dynamic arrays and amortization 5.3.1 implementing a dynamic array 5.4 efficiency of python's sequence types constant-time operation 字符串拼接 5.6 multidimensi

python学习笔记七:条件&循环语句

1.print/import更多信息 print打印多个表达式,使用逗号隔开 >>> print 'Age:',42 Age: 42   #注意个结果之间有一个空格符 import:从模块导入函数 import 模块 from 模块 import 函数 from 模块 import * 如果两个模块都有open函数的时候, 1)使用下面方法使用: module1.open()... module2.open()... 2)语句末尾增加as子句 >>> import ma

小猪的数据结构学习笔记(四)

小猪的数据结构学习笔记(四) 线性表之静态链表 --转载请注明出处:coder-pig 本章引言: 在二,三中中我们分别学习了顺序表中的线性表与单链表,线性表有点类似于 我们前面所学的数组,而单链表使用的最多的是指针,这里问个简单的问题, 如果是在以前没有指针的话,前辈先人们怎么实现单链表呢?大家思考下! 没有指针,那么用什么来代替呢?前辈先人们非常机智,想出了使用下标+游标的方式 来实现单链表的效果!也就是今天要讲的--静态链表! 当然你也可以直接跳过本章,因为有了单链表就没有必要用静态链表了

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

小猪的数据结构学习笔记(二)

小猪的数据结构学习笔记(二) 线性表中的顺序表 本节引言: 在上个章节中,我们对数据结构与算法的相关概念进行了了解,知道数据结构的 逻辑结构与物理结构的区别,算法的特性以及设计要求;还学了如何去衡量一个算法 的好坏,以及时间复杂度的计算!在本节中我们将接触第一个数据结构--线性表; 而线性表有两种表现形式,分别是顺序表和链表;学好这一章很重要,是学习后面的基石; 这一节我们会重点学习下顺序表,在这里给大家一个忠告,学编程切忌眼高手低,看懂不代表自己 写得出来,给出的实现代码,自己要理解思路,自己

第十七篇:博采众长--初探WDDM驱动学习笔记(七)

基于WDDM驱动的DirectX视频加速重定向框架设计与实现 现在的研究生的论文, 真正质量高的, 少之又少, 开题开得特别大, 动不动就要搞个大课题, 从绪论开始到真正自己所做的内容之间, 是东拼西凑地抄概念, 抄公式, 达到字数篇幅的要求, 而自己正真做了什么, 有哪些实际感受, 做出的内容, 相比前面的东拼西凑就几点内容, 之后就草草结束, 步入感谢的段落. 原因不光只有学生自己, 所谓的读研, 如果没有一个环境, 学生有再大的愿望, 再强的毅力, 到头来也只是空无奈. 有些导师要写书,

马哥学习笔记七——LAMP编译安装之MYSQL

1.准备数据存放的文件系统 新建一个逻辑卷,并将其挂载至特定目录即可.这里不再给出过程. 这里假设其逻辑卷的挂载目录为/mydata,而后需要创建/mydata/data目录做为mysql数据的存放目录. 2.新建用户以安全方式运行进程: # groupadd -r mysql # useradd -g mysql -r -s /sbin/nologin -M -d /mydata/data mysql # chown -R mysql:mysql /mydata/data 3.安装并初始化my

Python Click 学习笔记(转)

原文链接:Python Click 学习笔记 Click 是 Flask 的团队 pallets 开发的优秀开源项目,它为命令行工具的开发封装了大量方法,使开发者只需要专注于功能实现.恰好我最近在开发的一个小工具需要在命令行环境下操作,就写个学习笔记. 国际惯例,先来一段 "Hello World" 程序(假定已经安装了 Click 包). # hello.py import click @click.command() @click.option('--count', default