【数据结构】Python 中实现链表

一,C实现链表

上一篇博文写到的顺序表是线性表的顺序存储结构,也就是说在内存中存储单元地址是连续的。线性表还有一种链式存储结构,也就链表,链表由节点所构成,节点内含一个指向下一个节点的指针,节点依次链接成为链表。因此,链表这种数据结构通常在物理内存上是不连续的。C语言中对节点的定义如下:

typedef struct Node
{
	ElemType data; //数据域
	struct Node *Next; //指针域
}Node;
typedef struct Node *LinkList;

这里使用到了个人觉得是C语言最精髓的东西——指针,对于大神来时,指针就是神器,对于像我这样的菜菜,就是一脸懵。

二,Python的链表

C语言中实现链表使用到指针,但是在Python中并没有指针,那要怎么实现链表?
C语言实现基本的链表时节点中指针唯一的作用就是指向,指向下个节点的地址,不涉及指针运算,你可以把这里的指针当作一个变量,存储着下个节点的地址,这种方式在Python中称为“引用”,变量中记录数据的地址。Python里所有的变量其实都是对象的引用(连基本类型都是如此),而这个引用,说白了就是个指向实例的指针。所以Python是可以实现基本的数据结构的。

1,单链表

单向链表也叫单链表,是表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

class LNode(object):
    def __init__(self, item, next_=None):
        self.item = item
        self.next = next_


class SingleLinkList(object):
    """
    初始化单链表
    """

    def __init__(self):
        self.head = None

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

    def length(self):
        """获取链表长促"""
        cur = self.head
        count = 0
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.head
        while cur:
            print(cur.item, end='')
            if cur.next:
                print(',', end='')
            cur = cur.next
        print('')

    def append_front(self, item):
        """头部插入"""
        self.head = LNode(item, self.head)

    def append_rear(self, item):
        """尾部插入"""
        node = LNode(item)
        if self.is_empty():
            self.head = node
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = node

    def insert(self, item, pos):
        """在第pos个位置插入元素"""
        if pos <= 1:
            self.append_front(item)
        elif pos > self.length():
            self.append_rear(item)
        else:
            node = LNode(item)
            cur = self.head
            cur_pos = 1
            while cur.next and cur_pos < pos - 1:
                cur = cur.next
                cur_pos += 1
            node.next = cur.next
            cur.next = node

    def getitem(self, pos):
        """获取第pos个位置的元素"""
        if pos < 1 or pos > self.length():
            print('pos 超出范围')
            return None
        else:
            cur = self.head
            cur_pos = 1
            while cur.next and cur_pos < pos:
                cur = cur.next
                cur_pos += 1
            return cur.item

    def pop(self):
        """移除头部元素"""
        if self.is_empty():
            print('SingleLinkList is empty')
        else:
            e = self.head.item
            self.head = self.head.next
            return e

    def remove(self, pos):
        """删除第pos个位置元素"""
        if pos < 1 or pos > self.length():
            print('pos 超出范围')
        elif pos == 1:
            self.head = self.head.next
        else:
            cur = self.head
            cur_pos = 1
            while cur and cur_pos < pos - 1:
                cur = cur.next
                cur_pos += 1
            cur.next = cur.next.next

链表相对顺序表最大的缺点在于,如果想获取到第n个元素,需要从前面n-1个元素开始遍历。
这里想到一个算法题,如何在不知道元素个数的情况下,获取单链表的中间元素?
最简单的方法,先遍历一次链表,获得长度n,再从第一个遍历到n/2个元素,时间复杂度时间复杂度O(3N/2)
另一种算法就是使用快慢指针,让A指针每次走两步,B指针每次走一步,当A指针到末尾时,B指针则到达中间位置。时间复杂度时间复杂度O(N/2)。

if __name__ == '__main__':
    SingleLinkList = SingleLinkList()
    for i in range(1, 101):   # 随机插入100个元素
        SingleLinkList.append_rear(random.randint(1, 100))
    SingleLinkList.travel()
    a = SingleLinkList.head  # a 每次走两步
    b = SingleLinkList.head  # b 每次走一步
    while a.next:
        if a.next.next:
            a = a.next.next
            b = b.next
        else:
            a = a.next
    print(b.item)
2,静态链表

