Insertion 插入排序,不断将未排好的数插入到已经排好的序列中。和冒泡排序类似,都属于古老的基本排序算法。它的原理并不复杂,只需要找出插入的位置即可。我们用LeetCode上的算法题,感受一下它的原理。
Insertion 插入排序算法
INSERTION
英 [ɪnˈsɜːʃn] 美 [ɪnˈsɜːrʃn]
插入排序;插入;插入法;刊载;刊登
tion/ion 是名词后缀,像这类词还有很多
insert | 插入 | + ion | insertion |
educate | 教育 | 去e + ion | education |
graduate | 毕业 | 去e + ion | graduation |
instruct | 指示 |
ion | instruction |
suggest | 建议 | ion | suggestion |
collect | 收集 | ion | collection |
插入排序,不断将未排好的数插入到已经排好的序列中。和冒泡排序类似,都属于古老的基本排序算法。它的原理并不复杂,只需要找出插入的位置即可。在数组中使用插入排序可以通过改变下班实现。但我在 LeetCode 上遇到这道插入排序的算法题,规定了只能使用单向链表的数据结构。加大了插入的难度。知难而上,遇到了就解决掉。
LeetCode
这是 LeetCode 的第 147 题:
对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
题目的内容到这里就结束了。很好理解,思路也很清晰。但是,一顿操作猛如虎,一看提交不通过。这题,我调试了很多次才成功。之所以犯错就是对链表的指针理解不深入。
以下是我的解题思路:
第一步:定义一个虚拟节点。用于输出结果。这里不能用原始 head 节点。是因为原链表的第一个节点会因为排序导致位置变化,这会导致输出的结果不完整。
如,输入 head:8=>7=>9=>6。经过程序排序处理后,输出的结果却是 8=>9。
val dummy = ListNode()
dummy.next = head
第二步:定义两个节点。分别是 preNode 和 currNode。preNode 作为排序后的尾节点,也是排序后链表中的最大值。currNode 用来被比较和遍历链表。
var preNode = head
var currNode = head.next
第三步:检查 currNode 与 preNode 的值大小比较。若 currNode 大,我们只需要把 currNode 赋值给 preNode。反之就需要进行插入排序。
while (currNode != null) {
if (preNode!!.`val` <= currNode.`val`) {
preNode = currNode
currNode = currNode.next
} else {
}
}
第四步:插入排序的核心是找出需要插入的节点位置。通过 while 需要遍历找出小于 currNode 节点的位置。
var temp: ListNode = dummy
while (temp.next != currNode
&& temp.next!!.`val` < currNode.`val`) {
temp = temp.next!!
}
第五步:插入节点。交换位置前需要把当前节点剔除。然后把较大值作为当前节点的下一个节点。
while (currNode != null) {
if (preNode!!.`val` <= currNode.`val`) {
} else {
var temp: ListNode = dummy
while (temp.next != currNode
&& temp.next!!.`val` < currNode.`val`) {
temp = temp.next!!
}
preNode.next = currNode.next
currNode.next = temp.next
temp.next = currNode
currNode = preNode.next
}
}
举例说明,若 head -1=>10=>-2=>2=>5,在第二次开始遍历的情况如下:
preNode : 10=>-2=>2=>5
currNode : -2=>2=>5
temp : 0=>-1=>10=>-2=>2 =>5
执行 preNode.next=currNode.next代码,将当前节点 -2 剔除掉。剔除后的结果如下。
preNode:10=>2=>5
temp:0=>-1=>10=>2=>5
执行 currNode.next=temp.next 代码,断开 temp 和 -1 节点的连接,并将 -2 节点放在 -1 节点之前。
currNode:-2=>-1=>10=>2=>5
执行 temp.next=currNode 代码,重连 temp 和 -2 节点的连接。
temp:0=>-2=>-1=>10=>2=>5
执行 currNode=preNode.next 代码,开始下一个节点遍历。
完整代码如下:
fun main(args: Array<String>) {
headNode = ListNode()
generateListNode(headNode, 5)
printListNode(headNode?.next)
printListNode(insertionSortList(headNode?.next))
}
fun insertionSortList(head: ListNode?): ListNode? {
if (head?.next == null) return head
val dummy = ListNode()
dummy.next = head
var preNode = head
var currNode = head.next
while (currNode != null) {
if (preNode!!.`val` <= currNode.`val`) {
preNode = currNode
currNode = currNode.next
} else {
var temp: ListNode = dummy
while (temp.next != currNode
&& temp.next!!.`val` < currNode.`val`) {
temp = temp.next!!
}
preNode.next = currNode.next
currNode.next = temp.next
temp.next = currNode
currNode = preNode.next
}
}
return dummy.next
}
var headNode: ListNode? = null
var nodeIndex: Int = 1
class ListNode {
var `val`: Int = 0
var next: ListNode? = null
}
fun generateListNode(node: ListNode?, num: Int) {
if (nodeIndex <= num) {
val newNode = ListNode()
newNode.`val` = (-1 * num..2 * num).random()
newNode.next = null
node?.next = newNode
nodeIndex++
generateListNode(newNode, num)
}
}
fun printListNode(node: ListNode?) {
if (node == null) {
println()
return
}
var nextNode: ListNode? = node.next
var symbol = " => "
var printStr = "${node.`val`}$symbol"
while (nextNode != null) {
if (nextNode.next == null) {
symbol = ""
}
printStr += "${nextNode.`val`}$symbol"
nextNode = nextNode.next
}
println(printStr)
}
文章到这里就结束了,insertion "插入排序" 你会了么?
学英语会编程|ITDragon博客