数据结构和算法-链表

Posted zlone

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构和算法-链表相关的知识,希望对你有一定的参考价值。

链表分类

  • 单向链表
  • 双向链表
    优势:
    • 删除某个节点更加高效, 可以快速找到前驱节点
    • 可以方便的在某个节点前插入元素
  • 循环链表
    当要处理的数据具有环形结构的时候, 适合循环链表. 如约瑟夫环问题

  • 双向循环链表

数组的缺点是大小固定, 一旦声明长度就要占用连续的内存空间, 当空间不够用时更换更大的空间, 此时就需要将原数组的所有数据迁移过去, 比较费时. 链表则可以动态扩容.

数组在查询上可以更快, 链表在插入和删除上更快, 为了结合数组和链表的优点, 有同时使用的情况, 比如一个网站的用户注册, 可以以A-Z为数组, 在每个字母后面加入链表, 这样可以在添加新用户的时候能快速找到要添加的链表并进行插入, 同时在查询用户的时候也能缩短查询时间

常见边界条件

  • 链表为空
  • 只有一个节点
  • 只有两个节点
  • 指针在头结点和尾节点的处理

常见问题

  • 单链表反转
  • 检测链表中是否有环
  • 两个有序链表合并
  • 删除链表倒数第n个节点
  • 求链表的中间节点

单向链表

"""
单链表
"""


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


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

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

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

    def prepend(self, value):
        """
        在头部添加节点
        :param value:
        :return:
        """
        self.head = Node(value, self.head)

    def append(self, value):
        """
        在尾部追加节点
        :param value:
        :return:
        """
        node = Node(value)
        if self.is_empty():
            self.head = node
        else:
            current = self.head
            while current.next_ != None:
                current = current.next_
            current.next_ = node

    def insert(self, position, value):
        """
        指定位置插入节点, 从1开始计数
        :param position:
        :param value:
        :return:
        """
        if position <= 1:
            self.prepend(value)
        elif position > self.size():
            self.append(value)
        else:
            node = Node(value)
            tmp_pos = 1
            pre_node = None
            current = self.head
            while tmp_pos < position:
                pre_node = current
                current = current.next_
                tmp_pos += 1
            node.next_ = current
            pre_node.next_ = node

    def delete(self, value):
        if self.is_empty():
            raise Exception("empty")
        pre_node = None
        current = self.head
        while current != None:
            if current.data == value:
                # 判断删除的元素是不是第一个
                if not pre_node:
                    self.head = current.next_
                else:
                    pre_node.next_ = current.next_
                break
            else:
                pre_node = current
                current = current.next_

    def pop_first(self):
        if self.is_empty():
            raise Exception("empty")
        data = self.head.data
        self.head = self.head.next_
        return data

    def pop_last(self):
        if self.is_empty():
            raise Exception("empty")
        pre_node = None
        current = self.head
        while current.next_ != None:
            pre_node = current
            current = current.next_
        data = current.data
        if pre_node == None:
            self.head = None
        else:
            pre_node.next = None
        return data

    def find(self, value):
        status = False
        current = self.head
        while current != None and not status:
            if current.data == value:
                status = True
            else:
                current = current.next_
        return status

单链表反转

# coding:utf-8

"""
单链表反转
"""


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


def reverse(linked_list):
    head = linked_list
    pre = None
    while head != None:
        current = head
        head = current.next_
        current.next_ = pre
        pre = current
    return pre


def output(linked_list):
    current = linked_list
    res = []
    while current != None:
        res.append(current.data)
        current = current.next_
    print(res)


if __name__ == '__main__':
    link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9)))))))))
    output(link)
    root = reverse(link)
    output(root)

"""
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1]
"""

链表成对调换

# coding:utf-8

"""
链表成对调换

1 -> 2 -> 3 -> 4
调换后为
2 -> 1 -> 4 -> 3
"""


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


def swap(head):
    if head != None and head.next_ != None:
        after = head.next_
        head.next_ = swap(after.next_)
        after.next_ = head
        return after
    return head


def output(linked_list):
    current = linked_list
    res = []
    while current != None:
        res.append(current.data)
        current = current.next_
    print(res)


if __name__ == '__main__':
    link = Node(1, Node(2, Node(3, Node(4))))
    output(link)
    print('----')
    link1 = swap(link)

    output(link1)

"""
[1, 2, 3, 4]
----
[2, 1, 4, 3]
"""

