排序算法(冒泡-选择-插入-希尔-快速-归并)
冒泡排序
-
工作原理:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
-
分析:
- 对于n个元素的序列,需要进行n-1次冒泡过程
- 冒泡过程每增加1次,该次冒泡过程中需要两两比较元素的次数就减少1次
-
代码实现:
def bubble_sort(alist): '''冒泡排序''' l = len(alist) for j in range(l-1): for i in range(l-1-j): if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1],alist[i] return alist
-
时间复杂度
- 最优时间复杂度:O(n)
- 最坏时间复杂度:O(n2)
- 稳定性:稳定
选择排序
-
工作原理:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 以此类推,直到所有元素均排序完毕。
-
代码实现:
def select_sort(alist): '''选择排序''' l = len(alist) # 需要进行l-1轮选择操作 for j in range(l-1): #记录最小元素下标 min_index = j #从j位置开始,筛选出最小元素,然后放置到j位置 for i in range(j+1,l): if alist[i] < alist[min_index]: min_index = i #如果数据不在正确位置,则进行交换 if j != min_index: alist[j],alist[min_index] = alist[min_index],alist[j] return alist
-
时间复杂度
- 最优时间复杂度:O(n2)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定(比如5,5,2,第一遍选择选出2,然后2和5进行交互,则两个5的相对顺序会发生改变)
插入排序
-
工作原理:
- 通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
-
思路:
- 需要将原始序列分成两部分:有序部分和无序部分
- 初始情况下,有序部分为乱序序列的第一个元素,无序部分为剩下的n-1个元素
- 将无序部分的元素,逐一插入到有序部分的对应位置上
-
代码实现
def insert_sort(alist): '''插入排序''' #从下标为1的元素开始,逐个向前插入 for i in range(1,len(alist)): # 从第i个元素开始与前面进行比较,比前面数小,则交换位置 while i>0: if alist[i] < alist[i-1]: alist[i],alist[i-1] = alist[i-1],alist[i] i -= 1 else: break return alist
希尔排序
- 希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
-
工作原理:
- 将整个待排元素序列按照下标的指定增量进行分组,对每组使用直接插入排序算法排序;
- 随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个序列恰被分成一组,算法便终止。
-
思路:
- 首先要理解插入排序就是增量为1的希尔排序
- 实现插入排序的代码,将增量1改为gap(初始值len(alist) // 2)
- 循环执行插入排序的代码,每循环1次,增量gap减小为gap // 2
- 当增量减至1时,循环最后一次执行
-
代码实现:
def shell_sort(alist): '''希尔排序''' #初始增量 gap = len(alist) // 2 while gap >= 1: #按照指定增量进行插入排序 for i in range(gap,len(alist)): while i > 0: if alist[i] < alist[i-gap]: alist[i],alist[i-gap] = alist[i-gap],alist[i] i -= gap else: break #缩减增量 gap //= 2 return alist
-
时间复杂度
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定(同插入排序)
快速排序
-
工作原理:
- 将序列中的第一个元素设为基准,赋值给mid变量
- 然后将序列中所有比基准小的元素移动到基准左侧,比基准大的元素移动到右侧
- 再按此方法对基准两侧的序列分别进行快速排序
- 整个排序过程可以递归进行,以此达到整个序列变成有序序列。
-
思路:
- 定义两个指针:low指向序列起始元素,high指向序列末位元素
- 将序列中的起始元素定为基准元素
- 首先移动右侧指针,当指针指向的元素比基准小时停止移动,将该元素移动到左指针位置上,然后开始移动左侧指针,否则继续移动,直到左右两个指针重合
- 移动左侧指针时,当指针指向的元素大于基准时停止移动,将该元素移动到右指针位置上,然后开始移动右侧指针,否则继续移动,直到左右两个指针重合
- 两指针位置重合时,将基准元素放置在该重合位置上,基准左侧的元素均小于它,右侧的元素均大于它
- 然后分别让基准左右两侧的序列递归执行上述过程
-
代码实现
# coding:utf-8 def quick_sort(alist,left,right): '''快速排序''' #递归退出条件 if right <= left: return mid = alist[left] #序列起始元素为基准 low = left #左指针 high = right #右指针 #左右指针未重合时,循环执行 while low < high: #先移动右侧指针 while low < high: if alist[high] >= mid: high -= 1 else: alist[low] = alist[high] break #再移动左侧指针 while low < high: if alist[low] <= mid: low += 1 else: alist[high] = alist[low] break alist[low] = mid #左右指针重合,将基准放置到重合位置上 quick_sort(alist,left,low-1) #基准左侧序列进行快速排序 quick_sort(alist,high+1,right) #基准右侧序列进行快速排序 if __name__ == '__main__': alist = [4,2,7,6,8,1,9,3,5,0] quick_sort(alist,0,len(alist)-1) print(alist)
-
时间复杂度
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(n2)
- 稳定性:不稳定
归并排序
- 归并排序是采用分治法的一个非常典型的应用,归并排序的思想就是先递归分解序列,再合并序列。
-
思路:
- 将序列从中间位置分解成两个数列
- 再将这两个子序列按照第一步继续二分下去
- 直到所有子序列的长度都为1时,再两两合并成一个有序序列
- 基本思路:比较两个序列的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。
- 然后再比较,直至一个序列为空,最后把另一个序列的剩余部分复制过来即可。
-
代码实现
def merge_sort(alist): n = len(alist) #结束递归的条件 if n <= 1: return alist #中间索引 mid = n//2 left_li = merge_sort(alist[:mid]) right_li = merge_sort(alist[mid:]) #指向左右表中第一个元素的指针 left_pointer,right_pointer = 0,0 #合并数据对应的列表:该表中存储的为排序后的数据 result = [] while left_pointer < len(left_li) and right_pointer < len(right_li): #比较最小集合中的元素,将最小元素添加到result列表中 if left_li[left_pointer] < right_li[right_pointer]: result.append(left_li[left_pointer]) left_pointer += 1 else: result.append(right_li[right_pointer]) right_pointer += 1 #当左右表的某一个表的指针偏移到末尾的时候,比较大小结束,将另一张表中的数据(有序)添加到result中 result += left_li[left_pointer:] result += right_li[right_pointer:] return result
-
时间复杂度
- 最优时间复杂度:O(nlogn)
- 最坏时间复杂度:O(nlogn)
- 稳定性:稳定