插入排序
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。可以理解为玩扑克牌时的理牌;
复杂度分析: 时间复杂度O(N^2),空间复杂度O(1)。
注意:如果数组基本有序,那么插入排序会更好。因为插入排序每次是选择一个位置插入到有序的新数组中,如果数据基本有序的情况下,每次寻找插入位置的时间复杂度就基本上是O(1)了,那么总体的时间复杂度就可以是o(n)。
优化:进行二分插入,时间复杂度O(nlogn) 。
(图片来自CSDN博主Zandz_)
public class InsertionSort {
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
//定义待插入的数
int val = arr[i], j = i;
while (j > 0 && val < arr[j - 1]) {
// 待插入数与前面的元素一一比较
arr[j] = arr[j - 1];
j--;
}
arr[j] = val;
}
}
}
讲一下数据结构为链表时的插入排序: 先引入一道题:
147. 对链表进行插入排序
给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 重复直到所有输入数据插入完为止。
输入: head = [4,2,1,3] 输出: [1,2,3,4]
这里引入力扣官方解析:
如果是数组的插入排序,则数组的前面部分是有序序列,每次找到有序序列后面的第一个元素(待插入元素)的插入位置,将有序序列中的插入位置后面的元素都往后移动一位,然后将待插入元素置于插入位置。
对于链表而言,插入元素时只要更新相邻节点的指针即可,不需要像数组一样将插入位置后面的元素往后移动,因此插入操作的时间复杂度是 O(1),但是找到插入位置需要遍历链表中的节点,时间复杂度是 O(n),因此链表插入排序的总时间复杂度仍然是 O(n^2),其中 n 是链表的长度。
对于单向链表而言,只有指向后一个节点的指针,因此需要从链表的头节点开始往后遍历链表中的节点,寻找插入位置。
附上官方题解地址:对链表进行插入排序 - 对链表进行插入排序 - 力扣(LeetCode)
class Solution {
public ListNode insertionSortList(ListNode head) {
//判空
if (head == null) {
return head;
}
//创建哑节点,便于在 head 节点之前插入节点
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
//lastSorted 为链表已排序部分的最后一个节点,curr 为待插入的元素
ListNode lastSorted = head, curr = head.next;
while (curr != null) {
if (lastSorted.val <= curr.val) {
//不需要修改位置(指针)的情况
lastSorted = lastSorted.next;
} else {
ListNode prev = dummyHead;
//遍历已排序部分,找到插入位置
while (prev.next.val <= curr.val) {
prev = prev.next;
}
//交换位置/修改指针:
//1.lastSorted跟curr后面节点连上
lastSorted.next = curr.next;
//2.把curr从原来位置抽出,插入到插入位置(prev的下一个节点)
curr.next = prev.next;
prev.next = curr;
}
//重新让curr指向待插入的元素
curr = lastSorted.next;
}
return dummyHead.next;
}
}
这道题代码还可以优化:
可以利用前一个插入的位置进行提速,即每次先把当前要插入的元素和前一个插入点比较,如果比它大,那么就从这里开始搜索,否则才需要从链表头开始搜索。