判断是否交叉链表

  1. 头指针法
    • 求出两个链表的长度之差sub_size
    • 让较长链表快速走sub_size
    • 最后依次比较两条链表对应的值是否相等, 相等处则是交点
  2. 分别遍历两个链表, 判断最后一个节点的值是否一样
    时间复杂度O(m+n)

判断链表是否有环

定义两个游标first和later, first步长是1, later步长是2. 同时向前走, 如果有环一定会遇到. 复杂度O(n)

# -*- coding:utf-8 -*-

"""
判断链表是否有环
"""


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


def check(head):
    current1 = current2 = head
    while True:
        current1 = current1.next_  # 指向第一个节点
        current2 = current2.next_.next_  # 指向第二个节点

        if (not current1) or (not current2):
            break
        if current1.data == current2.data:
            return True

    return False


if __name__ == '__main__':
    node1 = Node(6)
    node2 = Node(2)
    node3 = Node(3)
    node4 = Node(4)
    node5 = Node(5)
    node6 = Node(6)

    node1.next_ = node2
    node2.next_ = node3
    node3.next_ = node4
    node4.next_ = node5
    node5.next_ = node6
    node6.next_ = node3  # 环交点

    assert check(node1) == True


查找单链表倒数第k个元素

定义两个指针first, later都初始化指向头节点, 然后first先走k步, 再同时走, 当first到尾节点的时候, 读出later节点的值. 复杂度是O(n)

# -*- coding:utf-8 -*-

"""
找到链表的倒数第K个元素

定义两个游标, 第二个游标先走k-1步, 之后再同时走, 此时第一个游标停留位置就是倒数第K个元素
"""

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



def find_reverse_k(head, k):
    c1 = head
    current = head
    for _ in range(k - 1):
        current = current.next_
    c2 = current

    while c2.next_ != None:
        c2 = c2.next_
        c1 = c1.next_

    return c1.data


if __name__ == '__main__':
    link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9)))))))))
    assert find_reverse_k(link, 3) == 7
    assert find_reverse_k(link, 1) == 9
    assert find_reverse_k(link, 2) == 8

合并两个有序链表

# coding:utf-8

"""
合并两个有序单链表

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
"""


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


def merge2linkedlist(l1, l2):
    """
    合并两个有序链表
    :param l1:
    :param l2:
    :return:
    """
    if l1 == None and l2 == None:
        raise Exception("None!!!")
    if l1 == None:
        return l2
    if l2 == None:
        return l1

    # 使用head为辅助节点
    head = Node(0)
    current = head

    while l1 and l2:
        if l1.data <= l2.data:
            current.next_ = l1
            l1 = l1.next_
        elif l1.data > l2.data:
            current.next_ = l2
            l2 = l2.next_
        current = current.next_

    if l1:
        current.next_ = l1
    if l2:
        current.next_ = l2

    return head.next_


if __name__ == "__main__":
    l1 = Node(1, Node(2, Node(4)))
    l2 = Node(1, Node(3, Node(4)))

    tmp = merge2linkedlist(l1, l2)

    res = []
    while tmp:
        res.append(tmp.data)
        tmp = tmp.next_
    print(res)

"""
[1, 1, 2, 3, 4, 4]
"""

合并K个有序单链表

  • 方法一
    把K个链表2个为一组进行合并, 不断分组, 最后合并为一个有序链表
  • 方法二
    遍历所有链表将所有元素放在一个数组中, 然后对数组进行排序, 最后生成一个有序链表.
    时间复杂度:
    如果所有链表共有n个元素, 遍历需要O(n), 对数组排序需要O(nlogn), 最后连接O(n). 所以总的复杂度是O(n + nlogn + n), 也就是O(nlogn)

注意

我们说空间复杂度的时候, 是指除了原本的数据存储空间外, 算法还需要的额外的存储空间, 即不管原来所占空间是多少

资料

  • <<漫画算法>>
  • <<大话数据结构>>
  • <<数据结构与算法>>

技术图片

以上是关于数据结构和算法-链表的主要内容,如果未能解决你的问题,请参考以下文章

3)数据结构和算法学习_链表

数据结构&算法-双向链表

数据结构&算法-双向链表

数据结构与算法什么是链表?并用代码手动实现一个单向链表

Day557.单向环形链表 -数据结构和算法Java

数据结构和算法学编程必知必会的50个代码实现,你都会了吗?