Python数据结构之链表(linked list)

数据结构 - 链表


  1. 链表(linked list):由一组被称为结点(也叫节点)的数据元素组成的数据结构,每个结点包含2部分:1)结点本身的数据信息(data),称为信息域;2)指向下一个结点的地址信息(next),称为指针域。

  2. head结点:是一个特殊的结节,用于存储链表第一个结点的地址信息,永远指向链表的第一个结点;

  3. tail结点:是一个特殊的结点,永远指向链表的最后一个节点。链表最后一个结点的地址信息(指针域)指向None值,因也叫接地点。

  4. 由于链表的每个结点都包含了可以链接起来的地址信息,所以用一个变量就能够访问整个结点序列。

  5. 链表是计算机科学领域应用最广泛的高阶数据结构之一,它实现了数据之间保持逻辑顺序,但存储空间不必按顺序的方法。

  6. 根据结构的不同,链表可以分为单向链表、单向循环链表、双向链表、双向循环链表等。其中,单向链表和单向循环链表的结构如下图所示:

  7. 链表常用方法
    LinkedList() :创建空链表,不需要参数,返回值是空链表;
    is_empty():测试链表是否为空,不需要参数,返回值是布尔值;
    append(data) :追加元素到链表尾部。参数是要追加的元素,无返回值;
    iter():生成器,遍历链表,无参数,无返回值;
    insert(idx, value) :插入一个元素,参数为插入元素的索引和值;
    remove(idx):移除1个元素,参数为要移除的元素或索引,并修改链表;
    size() :返回链表的元素数,不需要参数,返回值是个整数;
    search(item) :查找链表某元素,参数为要查找的元素或索引,返回是布尔值。

节点类


python用类(class)来实现链表的数据结构,节点(Node)是实现链表的基本模块,每个节点至少包括两个重要部分:值和指针(引用)。示例:

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

此节点类只有一个构建函数,接收一个数据参数,其中next表示指针域的指针,实例化后得到一个节点对象,如下示例,节点对象数据为4,指针指向None。

node = Node(4)
print(node)
<__main__.Node object at 0x10998b3c8>

这样一个节点对象,可以用一个图例来更形象的说明,如下:


链表类


先来看LinkedList类的构建函数:

class LinkedList(object):
    def __init__(self):
        self.head = None
        self.tail = None 

此类实例后会生成一个链表对象,初始化了head和tail节点,且两节点都指向None,实例化代码如下:

link_list = LinkedList()
print(link_list)
<__main__.LinkedList object at 0x10b521710>

也可以用图形象的表示这个链表对象,如下:


is_empty()函数

is_empty()函数,用于检查链表是否是一个空链表,这个方法只需要检查head节点是否指向None即可,代码如下:

def is_empty(self):
    return self.head is None

如果是空列表返回True,否则返回False。

append()函数


append()函数,用于追加元素到链表。如果链表为空,则添加为第一个节点,如果链表非空,则追加为最后一个元素。代码如下:

def append(self, data):
    node = Node(data)   # 把  Node类实例化得到一个node对象
    if self.head is None:
        # 如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点
        self.head = node    
        self.tail = node
    else:
        # 如果链表非空,则将tail节点和上一个节点的next均指向当前node,即将元素追加为最后一个节点
        self.tail.next = node   
        self.tail = node

首先把Node类实例化得到一个node对象。如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点;如果链表非空,则将tail节点和上一个节点的next均指向当前node,即将元素追加为最后一个节点。链表非空时追加元素的过程示意图:


iter()函数


iter()函数:生成器,可以遍历链表。遍历链表时,从head开始,直到一个节点的next指向None结束,特别地,需要考虑空链表的情况。代码如下:

def iter(self):
    # 如果链表为空,则返回None
    if not self.head:
        return 
    cur = self.head
    yield cur.data  # 当前节点data数据的生成器
    while cur.next:
        cur = cur.next  # 当前节点指向下一个节点并遍历
        yield cur.data  # 当前节点data数据的生成器

