数据结构和算法-链表

链表分类

  • 单向链表
  • 双向链表
    优势:

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

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

数组在查询上可以更快, 链表在插入和删除上更快, 为了结合数组和链表的优点, 有同时使用的情况, 比如一个网站的用户注册, 可以以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)

注意

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

资料

  • <>
  • <>
  • <>

原文地址:https://www.cnblogs.com/zlone/p/10993902.html

时间: 2024-08-14 08:08:23

数据结构和算法-链表的相关文章

数据结构与算法-链表的基本操作---ShinPans

//链表操作:建立.插入.删除.查找.倒置.删除等基本操作 #include<stdio.h> #include<stdlib.h> typedef  struct LNode {       int data;       structLNode *next; }LNode,*Llist; LNode *creat_head();//创建一个空表 void creat_list(LNode *,int);//创建一个长度为n的线性链表 void insert_list(LNode

python数据结构与算法——链表

具体的数据结构可以参考下面的这两篇博客: python 数据结构之单链表的实现: http://www.cnblogs.com/yupeng/p/3413763.html python 数据结构之双向链表的实现: http://www.cnblogs.com/yupeng/p/3413800.html 我这里只实现了单链表的类型,代码也相对精简一点: 先构造关于节点的类: 1 class Node: 2 def __init__(self,data=None,next=None): 3 self

数据结构和算法--链表一之单向链表的简单实现

链表在我们java中也是一种基础的数据结构,可以理解成是一种和数组同级的数组结构,正如我们所知,在我们使用这集合ArrayList和LinkedList的时候,总会学习底层数组实现的ArrayList和双向链表实现的LinkedList的区别.在这里,我们将要讲说的是单向链表的简单实现,让我们体会一下链表在实现增删改查的时候是怎么样的一个操作,在和前边涉及到的数组的增删改查进行对比,得到我们学习的结论,数组的增删效率低于链表结构,查改效率高于链表结构! 什么叫做单向链表,我们可以理解为一个一个节

数据结构与算法 - 链表

链表 题型1:数组和链表的区别是什么? 数组和链表的区别主要表现在以下几个方面: 1)逻辑结构.数组必须事先定义固定的长度,不能适应数据动态地增减.当数组中插入.删除数据项时,需要移动其他数据项.而链表采用动态分配内存的形式实现,可以适应数据动态第增减的情况,需要时可以用new/malloc分配内存空间,不需要时使用delete/free将已分配的空间释放,插入和删除元素不需要移动数据项. 2)内存结构.数组从栈中分配空间,链表从堆中分配空间. 3)数组中的数据在内存中是顺序存储的,而链表是随机

数据结构与算法 —— 链表linked list(02)

我们继续来看链表的第二道题,来自于leetcode: 两数相加 给定两个非空链表来代表两个非负整数,位数按照逆序方式存储,它们的每个节点只存储单个数字.将这两数相加会返回一个新的链表. 你可以假设除了数字 0 之外,这两个数字都不会以零开头. 示例: 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807 分析: 因为是位数按照逆序方式存储,所以链表的前置节点是地位,直接循环链表相加,生成新的

数据结构与算法-链表查找倒数第K个值

查找链表中倒数第k个结点题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针.链表结点定义如下: struct ListNode { int m_nKey; ListNode* m_pNext; }; int FindCoundDownInList(pListNode head,int num) { pListNode p1,p2; p1=p2=head; while(num-->0 && p1!=NULL) p1=p1->m_pNext; i

数据结构与算法-链表反转

1.输入一个链表的头结点,从尾到头反过来输出每个结点的值.链表结点定义如下:struct ListNode{      int       m_nKey;      ListNode* m_pNext;};分析:这是一道很有意思的面试题.该题以及它的变体经常出现在各大公司的面试.笔试题中.看到这道题后,第一反应是从头到尾输出比较简单.于是很自然地想到把链表中链接结点的指针反转过来,改变链表的方向.然后就可以从头到尾输出了. 接下来的想法是从头到尾遍历链表,每经过一个结点的时候,把该结点放到一个栈

数据结构与算法-链表就地逆置

链表操作,单链表就地逆置 void Inverse(LinkList &L) { LNode *p, *q; p = L->next; /*记录第一个结点地址*/ L->next = NULL; /*把链表设置成空表*/ while (p != NULL) /*依次按头插法将各结点插入,就实现了逆置*/ { q = p; /*用q记录待插入结点地址*/ p = p->next; /*用p记录待插入结点的后继结点地址*/*/ q->next = L->next; /*将

javascript数据结构与算法--链表

链表与数组的区别?  1. 定义: 数组又叫做顺序表,顺序表是在内存中开辟一段连续的空间来存储数据,数组可以处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小.而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费. 链表是一种常见的数据组织形式,它采用动态分配内存的形式实现.链表是靠指针来连接多块不连续的的空间,在逻辑上形成一片连续的空间来存储数据.需要时可以用new分