@listenviolet
2019-06-27T02:36:41.000000Z
字数 15004
阅读 5076
参考链接:
十大经典排序算法(动图演示)
基数排序
基数排序--MSD(桶排序)
十种常见排序算法可以分为两大类:
- 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
- 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
![time_complexity][2]
- 稳定: 如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定: 如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
- 时间复杂度: 对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
- 空间复杂度: 是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
(其中粗体表示当前比较的两个元素,斜体表示已经排序好的元素)
def bubbleSort(arr):
length = len(arr)
for i in range(length - 1):
for j in range(0, length - i - 1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
def selectionSort(arr):
length = len(arr)
for i in range(length - 1): # the first ind of the unsorted
minIdx = i # the minIdx of the unsorted
for j in range(i+1, length):
if arr[j] < arr[minIdx]:
minIdx = j
arr[i], arr[minIdx] = arr[minIdx], arr[i]
return arr
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
不稳定 |
def insertionSort(arr):
length = len(arr)
for i in range(1, length): # the current element index
preIndex = i - 1 # the index to insert
current = arr[i] # save the number otherwise it will be replace by others
while(preIndex >= 0 and arr[preIndex] > current):
arr[preIndex + 1] = arr[preIndex] # move -->
preIndex -= 1
arr[preIndex + 1] = current
return arr
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
def shellSort(start, end, number):
increment = end - start + 1
while increment > 1:
increment //= 3 + 1
for i in range(start + increment, end + 1):
if (number[i - increment] > number[i]):
temp = number[i]
j = i - increment
while (j >= start and number[j] > temp):
number[j + increment] = number[j]
j -= increment
number[j + increment] = temp
return number
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
与增量选取有关 | 不稳定 |
def mergeSort(arr):
if len(arr) < 2:
return arr
middle = len(arr) // 2
left_arr = arr[:middle]
right_arr = arr[middle:]
return merge(mergeSort(left_arr), mergeSort(right_arr))
def merge(left_arr, right_arr):
result = []
i, j = 0, 0
while i < len(left_arr) and j < len(right_arr):
if left_arr[i] < right_arr[j]:
result.append(left_arr[i])
i += 1
else:
result.append(right_arr[j])
j += 1
if i == len(left_arr):
result.extend(right_arr[j:])
elif j == len(right_arr):
result.extend(left_arr[i:])
return result
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
取数组第0个元素的值作为pivot
初始化两个位置标记left = 0; right = len(arr) - 1
1. 比较pivot和arr[right]的大小,若pivot <= arr[right],则right递减1;
2. 直到 pivot > arr[right] 且 left < right: 将 arr[right] 的值赋给 arr[left]。right 向前移动结束,至第 4 步,left 向后移动。
3. 若 left == right,则将pivot的值赋给arr[left],本轮排序结束。此时,pivot之前的元素均小于pivot; pivot之后的元素均大于pivot。递归地用快速排序算法对上一轮pivot前后的两个子序列分别排序。
4. 比较pivot和arr[left]的大小,若pivot >= arr[left],则left递增1;
5. 直到 pivot > arr[left] 且 left < right: 将 arr[left] 的值赋给 arr[right]。left 向前移动结束,至第 1 步,right 向前移动。
6. 若 left == right,转至第 3 步。
arr: [3 1 2 6 4 5] pivot = 3
<-- right
1. [3 1 2 6 4 5] pivot = 3; left = 0; right = 5
2. [3 1 2 6 4 5] pivot = 3; left = 0; right = 2
3. [2 1 2 6 4 5] pivot = 3; left = 0; right = 2; arr[left] = arr[right]
left-->
4. [2 1 2 6 4 5] pivot = 3; left = 2; right = 2;
5. [2 1 3 6 4 5] pivot = 3; left = 2; right = 2; arr[left] = pivot
对pivot前后的两个子序列递归快排:[2 1], [6 4 5],略。
# Python
def quickSort(arr):
if len(arr) == 0: return []
if len(arr) == 1: return arr
left = 0
right = len(arr) - 1
base = arr[left]
while(left < right):
while(arr[right] > base and right > left):
right -= 1
if right > left:
arr[left] = arr[right]
left += 1
while(arr[left] < base and left < right):
left += 1
if right > left:
arr[right] = arr[left]
right -= 1
if left == right:
arr[left] = base
result = quickSort(arr[:left]) + [arr[left]]
if left < len(arr) - 1:
result = result + quickSort(arr[left + 1:])
return result
// C++
int RandomInRange(int start, int end)
{
return rand() % (end - start + 1) + start;
}
void Swap(int *num1, int *num2)
{
int temp = *num1;
*num1 = *num2;
*num2 = temp;
}
int Partition(vector<int> &data, int start, int end)
{
int index = RandomInRange(start, end);
Swap(&data[index], &data[end]);
int small = start - 1;
for(index = start; index < end; ++index)
{
if(data[index] < data[end])
{
++small;
if(small != index) Swap(&data[index], &data[small]);
}
}
++small;
Swap(&data[small], &data[end]);
return small;
}
void QuickSort(vector<int> &data, int start, int end)
{
int index = Partition(data, start, end);
if(index < end) QuickSort(data, index + 1, end);
if(index > start) QuickSort(data, start, index - 1);
}
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
- 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:
- 大顶堆满足:子结点的键值或索引总是小于它的父节点;
- 小顶堆满足:子结点的键值或索引总是大于它的父节点。
给定一棵二叉树,将其调整为堆有自上而下调整方式,以使其满足最大堆/最小堆的性质。
98
/ \
86 68
/ \ /
58 42 42*
1.第一个非叶子节点为68,68 > 42*,故交换
98
/ \
86 42*
/ \ /
58 42 68
2.继续向前遍历,到节点86;86 > 58 > 42; 将86与最小的孩子节点42交换
98
/ \
42 42*
/ \ /
58 86 68
3.继续向前遍历,到根结点98;98 > 42 = 42*; 将98与42交换;
42
/ \
98 42*
/ \ /
58 86 68
4.将以98为根结点的子树调整为最小堆; 98 > 86 > 58; 将98与58交换;调整完毕。
42
/ \
58 42*
/ \ /
98 86 68
自上而下调整子树为最小堆:(见算法描述3)
# Adjust from top to bottom
# a: the arr
# i: adjust from node i
# n: the total number of nodes in heap
# j: the child of i
def MinHeapFixdown(a, i, n):
temp = a[i]
j = 2 * i + 1
while j < n:
if j + 1 < n and a[j + 1] < a[j]: # find the min of left and right child
j += 1
if a[j] < temp:
a[i] = a[j]
i = j
j = 2 * i + 1
else: break
a[i] = temp
最小堆的创建(初始化)
# Create the Min Heap
# Adjust from the first node which is not leaf
# Make root(i) whose heap to be the min heap
# then make the root(i - 1) whose heap to be the min heap...
def createMinHeap(a, n):
for i in range(n // 2 - 1, -1, -1):
MinHeapFixdown(a, i, n)
return a
42
/ \
58 42*
/ \ /
98 86 68
1. 取出根结点的值,加入到已排序的数组arr中,并交换42与最后一个节点68的值。
68
/ \
58 42*
/ \ /
98 86 42
arr = [42]
2. 此时认为42已被删除(被删除元素用斜体标注);对剩余的前5个元素进行调整,自上而下的调整为最小堆;
42*
/ \
58 68
/ \ /
98 86 42
arr = [42]
3. 最小堆调整完毕;取出根结点的值42*,加入到已排序的数组arr中,并交换42*与最后一个节点86的值。(注意:42已被删除,最后一个节点为86.)
86
/ \
58 68
/ \ /
98 42 42
arr = [42, 42*]
4. 此时认为42*已被删除;对剩余的前4个元素进行调整,自上而下的调整为最小堆;
58
/ \
86 68
/ \ /
98 42* 42
5. 最小堆调整完毕;取出根结点的值58,加入到已排序的数组arr中,并交换58与最后一个节点98的值。
98
/ \
86 68
/ \ /
58 42* 42
arr = [42, 42*, 58]
6. 同理进行调整,直至取出堆中最后一个元素,堆排序完成。arr = [42, 42*, 58, 68, 86, 98]
删除当前根结点,并调整剩余元素重新构成最小堆
# Delete from the heap
def MinHeapDeleteNumber(a, n):
a[0], a[n - 1] = a[n - 1], a[0]
MinHeapFixdown(a, 0, n - 1)
堆排序
def MinHeapSortToDescendArray(a, n):
for i in range(n - 1, 0, -1):
a[0], a[i] = a[i], a[0]
MinHeapFixdown(a, 0, i)
return a
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
不稳定 |
42
/ \
58 42*
/ \ / \
98 86 68 36
1.添加新元素36到已创建的最小堆中。
2.比较新节点的值36与其父节点的值; 36 < 42 不满足最小堆的性质了,需要进行调整; 交换36和其父节点42*。
42
/ \
58 36
/ \ / \
98 86 68 42*
3. 比较36与其父节点的值的大小; 36 < 42; 不满足最小堆的性质,需要进行调整; 交换36和其父节点42; 调整完毕。
36
/ \
58 42
/ \ / \
98 86 68 42*
自下而上调整为最小堆
# Adjust from bottom to top
# a: the arr
# i: adjust from node i
# j: the parent of i
def MinHeapFixup(a, i):
temp = a[i]
j = (i - 1) // 2 # adjust from the first node which is not leaf
while j >= 0 and i != 0: # if the parent > current: current <= parent
if a[j] < temp:
a[i] = a[j]
i = j
j = (i - 1) // 2
else: break
a[i] = temp
向堆中添加元素
# Add a number to the heap
# a: the arr
# n: the total number of the heap
def MinHeapAddNumber(a, n, number):
a[n] = number
MinHeapFixup(a, n)
arr = [2, 3, 2, 5, 3, 1]
min = 1
max = 5
C[1]: 1
C[2]: 2
C[3]: 2
C[4]: 0
C[5]: 1
C数组的下标即为arr数组中元素的值,arr数组中元素出现的次数,即为对应下标下C数组的值。比如arr中3出现了2次,C[3] = 2.
那么将C数组的下标按计数大小依次读出,即可获得arr中元素的值从小到大的排列。
得到:arr = [1, 2, 2, 3, 3, 5]
def countSort(arr, Max):
bucket = [0] * (Max + 1)
for i in range(len(arr)):
bucket[arr[i]] += 1
idx = 0
for i in range(len(bucket)):
while(bucket[i] > 0):
arr[idx] = i
idx += 1
bucket[i] -= 1
return arr
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
设有数组 array = [63, 157, 189, 51, 101, 47, 141, 121, 157, 156, 194, 117, 98, 139, 67, 133, 181, 13, 28, 109],对其进行桶排序
1. 设定分5个桶,每个桶的大小为36.4
2. 那么所分的桶的区间分别为:[13,49.4), [49.4, 85.8), [85.8, 122.2), [122.2, 158.6), [158.6, 195)
3. 遍历输入数据,将数据一个个放入对应的桶中。之后对每个桶中的数据进行插入排序。
index = 0
[13,49.4) => 13 -> 28 -> 47
index = 1
[49.4, 85.8)=> 51 -> 63 -> 67
index = 2
[85.8, 122.2) => 98 -> 101 -> 109 -> 117 -> 121
index = 3
[122.2, 158.6) => 133 -> 139 -> 141 -> 156 -> 157 -> 157
[158.6, 195) => 181 -> 189 -> 194
4. 合并数据。bucket 0 -> bucket 1 -> bucket 2 -> bucket3 -> bucket 4
5. 最终数据。 [13, 28, 47, 51, 63, 67, 98, 101, 109, 117, 121, 133, 139, 141, 156, 157, 157]
def bucketSort(arr, bucketSize):
if len(arr) == 0: return arr
minValue = min(arr)
maxValue = max(arr)
bucketsCount = (maxValue - minValue) // bucketSize + 1
buckets = [[]] * bucketsCount
for i in range(len(arr)):
ind = (arr[i] - minValue) // bucketSize
buckets[ind] = buckets[ind] + [arr[i]]
arr = []
for i in range(len(buckets)):
buckets[i] = insertSort(buckets[i])
for j in range(len(buckets[i])):
arr.append(buckets[i][j])
return arr
def insertSort(bucket):
for i in range(1, len(bucket)):
temp = bucket[i]
j = i - 1
while j >= 0 and bucket[j] > temp:
bucket[j + 1] = bucket[j]
j -= 1
bucket[j + 1] = temp
return bucket
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
LSD 最低位优先(Least Significant Digit first)法
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
MSD 最高位优先(Most Significant Digit first)法
先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
arr = [73, 22, 93, 43, 55, 14, 28, 65, 39, 81]
1. 首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0:
1: 81
2: 22
3: 73 93 43
4: 14
5: 55 65
6:
7:
8: 28
9: 39
2. 接下来将这些桶子中的数值重新串接起来,成为数列:arr = [81, 22, 73, 93, 43, 14, 55, 65, 28, 39]
3. 根据十位数来分配:
0:
1: 14
2: 22 28
3: 39
4: 43
5: 55
6: 65
7: 73
8: 81
9: 93
4. 接下来将这些桶子中的数值重新串接起来,成为数列:arr = [14, 22, 28, 39, 43, 55, 65, 73, 81, 93], 整个数列已经排序完毕。
arr = [12, 14, 54, 5, 6, 3, 9, 8, 47, 89]
1. 按照最高位十位遍历并分桶:
0: 5 6 3 9 8
1: 12 14
2:
3:
4: 47
5: 54
6:
7:
8: 89
9:
2. 然后再将每个桶分0-9号小桶
例如,对于0号桶,分小桶后:
0
1
2
3: 3
4
5: 5
6: 6
7
8: 8
9: 9
3. 分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下的数据序列:arr = [3, 5, 6, 8, 9, 12, 14, 47, 54, 89]
import math
def LSDRadixSort(arr):
Max = max(arr)
radix = 0
while(Max != 0):
Max /= 10
radix += 1
for i in range(1, radix + 1):
bukcet = [[] for i in range(10)]
mod = pow(10, i)
for j in range(len(arr)):
base = arr[j] % mod
base = base // pow(10, i - 1)
bukcet[base].append(arr[j])
arr = []
for k in range(10):
for ele in bukcet[k]:
arr.append(ele)
return arr
def MSDRadixSort(arr,radix):
n = len(arr)
Max = max(arr)
k = pow(10, radix - 1)
bucket = [[] for i in range(10)]
num = [0 for i in range(10)]
for a in arr:
index = (a // k) % 10
bucket[index].append(a)
num[index] += 1
cnt = 0
arr = []
for i in range(10):
if num[i] == 1:
arr.append(bucket[i][0])
elif num[i] > 1:
B = bucket[i][:]
B = MSDRadixSort(B, radix - 1)
arr.extend(B)
cnt += len(B)
return arr
时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|
稳定 |
LSD时间效率: 设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。
LSD与MSD比较: LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。