数据结构|十种排序算法

在排序算法中,我们可能会遇到In-place和Out-place

  • in-place 占用常数内存,不占用额外内存
  • out-place 占用额外内存
排序算法稳定性的意义:
稳定性的定义:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
如果我们只是面对简单的数字排序,那么稳定性确实也没有多大意义。那么排序算法的「稳定性」在什么情况下才会变得有意义呢?
举个例子,一个班的学生已经按照学号大小排好序了,我现在要求按照年龄从小到大再排个序,如果年龄相同的,必须按照学号从小到大的顺序排列。那么问题来了,你选择的年龄排序方法如果是不稳定的,是不是排序完了后年龄相同的一组学生学号就乱了,你就得把这组年龄相同的学生再按照学号拍一遍。如果是稳定的排序算法,我就只需要按照年龄排一遍就好了。
从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。要排序的内容是一个复杂对象的多个数字属性,且其原本的初始顺序存在意义,那么我们需要在二次排序的基础上保持原有排序的意义,才需要使用到稳定性的算法。
数据结构|十种排序算法
文章图片


1、冒泡排序(Bubble Sort)
1.1算法描述
  • 比较相邻的元素,如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,最后的元素是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。
1.2代码实现
def Bubble_Sort(arr): length=len(arr) if(length<1): return for i in range(length): for j in range(length-i-1): if arr[j] > arr[j+1]: arr[j], arr[j+1]=arr[j+1], arr[j] #Python的变量并不直接存储值,而只是引用一个内存地址,交换变量时,只是交换了引用的地址。

2、选择排序(Selection Sort)
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
选择排序为啥不是稳定性排序呢,举个例子:数组 6、7、6、2、8,在对其进行第一遍循环的时候,会将第一个位置的6与后面的2进行交换。此时,就已经将两个6的相对前后位置改变了。因此选择排序不是稳定性排序算法。
2.1 代码实现
def Selection_Sort(arr): length = len(arr) if (length < 1): return for i in range(length): index = i for j in range(i, length): if arr[j] < arr[index]: index = j arr[index], arr[i] = arr[i], arr[index]

3、插入排序(Insertion Sort)
工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
3.1 算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果扫描序列的元素大于该元素,将序列中元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
【数据结构|十种排序算法】3.2 代码实现
def Insertion_Sort(arr): length = len(arr) if (length < 1): return for i in range(1, length): current = arr[i] j=i-1 while j>=0 and arr[j]>current: arr[j+1]=arr[j] j-=1 arr[j+1]=current

4、希尔排序(Shell Sort)
4.1 算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。具体算法描述:
  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
如何对原始数组进行预处理呢?聪明的科学家想到了一种分组排序的方法,以此对数组进行一定的“粗略调整”。
数据结构|十种排序算法
文章图片

所谓分组,就是让元素两两一组,同组两个元素之间的跨度,都是数组总长度的一半,也就是跨度为4。

数据结构|十种排序算法
文章图片

接下来,我们让每组元素进行独立排序,排序方式用直接插入排序即可。由于每一组的元素数量很少,只有两个,所以插入排序的工作量很少。每组排序完成后的数组如下:
数据结构|十种排序算法
文章图片

这样一来,仅仅经过几次简单的交换,数组整体的有序程度得到了显著提高,使得后续再进行直接插入排序的工作量大大减少。这种做法,可以理解为对原始数组的“粗略调整”。
但是这样还不算完,我们可以进一步缩小分组跨度,重复上述工作。把跨度缩小为原先的一半,也就是跨度为2,重新对元素进行分组:
数据结构|十种排序算法
文章图片


如图所示,元素5,1,9,6一组,元素2,3,8,7一组,一共两组。
接下来,我们继续让每组元素进行独立排序,排序方式用直接插入排序即可。每组排序完成后的数组如下:
数据结构|十种排序算法
文章图片

此时,数组的有序程度进一步提高,为后续将要进行的排序铺平了道路。最后,我们把分组跨度进一步减小,让跨度为1,也就等同于做直接插入排序。经过之前的一系列粗略调整,直接插入排序的工作量减少了很多,排序结果如下:
数据结构|十种排序算法
文章图片

上面示例中所使用的分组跨度(4,2,1),被称为希尔排序的增量,增量的选择可以有很多种,我们在示例中所用的逐步折半的增量方法,是Donald Shell在发明希尔排序时提出的一种朴素方法,被称为希尔增量。
4.3 代码实现
def Shell_Sort(arr): length = len(arr) if (length < 1): returngap = length//2 while gap > 0: for i in range(gap, length): j = i while j >= gap and arr[j-gap] > arr[j]: arr[j-gap], arr[j] = arr[j], arr[j-gap] j -= gap gap = gap//2

5、归并排序(Merge Sort)
5.1 算法描述
  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
数据结构|十种排序算法
文章图片