如果链表为空,遍历时直接返回None;如果链表不为空,遍历时,先将当前节点的data返回生成器,再将当前节点指向下一个节点,进行while循环,同时将对应的data返回生成器,直到next指向None,就可以实现非空链表的遍历。

insert()函数


  1. insert()函数,用于将数据插入到链表的指定索引的位置。
  2. 通过实例,来说明insert()函数插入数据到链表的过程。例如,需要在链表数据域为6的节点处插入一个新节点,链表如下:
  3. 操作过程
    将新节点的next指针指向数据域为6的这个节点;
    将数据域为5的节点的next指针指向新加的节点。
  4. 过程示意图
  5. 注意事项
    操作过程的两个步骤不能颠倒,如果颠倒,数据域为6的节点会被丢失,数据域为7的节点不再是链表的节点;
    链表为空时,返回None或者抛出异常;
    插入位置超出链表节点的长度时,抛出异常;
    插入位置是链表的最后一个节点时,还需要移动tail结点指向新结点。
  6. 示例代码
def insert(self, idx, value):
    cur = self.head
    cur_idx = 0
    if cur is None:
        raise Exception('The linked_list is empty')
    while cur_idx < idx - 1:
        cur = cur.next
        if cur is None:
            raise Exception('list length less than index')
        cur_idx += 1
    node = Node(value)
    node.next = cur.next
    cur.next = node 
    if node.next is None:
        self.tail = node
  1. 在指定的索引位置插入一个节点,前提是需要找到这个位置,在链表中只有采用遍历的方式,具有O(n)的速度,最糟糕时会遍历链表的所有节点,当找到插入点时,其实并不需要当前节点的信息,而是需要前一个节点的信息,所以,代码中巧妙的使用了while cur_idx < idx-1:的方式,这样能使用cur这个变量能指向插入位置的上一个节点对象。

remove()函数


  1. remove()函数接收一个idx参数,表示要删除节点的索引。

  2. 使用remove()函数时需要考虑如下情况:
    空链表,直接抛出异常;
    删除第一个节点时,移动head到删除节点的next指针指向的对象;
    链表只有一个节点时,把head与tail都指向None即可;
    删除最后一个节点时,需要移动tail到上一个节点;
    遍历链表时要判断给定的索引是否大于链表的长度,如果大于则抛出异常信息。

  3. 示意图
  4. 示例代码:

def remove(self, idx):
    cur = self.head
    cur_idx = 0
    if self.head is None:   # 空链表时
        raise Exception('The linked_list is empty')
    while cur_idx < idx - 1:
        cur = cur.next
        if cur is None:
            raise Exception('list length less than index')
        cur_idx += 1
    if idx == 0:    # 当删除第一个节点时
        self.head = cur.next
        cur = cur.next
        return 
    if self.head is self.tail:  # 当只有一个节点的链表时
        self.head = None
        self.tail = None
        return
    cur.next = cur.next.next
    if cur.next is None:    # 当删除的节点是链表最后一个节点时
        self.tail = cur 


size()函数


size()函数不接收参数,返回链表中节点的个数,要获得链表的节点个数,必定会遍历链表,直到最后一个节点的next指针指向None时链表遍历完成,遍历时可以用一个累加器来计算节点的个数,如下代码:

def size(self):
    cur = self.head
    count = 0
    if cur is None:
        raise Exception('The linked_list is empty')
    while cur is not None:
        count += 1
        cur = cur.next
    return count


search()函数


search()函数接收一个参数,表示查找节点中数据域的值。查找时会遍历链表,每到一个节点把当前节点的data值与参数值作比较,最糟糕的情况下会全遍历链表。如果查找到有些数据则返回True,否则返回False,代码如下:

def search(self, item):
    cur = self.head
    found = False
    while cur is not None and not found:
        if cur.data == item:
            found = True
        else:
            cur = cur.next
    return found


Node类与LinkedList类完整代码

class Node():
    '创建节点'
    def __init__(self,data):
        self.data = data
        self.next = None

