链表是一种递归的数据结构,是一种线性结构,可是并不会按线性的顺序存储数据,而是在每个节点里存到下一个节点的指针(Pointer),简单来讲链表并不像数组那样将数组存储在一个连续的内存地址空间里,它们能够不是连续的由于他们每一个节点保存着下一个节点的引用(地址)java
单链表(又称单向链表)是链表中的一种,其特色是链表的连接方向是单向的,对链表的访问要从头部(head)开始,而后依次经过next指针读取下一个节点。node
单链表的数据结构能够分为两部分:数据域和指针域,数据域存储数据,指针域指向下一个存储节点的地址。注意: 单向链表只可向一个方向进行遍历算法
//(Kotlin描述)
class LinkedNode(var value: Int) {
var next: LinkedNode? = null //指向下一个存储节点的next指针
}
复制代码
//(Java描述)
public class LinkedNode {
int value;
LinkedNode next; //指向下一个存储节点的next指针
public LinkedNode(int value) {
this.value = value;
}
}
复制代码
双链表(又称双向链表),是链表中一种,与单链表不一样的是它的每一个节点都有两个指针,分别指向直接后继节点和直接前驱节点;因此,从双链表中的任意一个结点开始,均可以很方便地访问它的前驱结点和后继结点。设计模式
双链表的数据结构能够分为三部分:prev指针域、数据域和next指针域,prev指针域指向上一个存储节点的地址(也即指向直接前驱节点),数据域存储数据,next指针域指向下一个存储节点的地址(也即指向直接后继节点)。注意: 单向链表可向两个方向进行遍历,分别为正序和逆序遍历数组
//(Kotlin描述)
class LinkedNode(var value: Int) {
var prev: LinkedNode? = null //指向上一个存储节点的prev指针
var next: LinkedNode? = null //指向下一个存储节点的next指针
}
复制代码
//(Java描述)
public class LinkedNode {
int value;
LinkedNode prev; //指向上一个存储节点的prev指针
LinkedNode next; //指向下一个存储节点的next指针
public LinkedNode(int value) {
this.value = value;
}
}
复制代码
单向循环链表,只是在单链表的基础上,它的最后一个结点再也不为null而是指向头结点,造成一个环。而且在节点结构上和单链表是同样的。所以,从单向循环链表中的任何一个结点出发都能找到任何其余结点。数据结构
双向循环链表,只是在双链表的基础,它的头节点的prev指针再也不为null,而是直接指向它的尾节点;它的尾节点的next指针再也不为null,而是直接指向它的头节点。app
咱们知道一个节点类型的变量就能够表示一条链表,只要保证对应的每一个节点的next指针可以指向下一个节点便可或指向null(表示链表最后一个节点)函数
//链表结构定义
class LinkedNode(var value: Int) {
var next: LinkedNode? = null
}
//链表的构造
fun main(args: Array<String>) {
val node1 = LinkedNode(value = 1)//建立节点1
val node2 = LinkedNode(value = 2)//建立节点2
val node3 = LinkedNode(value = 3)//建立节点3
node1.next = node2//经过node1的next指针指向node2,把node1和node2链接起来
node2.next = node3//经过node2的next指针指向node3,把node2和node3链接起来
}
复制代码
class LinkedNode(var value: Int) {
var prev: LinkedNode? = null
var next: LinkedNode? = null
}
fun main(args: Array<String>) {
val node1 = LinkedNode(value = 1)//建立节点1 此时的prev,next均为null
val node2 = LinkedNode(value = 2)//建立节点2 此时的prev,next均为null
val node3 = LinkedNode(value = 3)//建立节点3 此时的prev,next均为null
node1.next = node2 //node1的next指针指向直接后继节点node2
node2.prev = node1 //node2的prev指针指向直接前驱节点node1
node2.next = node3 //node2的next指针指向直接后继节点node3
node3.prev = node2 //node3的prev指针指向直接前驱节点node2
}
复制代码
在链表表头插入一个节点是最简单的一种操做,通常处理方式,先建立一个oldFirst指向第一个节点,而后从新建立一个新的节点,将新节点的next指向oldFirst指向的节点,first指向新插入的节点。post
fun insertToHead(head: LinkedNode): LinkedNode {
var first: LinkedNode = head
val oldFirst: LinkedNode = head
first = LinkedNode(value = 6)
first.next = oldFirst
return first
}
复制代码
fun insertToHead(head: LinkedNode): LinkedNode {
var first: LinkedNode = head
val oldFirst: LinkedNode = head
first = LinkedNode(value = 6)
oldFirst.prev = first
first.next = oldFirst
return first
}
复制代码
fun deleteToHead(head: LinkedNode): LinkedNode? {
var first: LinkedNode? = head
first = first?.next
return first
}
复制代码
fun deleteToHead(head: LinkedNode): LinkedNode? {
var first: LinkedNode? = head
first = first?.next
first?.prev = null
return first
}
复制代码
fun insertToTail(head: LinkedNode): LinkedNode? {
var last = getTailNode(head) //经过遍历获得尾部节点
val oldLast = last
last = LinkedNode(value = 4)
oldLast?.next = last
return head
}
复制代码
fun insertToTail(head: LinkedNode): LinkedNode? {
var last = getTailNode(head) //经过遍历获得尾部节点
val oldLast = last
last = LinkedNode(value = 4)
oldLast?.next = last
last.prev = oldLast
return head
}
复制代码
fun insertToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到须要的插入位置的上一个节点
val newNode = LinkedNode(value = 6)
newNode.next = current?.next// 新插入的节点next指向插入位置的上一个节点的next
current?.next = newNode//而后断开插入位置的上一个节点的next,并把指向新插入的节点
return head
}
复制代码
fun insertToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到须要的插入位置的上一个节点
val newNode = LinkedNode(value = 6)
newNode.next = current?.next// 新插入的节点next指向插入位置的上一个节点的next
newNode.prev = current //新插入的节点prev指向插入位置的上一个节点
current?.next = newNode//而后断开插入位置的上一个节点的next,并把它指向新插入的节点
current?.next?.prev = newNode //而后断开插入位置的上一个节点的prev,并把它指向新插入的节点
return head
}
复制代码
fun deleteToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到须要的删除节点的上一个节点
current?.next = current?.next?.next
return head
}
复制代码
fun deleteToOther(head: LinkedNode): LinkedNode? {
val current = getDeletePrevNode(head) //拿到须要的删除节点的上一个节点
current?.next = current?.next?.next
current?.next?.prev = current
return head
}
复制代码
fun traverseLinkedList(head: LinkedNode?) {
var current = head
while (current != null){
println(current.value)
current = current.next
}
}
复制代码
fun getLength(head: LinkedNode?): Int {
var len = 0
var current = head
while (current != null){
len++
current = current.next
}
return len
}
复制代码
因为栈是一个表,所以任何实现表的方法都能实现栈。显然,Java中经常使用的ArrayList和LinkedList集合都是支持栈操做的。性能
单链表也是能实现栈的,经过在表的顶端插入实现栈的push压栈操做,经过删除表的顶端元素实现pop入栈操做。top操做只须要返回顶部的元素的值便可。
class LinkedStack {
private var first: Node? = null
private var len: Int = 0
fun push(value: Int) {//至关于链表从表头插入新的元素
val oldFirst = first
first = Node(value)
first?.next = oldFirst
len++
}
fun pop(): Int {//至关于链表从表头删除新的元素
val value = first?.value
first = first?.next
return value ?: -1
}
fun top(): Int {
return first?.value ?: -1
}
fun isEmpty(): Boolean {
return first == null
}
fun size(): Int {
return len
}
inner class Node(var value: Int) {
var next: Node? = null
}
}
复制代码
class LinkedQueue {
private var first: Node? = null
private var last: Node? = null
private var len: Int = 0
fun enqueue(value: Int) {//至关于链表从尾部插入新的节点
val oldLast = last
last = Node(value)
last?.next = null
if (isEmpty()) {
first = last
} else {
oldLast?.next = last
}
len++
}
fun dequeue(): Int {//至关于链表从尾部删除最后节点
val value = first?.value ?: -1
first = first?.next
if (isEmpty()) {
last = null
}
return value
}
fun isEmpty(): Boolean {
return first == null
}
fun size(): Int {
return len
}
inner class Node(var value: Int) {
var next: Node? = null
}
}
复制代码
链表反转(也称链表的逆序)是链表中一种比较经典的操做,在一些数据结构的题目链表的反转也是常考点,链表的反转也会作为一部分融入题目,好比回文链表问题等
二、实现过程
三、代码描述
fun reverseLinkedList(head: LinkedNode?): LinkedNode? {
var prev: LinkedNode? = null
var current: LinkedNode? = head
var next: LinkedNode? = head
while (current != null) {
next = current.next
current.next = prev
prev = current
current = next
}
return prev
}
复制代码
快慢指针追赶问题在链表中是很是经典的,快慢指针问题通常用于解决链表中间节点问题和链表是否含有环以及链表中环的入口位置等问题。
若是使用快慢指针是判断链表是否含有环的问题,咱们更但愿fast和slow指针的相对路程是正好是环的长度,(也就是slow指针刚进入环,而fast指针刚绕环一圈,此时两指针正好相遇)这样两个指针就相遇了。这样取每步的速度差可以被环长度整除的数字。可是咱们并不知道环的具体长度,因此只能取每步的速度差可以被环长度整除的数字为1(1能被全部的数整除),因此咱们取fast指针每次走2步,slow指针每次走1步,实际上只要保证二者速度差为1就能够了,你甚至能够fast每次走3步,slow指针每次走2步都是能够的,这样一来只要它们在环里面就必定能相遇。
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;//慢指针每次走1步
fast = fast.next.next;//快指针每次走2步
if(slow == fast){//若是链表存在环,那么slow和fast指针会相遇
return true;
}
}
return false;
}
复制代码
由快慢指针追赶的原理可知,若是fast指针和slow指针同时从链表(链表不含环)的头结点出发开始遍历,若是fast指针的每次遍历步数是slow指针的两倍,那么可获得若是fast遍历到链表的尾部,那么此时的slow指针应该处于链表的中间节点位置(具体题目可参考:LeetCode第876题)。
public ListNode middleNode(ListNode head) {
if(head == null) return null;
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
复制代码
一、删除链表的节点
二、反转链表
三、链表的中间节点
四、合并两个有序链表
五、删除排序链表中的重复元素
六、移除链表中的元素
七、相交链表
八、环形链表
九、回文链表
十、设计链表
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不按期翻译一篇Kotlin国外技术文章。若是你也喜欢Kotlin,欢迎加入咱们~~~
Kotlin邂逅设计模式系列:
数据结构与算法系列:
翻译系列:
原创系列:
Effective Kotlin翻译系列
实战系列: