目录
堆排序:是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆是一个特殊的完全二叉树
##排序
- 排序:将一堆无序的记录序列调整为一堆有序的记录序列
- 列表排序:将无序列表变为有序列表 >输入:列表 >输出:有序列表
- 内置排序函数:sort()
##补充(数组与链表的区别)
1. 内存分配
数组:内存连续分配,元素在内存中按顺序排列。
链表:内存非连续分配,元素通过指针连接,分散在内存中。
2. 大小灵活性
数组:大小固定,定义后不能动态调整。
链表:大小灵活,可以动态增删节点。
3. 访问效率
数组:支持随机访问,通过索引直接访问元素,时间复杂度为 O(1)。
链表:只能顺序访问,从头节点开始遍历,时间复杂度为 O(n)。
4. 插入和删除效率
数组:插入和删除需要移动元素,时间复杂度为 O(n)。
链表:插入和删除只需调整指针,时间复杂度为 O(1)(已知位置时)。
##常见的排序算法
- 菜鸟三人组:
- 冒泡排序
- 选择排序
- 插入排序
- 天赋三人组:
- 快速排序
- 堆排序
- 归并排序
##菜鸟三人组
##冒泡排序(Bubble sort)
- 冒泡排序:列表每俩个相邻的数,如果前面比后面大,则交换这俩个数。
- 区域:一趟排序完成后,则无序区少一个数,有序区多一个数、
- 关键点:趟和无序区的范围
##图片展示
默认升序排序!有颜色的是有序区,无颜色的是无序区,每一趟排序后都会多一个有序区,少一个无序区。每一趟排序都是排的无序区。指针指到谁,谁就和自身后面的元素比较,比它大就交换。当指针指向8时,8和1进行交换;8在此时已经变为有序区了!指针不会再指向8.
##代码
import random #导入随机的(random)模块
def bubble_sort(a_list): #命名一个冒泡排序的函数
for i in range(len(a_list)-1):
exchange = False
for j in range(len(a_list)-i-1):
if a_list[j] > a_list[j+1]:
a_list[j],a_list[j+1] = a_list[j+1],a_list[j]
exchange = True
if not exchange:
return
a_list = [random.randint(0,100) for i in range(10)]
print(a_list)
bubble_sort(a_list)
print(a_list)
Python中return的意思的是返回数值的意思,它可以选择性的返回一个值给调用的方法,语法格式“return[表达式]”,如果不带表达式则return返回的是None.这里它可以结束函数!
##运行结果
##时间复杂度O(n)
- O(n**2)
- 原因:代码中有俩个for循环,并且是嵌套的
##选择排序(select sort)
- 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
- 优化:从列表里随机选取一个数然后进行比较,这样时间复杂度就不会那么大
##图片展示
##代码
def select_sort(a_lsit):
b_list = []
for i in range(len(a_list)):#一个o(n)
min_val = min(a_list) #min操作时间复杂度也是o(n)
b_list.append(min_val)
a_list.remove(min_val) #此处不方便用pop,pop需要下标,remove操作时间复杂度也是o(n)
return b_list
a_list = [1,34,54,23,2,5,63,33,2]
print(select_sort(a_list))
#用这个做法的时间复杂度有o(n**3)这么大,不是最优方案
def select_sort(a_lsit):
for i in range(len(a_list)-1):
min_index = i
for j in range(i+1,len(a_list)):
if a_list[j] < a_list[min_index]:
min_index = j
a_list[i],a_list[min_index] = a_list[min_index], a_list[i]
a_list = [1,34,54,23,2,5,63,33,2]
select_sort(a_list)
print(a_list)
##运行结果
##时间复杂度O(n)
- O(n**2 or n**3)
##插入排序(Insert sort)
- 插入排序:将待排序的数据分为已排序和未排序两部分。初始时,已排序部分只包含第一个元素,未排序部分包含剩下的元素。从未排序部分取出一个元素,与已排序部分的元素逐个比较,找到合适的位置插入。如果已排序部分的元素大于当前元素,就将已排序部分的元素向后移动一位,为当前元素腾出插入位置。将当前元素插入到找到的位置,重复上述步骤,直到未排序部分为空。排序完成后,数组中的元素按照从小到大的顺序排列。
- 想法:把插入排序当成打扑克牌。初始时,手里只有一张牌(有序区),每次从(无序区)牌堆里摸一张牌出来,插入到手里的正确位置。
##图片展示
随便定一个数为有序区,所以只需要摸n-1张牌来插入到这个有序区中,插8时,把有序区的9往后挪,将8插入!
##代码
def insert_sort(a_list):
for i in range(1,len(a_list)):
tmp = a_list[i]
j = i-1
while j >=0 and a_list[j] > tmp:
a_list[j+1] = a_list[j]
j -=1
a_list[j+1] = tmp
a_list = [3,34,12,1,4,5,2]
insert_sort(a_list)
print(a_list)
##运行结果
##时间复杂度O(n)
- O(n**2)
##天赋三人组
##快速排序(Quick sort)
- 快速排序:从待排序序列中选一个元素作为基准(pivot)。重新排序数组,将小于基准值元素放在基准值左边,将大于基准值元素放在基准值右边。对左、右两边分别进行快速排序。
- 运作状况:递归完成排序(有点类似于二分查找)
- 最坏情况:列表是降序排列的,然后要很麻烦的重新排一次;解决办法:随机找一个值,然后与第一个进行交换。也会可能发生最坏情况,但是概率非常小。一般情况都是nlogn。
##图片展示
左边有空位就从右边的指针从右往左开始找比5小的数!反之亦然。当left指针与right指针重合了,就将5放回来!
##代码
def partition(a_list,left,right):
tmp = a_list[left]
while left < right:
while left < right and a_list[right] >= tmp:#从右边找比tmp小的数
right -= 1#右边的指针往左边走
a_list[left] = a_list[right]#把右边的值写在左边的空位上
while left < right and a_list[left] <= tmp:
left += 1#左边的指针往右边走
a_list[right] = a_list[left]#把左边的值写在右边的空位上
a_list[left] = tmp#把tmp归位
return left
def quick_sort(a_list,left,right):
if left < right:#至少俩个元素
mid = partition(a_list,left,right)
quick_sort(a_list,left,mid-1)
quick_sort(a_list,mid+1,right)
a_list = [3,4,1,2,5,7,8,9,6]
quick_sort(a_list,0,len(a_list)-1)
print(a_list)
##运行结果
##时间复杂度O(n)
- O(nlogn)
- 理解:Left与right遇上了结束,意思是经历了n列表的整个长度,就是partition,所以是o(n),在每一层都是一次partition,o(n)的复杂度,有多少层呢?,每次往下减一半,logn例如:log16!所以,时间复杂度是o(nlogn),logn是指以2为底的对数!
##堆排序(Heapsort)
堆排序:是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆是一个特殊的完全二叉树
- 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层的最左边的若干位置的二叉树。
- 堆排序的过程:
- 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
- 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
- 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
##图片展示
##代码
def sift(a_list,low,high): #写一个调整的函数;low:根节点的位置,hight:堆的最后一个元素的位置
i = low #i是表示父节点,最开始指的根节点
j = 2*i+1#j是孩子节点,最开始是左孩子
tmp = a_list[i]
while j <= high:#只要j小于堆的最后一个节点
if j+1 <= high and a_list[j+1] > a_list[j]:#如果右孩子有并且比左孩子大
j += 1 #j指向右孩子
if a_list[j] > tmp:
a_list[i] = a_list[j]
i = j #往下看一层
j = 2 *i +1
else:#tmp更大,把tmp放在i那里
a_list[i] = tmp #把tmp放到某一领导位置上
break
else:
a_list[i] = tmp#把tmp放到叶子节点上
def heap_sort(a_list):
n = len(a_list)
for i in range((n-2)//2,-1,-1):#从最下面的一层逐渐往上调整
#i表示建堆的时候调整的部分的根的下标
sift(a_list,i,n-1)#不管是分成的不同根堆还是整个根堆把hight都定位:最大堆的最后一个元素,方便计算
#建堆完成
#如何让它升序输出如下:(涉及到之前说的清空堆),以下的是大根堆弹出
for i in range(n-1,-1,-1):
#i指向当前堆的最后一个元素
a_list[0],a_list[i] = a_list[i],a_list[0]
sift(a_list,0,i-1)#i-1是新的high,节省内存
a_list=[i for i in range(100)]
import random
random.shuffle(a_list)
print(a_list)
heap_sort(a_list)
print(a_list)
random.shuffle()随机打乱这些数字
##运行结果
##时间复杂度 O(n)
-
O(n*logn)
##归并排序
- 归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为。
##图片展示
##代码
def merge(a_list,low,mid,high):#这个的前提是俩边已经排好序了,如上图9左边和右边的都排好序了
i = low
j = mid + 1
ltmp = []#临时列表ltmp
while i <= mid and j <= high:#左右两边都有数
if a_list[i] < a_list[j]:
ltmp.append(a_list[i])
i += 1
else:
ltmp.append(a_list[j])
j += 1
while i <= mid:
ltmp.append(a_list[i])
i += 1
while j <= mid:
ltmp.append(a_list[j])
j += 1
a_list[low:high+1] = ltmp
a_list = [2,4,5,9,1,3,7]
merge(a_list,0,3,6)
print(a_list)
##时间复杂度O(n)
- O(n*logn)
- 空间复杂度O(n)因为归并排序和其它排序(原地排序)不一样,它单独开设了一个itmp的空间,当要存的数据足够多时,它的空间复杂度是o(n)。