一、排序
假设含有n个记录的序列为{r1,r2,……,rn},其相应的关键字分别为{k1,k2,……,
kn},需确定 1,2,……,n的一种排列p1,p2,……,pn,使其相应的关键字满足kp1≤
kp2≤……≤kpn(非递减或非递增)关系,即使得序列成为一个按关键字有序的序列
{rp1,rp2,……,rpn},这样的操作就称为排序。
1、对一序列对象根据某个关键字进行排序
2、多个关键字的排序最终都可以转化为单个关键字的排序
3、稳定与不稳定
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
4、内排序与外排序
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
5、排序算法的性能三大影响因素
- 时间性能(时间复杂度): 一个算法执行所耗费的时间。
- 辅助空间 (空间复杂度):运行完一个程序所需内存的大小。
- 算法的复杂性 : 算法本身的复杂度,而不是指算法的时间复杂度
二、交换排序
【1】冒泡排序
(1)冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的
关键字,如果反序则交换,直到没有反序的记录为止。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端。
(2)冒泡排序复杂度分析
最好的情况,也就是要排序的表本身就是有序的,那么我们比较次数,可以推断出就是
n‐1 次的比较,没有数据交换,时间复杂度为O(n)。
最坏的情况,即待排序表是逆序的情况,此时需要比较1+2+3+4+…+(n-1)=n(n-1)/2次,
即时间复杂度为O(n**2)。
稳定性: 稳定
def BubbleSort(nums):
""" 冒泡排序
需要排n趟, i趟需要比较(n-i-1)
:param nums: 需要排序的数值
"""
nums_len = len(nums)
#需要排几次
for count in range(nums_len):
#每次需要比较几次
for index in range(nums_len - count -1):
#每次比较将最小的放到后面
if nums[index] < nums[index+1]:
nums[index],nums[index+1]= nums[index+1],nums[index]
return nums
if __name__ == '__main__':
nums = [12, 34, 23, 45, 66, 1, 2, 0]
sorted_nums = BubbleSort(nums)
print(sorted_nums)
【2】快速排序
(1)快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的
两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部
分记录继续进行排序,以达到整个序列有序的目的。
(2)层数为O(logn)(即调用栈的高度为O(logn)),而每层需要的时间为O(n)。因此整个算法需要的时间为O(n) * O(logn) = O(nlogn)
def QuickSort(nums):
if len(nums) <2:
return nums
else:
#寻找一个基准值
pivot =nums[0]
#小于基准值的放到一个列表
less = [i for i in nums[1:] if i <= pivot]
greater =[i for i in nums[1:] if i > pivot]
#利用成员连接操作符
return QuickSort(less) + [pivot] + QuickSort(greater)
if __name__ == '__main__':
nums = [12, 34, 23, 45, 66, 1, 2, 0]
sorted_nums = QuickSort(nums)
print(sorted_nums)
三、插入排序
【1】直接插入排序
(1)直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排
好序的有序表中,从而得到一个新的、记录数增 1 的有序表。
(2)原理:每次将排序中的元素,插入到前面已经排好序的有序序列中去,直到排序完成。
步骤:
第一步,a[0]为有序区,待排序区为a[1…n-1]。令i=1。
第二步,将a[1]与a[0]中元素比较,将小的元素放在第一个位置。
第三步,以此类推,直到待排序中全部元素插入完成为止。
(3)最好的情况,也就是要排序的表本身就是有序的, 因此没有移动的记录,时间复杂
度为 O(n)。
最坏的情况,即待排序表是逆序的情况,时间复杂度为 O(n**2)。
def InsertSort(nums):
"""直接插入排序"""
count =len(nums)
for i in range(1,count):
#key就是要插入的数值元素
key = nums[i]
#和有序表的最后一个元素开始比较(此时j=i-1)
j = i - 1
#直到比较到有序序列的第一个元素时结束(此时j=0退出while循环)
while j >= 0:
#有序列表中的最后一个和 插入的Key元素比较
if nums[j] >key:
nums[j+1] = nums[j]
nums[j] = key
#每比较一次,j指针往前走一位
j -= 1
return nums
if __name__ == '__main__':
nums =[5, 4, 3, 2, 1]
sort_nums =InsertSort(nums)
print(sort_nums)
【2】希尔排序
(1)希尔排序是D.L.Shell于 1959 年提出来的一种排序算法,在这之前排序算法的时间复杂度基本
都是O(n2)的,希尔排序算法是突破这个时间复杂度的第一批算法之一。
(2)基本思想:
算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的然后
再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行
直接插入排序后,排序完成。
def Shellsort(nums):
#希尔排序
n =len(nums)
gap =int(n/2)
while gap >0:
for i in range(gap,n):
temp =nums[i]
j = i
#比较同一组的 进行交换
while j >= gap and nums[j-gap]>temp:
nums[j] =nums[j -gap]
j -=gap
nums[j] = temp
gap =int(gap/2)
return nums
if __name__ == '__main__':
nums = [12, 34, 54, 2, 3, 4, 5, 2, 1, 44]
shell_sort_nums= Shellsort(nums)
print(shell_sort_nums)
四、选择排序
【1】简单选择排序
(1)选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
(2)时间复杂度
最优时间复杂度:O(n2 )
最坏时间复杂度:O(n2 )
稳定性:不稳定(考虑升序每次选择最大的情况)
def FindSmallest(nums):
smallest = nums[0]
smallest_index =0
for i in range(1,len(nums)):
if nums[i] < smallest:
#注意比较后,更新smallest
smallest = nums[i]
smallest_index = i
return smallest_index
def SelectionSort(nums):
Newnums= []
for i in range(len(nums)):
smallest= FindSmallest(nums)
Newnums.append(nums.pop(smallest))
return Newnums
if __name__ == '__main__':
nums =[12, 34, 23, 45, 0, 1, 2]
print(SelectionSort(nums))
【2】堆排序
堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,利用数组的特点快速
定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的
值都不大于其父节点的值。最大的值一定在堆顶。
五、归并排序
归并排序(Merge sort),是创建在归并操作上的一种有效的排序算法。该算法是采用分治法
(Divide and Conquer)的一个非常典型的应用。
分治法:
分割:递归地把当前序列平均分割成两半。
集成:在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)。
def merge_sort( li ):
#不断递归调用自己一直到拆分成成单个元素的时候就返回这个元素,不再拆分了
if len(li) == 1:
return li
#取拆分的中间位置
mid = len(li) // 2
#拆分过后左右两侧子串
left = li[:mid]
right = li[mid:]
#对拆分过后的左右再拆分 一直到只有一个元素为止
#最后一次递归时候ll和lr都会接到一个元素的列表
#最后一次递归之前的ll和rl会接收到排好序的子序列
ll = merge_sort( left )
rl =merge_sort( right )
# 我们对返回的两个拆分结果进行排序后合并再返回正确顺序的子列表
# 这里我们调用拎一个函数帮助我们按顺序合并ll和lr
return merge(ll , rl)
#这里接收两个列表
def merge( left , right ): # 从两个有顺序的列表里边依次取数据比较后放入result
# 每次我们分别拿出两个列表中最小的数比较,把较小的放入result
result = []
while len(left)>0 and len(right)>0 :
#为了保持稳定性,当遇到相等的时候优先把左侧的数放进结果列表,因为left本来也是大数列中比较靠左的
if left[0] <= right[0]:
result.append( left.pop(0) )
else:
result.append( right.pop(0) )
#while循环出来之后 说明其中一个数组没有数据了,我们把另一个数组添加到结果数组后面
result += left
result += right
return result
if __name__ == '__main__':
li = [5,4 ,3 ,2 ,1]
li2 = merge_sort(li)
print(li2)
六、基数排序
基数排序(radix sort)它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
def radix_sort(s):
"""基数排序"""
i = 0 # 记录当前正在排拿一位,最低位为1
max_num = max(s) # 最大值
j = len(str(max_num)) # 记录最大值的位数
while i < j:
bucket_list =[[] for _ in range(10)] #初始化桶数组
for x in s:
bucket_list[int(x / (10**i)) % 10].append(x) # 找到位置放入桶数组
print(bucket_list)
s.clear()
for x in bucket_list: # 放回原序列
for y in x:
s.append(y)
i += 1
if __name__ == '__main__':
a = [334,5,67,345,7,345345,99,4,23,78,45,1,3453]
radix_sort(a)
print(a)