排序算法总结
再一次复习排序算法,总结记录一下
一 先看两个不同的递归
def func3(x): if x>0: print(x) func3(x-1) def func4(x): if x>0: func4(x-1) print(x) func3(5) func4(5)
func3(5) 输出5,4,3,2,1
func4(5) 输出 1,2,3,4,5
要理解这两个递归的不同,func3是递归进去的时候进行打印,所以是5,4,3,2,1 . func4是递归出来的时候打印,
二 插入排序
def insert_sort(li): for i in range(1, len(li)): tmp = li[i] j = i - 1 while j >= 0 and li[j] > tmp: li[j + 1] = li[j] j = j - 1 li[j + 1] = tmp
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止.如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
效率:
时间复杂度:O(n^2)
三 选择排序
def select_sort(lst): for i in range(len(lst) - 1): min = i for j in range(i + 1, len(lst)): if lst[j] < lst[min]: min = j lst[i], lst[min] = lst[min], lst[i]
基本思想:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
操作方法:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推.....
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,直到整个序列按关键码有序
效率:
时间复杂度:O(n^2)
四 冒泡排序
def bubble_sort(lst): for i in range(len(lst) - 1): exchange = False for j in range(len(lst) - i - 1): if lst[j] > lst[j + 1]: lst[j], lst[j + 1] = lst[j + 1], lst[j] exchange = True # 如果发现一趟没有任何交换,说明已经排好了,剩下的就不用做了 if not exchange: break
基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。这里还进行了优化.
效率:
时间复杂度:O(n^2)
五 快速排序
第一种方式
def inner_sort(lst, left, right): tem = lst[left] while left < right: while left < right and lst[right] >= tem: right -= 1 lst[left] = lst[right] while left < right and lst[left] <= tem: left += 1 lst[right] = lst[left] lst[left] = tem return left def quick_sort_x(lst, left, right): while left < right: mid = inner_sort(lst, left, right) quick_sort_x(lst, left, mid - 1) quick_sort_x(lst, mid + 1, right) def quick_sort(lst): quick_sort_x(lst, 0, len(lst) - 1)
第二种方式
def quick_sort(lst): def qsort(lst, begin, end): if begin >= end: return pivot = lst[begin] i = begin for j in range(begin + 1, end + 1): if lst[j] < pivot: i += 1 lst[i], lst[j] = lst[j], lst[i] lst[begin], lst[i] = lst[i], lst[begin] qsort(lst, begin, i - 1) qsort(lst, i + 1, end) qsort(lst, 0, len(lst) - 1)
基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快速排序的示例:
效率:
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。
六 归并排序
def one_merge_sort(lst, begin, mid, end): left = begin right = mid + 1 # 这里的mid是前半段结束的那个元素的下标,一定要理解,因为下方是left <= mid , temp_list = [] while left <= mid and right <= end: if lst[left] < lst[right]: temp_list.append(lst[left]) left += 1 else: temp_list.append(lst[right]) right += 1 while left <= mid: temp_list.append(lst[left]) left += 1 while right <= end: temp_list.append(lst[right]) right += 1 # 注意这里一定不能写成lst[:]=temp_list因为在每个递归中,只能替换已经排好的部分,而不是全部替换 lst[begin:end + 1] = temp_list def _merge_sort(lst, begin, end): if begin < end: mid = (begin + end) // 2 _merge_sort(lst, begin, mid) _merge_sort(lst, mid + 1, end) one_merge_sort(lst, begin, mid, end) def merge_sort(lst): _merge_sort(lst, 0, len(lst) - 1)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。可以看出合并有序数列的效率是比较高的,可以达到O(n)。
解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
七 堆排序
def shift(data, low, hight): """假设除堆顶点之外,其余部分都已是堆的一次的调整""" i = low j = 2 * i + 1 tem = data[i] while j <= hight: if j + 1 <= hight and data[j + 1] > data[j]: j += 1 if data[j] > tem: data[i] = data[j] i = j j = 2 * i + 1 else: break data[i] = tem def heap_sort(data): n = len(data) #这个for 循环是为了建堆 for i in range(n // 2 - 1, -1, -1): shift(data, i, n - 1) #此时已建好堆,接下来就是从堆中取数 for i in range(n - 1, -1, -1): data[i], data[0] = data[0], data[i] shift(data, 0, i - 1)
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
基本思想:堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足父节点都大于(或都小于)子节点时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:
(a)大顶堆序列:(96, 83,27,38,11,09)
(b) 小顶堆序列:(12,36,24,85,47,30,53,91)
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
实现方法如下:
- 先假设一个堆(不能称为堆,因为他的顶点不满足堆的条件,这里姑且这么叫),这个堆除了顶点之外,其他各节点都是一个完全二叉树的堆,对这个堆做调整,使之成为一个真正的完全二叉树的堆,这个过程就是shift函数,
- 从最后一下有子节点的父节点开始循环,这个父节点和他的所有子节点就可以看成是 1) 所列示的情况.在循环中执行shift函数,当这个循环结束之后这个堆也已经建好,
- 从堆顶依次数,方法如下,1 .先把堆顶的数取出来,把最后一个数拿上去,把最后一个数拿上去之后,这个堆又变成1)中形式的堆,又执行一次1)的操作,再取数就行,为了简化理解,可以把堆顶的数取出来之后放入一个空数组中,但是这里为了不开多余的内存就把拿出来的数放在原列表的最后一个,下次循环时,就把这个序列的n-1,作为堆进行循环.
时间复杂度也为:O(nlogn )
八 总结
其实各种算法,特别是后三种较复杂的算法,要用语言把这些算法的过程描述清楚,真的有点因难,掌握这些算法唯一的方法就是多复习,多写几次