静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。
具体实现原理可以参考:http://data.biancheng.net/view/163.html
对于静态链表有以下几点需要注意:
1,数组的第一个位置和最后一个位置做特殊处理,并不存放数据。
2,将未使用到数组位置称为备用链表。
3,数组第一个位置,即下标为0的元素对应的cur,存放备用链表第一个节点的下标,数组的最后一位置,即下标为MAXSIZE-1的元素对应的cur,存放第一个有值元素的下标,相当于链表的头结点。

class LNode(object):
    def __init__(self, cur, item=None):
        self.item = item
        self.cur = cur


# 最后一个下标的cur 存储第一个非空元素节点的下标,
# 第一个下标的cur  存储第一个空元素节点的下标


class StaticLinkList(object):
    def __init__(self):
        """初始化,将每个节点的cur,存储下一个节点的下标"""
        self.MAXSIZE = 100
        self.array = list()
        for i in range(0, self.MAXSIZE):
            self.array.append(LNode(i + 1))
        self.array[self.MAXSIZE - 1] = LNode(0)

    def is_empty(self):
        return self.array[self.MAXSIZE - 1].cur == 0

    def length(self):
        k = self.array[self.MAXSIZE - 1].cur
        count = 0
        while self.array[k].item:
            count += 1
            k = self.array[k].cur
        return count

    def travel(self):
        k = self.array[self.MAXSIZE - 1].cur
        n = self.array[0].cur
        print('第一个非空节点下标为', k)
        print('第一个空节点下标为', n)
        while self.array[k].item:
            print('index=', k, 'item=', self.array[k].item, 'cur=', self.array[k].cur)
            k = self.array[k].cur

    def malloc_node(self):
        """从备用链表中申请一个节点"""
        i = self.array[0].cur
        self.array[0].cur = self.array[i].cur  # 下一个备用节点
        return i

    def insert(self, item, pos):
        k = self.MAXSIZE - 1
        if pos < 1 or pos > self.length() + 1:
            print('插入位置非法')
            return None
        if self.length() + 1 > self.MAXSIZE - 2:
            print('数组空间已满')
            return None

        i = self.malloc_node()
        if i:
            self.array[i].item = item
            for j in range(1, pos):
                k = self.array[k].cur
            self.array[i].cur = self.array[k].cur
            self.array[k].cur = i

    def remove(self, pos):
        if pos < 1 or pos > self.length():
            print('删除位置非法')
            return None
        k = self.MAXSIZE - 1
        for i in range(1, pos):
            k = self.array[k].cur
        j = self.array[k].cur  # 需要删除元素的下标
        self.array[k].cur = self.array[j].cur  # 指向删除元素的下一个元素

        # 清除pos的数据,将pos 加入备用链表中
        self.array[j].item = None
        self.array[j].cur = self.array[0].cur
        self.array[0].cur = j


3,单向循环链表

单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头结点。

class LNode(object):
    def __init__(self, item, next_=None):
        self.item = item
        self.next = next_


class SCLinkList(object):
    def __init__(self):
        """初始化"""
        self.head = None
        # self.head.next = self.head

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

    def length(self):
        if self.is_empty():
            print('链表为空')
            return 0
        else:
            cur = self.head
            count = 1
            while cur.next != self.head:
                cur = cur.next
                count += 1
            return count

    def travel(self):
        """遍历链表"""
        if self.is_empty():
            print('链表为空')
            return
        else:
            cur = self.head
            while cur.next != self.head:
                print(cur.item, end=',')
                cur = cur.next
            print(cur.item)

    def insert(self, val, pos):
        node = LNode(val)
        if self.is_empty():
            self.head = node
            node.next = self.head
        else:
            if pos == 1:
                cur = self.head
                while cur.next != self.head:
                    cur = cur.next
                node.next = self.head
                cur.next = node
                self.head = node
            else:
                cur = self.head
                cur_pos = 1
                while cur.next != self.head and cur_pos < pos - 1:
                    cur = cur.next
                    cur_pos += 1
                node.next = cur.next
                cur.next = node

    def remove(self, pos):
        if self.is_empty():
            print('链表为空')
            return
        else:
            if pos == 1:
                cur = self.head
                pre = self.head
                while cur.next != self.head:
                    cur = cur.next
                self.head = pre.next
                cur.next = pre.next
            else:
                cur = self.head
                cur_pos = 1
                while cur.next != self.head and cur_pos < pos - 1:
                    cur = cur.next
                    cur_pos += 1
                cur.next = cur.next.next

