㈠线性表:
线性表的链式存储结构是用一组任意的存储单元来存放线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。
对每个数据元素ai,除了存储其本身的信息之外,还需存储一个指示其直接后继存放位置的指针。这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存放位置的域称为指针域。
如果每个结点只设置一个指向其后继结点的指针成员,这样的链表称为线性单向链接表,简称单链表。
如果每个结点中设置两个指针成员,分别用以指向其前驱结点和后继结点,这样的链表称之为线性双向链接表,简称双链表。
一些补充:
1.链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
2.链表和数组不同和两者优缺点:(重点)
链表: 数组:
不是连续空间 连续空间
优点:增加和删除方便;优点:易于访问和连接;
缺点:查找很难; 缺点:不利于增加和删除;
力扣练习题:
203.移除链表元素
思路:
移除元素迅速想到可能移除的是头节点,因此需要设置虚拟头结点,然后按正常移除链表其他节点一样移除头结点
想到要设置两个指针,一个指向当前节点位置,一个搜寻不需要移除元素的指针,然后把当前指针节点的next指针指向不需要移除元素的指针即可
707.设计链表
思路
实现单向链表,即每个节点仅存储本身的值和后继节点。除此之外,我们还需要一个哨兵(sentinel)节点作为头节点,和一个 size 参数保存有效节点数。
初始化时,只需创建头节点 head 和 size 即可。
实现 get(index) 时,先判断有效性,再通过循环来找到对应的节点的值。
实现 addAtIndex(index, val) 时,如果 index 是有效值,则需要找到原来下标为 index 的节点的前驱节点 pred,并创建新节点 to_add,将to_add 的后继节点设为 pred 的后继节点,将 pred 的后继节点更新为 to_add,这样就将 to_add 插入到了链表中。最后需要更新 size。这样的操作对于 index=0 也成立。
实现 addAtHead(val) 和 addAtTail(val) 时,可以借助 addAtIndex(index, val) 来实现。
实现 deleteAtIndex(index),先判断参数有效性。然后找到下标为 index 的节点的前驱节点pred,通过将 pred 的后继节点更新为 pred 的后继节点的后继节点,来达到删除节点的效果同时也要更新 size。
2.两数相加
Python3.根据题目的意思:传入两个单链表,每个链表结点存放的都是一个整数,其实可以按照初等数学那样将两个链表对应的值进行相加的。这里我们需要使用新定义的一个哑结点来记录新的链表的头,如上面的result对应的链表,它其实也是我们最后需要返回的链表,在python3里面定义非常简单。定义完成之后,初始化一个空的值val记录相加后的结果,结果超出10吗,不超出就存放在当前的位置,工作指针向下移动;如果超出当前的值了,在当前的结点保存超出的部分,也就是余数,进位和两个链表的工作指针下移的新节点继续进行;期间只要有三个条件成立:l1为空,l2为空以及没有进位了,计算自然就跳出,返回新链表的头结点(哑结点)程序结束。时间复杂度:O(n),虽然有两个链表,但是他们是同时进行的。空间复杂度:S(n),另外新开一个链表。
Python.
㈡递归:
①.递归概述
递归:在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。
若调用自身,称之为直接递归。
若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归。
如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归。
②.以下三种情况常常要用到递归的方法:
1.定义是递归的
2.数据结构是递归的
3.问题的求解方法是递归的
③.递归方法的基本思路
1.首先定义一个cur指针,指向头结点。
2.再定义一个pre指针,初始化为null。
3.开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
........
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。
此时我们return pre指针就可以了,pre指针就指向了新的头结点。
力扣练习题:
206.反转链表
解题方法
1.方法一:迭代
(1)、迭代需要三个指针,pre,cur,nxt,分别按顺序指向三个节点
(2)、三个指针的初始化:pre指向空节点,cur指向头结点head,nxt指向head.next
因为head.next可能不存在,nxt在循环中定义,这样如果head为空就不会进入循环
(3)、迭代过程
nxt指向cur.next
cur.next指向pre
pre移动到cur位置
cur移动到nxt位置
(4)、当cur为空时,返回pre
时间复杂度:O(n)
空间复杂度:O(1)
2.方法二:递归
(1)、递归上来就先写终止条件:如果head为空或者head.next为空,返回head
(2)、新头结点newHead指向尾结点,此处进入递归,递归一直到遍历到尾结点时才会返回
(3)、每一层递归,该层递归中的head会让下一个节点指向自己,head.next.next = head;然后head自己指向空。以此达到反转的目的。
(4)、返回新链表的头结点newHead
时间复杂度:O(n)
空间复杂度:O(n)
24.两两交换链表中的节点
方法一:递归
思路与算法:
可以通过递归的方式实现两两交换链表中的节点。递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。
用 head 表示原始链表的头节点,新的链表的第二个节点,用 newhead 表示新的链表的头节点,原始链表的第二个节点,则原始链表中的其余节点的头节点是 newhead.next。令 head.next = swapPairs(newhead.next),表示将其余节点进行两两交换,交换后的新的头节点为 head 的下一个节点。然后令 newhead.next = head,即完成了所有节点的交换。最后返回新的链表的头节点 newhead。
方法二:暴力法
创建dhead作为头结点,把head拼接上去,temp指针指向dhead用于移动循环链表。结束条件是偶数情况temp指针后面没有元素了,奇数情况是temp指针的后面的后面。 为了避免自己写next写到头晕,所以设置了node1和node2。拆指针的三句根据动图去看,这样写才不会找不到下一个节点,千万别弄错顺序,交换完成后,把temp指针后移。
递归形式的代码:
用函数求n的阶乘