【算法】4、十大排序之插入排序(Insert Sort)

259 阅读3分钟

插入排序

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。可以理解为玩扑克牌时的理牌;

复杂度分析: 时间复杂度O(N^2),空间复杂度O(1)。

注意:如果数组基本有序,那么插入排序会更好。因为插入排序每次是选择一个位置插入到有序的新数组中,如果数据基本有序的情况下,每次寻找插入位置的时间复杂度就基本上是O(1)了,那么总体的时间复杂度就可以是o(n)。

优化:进行二分插入,时间复杂度O(nlogn)

f8951b09b88c4c4a9551835a3c97e77f.gif (图片来自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;
    }
}

这道题代码还可以优化:

可以利用前一个插入的位置进行提速,即每次先把当前要插入的元素和前一个插入点比较,如果比它大,那么就从这里开始搜索,否则才需要从链表头开始搜索。