排序算法可以分为内部排序和外部排序
内部排序的基本操作:
对内部排序而言,其基本操作有两种:
- 比较两个关键字的大小;
- 存储位置的移动:从一个位置移动到另一个位置。
第一种操作是必不可少的;而第二种操作却不是必须的,取决于记录的存储方式,具体情况是:
(1)记录存储在一组连续地址的存储空间:记录之间的逻辑顺序关系是通过其物理存储位置的相邻来体现,记录的移动是必不可少的;
(2)记录采用链式存储方式:记录之间的逻辑顺序关系是通过结点中的指针来体现,排序过程仅需修改结点的指针,而不需要移动记录;
(3)记录存储在一组连续地址的存储空间:构造一个辅助表来保存各个记录的存放地址(指针):排序过程不需要移动记录,而仅需修改辅助表中的指针,排序后视具体情况决定是否调整记录的存储位置。
(1)比较适合记录数较少的情况;而(2)和(3)则适合记录数较少的情况。
为讨论方便,假设待排序的记录是以(1)的情况存储,且设排序是按升序排列的;关键字是一些可直接用比较运算符进行比较的类型。
而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
def bubble_sort(arry):
n = len(arry) #获得数组的长度
for i in range(n):
for j in range(1,n-i):
if arry[j-1] > arry[j] : #如果前者比后者大
arry[j-1],arry[j] = arry[j],arry[j-1] #则交换两者
print(arry)
return arry
arry = [5,4,7,9,2,1,6]
bubble_sort(arry)
#实验结果:1,2,4,5,6,7,9
def select_sort(ary):
n = len(ary)
for i in range(0,n):
min = i #最小元素下标标记
for j in range(i+1,n):
if ary[j] < ary[min] :
min = j #找到最小值的下标
ary[min],ary[i] = ary[i],ary[min] #交换两者
print(ary)
return ary
ary = [6,4,3,1,7,4,9,2]
select_sort(ary)
#实验结果:1,2,3,4,4,6,7,8
插入排序:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,直到全部插入完成。
def insert_sort(ary):
n = len(ary)
for i in range(1,n):
if ary[i] < ary[i-1]:
temp = ary[i]
index = i #待插入的下标
for j in range(i-1,-1,-1): #从i-1 循环到 0 (包括0)
if ary[j] > temp :
ary[j+1] = ary[j]
index = j #记录待插入下标
else :
break
ary[index] = temp
print(ary)
return ary
ary = [1,4,5,0,8,2,7,3,9,0]
insert_sort(ary)
#实验结果:0,0,1,2,3,4,5,7,8,9
把待排序的数据元素分成若干个小组,对同一小组内的数据元素用直接插入法排序;小组的个数逐次减少;当完成了所有数据元素都在一个组内的排序后,排序过程结束。希尔排序又称作缩小增量排序。
def shell_sort(ary):
n = len(ary)
gap = round(n/2) #初始步长 , 用round四舍五入取整
while gap > 0 :
for i in range(gap,n): #每一列进行插入排序 , 从gap 到 n-1
temp = ary[i]
j = i
while ( j >= gap and ary[j-gap] > temp ): #插入排序
ary[j] = ary[j-gap]
j = j - gap
ary[j] = temp
gap = round(gap/2) #重新设置步长
print(ary)
return ary
ary = [1,5,7,2,9,4,3,1,0]
shell_sort(ary)
#实验结果:0,1,1,2,3,4,5,7,9
归并排序主要是二路归并排序。
归并排序是采用分治法的一个非常典型的应用。归并
排序的思想就是先递归
分解数组,再合
并数组。
先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
def merge_sort(ary):
if len(ary) <= 1 : return ary
num = int(len(ary)/2) #二分分解
left = merge_sort(ary[:num])
right = merge_sort(ary[num:])
return merge(left,right) #合并数组
def merge(left,right):
‘’‘合并操作,
将两个有序数组left[]和right[]合并成一个大的有序数组’‘’
l,r = 0,0 #left与right数组的下标指针
result = []
while l<len(left) and r<len(right) :
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:]
result += right[r:]
print(result)
return result
ary = [5,6,2,1,9,7,0,1,3]
merge_sort(ary)
#实验结果:0,1,1,2,3,5,6,7,9
快速排序是一种二叉树结构的交换排序方法。
def quick_sort(ary):
return qsort(ary,0,len(ary)-1)
def qsort(ary,left,right):
#快排函数,ary为待排序数组,left为待排序的左边界,right为右边界
if left >= right : return ary
key = ary[left] #取最左边的为基准数
lp = left #左指针
rp = right #右指针
while lp < rp :
while ary[rp] >= key and lp < rp :
rp -= 1
while ary[lp] <= key and lp < rp :
lp += 1
ary[lp],ary[rp] = ary[rp],ary[lp]
ary[left],ary[lp] = ary[lp],ary[left]
qsort(ary,left,lp-1)
qsort(ary,rp+1,right)
print(ary)
return ary
ary = [5,1,4,8,9,2,0,2,7,3]
quick_sort(ary)
#实验结果:0,1,2,3,4,5,7,8,9
二叉堆具有以下性质:
n/2
开始的元素均为大根堆。于是只要从n/2-1
开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。heap[0]
与heap[n-1]
交换,再对heap[0...n-2]
做最大堆调整。第二次将heap[0]
与heap[n-2]
交换,再对heap[0...n-3]
做最大堆调整。重复该操作直至heap[0]
和heap[1]
交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。动画演示:
def heap_sort(ary) :
n = len(ary)
first = int(n/2-1) #最后一个非叶子节点
for start in range(first,-1,-1) : #构造大根堆
max_heapify(ary,start,n-1)
for end in range(n-1,0,-1): #堆排,将大根堆转换成有序数组
ary[end],ary[0] = ary[0],ary[end]
max_heapify(ary,0,end-1)
print(ary)
return ary
#最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary,start,end):
root = start
while True :
child = root*2 +1 #调整节点的子节点
if child > end : break
if child+1 <= end and ary[child] < ary[child+1] :
child = child+1 #取较大的子节点
if ary[root] < ary[child] : #较大的子节点成为父节点
ary[root],ary[child] = ary[child],ary[root] #交换
root = child
else :
break
ary = [5,2,1,7,8,3,9,0,4]
heap_sort(ary)
#实验结果:0,1,2,3,4,5,7,8,9
计数排序是一种非常快捷的稳定性强的排序方法,时间复杂度O(n+k),其中n为要排序的数的个数,k为要排序的数的组大值。
计数排序对一定量的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只限于对整数进行排序。
计数排序是消耗空间发杂度来获取快捷的排序方法,其空间发展度为O(K)同理K为要排序的最大值。
def CountingSort(a, b, k):
c=[] for i in range(k+1): c.append(0) for j in range(len(a)): c[a[j]] = c[a[j]] + 1 for i in range(1, k+1): c[i] = c[i] + c[i-1] for j in range(len(a)-1, -1, -1): b[c[a[j]]-1] = a[j] c[a[j]] = c[a[j]] - 1 print(b)
a=[2, 5, 7, 8, 1, 3, 9, 4]
b=[None for i in range(len(a))]
CountingSort(a, b, max(a))
#运行结果:1,2,3,4,5,7,8,9
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。
为了使桶排序更加高效,我们需要做到这两点:
动画演示:
def bucket_sort(the_list):
#设置全为0的数组
all_list = [0 for i in range(max(the_list)+1)]
last_list = []
for v in the_list:
if all_list[v]==0:
all_list[v] = 1
else:
all_list[v]+=1
for i,t_v in enumerate(all_list):
if t_v != 0:
for j in range(t_v):
last_list.append(i)
return last_list
if __name__ == '__main__':
the_list = [10, 1, 18, 35, 23, 12, 7, 5, 18, 17,19]
print("排序前:" + str(the_list))
print("排序后:" + str(bucket_sort(the_list)))
#排序前:[10, 1, 18, 35, 23, 12, 7, 5, 18, 17, 19]
#排序后:[1, 5, 7, 10, 12, 17, 18, 18, 19, 23, 35]
基数排序又称为“桶子法”,从低位开始将待排序的数按照这一位的值放到相应的编号为0~9的桶中。等到低位排完得到一个子序列,再将这个序列按照次低位的大小进入相应的桶中,一直排到最高位为止,数组排序完成。
分为两类:
动画演示:
def RadixSort(Lst): #列表中的最大元素的位数 d = len(str(max(Lst))) #0-9一共10个桶 for i in range(d): BucketLst = [[] for k in range(10)] for j in range(len(Lst)): #10**i是关注意点,之前一直是10**d,debug才发现 BucketLst[Lst[j]//(10**i)%10].append(Lst[j]) Lst = [number for B in BucketLst for number in B]
return Lst
#列表中的最大元素的位数
Lst = [4,6,2,1,9,7,0,3,8]
print(RadixSort(Lst))
#实验结果:0, 1, 2, 3, 4, 6, 7, 8, 9