插入排序
前言
插入排序的思想很直观,其在性能上不如一些分治的排序策略,例如快速排序,归并排序。插入排序是一种基于比较的排序算法。
算法思想
其类似于打牌的抓牌环节,抓到牌后都会按照点数插入到合适的位置,核心思想是保证一个已排序区间,剩下的未排序区间依次与前面的已排序区间进行比较,插入到对应的位置。
插入
有关于插入,若是序列是使用数组进行存储,则可以从右向左扫描已排序数组,因为这样可以在性能上有所优化。因为对于数组的插入,其不像链表一样,只需要断链,然后链接,其需要移动大量的元素,才能够进行插入,若是从左向右,譬如数组的长度为n
,在位置i
插入,则需要扫描i
次,然后移动n-i+1
次,最后插入数据。若是从右向左,最多需要i
次扫描,然后可以边扫描边右移动
基本插入算法
- 复杂度:O(n^2)
- 比较次数(查找): O(n^2)
- 移动次数(插入后导致的移动): O(n^2)
def insertSort(arraylist): arrayLen = len(arraylist) for i in range(1, arrayLen): current = arraylist[i] j = i - 1 while j > 0 and arraylist[j] < current: arraylist[j+1] = arraylist[j] j = j - 1 arraylist[j+1] = current return arraylist
优化一: 使用鶸二分查找
因为已经确定了前面的排序区间是有序的,所以可以直接使用二分查找来确定插入的位置,这样查找的时间就得到了优化
- 复杂度: O(n^2)
- 比较次数: O(nlgn)
- 移动次数: O(n^2)
```python
def binarySearch(arraylist, key):
low = 0
high = len(arraylist)
while low < high:
return lowmid = (log + hig)//2 if arraylist[mid] == key: return mid elif arraylist[mid] > key: high = mid else: low = mid + 1
def insertSort(arraylist):
arrayLen = len(arraylist)
for i in range(1, arrayLen):
current = arraylist[i]
position = binarySearch(arraylist[:i], key)
for i in range(i, position, -1):
arraylist[i] = arraylist[i-1]
arraylist[position] = current
return arraylist
## 优化二: 为了优化而优化的链表
使用链表之后,改变其数据结构,可以优化插入的时间,由最初的O(n)优化到了O(1)
+ 复杂度: O(n^2)
+ 比较次数: O(n^2)
+ 移动次数: O(1)
```python
def insert(arrayList, next, i):
j = -1
# print("当前j为{}, next[j]={},arrayList[next[j]]={} arrayList[i]={}".format(j, next[j], arrayList[next[j]], arrayList[i]))
while next[j] != -1 and arrayList[next[j]] < arrayList[i]:
j = next[j]
# print("目前j={}".format(j))
next[j], next[i] = i, next[j]
# print("交换后的next[j]={}, next[i]={} \n".format(next[j], next[i]))
def reorder(arrayList, next):
i = -1
ys = []
while next[i] != -1:
ys.append(arrayList[next[i]])
i = next[i]
return ys
def insertSortIII(arrayList):
arrayLen = len(arrayList)
next = [-1]*(arrayLen+1)
for i in range(arrayLen):
insert(arrayList, next, i)
return reorder(arrayList, next)
优化三: 终极进化之二叉搜索树
对于以上的优化,只能在插入和查找之中二选一的进行优化。单独提高其中一个仍会使插入算法保持在O(n^2)中,使用二分查找能够将查找次数降到最低—-O(lgN),对于插入,为了能实现常数时间的插入,必定需要改变其数据结构。
二叉搜索树刚好满足以上的所有的需求,但是对于这个,应该不算是严格意义的插入排序了,应是更宽泛的定位—-基于比较的排序。
- 第一,其本身定义就支持二分查找。
- 同时查找到对应的值之后,即可插入,所以插入的时间为常数项O(1)。
- 对于其频繁的插入删除导致的性能降低,目前的排序涉及不多
总结
- 复杂度: O(nlgn)
- 移动次数: O(1)
- 比较次数 O(lgn)
实现
首先将数据构造为二叉搜索树,之后再进行中序遍历即可。
class TreeNode:
def __init__(self, data):
self.data = data
self.leftChild = None
self.rightChild = None
def insert(self, newData):
"""
插入数据
# edage case: 若是value相同如何解决 Word天
:param newData:
:return:
"""
if newData == self.data:
return False
elif newData < self.data:
if self.leftChild == None:
self.leftChild = TreeNode(newData)
else:
self.leftChild.insert(newData)
else:
if self.rightChild == None:
self.rightChild = TreeNode(newData)
else:
self.rightChild.insert(newData)
# 以下的前中后遍历均为递归的遍历
def preOrder(self):
"""
前序遍历
根左右
:return:
"""
print(self.data)
if self.leftChild:
self.leftChild.preOrder()
else:
self.rightChild.preOrder()
def inOrder(self):
"""
中序遍历
左根右
:return:
"""
if self.leftChild:
self.leftChild.inOrder()
print(self.data)
if self.rightChild:
self.rightChild.inOrder()
References
- 算法新解
- 算法第四版