📅  最后修改于: 2020-09-02 04:47:38             🧑  作者: Mango
如果您主修计算机科学,那么插入排序很可能是您听说过的最早的排序算法之一。它直观且易于实现,但是在大型数组上非常慢,并且几乎从未用于对其进行排序。
插入排序通常通过将其与玩拉米纸牌时对一手纸牌进行比较来说明。对于那些不熟悉游戏的人,大多数玩家希望他们手中的牌以升序排列,以便他们可以快速查看自己可以使用的组合。
经销商向您发出14张卡,您一张一张地捡起它们,然后以正确的顺序将它们放在您的手中。在整个过程中,您的手握住卡片的排序后的“子阵列”,而桌上剩余的正面朝下的卡片未排序-从中您一张一张地取出卡片,然后将其放在手中。
插入排序的实现非常简单直观,这是它在编程的早期阶段通常就被教导的原因之一。这是一种稳定的,就地算法,对几乎排序或较小的阵列非常有效。
让我们详细说明这些术语:
需要注意的另一件事:插入排序不需要在排序之前预先知道整个数组。该算法一次可以接收一个元素。如果我们想添加更多要排序的元素,那就太好了-该算法仅将该元素插入到适当的位置,而无需“重新进行”整个排序。
数组分为“排序”子数组和“未排序”子数组。首先,排序后的子数组仅包含原始数组的第一个元素。
对未排序数组中的第一个元素进行求值,以便我们可以将其插入到已排序子数组中的适当位置。
插入是通过将所有比新元素大的元素向右移动一个位置来完成的。
继续执行此操作,直到对整个数组进行排序。
但是请记住,当我们说一个元素大于或小于另一个元素时,它不一定意味着更大或更小的整数。
我们可以定义单词“更大”和“更小”,但是我们喜欢使用自定义对象。例如,如果点A远离坐标系统的中心,则它可以比点B “更大” 。
我们将使用粗体数字标记排序后的子数组,并使用以下数组来说明算法:
8、5、4、10、9
第一步是将8 “添加” 到排序后的子数组。
8,5,4,10%,9
现在我们来看看第一个未排序的元素-5。我们将该值保存在一个单独的变量中,例如current
为了安全起见。5小于8。我们将8向右移动一位,有效地覆盖了之前存储在其中的5(因此为了安全起见,使用了单独的变量):
8,8,如图4所示,10%,9(
current
= 5)
5比排序的子数组中的所有元素都要小,因此我们将其插入到第一个位置:
5,8,如图4所示,10%,9
接下来,我们看数字4。我们将该值保存在中current
。4小于8,所以我们将8移到右边,并对5进行同样的操作。
5,5,8,10,9(
current
= 4)
同样,我们遇到的元素要小于整个排序的子数组,因此我们将其放在第一位:
4,5,8,10,9
10大于我们在已排序子数组中最右边的元素,因此大于8左边的任何元素。因此,我们简单地转到下一个元素:
4,5,8,10,9
9小于10,因此我们将10向右移动:
4,5,8,10,10(
current
= 9)
但是,9大于8,因此我们只需在8之后紧接插入9。
4,5,8,9,10
实现
如前所述,插入排序非常容易实现。我们将首先在一个简单的整数数组上实现它,然后在一些自定义对象上实现它。
实际上,很有可能您将使用对象并根据特定条件对它们进行排序。
def insertion_sort(array):
# We start from 1 since the first element is trivially sorted
for index in range(1, len(array)):
currentValue = array[index]
currentPosition = index
# As long as we haven't reached the beginning and there is an element
# in our sorted array larger than the one we're trying to insert - move
# that element to the right
while currentPosition > 0 and array[currentPosition - 1] > currentValue:
array[currentPosition] = array[currentPosition -1]
currentPosition = currentPosition - 1
# We have either reached the beginning of the array or we have found
# an element of the sorted array that is smaller than the element
# we're trying to insert at index currentPosition - 1.
# Either way - we insert the element at currentPosition
array[currentPosition] = currentValue
array = [4, 22, 41, 40, 27, 30, 36, 16, 42, 37, 14, 39, 3, 6, 34, 9, 21, 2, 29, 47]
insertion_sort(array)
print("sorted array: " + str(array))
sorted array: [2, 3, 4, 6, 9, 14, 16, 21, 22, 27, 29, 30, 34, 36, 37, 39, 40, 41, 42, 47]
while
循环中颠倒条件的顺序,从技术上来说是不正确的。也就是说,如果我们先检查是否,array[currentPosition-1] > currentValue
然后再检查是否currentPosition > 0
。 class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return str.format("({},{})", self.x, self.y)
insertion_sort
方法进行了一些更改以适应自定义排序: def insertion_sort(array, compare_function):
for index in range(1, len(array)):
currentValue = array[index]
currentPosition = index
while currentPosition > 0 and compare_function(array[currentPosition - 1], currentValue):
array[currentPosition] = array[currentPosition - 1]
currentPosition = currentPosition - 1
array[currentPosition] = currentValue
A = Point(1,2)
B = Point(4,4)
C = Point(3,1)
D = Point(10,0)
array = [A,B,C,D]
# We sort by the x coordinate, ascending
insertion_sort(array, lambda a, b: a.x > b.x)
for point in array:
print(point)
(1,2)
(3,1)
(4,4)
(10,0)
插入排序似乎是一种缓慢的算法,实际上,在大多数情况下,插入排序由于其O(n 2)时间复杂性而对于任何实际使用而言都太慢了。但是,正如我们已经提到的,它在小型阵列和几乎排序的阵列上非常有效。
这使得插入排序非常适合与在大型数据集上运行良好的算法结合使用。
例如,Java使用Dual Pivot 快速排序作为主要排序算法,但只要数组(或由快速排序创建的子数组)少于7个元素,就使用插入排序。
另一个有效的组合是简单地忽略由快速排序创建的所有小子数组,然后通过插入排序传递最终的,几乎排序的数组。
插入排序留下它标记的另一个地方是一种非常流行的算法,称为Shell Sort。Shell Sort的工作方式是调用“插入排序”以对彼此远离的元素对进行排序,然后逐渐减小要比较的元素之间的间隔。
本质上,首先要对小的,然后是几乎排序的较大数组进行大量的Insertion Sort调用,以利用其所有优点。
插入排序是一种非常简单的,通常效率低下的算法,尽管如此,它仍然具有一些特定的优点,即使在开发了许多其他更有效的算法之后,它仍然具有重要意义。
它仍然是将未来的软件开发人员引入排序算法领域的绝佳算法,并且在实践中仍可用于特定场合。