python实现各种算法详解,以及时间复杂度
Posted WELCOME TO ivanlee717!!!!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python实现各种算法详解,以及时间复杂度相关的知识,希望对你有一定的参考价值。
python实现各种排序
1. 快速排序
1:首先取序列第一个元素为基准元素pivot=R[low]。i=low,j=high。
2:从后向前扫描,找小于等于pivot的数,如果找到,R[i]与R[j]交换,i++。
3:从前往后扫描,找大于pivot的数,如果找到,R[i]与R[j]交换,j--。
4:重复2~3,直到i=j,返回该位置mid=i,该位置正好为pivot元素。 完成一趟排序后,以mid为界,将序列分为两部分,左序列都比pivot小,有序列都比pivot大,然后再分别对这两个子序列进行快速排序。
以序列(30,24,5,58,16,36,12,42,39)为例,进行演示:
1、初始化,i=low,j=high,pivot=R[low]=30:
i | j | |||||||
---|---|---|---|---|---|---|---|---|
30 | 24 | 5 | 58 | 16 | 26 | 12 | 41 | 39 |
2、从后往前找小于等于pivot的数,找到R[j]=12
i | j | |||||||
---|---|---|---|---|---|---|---|---|
30 | 24 | 5 | 58 | 16 | 26 | 12 | 41 | 39 |
- R[i]与R[j]交换,i++
i | j | |||||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 58 | 16 | 26 | 30 | 41 | 39 |
3、从前往后找大于pivot的数,找到R[i]=58
i | j | |||||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 58 | 16 | 26 | 30 | 41 | 39 |
- R[i]与R[j]交换,j--
i | j | |||||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 30 | 16 | 26 | 58 | 41 | 39 |
4、从后往前找小于等于pivot的数,找到R[j]=16
i | j | |||||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 30 | 16 | 26 | 58 | 41 | 39 |
- R[i]与R[j]交换,i++
i,j | ||||||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 16 | 30 | 26 | 58 | 41 | 39 |
5、从前往后找大于pivot的数,这时i=j,第一轮排序结束,返回i的位置,mid=i
Low | mid-1 | mid | mid+1 | High | ||||
---|---|---|---|---|---|---|---|---|
12 | 24 | 5 | 16 | 30 | 26 | 58 | 41 | 3 |
此时已mid为界,将原序列一分为二,左子序列为(12,24,5,18)元素都比pivot小,右子序列为(36,58,42,39)元素都比pivot大。然后在分别对两个子序列进行快速排序,最后即可得到排序后的结果。
def quicksort(low,high,li):
if low >= high:
return li
i = low
j= high
pivot = li[low]
while i<j:
while i<j and li[j]>pivot:
j -= 1
li[i] = li[j]
while i<j and li[i]<pivot:
i += 1
li[j] = li[i]
li[j] = pivot
quicksort(low,i-1,li)
quicksort(i+1,high,li)
return li
lists=[30,24,5,58,18,36,12,42,39]
print("排序前的序列为:")
for i in lists:
print(i,end =" ")
print("\\n排序后的序列为:")
for i in quicksort(0,len(lists)-1,lists):
print(i,end=" ")
2. 冒泡排序
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubblesort(li):
for i in range(len(li)-1,0,-1):
for j in range(1,i):
if li[j-1]>li[j]:
li[j-1],li[j] = li[j],li[j-1]
return li
li = [54,26,93,17,77,31,44,55,20]
print(bubblesort(li))
3. 插入排序
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
这个算法和冒泡排序有点类似,都是两层for循环,一个是从正面挨个比,一个是倒着比
def insertsort(li):
for i in range(1,len(li)):
for j in range(i,0,-1):
if li[j-1]>li[j]:
li[j-1],li[j] = li[j],li[j-1]
return li
li = [54,26,93,17,77,31,44,55,20]
print(insertsort(li))
4. 希尔排序
希尔排序是先取一个小于待排序列表长度的正整数d1,把所有距离为d1的数据看成一组,在组内进行插入排序。然后取一个小于d1的正整数d2,继续用d2分组和进行组内插入排序。每次取的分组距离越来越小,组内的数据越来越多,直到di=1,所有数据被分成一组,再进行插入排序,则列表排序完成。
希尔排序是基于插入排序进行优化的排序算法。在插入排序中,如果数据几乎已经排好序时,效率是很高的(想一下抓牌和插牌),时间复杂度为 O(n) 。但数据的初始状态并不一定是“几乎已经排好序”的,用插入排序每步只能将待插入数据往前移动一位,而希尔排序将数据分组后,每次可以往前移动di位,在di>1时进行的分组和组内排序可以将数据变成“几乎排好序”的状态,此时再进行最后一次插入排序,提高效率。
希尔排序的原理如下:
-
选择小于待排序列表长度 n 的正整数序列 d1,d2,...,di,其中 n>d1, d(i-1)>di, di=1 ,作为数据的间隔距离对列表进行分组。这里对 di 的取值和个数没有要求,只要是整数,d1<n,依次变小即可。
-
依次用 d1, ..., di 作为数据的距离对列表进行分组和组内插入排序,一共需要进行 i 轮排序。
-
在最后一轮排序前,列表中的数据达到了“几乎排好序”的状态,此时进行最后一轮插入排序。
以列表 [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21] 进行升序排列为例。
要进行升序排列,则分组后所有组内插入排序都进行升序排列。本例中以列表长度的length/3作为初始的分组距离 d1 ,d1=4。
-
从列表的开头开始,对所有数据按 d1 作为距离进行分组,分组只保证数据的间隔距离相等,不保证每组的数据个数一样,只是本例中刚好每组数据一样多。本例的数据可以分为4组,也就是第1,5,9作为排序对象,然后2,6,10;3,7,11;4,8,12.一共要排序4次。排完序为
-
第二次排序的话,这个距离为4//3 = 1,也就是最后一轮排序。这也和直接插入排序完全一样了,先将第一个数据作为已排序序列,后面的数据作为未排序序列,然后依次将未排序序列中的数据插入到已排序序列中。
def shellsort(li):
n = len(li)
gap = n//3
while gap > 0:
for i in range(gap,n):
j = i
while j>=gap and li[j-gap]>li[j]:
li[j-gap],li[j] = li[j],li[j-gap]
j -= gap
gap = gap//3
alist = [54,26,93,17,77,31,44,55,20]
shellsort(alist)
print(alist)
5. 归并排序
将原问题划分成 n 个规模较小而结构与原问题相似的子问题;递归地解决这些问题,然后再合并其结果,就得到原问题的解。从上图看分解后的数列很像一个二叉树。
归并排序采用分而治之的原理:
- 将一个序列从中间位置分成两个序列;
- 在将这两个子序列按照第一步继续二分下去;
- 直到所有子序列的长度都为1,也就是不可以再二分截止。这时候再两两合并成一个有序序列即可。
def merge(left,right):
r,l = 0,0
res = []
while r < len(right) and l < len(left):
if left[l] < right[r]: # 排序,小于等于的放左边
res.append(left[l])
l += 1
else:
res.append(right[r]) # 排序,大的放右边
r += 1
res +=(left[l:])
res +=(right[r:])
return res
def mergesort(li):
if(len(li)) <2:
return li
mid = len(li)//2
left = mergesort(li[:mid])
right = mergesort(li[mid:])
return merge(left,right)
li = [8,0,4,3,6,2]
print(mergesort(li))
6. 选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
def selection_sort(alist):
n = len(alist)
# 需要进行n-1次选择操作
for i in range(n-1):
# 记录最小位置
min_index = i
# 从i+1位置到末尾选择出最小数据
for j in range(i+1, n):
if alist[j] < alist[min_index]:
min_index = j
# 如果选择出的数据不在正确位置,进行交换
if min_index != i:
alist[i], alist[min_index] = alist[min_index], alist[i]
alist = [54,226,93,17,77,31,44,55,20]
selection_sort(alist)
print(alist)
7. 堆排序
堆排序(Heap Sort)是利用堆这种数据结构而设计的一种排序算法,是一种选择排序。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序思路为: 将一个无序序列调整为一个堆,就能找出序列中的最大值(或最小值),然后将找出的这个元素与末尾元素交换,这样有序序列元素就增加一个,无序序列元素就减少一个,对新的无序序列重复操作,从而实现排序。
- 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆);
- 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素;
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素;
- 如此反复进行交换、重建、交换,直到整个序列有序。
def heap_sort(array):
first = len(array) // 2 - 1
for start in range(first, -1, -1):
# 从下到上,从右到左对每个非叶节点进行调整,循环构建成大顶堆
big_heap(array, start, len(array) - 1)
for end in range(len(array) - 1, 0, -1):
# 交换堆顶和堆尾的数据
array[0], array[end] = array[end], array[0]
# 重新调整完全二叉树,构造成大顶堆
big_heap(array, 0, end - 1)
return array
def big_heap(array, start, end):
root = start
# 左孩子的索引
child = root * 2 + 1
while child <= end:
# 节点有右子节点,并且右子节点的值大于左子节点,则将child变为右子节点的索引
if child + 1 <= end and array[child] < array[child + 1]:
child += 1
if array[root] < array[child]:
# 交换节点与子节点中较大者的值
array[root], array[child] = array[child], array[root]
# 交换值后,如果存在孙节点,则将root设置为子节点,继续与孙节点进行比较
root = child
child = root * 2 + 1
else:
break
if __name__ == \'__main__\':
array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
print(heap_sort(array))
8. 计数排序
计数排序(Counting Sort)是一种不比较数据大小的排序算法,是一种牺牲空间换取时间的排序算法。
计数排序适合数据量大且数据范围小的数据排序,如对人的年龄进行排序,对考试成绩进行排序等。
计数排序先找到待排序列表中的最大值 k,开辟一个长度为 k+1 的计数列表,计数列表中的所有初始值都为 0。走访待排序列表,如果走访到的元素值为 i,则计数列表中索引 i 的值加1,走访完整个待排序列表,就可以统计出待排序列表中每个值的数量。然后创建一个新列表,根据计数列表中统计的数量,依次在新列表中添加对应数量的 i ,得到排好序的列表。
def counting_sort(array):
if len(array) < 2:
return array
max_num = max(array)
count = [0] * (max_num + 1)
for num in array:
count[num] += 1
new_array = list()
for i in range(len(count)):
for j in range(count[i]):
new_array.append(i)
return new_array
if __name__ == \'__main__\':
array = [5, 7, 3, 7, 2, 3, 2, 5, 9, 5, 7, 6]
print(counting_sort(array))
9. 桶排序
桶排序(Bucket sort)是一种通过分桶和合并实现的排序算法,又被称为箱排序。
桶排序先将数据分到有限数量的桶里,然后对每一个桶内的数据进行排序(桶内排序可以使用任何一种排序算法,如快速排序),最后将所有排好序的桶合并成一个有序序列,列表排序完成。
桶排序需要占用很多额外的空间,对桶内数据进行排序,选择哪种排序算法对于性能的影响至关重要。桶排序适用的场景并不多,用得多一点的是基于桶排序思想的计数排序和基数排序。
桶排序的原理如下:
-
求出待排序列表中的最大值和最小值,得到数据的范围。
-
根据数据的范围,选择一个适合的值构建有限数量的桶,确定每个桶的数据范围。如数据范围是[0,100),将数据分成10个桶,第一个桶为[0,10),第二个桶为[10,20),以此类推。
-
将待排序列表中的数据分配到对应的桶中。
-
对每一个桶内的数据进行排序,这里可以采用任意一种排序算法,建议采用时间复杂度小的排序算法。
-
将所有桶中的数据依次取出,添加到一个新的有序序列中,列表排序完成。
# coding=utf-8
def bucket_sort(array):
min_num, max_num = min(array), max(array)
bucket_num = (max_num-min_num)//3 + 1
buckets = [[] for _ in range(int(bucket_num))]
for num in array:
buckets[int((num-min_num)//3)].append(num)
new_array = list()
for i in buckets:
for j in sorted(i):
new_array.append(j)
return new_array
if __name__ == \'__main__\':
array = [5, 7, 3, 7, 2, 3, 2, 5, 9, 5, 7, 8]
print(bucket_sort(array))
本文来自博客园,作者:ivanlee717,转载请注明原文链接:https://www.cnblogs.com/ivanlee717/p/17292751.html
数据结构—各类‘排序算法’实现(下)
在上一篇博客中,主要是实现各种的排序算法,并针对一些算法进行了优化的处理,下面主要讨论一下非比较排序的算法(计数排序、基数排序),同时并对各种排序算法的性能、时间复杂度、空间复杂度、优缺点、以及适用场景做总结分析。
1.计数排序
主要思想:主要是需要统计次数,使用直接定址法,统计最大数和最小数,开辟两个数相差的空间大小,对于重复数据,使用count用来计数,时间复杂度O(N+范围个数),空间复杂度O(范围个数)计数排序适合于数据较为密集的情况,当数据密集且没有重复的数据,可以直接使用‘位图’,更能够省下空间
void CountSort(int* a, size_t size) { assert(a); int max = a[0]; int min = a[0]; int count = 0; for (size_t i = 0; i < size; ++i) //寻找数组中的最大数和最小数 { if (a[i] < min) { min = a[i]; } if (a[i] > max) { max = a[i]; } } int* tmp = new int[max - min + 1]; //开辟存储空间,并初始化 memset(tmp, 0, sizeof(int)*(max - min + 1)); for (size_t i = 0; i < max - min + 1; ++i) //直接定址法 { int num = a[i] - min; tmp[num]++; } for (size_t i = 0; i < size;) //将排序好的顺序写入a数组中 { for (size_t j = 0; j < max - min + 1; ++j) { count = tmp[j]; while (count--) //对于重复数据需要多次进行写入 { a[i] = j + min; i++; } } } delete[] tmp; }
2.基数排序
主要思想:和‘快速转置’的思想相像,开辟两个数组count和start,count用来统计个位上分别为0~9的数据个数,start用来统计数据的开始位置(起始位置为0,下一位的数据开始的位置=上一个数据的开始位置+上一位总的数据个数),另开辟size大小的空间来存放每次排序,下面是低位基数排序,从个位开始排序,然后排序十位,进而百位,直到排到最大数据的最高位,排序结束。
int GetMaxRadix(int* a, size_t size) //寻找最大数据的位数 { int index = 1; //数据最小有一位 int max = 10; for (size_t i = 0; i < size; ++i) { while (a[i] >= max) //数据大于1位 { index++; max = max * 10; } } return index; } void LSDSort(int* a, size_t size) { assert(a); int index = GetMaxRadix(a, size); //求最大数据的位数 int count[10] = { 0 }; //记录数据出现的次数 int start[10] = { 0 }; //记录数据的开始位置 int radex = 1; int* bucket = new int[size]; for (int k = 1; k <= index; ++k) //从各位到最高位排序 { memset(count, 0, sizeof(int)* 10); //每次排序之前需将count置0 //计数(各位分别为0~9的数据个数) for (size_t i = 0; i < size; ++i) { int num = (a[i] / radex) % 10; //取个位 count[num]++; } //记录数据开始的位置 start[0] = 0; int j = 1; while (j < 10) { start[j] = start[j - 1] + count[j - 1]; j++; } for (size_t i = 0; i < size; ++i) //将数据按顺序放入bucket中 { int num = (a[i] / radex) % 10; bucket[start[num]++] = a[i]; } radex *= 10; memcpy(a, bucket, sizeof(int)* size); } delete[] bucket; }
3.排序算法总结
(1)各种排序算法的性能分析
其中:r为数据范围的个数
稳定性分析:
稳定性:指的是需要排序的数据之中如果有相同的数据元素,在排序前、后的相对位置是不变的,即就是当排序{1,3,5,7,2,5,6},通过排序后‘5’在‘5‘之前,而不是相互交换了。
在介绍的这几种排序之中插入排序、冒泡排序、归并排序、计数排序是稳定的。快速排序、希尔排序、选择排序、堆排序是不稳定的
空间复杂度:
排序算法中,快速排序(需要进行递归)、归并排序、计数排序、基数排序都是需要额外的空间进行排序。其余的排序算法就不需要借助任何的空间。
时间复杂度:
O(N^2):
插入排序、冒泡排序、选择排序都是空间复杂度为O(N^2),排序效率基本上都是比较低,选择排序是最不好的,因为在最有的情况下,也需要N^2的时间复杂度,相对来说,插入和冒泡能好一点,在优化的情况下,能够减少排序的时间。但是当数据量较大时,冒泡的时间代价会更高。
O(N*lgN):
平均性能为O(N*lgN)的算法:快速排序、归并排序、堆排序算法,快速排序经过各种的优化算法(三数取中法、区间较小时利用直接插入算法,【上篇博客中详细的介绍了快速排序的优化】)已经相对来说效率提高了很多。
归并排序又称为外排序,外排序其实就是指能够对内存之外(磁盘中)数据进行排序,对于大数据的文件,不能够直接加载到内存中进行排序,我们可以采取将文件划分成小文件,将小的文件加载到内存中进行排序,然后将排好序的数据进行重写,将两个有序的数据文件在重新排序,就能够排好大数据文件。这个读者可以下去进行试验,这里就不做详细的解释。
堆排序的效率虽然还是挺高的,但是堆排序有个致命的缺点,就是只能够对数组进行排序,因为需要通过数组的下标来确定数据在堆中的具体位置。所以堆排序只能对数组进行排序。
本文出自 “无心的执着” 博客,请务必保留此出处http://10740590.blog.51cto.com/10730590/1782091
以上是关于python实现各种算法详解,以及时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章
策略设计模式详解C/Java/JS/Go/Python/TS不同语言实现
通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码