赞
踩
桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序,介于MSD基数排序和LSD基数排序之间。
建立一堆buckets;
遍历原始数组,并将数据放入到各自的buckets当中;
对非空的buckets进行排序;
按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。
图示
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。
对N个关键字进行桶排序的时间复杂度分为两个部分
:
(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是
O
(
N
)
O(N)
O(N)。
(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为
∑
O
(
N
i
∗
l
o
g
N
i
)
∑O(N_i*logN_i)
∑O(Ni∗logNi)。其中
N
i
N_i
Ni为第i个桶的数据量。
很显然,
第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)
了。因此,我们需要尽量做到下面两点:
(1) 映射函数f(k)能够将N个数据平均的分配到M
个桶中,这样每个桶就有[N/M]
个数据量。
(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。
对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O
(
N
)
+
O
(
M
∗
(
N
M
)
∗
l
o
g
(
N
M
)
)
=
O
(
N
+
N
∗
(
l
o
g
N
−
l
o
g
M
)
)
=
O
(
N
+
N
∗
l
o
g
N
−
N
∗
l
o
g
M
)
O(N)+O(M*({N\over M})*log({N\over M}))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
O(N)+O(M∗(MN)∗log(MN))=O(N+N∗(logN−logM))=O(N+N∗logN−N∗logM)当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。
总结
:桶排序的平均时间复杂度
为线性的O(N+C)
,其中C=N*(logN-logM)
。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
""" 桶排序: 基本思想: 将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。 也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序。 时间复杂度:O(N)+O(M*(N/M)*log(N/M)) = O(N+N*(logN-logM)) = O(N+N*logN-N*logM) """ class node(object): def __init__(self, initdata): self.data = initdata self.next = None def getBucketIndex(value): return value//interval def printBuckets(bucket): cur = bucket while cur: print(cur.data, " ",end = "") cur = cur.next print("\n") def bucketSort(data): bucket = [] for i in range(n_bucket): bucket.append(None) for i in range(n_data): pos = getBucketIndex(data[i]) # O(N) + O(M) current = node(data[i]) cur = bucket[pos] # 比较排序 if cur == None or cur.data > current.data: # O(N/Mlog(N/M)) current.next = cur bucket[pos] = current bucket[pos] = current else: last = cur while cur != None and cur.data < current.data: last = cur cur = cur.next current.next = cur last.next = current for i in range(n_bucket): print("Bucket[", i,"] : ", end = "") printBuckets(bucket[i]) result = [] for i in range(n_bucket): cur = bucket[i] while cur is not None: result.append(cur.data) cur = cur.next return result if __name__ == '__main__': data = [20, 40, 30, 10, 80, 50, 60, 90] n_data = len(data) # data size n_bucket = 5 # bucket size interval = 20 # bucket range print("before sort:", data) data_order = bucketSort(data) print("after sort:", data_order)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
输出结果:
before sort: [20, 40, 30, 10, 80, 50, 60, 90]
Bucket[ 0 ] : 10
Bucket[ 1 ] : 20 30
Bucket[ 2 ] : 40 50
Bucket[ 3 ] : 60
Bucket[ 4 ] : 80 90
after sort: [10, 20, 30, 40, 50, 60, 80, 90]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
在上图中,首先将所有待比较树脂统一为统一位数长度,接着从最低位开始,依次进行排序。
排序后,数列就变成了一个有序序列。
radix_sort(a, n)的作用是对数组a进行排序:
(01) 个位的数值范围是[0,10)。因此,参见桶数组buckets[],将数组按照个位数值添加到桶中。
(02) 接着是根据桶数组buckets[]来进行排序。假设将排序后的数组存在output[]中;找出output[]和buckets[]之间的联系就可以对数据进行排序了。
空间
空间采用顺序分配,显然不合适,由于每个口袋都有可能存放所有的待排序的整数,所以,额外空间的需求为10n,太大了。
采用链表分配是合理的,额外空间的需求为n,通常再增加指向每个口袋的首尾指针就可以了。在一般情况下,设每个关键字的取值范围为radix, 首尾指针共计2×radix个,总的空间为O(n+2×radix)。
时间
如果每个数共有2位,因此执行2次分配和收集就可以了。在一般情况下,每个结点有d位关键字,必须执行d次分配和收集操作。
• 每次分配的代价:O(n)
• 每次收集的代价:O(radix)
• 总的代价为:O(d×(n+radix))
""" 基数排序: 基本思想: 基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。 时间复杂度:O(d(n+r)) """ def countSort(data, exp): data_order = [0] * len(data) # 初始化计数数组 count_arr = [0] * ((max(data) - min(data)) + 1) # 统计i的次数 for i in range(len(data)): # O(n) count_arr[((data[i] - min(data))//exp)%10] += 1 # 对所有的计数累加 for i in range(len(count_arr)-1): # O(r) r表示基数,本例中为10 count_arr[i+1] += count_arr[i] # 逆向遍历源数组(保证稳定性),根据计数数组中对应的值填充到先的数组中 for i in range(len(data)-1, -1, -1): # O(n) data_order[count_arr[((data[i] - min(data))//exp)%10]-1] = data[i] count_arr[((data[i] - min(data)) // exp) % 10] -= 1 for i in range(len(data)): # O(n) data[i] = data_order[i] def getMax(data): max = data[0] for i in range(len(data)): if data[i] > max: max = data[i] return max def radixSort(data): exp = 1 max = getMax(data) while max/exp > 0: # O(d) d表示最大数的长度 countSort(data, exp) exp *= 10 if __name__ == '__main__': data = [234, 48, 76, 10, 98, 1, 237, 227] print("before sort:", data) radixSort(data) print("after sort:", data)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
运行结果:
before sort: [234, 48, 76, 10, 98, 1, 237, 227]
after sort: [1, 10, 48, 76, 98, 227, 234, 237]
- 1
- 2
目前介绍的利用比较元素进行排序的方法对数据表长度为n的数据表进行排序时间复杂度不可能低于O(nlogn)。但是如果知道了一些数据表的信息,那么就可以实现更为独特的排序方式,甚至是可以达到线性时间的排序。
它是一个不需要比较的,类似于桶排序的线性时间排序算法。该算法是对已知数量范围的数组进行排序。其时间复杂度为O(n),适用于小范围集合的排序。计数排序是用来排序0到100之间的数字的最好的算法。
比如100万学生参加高考,我们想对这100万学生的数学成绩(假设分数为0到100)做个排序。
当数据表长度为n,已知数据表中数据的范围有限,比如在范围0−k之间,而k又比n小许多,这样可以通过统计每一个范围点上的数据频次来实现计数排序。
总体思路:
根据获得的数据表的范围,分割成不同的buckets,然后直接统计数据在buckets上的频次,逐个累加,确定元素排序后的位置下标,然后倒序遍历原数组,逐个找到元素位置构成收集后的数据表,倒序的目的是保证相同的数字保持原来相对位置不变,保证排序稳定性。
下面以示例来说明这个算法:
假设a={8,2,3,4,3,6,6,3,9}, max=10。此时,将数组a的所有数据都放到需要为0-9的桶中。如下图:
在将数据放到桶中之后,再通过一定的算法,将桶中的数据提出出来并转换成有序数组。就得到我们想要的结果了。
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
时间复杂度为O(n),且排序是稳定的。
""" 计数排序: 基本思想: 当数据表长度为n,已知数据表中数据的范围有限,比如在范围0−k之间,而k又比n小许多,这样可以通过统计每一个范围点上的数据频次来实现计数排序。 算法复杂度:O(n) """ def countSort(data): data_order = [0] * len(data) # 初始化计数数组 count_arr = [0] * ((max(data) - min(data)) + 1) # 统计i的次数 for i in range(len(data)): count_arr[data[i] - min(data)] += 1 # 对所有的计数累加 for i in range(len(count_arr)-1): count_arr[i+1] += count_arr[i] # 逆向遍历源数组(保证稳定性),根据计数数组中对应的值填充到先的数组中 for i in range(len(data)-1, -1, -1): data_order[count_arr[data[i] - min(data)]-1] = data[i] count_arr[data[i] - min(data)] -= 1 return data_order if __name__ == '__main__': data = [20, 40, 30, 10, 60, 50] print("before sort:", data) # num_order = insertSort(num_list, num_len) data_order = countSort(data) print("after sort:", data_order)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
运行结果:
before sort: [20, 40, 30, 10, 60, 50]
after sort: [10, 20, 30, 40, 50, 60]
- 1
- 2
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。