与单向循环链表相关的一个算法问题题就是约瑟夫问题,具体由来可以直接百度。
简单来说,就是按照如下规则去排除人:
所有人围成一圈
顺时针报数从1报到q,每次报到q的人将被排除掉
然后从被排除掉的下一个人重新从1报数,继续报到q,再清除,直到剩余一人。
解决思路很多,但是个人觉得使用单向循环链表是最契合这个规则的。

    def joseph(self, n, m):
        """
        单循环链表实现约瑟夫环
        n : 成还人数
        m : 报数间隔
        """
        for i in range(1, n + 1):
            self.insert(i, i)
        cur = self.head
        while cur != cur.next:
            for i in range(1, m - 1):
                cur = cur.next
            print(cur.next.item, end='->')
            cur.next = cur.next.next
            cur = cur.next
            n -= 1
        print(cur.item)

if __name__ == '__main__':
    SCLinkList = SCLinkList()
    SCLinkList.joseph(10, 12)  # 2->5->9->6->4->8->7->3->1->10
    SCLinkList.joseph(10, 8)    # 8->6->5->7->10->3->2->9->4->1
4,双向循环链表

之前接触到的链表都只有一个指针,指向直接后继,整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”。
如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率。
为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素。这样的链表被称为“双向链表”或者“双链表”。
双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继。当第一个节点,也就头结点的前趋指向最后一个节点,最后一个节点的后继指向头结点,那么就构成双向循环链表。
在这里插入图片描述

class DLNode(object):
    def __init__(self, item, prior_=None, next_=None):
        self.item = item
        self.prior = prior_
        self.next = next_


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

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

    def length(self):
        if self.is_empty():
            print('链表为空')
            return 0
        else:
            cur = self.head
            count = 1
            while cur.next != self.head:
                cur = cur.next
                count += 1
            return count

    def travel(self):
        """遍历链表"""
        if self.is_empty():
            print('链表为空')
            return
        else:
            cur = self.head
            while cur.next != self.head:
                print(cur.item, end=',')
                cur = cur.next
            print(cur.item)

    def insert(self, val, pos):
        node = DLNode(val)
        if self.is_empty():
            self.head = node
            node.next = self.head
            node.prior = self.head
        else:
            if pos == 1:
                cur = self.head
                node.next = cur
                node.prior = cur.prior
                cur.prior.next = node
                cur.prior = node
                self.head = node

            else:
                cur = self.head
                cur_pos = 1
                while cur.next != self.head and cur_pos < pos - 1:
                    cur = cur.next
                    cur_pos += 1
                node.next = cur.next
                node.prior = cur
                cur.next.prior = node
                cur.next = node

    def remove(self, pos):
        if self.is_empty():
            print('链表为空')
            return
        else:
            if pos == 1:
                cur = self.head
                cur.prior.next = cur.next
                cur.next.prior = cur.prior
                self.head = cur.next
            else:
                cur = self.head
                cur_pos = 1
                while cur.next != self.head and cur_pos < pos:
                    cur = cur.next
                    cur_pos += 1
                cur.prior.next = cur.next
                cur.next.prior = cur.prior

举个栗子,能帮助理解双向链表,现有一个序列S,给定一个整数N,当N>0,则将序列向左循环移动N位,得到新序列S1,当N<0,则将序列向右循环移动N位,得到新序列S2。
序列S
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
当N=3时,输出
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
当N=-3时,输出
X Y Z A B C D E F G H I J K L M N O P Q R S T U V W

    def caesars(self, n):
        for i in range(0, 26):
            val = chr(ord('A') + i)
            pos = i + 1
            self.insert(val, pos)
        if n > 0:
            while n:
                self.head = self.head.next
                n -= 1

        if n < 0:
            while n:
                self.head = self.head.prior
                n += 1
        cur = self.head
        for j in range(0, 26):
            print(cur.item, end=' ')
            cur = cur.next

参考
(小甲鱼)数据结构和算法

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值