各种排序算法总结
1 冒泡排序
算法思想:从第一个开始,相邻两个数进行比较,如果前1个数大于后一个数,则进行调换,这样换到最后,最大的那个就会在最后面,重复这个过程,较大的就会逐个累积在后面,本质思想就是大的在一轮中会逐渐冒泡到后排。
python代码实现:
def bubbling_sort(num): if not num or len(num) <= 1: return num_len = len(num) for i in range(num_len-1): swap_flag = True for j in range(0, num_len-i-1): if num[j] > num[j+1]: tmp = num[j] num[j] = num[j+1] num[j+1] = tmp swap_flag=False if swap_flag: return
算法时间复杂度:O(n²)
算法空间复杂度:O(1)
算法稳定性:稳定
算法稳定性概念:假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的
2 选择排序
算法思想:从第一个开始,然后从下一个开始遍历一遍找到最小的,然后跟第一个进行交换,这样就完成一轮,然后选择第二个,开始第二轮,直到num_len-1轮
python代码实现:
def chose_sort(num): if not num or len(num) <= 1: return num_len = len(num) for i in range(num_len-1): j_tmp = i min = num[i] for j in range(i+1, num_len): if num[j] < min: j_tmp = j min = num[j] if i != j_tmp: tmp = num[i] num[i] = num[j_tmp] num[j_tmp] = tmp
算法复杂度:O(n²)
算法空间复杂度:O(1)
算法稳定性:不稳定(比如,5,5,2;则第一个5会与2交换)
3 插入排序
算法思想:类似于打扑克牌拿牌的时候,拿到第一张,然后拿第二张,如果第二张小于第一张则放到第一张的前面,拿到第三张,如果小于第二张,则第三张放到第二张的位置,原本的第二张及以后的都往后退一步。
python代码实现:
def insert_sort(num): if not num or len(num) <= 1: return num_len = len(num) for i in range(1, num_len): for j in range(0, i+1): if num[i] < num[j]: break if j <= i: tmp_i = i x = num[i] while tmp_i > j: tmp = num[tmp_i] num[tmp_i] = num[tmp_i-1] num[tmp_i-1] = x tmp_i -= 1 num[j] = x
算法复杂度:O(n²)
算法空间复杂度:O(1)
算法稳定性:稳定
4 快速排序
标准算法思想:选中第一个作为基准参考,将其保存到X变量中,然后这里当成是挖了一个坑,然后两个变量分别指定到列表开头序号+1 start_tmp和列表序号结尾end_tmp,从end_tmp开始与X对比,当num[end_tmp]大于等于X时,end_tmp往前挪一步,然后继续比较,否则我们就把它放到坑上,然后当前end_tmp这里变成是一个坑,end_tmp往后挪一步即-1,然后换到start_tmp那边进行比较,当num[end_tmp]小于等于X时我们就把start_tmp往前挪一步即+1,否则就把它放到坑上,然后当前start_tmp变成一个坑,start_tmp往前挪一步,又换到end_tmp那边,如此反复直到start_tmp>=end_tmp时结束,此时把X值保存到坑上,这样就会以X保存的值为标准,将小于等于X值的都放到左边,大于等于X值得都放到右边,然后再分别对两边再执行如上操作即可。
python代码递归实现:
def quick_sort(num, start=0, end=0): if not num or len(num) <= 1 or start > end: return if start == 0 and end == 0: end = len(num) - 1 start_tmp = start end_tmp = end x = num[start_tmp] while start_tmp < end_tmp: while start_tmp < end_tmp and num[end_tmp] >= x: end_tmp -= 1 if start_tmp < end_tmp: num[start_tmp] = num[end_tmp] start_tmp += 1 while start_tmp < end_tmp and num[start_tmp] <= x: start_tmp += 1 if start_tmp < end_tmp: num[end_tmp] = num[start_tmp] end_tmp -= 1 num[start_tmp] = x quick_sort(num, start, start_tmp - 1) quick_sort(num, start_tmp + 1, end) num = [3, 5, 1, 7, 2, 8, 4, 100, 76, 78] quick_sort(num) print num
算法复杂度:O(nlgn)
算法空间复杂度:O(1)
算法稳定性:不稳定(很多交换的)
该算法的时间复杂度可以这样理解:假设有n个数字要排序,每一次的划分成左右两边数列就类似于树的一个节点分成了两个子树,每一层就是一次复杂度n的比较排序,直到划分到最后一层叶子节点时即排完序,假设树的高度为x,根据满二叉树的性质,2的x次方等于n,则x=lgn,所以算法平均复杂度是nlgn,为啥说是平均复杂度呢,因为实际排序时并不是这么好,每次都恰好分到左右两子树两等分,从树的角度上来说就是满二叉树是最好的,但有可能出现全是1度节点这种,那么数的高度就变成n了,复杂度直接变为n²,在要排序的数一开始是有序的就会形成这样的结果。
两头交换思想:选取一个中间的值作为基准,然后左边找一个大于等于该基准的值,右边找一个小于等于该基准的值,找到后如果左边序号小于等于右边序号则进行交换,并左边序号+1,右边序号-1,直到左边序号大于右边序号时退出,这样最后也是以基准值为区分把小于等于基准值的放到左边,大于等于基准值的放到右边,然后再分别递归。注意while start_tmp <= end_tmp这里要取<=号,因为下面分别递归调用时才不会有重叠,也就是左边的start_tmp和右边的end_tmp不要相等,要让它们再互相走多一步。
python代码实现:
def quick_sort2(num, start=0, end=0): if not num or len(num) <= 1 or start > end: return if start == 0 and end == 0: end = len(num) - 1 start_tmp = start end_tmp = end x = num[(start_tmp + end_tmp) / 2] while start_tmp <= end_tmp: while num[start_tmp] < x: start_tmp += 1 while num[end_tmp] > x: end_tmp -= 1 if start_tmp <= end_tmp: tmp = num[start_tmp] num[start_tmp] = num[end_tmp] num[end_tmp] = tmp start_tmp += 1 end_tmp -= 1 if start < end_tmp: quick_sort2(num, start, end_tmp) if start_tmp < end: quick_sort2(num, start_tmp, end)
算法复杂度:O(nlgn)
5 归并排序
算法思想:是一种分治思想,归并的归是递归分解数列,并是合并有序数列
python代码实现:
def merge_array(num, first, mid, last, num_tmp): i = first j = mid+1 tmp = 0 while i <= mid and j <= last: if num[i] <= num[j]: num_tmp[tmp] = num[i] i += 1 else: num_tmp[tmp] = num[j] j += 1 tmp += 1 while i <= mid: num_tmp[tmp] = num[i] i += 1 tmp += 1 while j <= last: num_tmp[tmp] = num[j] j += 1 tmp += 1 for k in range(0, tmp): num[first + k] = num_tmp[k] def rec_merge_sort(num, first, last, num_tmp): if first < last: mid = (first + last) / 2 rec_merge_sort(num, first, mid, num_tmp) rec_merge_sort(num, mid+1, last, num_tmp) merge_array(num, first, mid, last, num_tmp) def merge_sort(num): if not num or len(num) <= 1: return num_len = len(num) num_tmp = [0] * num_len rec_merge_sort(num, 0, num_len-1, num_tmp) num = [3, 5, 1, 7, 2, 8, 4, 100, 76, 78] merge_sort(num) print num
算法复杂度:O(nlgn)
算法空间复杂度:O(n)
算法稳定性:稳定
该算法的时间复杂度可以这样理解:假设要比较的数有n个,把要排序的数字看成是完全二叉树的叶子节点,合并时就相当于往上构建一颗树,直到根节点,每一层都执行了n次比较,到数的根节点时,比较完成,层数设为x,根据满二叉树的特性,则2的x次方等于N-1,所以x=lgn,因此总的算法复杂度是nlgn
这里顺便讲一下二叉树的一些关系:
假设2度节点(即有两个子节点)有x个,1度节点有y个,叶子结点有z个,总的节点个数有N个,则很明显有x + y + z = N
观察下二叉树可以知道每个节点都对应一个树枝,除了根节点,所以有N-1个树枝,因为二度节点有2个树枝,1度节点有1个树枝,叶子节点没有树枝,所以有2x + y = N - 1
联合上面两个式子可以知道z = x + 1,也就是叶子节点的个数总是等于2度节点的个数+1
如果是满二叉树,则y=0,则2x + 1 = N,则知道N就可以计算出叶子节点和非叶子结点,相反也成立
6 堆排序
算法思想:先将数列进行最大堆初始化,形成一个最大堆,然后将最大堆的顶部数与最后面的待交换数进行交换,交换后再进行最大堆调整,调整后最大的数又到了顶部,再交换,一直做这样的操作直到交换到根部时停止。
最大堆初始化:首先我们要理清一些关系,假设父节点是序数是i,则左子节点是2i+1,右子节点时2i+2,而假设子节点是i,则父节点是(i-1)/2;最大堆的概念是节点要大于等于左右子节点的值;调整是从第一个非叶子节点开始调整,调用“调整最大堆”函数进行调整,思想可以看“调整最大堆”,直到调整到根节点即表示初始化完最大堆了。
调整最大堆:比如要调整的是序数i,则跟它的左右子节点2i+1和2i+2进行比较,如果左右子节点的值有大于该节点,则进行交换,然后再用该替换的子节点重新作为调整序号,跟它的左右子节点值比较,直到没有交换就停止
python代码实现:
def swap(num, num_a, num_b): tmp = num[num_a] num[num_a] = num[num_b] num[num_b] = tmp def max_heap_down(num, cur, last): max = cur while True: if 2*cur+1 <= last and num[2*cur+1] > num[max]: max = 2*cur+1 if 2*cur+2 <= last and num[2*cur+2] > num[max]: max = 2*cur+2 if max != cur: swap(num, cur, max) cur = max else: return def initial_max_heap(num, num_len): for i in range((num_len-1)/2, -1, -1): max_heap_down(num, i, num_len-1) def heap_sort(num): if not num or len(num) <= 1: return num_len = len(num) initial_max_heap(num, num_len) swap(num, 0, num_len-1) for i in range(num_len-2, 0, -1): max_heap_down(num, 0, i) swap(num, 0, i)
算法时间复杂度:O(nlgn)
算法空间复杂度:O(1)
算法稳定性:不稳定
该算法的算法复杂度可以这样理解:max_heap_down该函数调整最大堆的复杂度是O(lgn),而初始化最大堆会调用非叶子节点次,设非叶子节点个数为x,则x<n,所以初始化最大堆时间复杂度是xlgn;开始交换数据时,一共要交换n-2次,每次会涉及到一次max_heap_down调用,所以这里时间复杂度是(n-2)lgn,所以总的时间复杂度是nlgn
7 计数排序
适用于某些有特性的数列,比如某段范围内的正整数排序,当然负数也可以,做下映射就可以了
算法思想:由于是正整数,是有范围的,设最大为m,所以可以开辟一个长度为m+1的数列,用来记录某个正整数有几个,然后遍历一遍要排序的数列,把对应数字的个数记录下来,再通过记录下来的数字,复写到原数列中。
python代码实现:
def counting_sort(num, m): if not num or len(num) <= 1: return num_len = len(num) tmp_num = [0] * (m+1) for i in range(0, num_len): tmp_num[num[i]] += 1 tmp = 0 for i in range(0, m+1): if tmp >= num_len: break while tmp_num[i] > 0: num[tmp] = i tmp_num[i] -= 1 tmp += 1 num = [3, 5, 1, 7, 2, 8, 4, 100, 76, 78]
算法时间复杂度:O(n+m)
算法空间复杂度:O(m)
算法稳定性:稳定
8 桶排序
算法思想:把一段区间分成n等分,比如有100个数字,设置10个桶,则1-10就会在第1个桶,11-20就会在第2个桶,以此类推,然后遍历一遍数组,将对应的数字放到对应的桶中,然后分别对每个桶的队列进行排序,最后重新赋值到原数组中。这里比较关键的是怎么划分区域,怎么通过计算就可以得到某个数值对应哪个桶,这样理解会更简单点,把一个数值当做是一个苹果,首先确定一共有多少个苹果,然后选定一个桶的数量,我这里的算法是如果苹果数量少于10个,则桶的数量等于苹果的数量,否则桶的数量设置为10,首先用苹果数量除以桶来保证每个桶平均能分配到多少个,得到avg_num值,剩余的就都给最后一个桶。这样我们确定哪个值属于哪个桶时就很好确定,直接用该数值减去最小值然后除以avg_num即可,如果得到的大于桶的数量,则视为最后一个桶。都放进各个桶后,分别对各个桶排序,这里使用了快速排序来对各个桶进行排序。
python代码实现:
def bucket_sort(num): if not num or len(num) <= 1: return num_len = len(num) max_num = max(num) min_num = min(num) bucket_num = 10 if (max_num - min_num + 1) < 10: bucket_num = max_num - min_num avg_num = (max_num - min_num) / bucket_num tmp_arr = [[] for i in range(bucket_num)] for i in range(num_len): bucket_pos = (num[i] - min_num + 1) / avg_num if bucket_pos >= bucket_num: bucket_pos = bucket_num - 1 tmp_arr[bucket_pos].append(num[i]) tmp = 0 for i in range(bucket_num): quick_sort(tmp_arr[i]) for j in tmp_arr[i]: num[tmp] = j tmp += 1
算法时间复杂度:有n个数字,设有m个桶,每个桶有k个数,O(n) + O(m) * klgk,当桶足够多,每个桶只有1个数字时,就变成O(n + m)
算法空间复杂度:O(n+m)
算法稳定性:取决于在对每个桶进行排序使用的是什么排序算法,如果是快速排序则是不稳定的,如果是冒泡排序则是稳定的
9 基数排序
算法思想:基数排序(radix sorting)将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。 然后从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
用的比较少,这里就不实现了
算法时间复杂度:O(n)
算法空间复杂度:O(n)
算法稳定性:稳定
10 希尔排序
算法思想:希尔排序是借鉴了插入排序在基本有序下是非常高效的特点,所以希尔排序会先设定个step(我们这里选了数列长度的一半),每隔step个数字,组成一个要排序的数列进行排序,这样就会有多个数列进行插入排序,排完一轮后,step除于2,然后再组建数列再插入排序,直到step变为1后就是整个数列再进行插入排序一次,然后终止。该算法会比插入排序好的原因是因为它提前对让数值顺序尽快接近有序,发挥插入排序在基本有序情况下高效的特点。
python代码实现:
def shell_sort(num): if not num or len(num) <= 1: return num_len = len(num) step = num_len / 2 while step > 0: for i in range(step): for j in range(i+step, num_len, step): for k in range(i, j, step): if num[j] < num[k]: break else: k += step if k < j: tmp_k = k tmp = num[j] while tmp_k >= k: x = num[tmp_k] num[tmp_k] = num[tmp_k+step] num[tmp_k+step] = x tmp_k -= step num[k] = tmp step /= 2
算法时间复杂度:比插入排序好,某些情况下可达到O(n*1.3)
算法空间复杂度:O(1)
算法稳定性:稳定