zoukankan      html  css  js  c++  java
  • [Swift]插入排序 | Insertion sort

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    ➤微信公众号:山青咏芝(shanqingyongzhi)
    ➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
    ➤GitHub地址:https://github.com/strengthen/LeetCode
    ➤原文地址:https://www.cnblogs.com/strengthen/p/11032154.html 
    ➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
    ➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

    1、原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

    2、算法描述:

    (1)、从第一个元素开始,该元素可以认为已经被排序。

    (2)、取出下一个元素,在已经排序的元素序列中从后向前扫描。

    (3)、如果该元素(已排序)大于新元素,将该元素移到下一位置。

    (4)、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。

    (5)、将新元素插入到该位置后。

    (6)、重复步骤(2)~(5)。

    如果比较操作的代价比交换操作大,则可以采用二分查找法来减少比较操作的数目。称为二分查找插入排序。

    3、插入排序的几个分类:

    (1)、直接插入排序:把n个待排序的元素看成为一个有序数组和一个无序数组。开始时有序数组中只包含1个元素,无序数组中包含有n-1个元素,排序过程中每次从无序数组中取出第一个元素,将它插入到有序数组中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。

     1 //MARK: 直接插入排序
     2 func directInsertSort(_ arr:inout [Int])
     3 {
     4     for i in 1..<arr.count
     5     {
     6         //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
     7         for j in stride(from:i - 1,through: 0,by:-1)
     8         {
     9             if arr[j] < arr[i] {break}
    10              //如找到了一个合适的位置
    11             if j != i - 1
    12             {
    13                 let temp:Int = arr[i]
    14                 //将比a[i]大的数据向后移
    15                 for k in stride(from: i - 1, to: j, by: -1)
    16                 {
    17                     arr[k + 1] = arr[k]
    18                     //将a[i]放到正确位置上
    19                     arr[k + 1] = temp
    20                 }
    21             }
    22         }
    23     }
    24 }
    测试:
    1 var arr:[Int] = [1,9,2,8,3,7,4,6,5,10,11,12]
    2 directInsertSort(&arr)
    3 print(arr)
    4 //Print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

    (2)、二分插入排序:在插入第i个元素时,对前面的0~i-1元素进行折半,先跟他们中间的那个元素比,如果小,则对前半再进行折半,否则对后半进行折半,直到left<right,然后再把第i个元素前1位与目标位置之间的所有元素后移,再把第i个元素放在目标位置上。

     1 //MARK: 二分插入排序
     2 func binarySearchInsertSort(_ arr:inout [Int])
     3 {
     4     for i in 1..<arr.count
     5     {
     6         let temp:Int = arr[i]
     7         var low:Int = 0
     8         var high:Int = i - 1
     9         var mid:Int = -1
    10         while(low <= high)
    11         {
    12              mid = low + (high - low) / 2
    13             if arr[mid] > temp
    14             {
    15                 high = mid - 1
    16             }
    17             else
    18             {
    19                 //元素相同时,也插入在后面的位置
    20                 low = mid + 1
    21             }
    22         }
    23         for j in stride(from: i - 1, through: low, by: -1)
    24         {
    25             arr[j + 1] = arr[j]
    26         }
    27         arr[low] = temp
    28     }
    29 }
    30
    测试:
    1 var arr:[Int] = [1,9,2,8,3,7,4,6,5,10,11,12]
    2 binarySearchInsertSort(&arr)
    3 print(arr)
    4 //Print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

    (3)、链表插入排序:LeetCode147

    从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。 插入排序算法:

    (a)、插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。

    (b)、每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。

    (c)、重复直到所有输入数据插入完为止。

     1 /**
     2  * Definition for singly-linked list.
     3  * public class ListNode {
     4  *     public var val: Int
     5  *     public var next: ListNode?
     6  *     public init(_ val: Int) {
     7  *         self.val = val
     8  *         self.next = nil
     9  *     }
    10  * }
    11  */
    12 class Solution {
    13     func insertionSortList(_ head: ListNode?) -> ListNode? {
    14         let dummyHead = ListNode(-1)
    15         var sortedNodeIdx: ListNode? = dummyHead
    16         var curr = head
    17         
    18         while let _ = curr {
    19             while let sortedNodeIdxNext = sortedNodeIdx?.next,
    20                 sortedNodeIdxNext.val < curr!.val {
    21                     sortedNodeIdx = sortedNodeIdxNext
    22             }
    23             
    24             let currNext = curr?.next
    25             let sortedNodeIdxNext = sortedNodeIdx?.next
    26             sortedNodeIdx?.next = curr
    27             curr?.next = sortedNodeIdxNext
    28             sortedNodeIdx = dummyHead
    29             curr = currNext
    30         }
    31         
    32         return dummyHead.next
    33     }
    34 }

    (4)、希尔排序也称“缩小增量排序”(Diminishing Increment Sort),是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法首先对彼此远离的元素对进行排序,然后逐步减小要比较的元素之间的差距。从相距很远的元素开始,它可以比一个简单的最近邻交换更快地将一些不合适的元素移动到位。Donald Shell于1959年发布了这种类型的第一个版本。Shellsort的运行时间在很大程度上取决于它使用的间隙序列。对于许多实际的变体,确定它们的时间复杂度仍然是一个未解决的问题。 

    希尔排序是基于插入排序的以下两点性质而提出改进方法的:

    (1)、插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

    (2)、但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

    排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。

    希尔分析:希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。

    例如:假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样:

    13 14 94 33 82
    25 59 94 65 23
    45 27 73 25 39
    10

    然后我们对每列进行排序:

    10 14 73 25 23
    13 27 94 33 39
    25 59 94 65 82
    45

    将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].这时10已经移至正确位置了,然后再以3为步长进行排序:

    10 14 73
    25 23 13
    27 94 33
    39 25 59
    94 65 82
    45

    排序之后变为:

    10 14 13
    25 23 33
    27 25 59
    39 65 73
    45 94 82
    94

    最后以1步长进行排序(此时就是简单的插入排序)。

    步长序列:步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为普通插入排序,这就保证了数据一定会被排序。

    已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)。

    这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序要快,甚至在小数组中比快速排序和堆排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

    另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713,…)

     1 //MARK: 希尔排序
     2 func shellSort(_ arr:inout [Int])
     3 {
     4     var number:Int = arr.count / 2
     5     var j:Int = 0
     6     var temp:Int = 0
     7     while (number >= 1)
     8     {
     9         for i in number..<arr.count
    10         {
    11             temp = arr[i]
    12             j = i - number
    13             //需要注意的是,这里array[j] < temp将会使数组从大到小排列
    14             while (j >= 0 && arr[j] > temp)
    15             {
    16                 arr[j + number] = arr[j]
    17                 j = j - number
    18             }
    19             arr[j + number] = temp
    20         }
    21         number = number / 2
    22     }
    23 }

    测试:

    1 var arr:[Int] = [1,9,2,8,3,7,4,6,5,10,11,12]
    2 shellSort(&arr)
    3 print(arr)
    4 //Print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    
    
  • 相关阅读:
    Spring 中的重试机制,简单、实用!
    Docker 常用命令,还有谁不会?
    Java 对象不使用时为什么要赋值为 null?
    为什么 Nginx 比 Apache 更牛叉?
    你还在用命令看日志?快用 Kibana 吧,一张图片胜过千万行日志!
    golang如何体现面向对象思想
    golang 三⾊标记+GC混合写屏障机制
    Golang中逃逸现象-变量何时 栈何时堆
    golang调度器原理与GMP模型设计思想
    golang 程序内存分析方法
  • 原文地址:https://www.cnblogs.com/strengthen/p/11032154.html
Copyright © 2011-2022 走看看