数据结构-02-链表(Linkied List)

##Linked List - 链表 链表是线性表的一种。线性表是最基本、最简单、也是最经常使用的一种数据结构。线性表数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素以外,其它数据元素都是首尾相接的。线性表有两种存储方式,一种是顺序存储结构,另外一种是链式存储结构。咱们经常使用的数组就是一种典型的顺序存储结构。node

相反,链式存储结构就是两个相邻的元素在内存中可能不是相邻的,每个元素都有一个指针域,指针域通常是存储着到下一个元素的指针。这种存储方式的优势是插入和删除的时间复杂度为 O(1),不会浪费太多内存,添加元素的时候才会申请内存,删除元素会释放内存。缺点是访问的时间复杂度最坏为 O(n)。数组

顺序表的特性是随机读取,也就是访问一个元素的时间复杂度是O(1),链式表的特性是插入和删除的时间复杂度为O(1)。数据结构

链表就是链式存储的线性表。根据指针域的不一样,链表分为单向链表、双向链表、循环链表等等。翻译

请看示例:指针

class listNode:
	def __init__(self, val):
		self.val = val
		self.next = None

##链表的基本操做 ###反转链表 ####单向链表code

链表的基本形式是:1 -> 2 -> 3 -> null,反转须要变为 3 -> 2 -> 1 -> null。这里要注意:内存

  • 访问某个节点 curt.next 时,要检验 curt 是否为 null。
  • 要把反转后的最后一个节点(即反转前的第一个节点)指向 null。

示例:ci

class listNode:
	def __init__(self, val):
		self.val = val
		self.next = None


	def reserse(self, head):

		prev = None

		while head:
			temp = head.next
			head.next = prev
			prev = head
			head = temp
			
		return prev

####双向链表it

和单向链表的区别在于:双向链表的反转核心在于next和prev域的交换,还须要注意的是当前节点和上一个节点的递推。ast

class DListNode:
    def __init__(self, val):
        self.val = val
        self.prev = self.next = null

    def reverse(self, head):
        curt = None
        while head:
            curt = head
            head = curt.next
            curt.next = curt.prev
            curt.prev = head
        return curt

###删除链表中的某个节点 删除链表中的某个节点必定须要知道这个点的前继节点,因此须要一直有指针指向前继节点。还有一种删除是伪删除,是指复制一个和要删除节点值同样的节点,而后删除,这样就没必要知道其真正的前继节点了。

而后只须要把 prev -> next = prev -> next -> next 便可。可是因为链表表头可能在这个过程当中产生变化,致使咱们须要一些特别的技巧去处理这种状况。就是下面提到的 Dummy Node。

###链表指针的鲁棒性 综合上面讨论的两种基本操做,链表操做时的鲁棒性问题主要包含两个状况:

  • 当访问链表中某个节点 curt.next 时,必定要先判断 curt 是否为 null。
  • 所有操做结束后,判断是否有环;如有环,则置其中一端为 null。 ###Dummy Node Dummy node 是链表问题中一个重要的技巧,中文翻译叫“哑节点”或者“假人头结点”。

Dummy node 是一个虚拟节点,也能够认为是标杆节点。Dummy node 就是在链表表头 head 前加一个节点指向 head,即 dummy -> head。Dummy node 的使用多针对单链表没有前向指针的问题,保证链表的 head 不会在删除操做中丢失。除此以外,还有一种用法比较少见,就是使用 dummy node 来进行head的删除操做,好比 Remove Duplicates From Sorted List II,通常的方法current = current.next 是没法删除 head 元素的,因此这个时候若是有一个dummy node在head的前面。

因此,当链表的 head 有可能变化(被修改或者被删除)时,使用 dummy node 能够很好的简化代码,最终返回 dummy.next 即新的链表。

快慢指针 快慢指针也是一个能够用于不少问题的技巧。所谓快慢指针中的快慢指的是指针向前移动的步长,每次移动的步长较大即为快,步长较小即为慢,经常使用的快慢指针通常是在单链表中让快指针每次向前移动2,慢指针则每次向前移动1。快慢两个指针都从链表头开始遍历,因而快指针到达链表末尾的时候慢指针恰好到达中间位置,因而能够获得中间元素的值。快慢指针在链表相关问题中主要有两个应用:

快速找出未知长度单链表的中间节点 设置两个指针 fast、slow 都指向单链表的头节点,其中fast的移动速度是slow的2倍,当*fast指向末尾节点的时候,slow正好就在中间了。 判断单链表是否有环 利用快慢指针的原理,一样设置两个指针 *fast、*slow 都指向单链表的头节点,其中 fast的移动速度是slow的2倍。若是 *fast = NULL,说明该单链表 以 NULL结尾,不是循环链表;若是 *fast = *slow,则快指针追上慢指针,说明该链表是循环链表。

###快慢指针 快慢指针也是一个能够用于不少问题的技巧。所谓快慢指针中的快慢指的是指针向前移动的步长,每次移动的步长较大即为快,步长较小即为慢,经常使用的快慢指针通常是在单链表中让快指针每次向前移动2,慢指针则每次向前移动1。快慢两个指针都从链表头开始遍历,因而快指针到达链表末尾的时候慢指针恰好到达中间位置,因而能够获得中间元素的值。快慢指针在链表相关问题中主要有两个应用:

快速找出未知长度单链表的中间节点 设置两个指针 *fast、*slow 都指向单链表的头节点,其中*fast的移动速度是*slow的2倍,当*fast指向末尾节点的时候,slow正好就在中间了。 判断单链表是否有环 利用快慢指针的原理,一样设置两个指针 *fast、*slow 都指向单链表的头节点,其中 *fast的移动速度是*slow的2倍。若是 *fast = NULL,说明该单链表 以 NULL结尾,不是循环链表;若是 *fast = *slow,则快指针追上慢指针,说明该链表是循环链表。

class NodeCircle:
    def __init__(self, val):
        self.val = val
        self.next = None

    def has_circle(self, head):
        slow = head
        fast = head
        while (slow and fast):
            fast = fast.next
            slow = slow.next
            if fast:
                fast = fast.next
            if fast == slow:
                break
        if fast and slow and (fast == slow):
            return True
        else:
            return False
相关文章
相关标签/搜索