尊重知识产权
冒泡排序
从序列的一端开始往另一端冒泡(可以从左往右,也可以从右往左),依此比较相邻的两个数的大小
图解冒泡排序
以[8,2,5,9,7]这组数字来做示例,上图:
从左往右依次冒泡,将小的往右移动
首先比较第一个数和第二个数的大小,我们发现 2 比 8 要小,那么保持原位,不做改动。位置还是 8,2,5,9,7 。
指针往右移动一格,接着比较:
比较第二个数和第三个数的大小,发现 2 比 5 要小,所以位置交换,交换后数组更新为:[ 8,5,2,9,7 ]。
.....
冒泡的代码还是相当简单的,两层循环,外层冒泡轮数,里层依次比较,江湖中人人尽皆知。
我们看到嵌套循环,应该立马就可以得出这个算法的时间复杂度为O(n^2)。
代码:
def bubble_sort(arr):
if len(arr)==0:
return None
if len(arr)==1:
return arr[0]
for i in range(len(arr)):
for j in range(len(arr)-1-i):
if arr[j]>arr[j+1]:
arr[j],arr[j+1]= arr[j],arr[j+1]
else:
arr[j],arr[j+1]= arr[j+1],arr[j]
return arr
a = bubble_sort([8,2,5,9,7])
print(a)
#结果:
[9, 8, 7, 5, 2]
[Finished in 0.4s]冒泡优化
冒泡有一个最大的问题就是这种算法不管你有序还是无序,反正先把循环比较了再说。
比如,例子数组[9,8,7,6,5] ,一个有序的数组,根本不需要排序,它仍然是双层循环一个不少的把数据遍历干净,这其实是做了没必要的事情。
针对这个问题,我们可以设定一个临时遍历来标记该数组是否已经有序,如果有序了就不用遍历了。
def bubble_sort(arr):
if len(arr)==0:
return None
if len(arr)==1:
return arr[0]
for i in range(len(arr)-1):
Is_sort = True
for j in range(len(arr)-1-i):
if arr[j]>arr[j+1]:
arr[j],arr[j+1]= arr[j],arr[j+1]
else:
arr[j],arr[j+1]= arr[j+1],arr[j]
Is_sort = False
if Is_sort:
break
return arr
a = bubble_sort([9, 8, 7, 5, 2])
print(a)选择排序
选择排序:首先找到数组中最小的元素,领出来,将它和数组的第一个元素交换位置,第二步,在剩下的元素中继续寻找最小的元素,领出来,和数组的第二个元素交换位置,如此循环,直到整个数组排序完成。
至于选大还是选小,这个都无所谓,你也可以每次选择最大的拎出来排,也可以每次选择最小的拎出来的排,只要你的排序的手段是这种方式,都叫选择排序。
例子:
我们还是以[ 8,2,5,9,7 ]这组数字做例子。
第一次选择,先找到数组中最小的数字 2 ,然后和第一个数字交换位置。(如果第一个数字就是最小值,那么自己和自己交换位置,也可以不做处理,就是一个 if 的事情)
第二次选择,由于数组第一个位置已经是有序的,所以只需要查找剩余位置,找到其中最小的数字5,然后和数组第二个位置的元素交换。
......
最后一个到达了数组末尾,没有可对比的元素,结束选择。
双层循环,时间复杂度和冒泡一模一样,都是O(n^2)。
代码:
def select_sort(arr):
if len(arr)==0:
return None
if len(arr)==1:
return arr[0]
for i in range(len(arr)):
#第一步找到最小值
min = i
for j in range(i+1,len(arr)):
#拎出来
if arr[j]>arr[min]:
min = j
#找到最小值,将最小值与数组的d第i个位置互换
arr[i],arr[min] = arr[min], arr[i]
return arr
a = select_sort([9, 8, 2, 5, 7])
print(a)小小知识课堂
我们可以先观察冒泡排序和选择排序。
冒泡主要是两两相比,小的(或是大的)冒出来。
选择是将大的(或是小的)拎出来;在拎出来的过程中需要一个中间值
插入排序
插入排序的思想和我们打扑克摸牌的时候一样,从牌堆里一张一张摸起来的牌都是乱序的,我们会把摸起来的牌插入到左手中合适的位置,让左手中的牌时刻保持一个有序的状态。
例子:数组初始化:[ 8,2,5,9,7 ],我们把数组中的数据分成两个区域,已排序区域和未排序区域,初始化的时候所有的数据都处在未排序区域中,已排序区域是空。
第一轮,从未排序区域中随机拿出一个数字,既然是随机,那么我们就获取第一个,然后插入到已排序区域中,已排序区域是空,那么就不做比较,默认自身已经是有序的了。(当然了,第一轮在代码中是可以省略的,从下标为1的元素开始即可)
第二轮,继续从未排序区域中拿出一个数,插入到已排序区域中,这个时候要遍历已排序区域中的数字挨个做比较,比大比小取决于你是想升序排还是想倒序排,这里排升序:
......
最好情况的时间复杂度是O(n),最坏情况的时间复杂度是 O(n^2),然而时间复杂度这个指标看的是最坏的情况,而不是最好的情况,所以插入排序的时间复杂度是 O(n^2)。
上面的图是不是有些繁杂?
来个简单的吧!
插入排序的工作原理是,对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
从第一个元素开始,该元素可以认为已经被排序
取下一个元素,在已经排序的元素序列中从后向前扫描
如果被扫描的元素(已排序)大于新元素,将该元素后移一位
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置后
重复步骤2~5
def insert_sort(arr):
if len(arr)==0:
return None
if len(arr)==1:
return arr[0]
for i in range(1,len(arr)):
#第一个值是初始值
for j in range(i):
#倒着来吧
#如果新进来 的家伙比较大,往前
#这里有注意,当j=0时,i-j=i
if arr[i-j]>arr[i-j-1]:
arr[i-j],arr[i-j-1] = arr[i-j-1], arr[i-j]
return arr
a = insert_sort([8, 9, 2, 5, 7])
print(a)希尔排序
希尔排序这个名字,来源于它的发明者希尔,也称作“缩小增量排序”,是插入排序的一种更高效的改进版本。
我们知道,插入排序对于大规模的乱序数组的时候效率是比较慢的,因为它每次只能将数据移动一位,希尔排序为了加快插入的速度,让数据移动的时候可以实现跳跃移动,节省了一部分的时间开支。
希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。希尔排序是非稳定排序算法。
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例子:例如,假设有这样一组数[ 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步长进行排序(此时就是简单的插入排序了)。
或者可以这样理解:
代码:
def shell_sort(arr):
if len(arr)==0:
return None
if len(arr)==1:
return arr[0]
gap = len(arr)//2
#首先gap不能为0,gap=1中止
while gap>0:
for i in range(gap,len(arr)):
if arr[i-gap]>arr[i]:
arr[i-gap],arr[i] = arr[i-gap],arr[i]
else:
arr[i-gap],arr[i] = arr[i],arr[i-gap]
gap = gap//2
return arr
a = shell_sort([8, 9, 2, 5, 7])
print(a)归并排序
归并字面上的意思是合并,归并算法的核心思想是分治法,就是将一个数组一刀切两半,递归切,直到切成单个元素,然后重新组装合并,单个元素合并成小数组,两个小数组合并成大数组,直到最终合并完成,排序完毕。
小小知识课堂
归并乍一看和希尔差不多。但是不是的,归并是一分为二,而希尔是以gap为距离的。
- 先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
- 再考虑递归分解,基本思路是将数组分解成
left和right,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可以再二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻二个小组即可。
代码:
def split(lists):
if len(lists)<=1:
return lists
temp = len(lists)//2
left = lists[:temp]
right = lists[temp:]
#递归
split(left)
split(right)
def merge_sort(left,right):
i,j = 0,0
result = []
while i<len(left) and j<len(right) :
if left[i] < right[j]:
result.append(left[i])
i +=1
else:
result.append(right[j])
j+=1
result += left[i:]
result += right[j:]
return result
a = [1,3,5,6,7,8]
b = [2,4,6]
print(merge_sort(a, b))