def Merge(arr1:List[int], arr2:List[int]): result = [] while arr1 and arr2: if arr1[0] < arr2[0]: result.append(arr1.pop(0)) else: result.append(arr2.pop(0)) if arr1: result += arr1 if arr2: result += arr2 return resultdef Merge_Sort(arr:List[int]): if (len(arr)<=1): return arr mid = len(arr) //2 Merge_Sort(arr[:mid]) Merge_Sort(arr[mid:]) return Merge(Merge_Sort(arr[:mid]), Merge_Sort(arr[mid:]))

6、快速排序(Quick Sort)
快速排序的操作是这样的:首先从数列的右边开始往左边找,我们设这个下标为 i,也就是进行减减操作(i--),找到第 1 个比基准数小的值,让它与基准值交换;接着从左边开始往右边找,设这个下标为 j,然后执行加加操作(j++),找到第 1 个比基准数大的值,让它与基准值交换;然后继续寻找,直到 i 与 j 相遇时结束,最后基准值所在的位置即 k 的位置,也就是说 k 左边的值均比 k 上的值小,而 k 右边的值都比 k 上的值大。
6.1 代码实现
def Quick_Sort(arr:List[int], left, right): if left >= right: return low = left high = right key = arr[low] while left < right: while left < right and arr[right] > key: right -= 1 arr[left] = arr[right] while left < right and arr[left] < key: left += 1 arr[right] = arr[left] arr[right] = key Quick_Sort(arr, low, left-1) Quick_Sort(arr, left+1, high)

7、堆排序(Heap Sort)
堆排序详细介绍见:堆排序算法(图解详细流程)_阿顾的博客-CSDN博客_堆排序过程图解
7.1 代码实现
def BuildMaxHeap(arr): n = len(arr) lastparent = (n - 1) // 2 for i in range(lastparent, -1, -1): MaxHeapFy(arr, n, i)def MaxHeapFy(arr, len, parent): left = 2*parent + 1 right = 2*parent + 2 lagest = parent if left < len and arr[left] > arr[lagest]: lagest = left if right < len and arr[right] > arr[lagest]: lagest = right if parent != lagest: arr[parent], arr[lagest] = arr[lagest], arr[parent] MaxHeapFy(arr, len, lagest)def Heap_Sort(arr): BuildMaxHeap(arr) n = len(arr) for i in range(n-1, -1, -1): arr[0], arr[i] = arr[i], arr[0] MaxHeapFy(arr, i, 0) return arr

8、计数排序(Counting Sort)
详细介绍见:https://www.cxyxiaowu.com/5437.html
一是需要排序的元素必须是整数,二是排序元素的取值要在一定范围内,并且比较集中,只有这两个条件都满足,才能最大程度发挥计数排序的优势。计数排序,不是基于元素比较,而是利用数组下标来确定元素的正确位置的。
# 计数排序 def Counting_Sort(arr): if len(arr) < 2: return arr max_num = max(arr) min_num = min(arr) difference = max_num-min_num+1 countArray = [0]*difference sortedArray = [0] * len(arr)# 创建统计数组,并记录对应元素个数 for i in range(len(arr)): countArray[arr[i]-min_num] += 1# 统计数组变形,后面元素等于前面元素和 sum = 0 for i in range(len(countArray)): sum += countArray[i] countArray[i] = sum#倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组 for i in range(len(arr)-1, -1, -1): sortedArray[countArray[arr[i]-min_num]-1] = arr[i] countArray[arr[i] - min_num] -= 1 return sortedArray

9、桶排序(Bucket Sort)
桶排序的原理如下:
1. 求出待排序列表中的最大值和最小值,得到数据的范围。
2. 根据数据的范围,选择一个适合的值构建有限数量的桶,确定每个桶的数据范围。如数据范围是[0,100),将数据分成10个桶,第一个桶为[0,10),第二个桶为[10,20),以此类推。
3. 将待排序列表中的数据分配到对应的桶中。
4. 对每一个桶内的数据进行排序,这里可以采用任意一种排序算法,建议采用时间复杂度小的排序算法。
5. 将所有桶中的数据依次取出,添加到一个新的有序序列中,列表排序完成。
9.1 代码实现
# 桶排序 def Bucket_Sort(arr): min_num = min(arr) max_nam = max(arr) bucket_num = (max_nam-min_num)//3+1 #设置三个桶 buckets = [[] for _ in range(int(bucket_num))] for i in arr: buckets[int((i-min_num)//3)].append(i) new_array = list() for i in buckets: for j in sorted(i):#桶内采用python内置函数进行排序 new_array.append(j) return new_array

10、基数排序(Radix Sort)
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
从低位开始排序的原因是,如果从高位开始排序,那么次高位的排序会影响高位排序的结果,在数学中,数位越高,数值位对数的大小影响就越大。
10.1 代码实现
# 基数排序 def Radix_Sort(arr): n=len(str(max(arr)))#记录最大值的位数 for k in range(n): bucket_list = [[] for i in range(10)] #因为每一位数字都是0~9,故建立10个桶 for i in arr: bucket_list[i//(10**k)%10].append(i) arr.clear() for i in bucket_list: for j in i: arr.append(j) return arr














    推荐阅读