class LinkedList(object):
    '创建列表'
    def __init__(self):
        '初始化列表'
        self.head = None
        self.tail = None 

    def is_empty(self):
        '判断链表是否为空'
        return self.head is None

    def append(self, data):
        '追加元素'
        node = Node(data)   # 把Node类实例化得到一个node对象
        if self.head is None:
            # 如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点
            self.head = node    
            self.tail = node
        else:
            # 如果链表非空,则将tail节点和上一个节点的next均指向当前node
            self.tail.next = node   
            self.tail = node    

    def iter(self):
        '遍历链表'
        # 如果链表为空,则返回None
        if not self.head:
            return 
        cur = self.head
        yield cur.data  # 当前节点data数据的生成器
        while cur.next:
            cur = cur.next  # 当前节点指向下一个节点并遍历
            yield cur.data  # 当前节点data数据的生成器

    def insert(self, idx, value):
        '插入元素'
        cur = self.head
        cur_idx = 0
        if cur is None:
            raise Exception('The linked_list is empty')
        while cur_idx < idx - 1:
            cur = cur.next
            if cur is None:
                raise Exception('list length less than index')
            cur_idx += 1
        node = Node(value)
        node.next = cur.next
        cur.next = node 
        if node.next is None:
            self.tail = node

    def remove(self, idx):
        '删除元素'
        cur = self.head
        cur_idx = 0
        if self.head is None:   # 空链表时
            raise Exception('The linked_list is empty')
        while cur_idx < idx - 1:
            cur = cur.next
            if cur is None:
                raise Exception('list length less than index')
            cur_idx += 1
        if idx == 0:    # 当删除第一个节点时
            self.head = cur.next
            cur = cur.next
            return 
        if self.head is self.tail:  # 当只有一个节点的链表时
            self.head = None
            self.tail = None
            return
        cur.next = cur.next.next
        if cur.next is None:    # 当删除的节点是链表最后一个节点时
            self.tail = cur 

    def size(self):
        '统计元素个数'
        cur = self.head
        count = 0
        if cur is None:
            raise Exception('The linked_list is empty')
        while cur is not None:
            count += 1
            cur = cur.next
        return count

    def search(self, item):
        '查找指定元素'
        cur = self.head
        found = False
        while cur is not None and not found:
            if cur.data == item:
                found = True
            else:
                cur = cur.next
        return found


if __name__ == '__main__':
    link_list = LinkedList()    # 实例化
    print(link_list.is_empty())     # 判断是否为空
    for i in range(15):
        link_list.append(i)     # 追加15个元素
    print(link_list.size())     # 元素个数
    print(link_list.is_empty())     # 判断是否为空
    link_list.insert(5, 1000)       # 插入一个元素:1000
    print(link_list.size())     # 元素个数
    link_list.remove(0)     # 删除第一个元素
    print(link_list.size())     # 元素个数
    print(link_list.search(10))     # 查找是否存在元素10

    for node in link_list.iter():
        print(f'node is {node}')    # 遍历元素

True
15
False
16
15
True
node is 1
node is 2
node is 3
node is 4
node is 1000
node is 5
node is 6
node is 7
node is 8
node is 9
node is 10
node is 11
node is 12
node is 13
node is 14
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容

  • 普天并未有多少涨幅,落后于充电装板块,可以再拿着,栖霞也是,看地产能起来不,山水这几天也是天天冲高回落,考验耐心啊...
    烟花扬州阅读 455评论 0 50
  • Bunny高效率慢生活践行第56天 * 2018年7月27日 天气: 晴 心情: 非常高兴 □ 时间管理的核心是早...
    Bunny妮妮阅读 149评论 0 0
  • 又是一年双枪季,南方农村的孩子一定不会忘记这个时季。赶着这十几天的日子,将田里成熟的稻谷收回来,也要及时把下一季的...
    槿柔伊阅读 456评论 0 0
  • 诗翁总爱把梅吟, 梅在深山不见君。 你问世间谁懂你, 世间问你懂谁人。
    瓶会泉_杨丽萍阅读 282评论 0 0
  • 生活中我们经常发现,很多男性朋友结婚前精瘦精瘦的,结果结了婚当了爹,一个个都发福了!长期研究父亲的健康问题的西北大...
    鱼河水阅读 355评论 0 0