一直以来都知道自己在数据结构上是个弱点,大学时期学的东西到现在就只能记得一个概念了,自从期末考完试就都还给老师了。要开始找工作面试了,决定把这些东西都重新温习一遍。
数据结构中最基础的应该就是线性表(线性表:零个或多个数据元素的有限序列)了,线性表根据物理结构的不同分为顺序存储结构和链式存储结构,顺序结构实现就是数组了,链式结构就是链表了。
定义:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表分为单项链表,双向链表和循环链表,一般情况下单项链表居多。推荐看一下J_Knight_在掘金上的文章(末尾有链接,链表要实现节点(Node)和链式结构(List)
节点是链表的基本单元,单项链表节点只有next指针,双向链表包含next和previous两个指针
ADT 节点(node)
Data
value:持有的数据
Operation
init:初始化
previous:指向上一节点的指针
next:指向下一节点的指针
endADT
链表实现了一些主要的操作功能,如插入节点,删除节点,查询节点等(增删改查)
ADT 链表(linked list)
Data
linked list:持有的线性表
Operation
init:初始化
count:持有节点总个数
isEmpty:是否为空
first:头节点
last:尾节点
node:传入index返回节点
insert:插入node到指定index
insertToHead:插入节点到表头
appendToTail:插入节点到表尾
removeAll:移除所有节点
remove:移除传入的节点
removeAt:移除传入index的节点
endADT
具体代码就不贴了,J_Knight_在掘金上写的很清楚了,建议自己敲一遍,还是很有作用的,我想分享一下在iOS面试之道上涉及到链表的三道题并记录一下我当时的想法。
question1:给出一个链表和一个值x,要求将链表中所有小于x的值放到左边,大于等于x的值放到右边,并且保证原链表节点的顺序不变,例子:List:1->5->3->2->4->2 x = 3 结果为:1->2->3->5->3->4
解题思路:使用两个链表,一个记录比x小的值,一个记录比x大的值,使用尾插法来保证节点的顺序是不变的,最后将两个链表连接起来。
Dummy节点:它的作用就是作为一个虚拟的头前结点。我们引入它的原因是我们不知道要返回的新链表的头结点是哪一个,它有可能是原链表的第一个节点,可能在原链表的中间,也可能在最后,甚至可能不存在(nil)。
实现代码:
func LinkedListSort(_ head : LinkedList<Int>, _ x : Int) -> LinkedList<Int>{
let prevDummy = LinkedListNode<Int>(value: 0) , postDummy = LinkedListNode<Int>(value: 0)
var prev = prevDummy , post = postDummy
var node = head.first
while node != nil {
if((node?.value)! < x){
prev.next = node
prev = node!
}else{
post.next = node
post = node!
}
node = node!.next
}
post.next = nil
prev.next = postDummy.next
return head
}
question2:如何检测链表中是否有环存在(这道题在去苏宁面试的时候问过的)
解题思路:用两个指针同时访问链表,其中一个的速度是另一个的两倍(快指针和慢指针),如果他们变成相等的,那么链表存在环,反之则链表不存在环(这个理论我思考了很久,不明白为什么快指针一定会和慢指针指向同一个节点,后来想通了,因为有环的链表是不会over的,两个指针一定会指向同一个节点)
实现代码:
func isExistCircle(_ List : LinkedList<Int>) -> Bool{
let head = List.first
var slow = head
var fast = head
while fast != nil && fast?.next != nil{
slow = slow?.next
fast = fast?.next?.next
if slow === fast{
return true
}
}
return false
}
Question 3:给出链表和一个值x,要求将链表中倒数第x个节点删掉(链表为一个未知长度的单向链表)
解题思路:使用两个速度相同的指针,快指针领先慢指针x个节点,当快指针到达终点时,慢指针的下一个节点就是我们要删除的节点。(我当时直接用链表里面的size函数获得链表长度,用remove函数删除了对应的节点,但是remove函数是基于双向链表实现的,如果是单项链表的话是不可以直接remove的)
实现代码:
func removeFromEnd(_ List : LinkedList<Int>, _ x : Int ) -> LinkedList<Int>{
let head = List.first
var slow = head
var fast = head
//设置快指针初始位置
for _ in 0 ..< x {
fast = fast?.next
}
while fast != nil && fast?.next != nil{
slow = slow?.next
fast = fast?.next
}
//List.remove(node: (slow?.next)!)//remove函数是基于双向链表实现的,单项链表无法使用
slow?.next = slow?.next?.next
return List
}
链表章节就这三道题了,主要还是学到了dummy节点和快行指针这样的概念,比较有收获。
链接:
J_Knight_掘金地址
iOS面试之道(唐巧,故胤道长)