重新看了一段时间的数据结构和算法,写点总结和梳理。
使用 Python 和 C/C++/Java 进行描述。
第3章 排序算法
3.1 简单排序算法
3.1.1 冒泡排序
两重循环的交换排序,无需细述。
将列表分为顶部的有序区(初始为空)和底部的无序区(初使为整个列表);
每轮排序在无序区中,从底端到顶端两两比较,将该区域的最小元素一步步交换(冒泡)到顶端,最后并入有序区作为新底端。
剩下的无序区循环进行比较直至整体有序。
平均/最差时间复杂度:O(n^2)
最好时间复杂度:O(n)
空间复杂度:O(1)
稳定性:稳定
(稳定指:原序列中若有元素 a、b 值相同,且 a 在 b 之前;则排序后 a 仍在 b 之前)
# Python 实现
def bubble_sort(array):
# i为轮次,也是有序区的长度,当有序区长度为N-1时不再比较。
for i in range(len(array)-1):
# 从最后的数字开始两两比较,到 i+1 与 i 对比并交换完成后为止。
for j in range(len(array)-1,i,-1):
if array[j-1]>array[j]:
array[j-1], array[j] = array[j], array[j-1]
// C 实现
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
void BubbleSort(ElemType *A, int count){
for(int i = 0; i<count-1; i++){
for(int j = count-1; j>i; j--){
if(A[j-1]>A[j]) Swap(&A[j], &A[j-1]);
}
}
}
// C++ 实现
template <typename ElemType>
void BubbleSort(vector<ElemType> &A){
for(int i = 0; i<A.size()-1; i++){
for(int j = A.size()-1; j>i; j--){
if(A[j-1]>A[j]) swap(A[j], A[j-1]);
}
}
}
// Java
public static <T extends Comparable<T>> void bubbleSort(T[] arr){
for(int i = 0; i<arr.length; i++){
for(int j = arr.length-1; j>i; j--){
if(arr[j].compareTo(arr[j-1])>0){
T temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
}
3.1.2 插入排序
从第二个元素开始,依次扫描序列,对于扫描到的每一个元素,均与该元素之前的序列对比,并按照大小顺序插入到该序列中的正确位置
平均/最差时间复杂度:O(n^2)
最好时间复杂度:O(n)
空间复杂度:O(1)
稳定性:稳定
# Python 实现
def insertion_sort(array):
# 第一个元素已经完成插入,从第二个元素开始
for i in range(1,len(array)):
# 将「无序区」的第一个元素取出作为 key,准备插入
key = array[i]
# 从「有序区」的最后一个元素开始比较
j = i-1
while j>=0 and array[j]>key:
# 每个大于 key 的元素都后移一个位置
array[j+1] = array[j]
j -= 1
# 腾出来的位置就是插入 key 的位置
array[j+1] = key
// C 实现
void InsertionSort(ElemType *A, int count){
ElemType key;
// 第一个元素已经完成插入,从第二个元素开始
for(int i=1; i<count; i++){
// 将「无序区」的第一个元素取出作为 key,准备插入
key = A[i];
// 从「有序区」的最后一个元素开始比较
int j;
for(j=i-1; j>=0 && A[j]>key; j--){
// 每个大于 key 的元素都后移一个位置
A[j+1] = A[j];
}
// 腾出来的位置就是插入 key 的位置
A[j+1]=key;
}
}
// C++ 实现
template <typename ElemType>
void InsertionSort(vector<ElemType> &A){
ElemType key;
// 第一个元素已经完成插入,从第二个元素开始
for(int i=1; i<A.size(); i++){
// 将「无序区」的第一个元素取出作为 key,准备插入
key = A[i];
// 从「有序区」的最后一个元素开始比较
int j;
for(j=i-1; j>=0 && A[j]>key; j--){
// 每个大于 key 的元素都后移一个位置
A[j+1] = A[j];
}
// 腾出来的位置就是插入 key 的位置
A[j+1]=key;
}
}
// Java
// 基本同 C++
public static <T extends Comparable<T>> void insertionSort(T[] arr){
T key;
for(int i=1; i<arr.length; i++){
key = arr[i];
int j;
for(j=i-1; j>=0 && A[j].compareTo(key)<0; j--){
arr[j+1] = arr[j];
}
arr[j+1] = key;
}
}
3.1.3 选择排序
将列表分为左侧的有序区(初始为空)和右侧的无序区(初使为整个列表);
每轮排序在无序区中,寻找到最小元素,并直接与有序区末尾的下一个元素交换。反复进行直到排序完成。
平均/最差/最好时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
# Python 实现
def selection_sort(array):
# 「无序区」初始为整个列表
# 倒数第二个元素排列完成后,最后一个元素一定是最大的元素
for i in range(0,len(array)-1):
# 寻找「无序区」最小元素
# (第 i 次循环时,「无序区」的最小元素就是全序列中第 i 大的元素)
index_min = i
for j in range(i+1,len(array)):
if array[j]<array[index_min]:
index_min = j
# 与「有序区」的下一个元素交换
array[i], array[index_min] = array[index_min], array[i]
// C 实现
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
void SelectionSort(ElemType *A, int count){
int index_min;
// 「无序区」初始为整个列表
for(int i=0; i<count-1; i++){
// 寻找「无序区」最小元素
// (第 i 次循环时,「无序区」的最小元素就是全序列中第 i 大的元素)
index_min = i;
for(int j=i+1; j<count; j++){
if(A[j]<A[index_min]) index_min = j;
}
// 与「有序区」的下一个元素交换
Swap(&A[index_min], &A[i]);
}
}
// C++ 实现
template <typename ElemType>
void SelectionSort(vector<ElemType> &A){
int index_min;
// 「无序区」初始为整个列表
for(int i=0; i<A.size()-1; i++){
// 寻找「无序区」最小元素
// (第 i 次循环时,「无序区」的最小元素就是全序列中第 i 大的元素)
index_min = i;
for(int j=i+1; j<A.size(); j++){
if(A[j]<A[index_min]) index_min = j;
}
// 与「有序区」的下一个元素交换
swap(A[index_min], A[i]);
}
}
// Java
// 基本同 C++
public static <T extends Comparable<T>> void selectionSort(T[] arr){
int indexMin;
for(int i=0; i<arr.length-1; i++){
indexMin = i;
for(int j=i+1; j<arr.length; j++){
if(arr[j].compareTo(arr[indexMin])<0) indexMin = j;
}
T temp = arr[indexMin];
arr[indexMin] = arr[i];
arr[i] = temp;
}
}
3.2 希尔排序
希尔排序,也称递减增量排序算法,是对插入排序的改进,主要基于插入排序的两个性质:
- 对于基本有序的序列进行插入排序时,效率很高,接近线性
- 插入排序通常是低效的,因为每次只能将数据移动一位
希尔排序首先取一个增量 Step,然后将序列分组,所有间距为 Step 的元素划入同一组,然后对每一组进行插入排序。
然后不断减少增量 Step 来进行多轮排序;最终增量减小到 1 时,即为普通的插入排序。(此时数据已经基本有序,插入排序的速度将非常快)
3.3 堆排序
将所有的元素建立成一个最小/最大堆,然后依次弹出堆首元素并组成一个新表,排序完成。
建立一个最大堆,每次弹出元素变为「交换堆首和堆尾,然后减小堆的尺寸」,可以实现原地排序。
适合 n 大的情况
3.3.1 时间复杂度:O(nlogn)
平均/最好/最差:O(nlogn)
3.3.2 空间复杂度:O(1)
原地:O(1)
3.3.3 稳定性:不稳定
3.3.4 实现
# Python 实现
# 非原地简易实现,可直接使用 heapq 模块
from heapq import heapify, heappop
def heap_sort(array):
heapify(array)
array[:] = [heappop(array) for _ in range(len(array))]
# 原地实现
def heap_sort(array):
'''原地堆排序'''
def shift_down(root):
'''维护最大堆'''
while True:
child = 2 * root + 1
if child>last:
break
# 寻找最大子节点
if child<last and array[child+1]>array[child]:
child += 1
if array[root]<array[child]:
array[root],array[child] = array[child], array[root]
root = child
else:
break
last = len(array)-1
root = (last-1)//2
# 建立最大堆
while root:
root -= 1
shift_down(root)
# 堆首交换到堆尾,并减少堆的长度
while last:
array[0], array[last] = array[last], array[0]
last -= 1
shift_down(0)
// C 实现
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
void ShiftDown(ElemType *A, int root, int last){
/*维护最大堆*/
for(;;){
int child = 2 * root + 1;
if(child>last) break;
// 寻找最大子节点
if(child<last && A[child+1]>A[child]) child++;
if(A[root]<A[child]){
Swap(&A[root], &A[child]);
root = child;
}
else break;
}
}
void HeapSort(ElemType *A, int n){
/*原地堆排序*/
int last = n - 1;
int root = (last-1)/2;
// 建立最大堆
while(root>=0){
ShiftDown(A, root--, last)
}
// 堆首交换到堆尾,并减少堆的长度
while(last>0){
Swap(&A[0], &A[last]);
ShiftDown(A, 0, --last);
}
}
// C++实现
// 直接使用 STL
template <typename T>
void HeapSort(vector<T>& A){
make_heap(A.begin(), A.end());
sort_heap(A.begin(), A.end());
}
// 或者直接实现参考 C 实现
// Java 实现
// 参考 C 实现
private static <T extends Comparable<T>> void shiftDown(T[] arr,int root,int last){
for(;;){
int child = 2 * root +1;
if(child>last) break;
if(child<last && arr[child+1].compareTo(arr[child])>0) child++;
if (arr[root].compareTo(arr[child])<0){
T temp = arr[root];
arr[root] = arr[child];
arr[child] = temp;
}
else break;
}
}
public static <T extends Comparable<T>> void heapSort(T[] arr){
int last = arr.length-1;
int root = (last-1)/2;
while(root>=0){
shiftDown(arr, root--, last);
}
while(last>0){
T temp = arr[0];
arr[0] = arr[last];
arr[last] = temp;
shiftDown(arr, 0, --last);
}
}
3.4 快速排序
通过分治法,取基准值,将序列分区为小于/大于基准值的两个子序列。然后对子序列递归排序。
快速排序适合序列长度(n)较大的情况。
3.4.1 基准值的选择
1)不合适的取法
不建议直接选择首/尾元素作为基准值。
否则,在反序/顺序的情况下,会产生劣质分割。
2)随机数法
通常可以使用随机数选择基准值,但产生随机数的消耗较大。多数情况下,随机数法即可很好的满足需求。
3)三数取中法
更细致的方式是,取三个元素(通常取下标值左、中、右对应的元素),将三个元素进行原地排序,把其中的中位数作为基准值。
例如,序列 [8, 1, 4, 9, 6, 3, 5, 2, 7, 0],left、center、right 的值为 8、6、0。
排序后,序列变为 [0, 1, 4, 9, 6, 3, 5, 2, 7, 8],并取中位数 6 为 pivot。
因为取中位数的过程中已经对这三个数进行了原地排序,所以后续排序时无需再调整 left 和 right 的对应值。
3.4.2 非原地排序
当对空间不敏感时,可以使用非原地排序,代码会非常简洁(虽然无实用价值)。
# Python 实现
# 随机数法,非原地快排
from random import choice
def quick_sort(array):
if len(array)<=1:
return array
small, pivots, large = [], [], []
pivot = choice(array)
# 分别找出比 pivot 小的,比 pivot 大的,以及等于 pivot 的元素列表
for item in array:
if item < pivot:
small.append(item)
elif item > pivot:
large.append(item)
else:
pivots.append(item)
# 分别递归排序,然后合并
quick_sort(small)
quick_sort(large)
array[:] = small + pivots + large
// C 实现
// 随机数法,非原地快排
void QuickSort(ElemType *A, int count){
if(count<=1) return;
ElemType pivot = A[rand() % count];
ElemType Small[count], Large[count], Pivots[count];
int scount = 0, lcount = 0, pcount =0;
// 分别找出比 pivot 小的,比 pivot 大的,以及等于 pivot 的元素列表
for(int i=0; i<count; i++){
if(A[i]<pivot)
Small[scount++] = A[i];
else if(A[i]>pivot)
Large[lcount++] = A[i];
else
Pivots[pcount++] = A[i];
}
// 分别递归排序,然后合并
QuickSort(Small,scount);
QuickSort(Large,lcount);
for(int i=0;i<scount;i++) A[i]=Small[i];
for(int i=0;i<pcount;i++,scount++) A[scount]=Pivots[i];
for(int i=0;i<lcount;i++,scount++) A[scount]=Large[i];
}
// C++ 实现
// 没什么用,略过
// Java 实现
// 没什么用,略过
3.4.3 原地分割策略
首先将基准值 pivot 与末端元素交换,从而使得基准值离开序列,然后进行单向/双向扫描。
1)单向扫描
类似于选择排序,将序列分为左侧的「小值区」(初始为空)和右侧的「混合区」(初使为除了末端外的整个序列),序列末端为基准值;
每轮排序取「混合区」中第一个小值,将其交换至该区前端,并入「小值区」。反复进行直到「混合区」扫描完成,此时「混合区」变成「大值区」。
交换基准值与「大值区」的第一个值,则序列被分割成「小值区」、基准值、「大值区」。
详见注释。
# Python 实现
from random import randint
def quick_sort(array):
def qs_partition(left, right):
# 随机数法取基准值,并交换至末端,末端即基准值
index = randint(left, right)
array[right], array[index] = array[index], array[right]
pivot = array[right]
# 序列分为左侧的「小值区」[0, i] 和右侧的「混合区」(i, right)
# 扫描「混合区」,遇到小值,则交换到该区前端,并入「小值区」, 此时 i 增长
i = left
for j in range(left,right):
if array[j]<pivot:
if i!=j: array[i], array[j] = array[j], array[i]
i += 1
# 扫描完成后,「混合区」即成为「大值区」,将基准值交换到「大值区」左端,基准值一定存在,且将序列正确分割
array[i], array[right] = array[right], array[i]
return i
def qs_sort(left, right):
if left<right:
i = qs_partition(left,right)
qs_sort(left,i-1)
qs_sort(i+1,right)
qs_sort(0,len(array)-1)
// C 实现
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
int Partition(ElemType *A, int left, int right){
// 随机数法取基准值,并交换至末端,末端即基准值
int i = rand() % (right - left) + left;
Swap(&A[i], &A[right]);
ElemType pivot = A[right];
// 序列分为左侧的「小值区」[0, i] 和右侧的「混合区」(i, right)
// 扫描「混合区」,遇到小于的值,则「小值区」长度加一,将其末端值与 A[j] 交换,保证仍为「小值区」
i = left;
for(int j = left; j<right; j++){
if(A[j]<pivot){
if(i!=j) Swap(&A[i], &A[j]);
i++;
}
}
// 扫描完成后,「混合区」即成为「大值区」,将基准值交换到「大值区」左端,基准值一定存在,且将序列正确分割
Swap(&A[i], &A[right]);
return i;
}
void QuickSortCore(ElemType *A, int left, int right){
/*快速排序递归过程*/
if(left<right) {
int i = Partition(A, left, right);
QuickSortCore(A, left, i-1);
QuickSortCore(A, i+1, right);
}
}
void QuickSort(ElemType *A, int count){
QuickSortCore(A, 0, count-1);
}
// C++ 实现
template <typename ElemType>
int Partition(vector<ElemType> &A, int left, int right){
// 随机数法取基准值,并交换至末端,末端即基准值
int i = rand() % (right - left) + left;
swap(A[i], A[right]);
ElemType pivot = A[right];
// 序列分为左侧的「小值区」[0, i] 和右侧的「混合区」(i, right)
// 扫描「混合区」,遇到小于的值,则「小值区」长度加一,将其末端值与 A[j] 交换,保证仍为「小值区」
i = left;
for(int j = left; j<right; j++){
if(A[j]<pivot){
if(i!=j) swap(A[i], A[j]);
i++;
}
}
// 扫描完成后,「混合区」即成为「大值区」,将基准值交换到「大值区」左端,基准值一定存在,且将序列正确分割
swap(A[i], A[right]);
return i;
}
template <typename ElemType>
void QuickSortCore(vector<ElemType> &A, int left, int right){
/*快速排序递归过程*/
if(left<right) {
int i = Partition(A, left, right);
QuickSortCore(A, left, i-1);
QuickSortCore(A, i+1, right);
}
}
template <typename ElemType>
void QuickSort(vector<ElemType> &A){
QuickSortCore(A, 0, A.size()-1);
}
// Java 实现
public static <T extends Comparable<T>> int partition(T[] arr, int left, int right){
int i = new Random().nextInt(right-left+1) + left;
T pivot = arr[i];
arr[i] = arr[right];
arr[right] = pivot;
i = left;
for(int j=left; j<right; j++){
if(arr[j].compareTo(pivot)<0){
if(i!=j){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
i++;
}
}
arr[right] = arr[i];
arr[i] = pivot;
return i;
}
public static <T extends Comparable<T>> void quickSortCore(T[] arr, int left, int right){
if(left<right){
int i = partition(arr, left, right);
quickSortCore(arr, left, i-1);
quickSortCore(arr, i+1 ,right);
}
}
public static <T extends Comparable<T>> void quickSort(T[] arr){
quickSortCore(arr, 0, arr.length-1);
}
2)双向扫描
使得 i 为0,j 为倒数第二个下标。
开始循环:
若 i<j,则 i 右移绕过每一个小于 pivot 的元素; 若 i<j,则 j 左移绕过每一个大于 pivot 的元素; 等 i、j 均停止时,。
若此时仍满足 i<j,则 s[i] 为大元素,s[j] 为小元素。
交换 s[i]、s[j], 使大元素移动到右边,小元素移动到左边。
然后重复上述过程,直到不再满足 i<j 时,循环终止。
此时交换 s[i] 和 pivot:
此时序列即被 pivot 分割。
# Python 实现
# 随机数法,原地快排,双向扫描
from random import randint
def quick_sort(array):
def qs_partition(left,right):
'''随机数法对序列进行分区'''
i = randint(left, right)
array[right], array[i] = array[i], array[right]
pivot = array[right]
i, j = left, right
while i!=j:
while array[i]<=pivot and i<j:
i += 1
while array[j]>=pivot and i<j:
j -= 1
if i<j:
array[i], array[j] = array[j], array[i]
array[i], array[right] = array[right], array[i]
return i
def qs_sort(left, right):
'''快速排序递归过程'''
if left<right:
i = qs_partition(left,right)
qs_sort(left,i-1)
qs_sort(i+1,right)
qs_sort(0,len(array)-1)
# 三数中值法,原地快排,双向扫描
def quick_sort(array):
def qs_sort2(left, right):
'''两数排序'''
if array[left]>array[right]:
array[left], array[right] = array[right], array[left]
def qs_sort3(left, center, right):
'''三数排序'''
qs_sort2(left, center)
qs_sort2(left, right)
qs_sort2(center, right)
def qs_median3(left, right):
'''取三数排序返回中位数'''
center = (left + right)//2
qs_sort3(left, center, right)
array[center], array[right-1] = array[right-1], array[center]
return array[right-1]
def qs_partition(left, right):
'''对序列进行分区'''
pivot = qs_median3(left, right)
i, j = left, right
while True:
while True:
i += 1
if array[i]>=pivot: break
while True:
j -= 1
if array[j]<=pivot: break
if i>=j: break
array[i], array[j] = array[j], array[i]
array[i], array[right-1] = array[right-1], array[i]
return i
def qs_sort(left, right):
'''快速排序递归过程'''
if right-left>2:
i = qs_partition(left,right)
qs_sort(left, i-1)
qs_sort(i+1, right)
elif right-left==2:
qs_sort3(left, left+1, right)
elif right-left==1:
qs_sort2(left, right)
qs_sort(0, len(array)-1)
// C 实现
// 随机数法,原地快排,双向扫描
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
int Partition(ElemType *A, int left, int right){
/*随机数法对序列进行分区*/
int i = rand()%(right-left) + left;
Swap(&A[i], &A[right]);
ElemType pivot = A[right];
i = left;
int j = right;
while(i!=j){
while(A[i]<=pivot && i<j) i++;
while(A[j]>=pivot && i<j) j--;
if(i<j) Swap(&A[i], &A[j]);
}
Swap(&A[i], &A[right]);
return i;
}
void QuickSortCore(ElemType *A, int left, int right){
/*快速排序递归过程*/
if(left<right) {
int i = Partition(A, left, right);
QuickSortCore(A, left, i-1);
QuickSortCore(A, i+1, right);
}
}
void QuickSort(ElemType *A, int count){
QuickSortCore(A, 0, count-1);
}
// C 实现
// 三数中值法,原地快排,双向扫描
void Swap(ElemType *x, ElemType *y){
ElemType temp = *x;
*x = *y;
*y = temp;
}
void Sort3(ElemType *A, int left, int center, int right){
/*三数排序*/
if(A[left]>A[center]) Swap(&A[left],&A[center]);
if(A[left]>A[right]) Swap(&A[left],&A[right]);
if(A[center]>A[right]) Swap(&A[center],&A[right]);
}
ElemType Median3(ElemType *A, int left, int right){
/*取三数排序返回中位数*/
int center = (left + right)/2;
Sort3(A, left, center, right);
Swap(&A[center], &A[right-1]);
return A[right-1];
}
int Partition(ElemType *A, int left, int right){
/*对序列进行分区*/
ElemType pivot = Median3(A, left, right);
int i = left;
int j = right - 1;
for( ; ; ){
while(A[++i]<=pivot) ;
while(A[--j]>=pivot) ;
if(i>=j) break;
Swap(&A[i],&A[j]);
}
Swap(&A[i],&A[right-1]);
return i;
}
void QuickSortCore(ElemType *A, int left, int right){
/*快速排序递归过程*/
if(right-left>2) {
int i = Partition(A, left, right);
QuickSortCore(A, left, i-1);
QuickSortCore(A, i+1, right);
}
else if(right-left==2) Sort3(A, left , left+1, right);
else if(right-left==1 && A[left]>A[right]) Swap(&A[left],&A[right]);
}
void QuickSort(ElemType *A, int count){
QuickSortCore(A, 0, count-1);
}
// C++ 实现
// 随机数法,原地快排,双向扫描
template <typename ElemType>
int Partition(vector<ElemType> &A, int left, int right){
/*随机数法对序列进行分区*/
int i = rand()%(right-left) + left;
swap(A[i], A[right]);
ElemType pivot = A[right];
i = left;
int j = right;
while(i!=j){
while(A[i]<=pivot && i<j) i++;
while(A[j]>=pivot && i<j) j--;
if(i<j) swap(A[i], A[j]);
}
swap(A[i], A[right]);
return i;
}
template <typename ElemType>
void QuickSortCore(vector<ElemType> &A, int left, int right){
/*快速排序递归过程*/
if(left<right) {
int i = Partition(A, left, right);
QuickSortCore(A, left, i-1);
QuickSortCore(A, i+1, right);
}
}
template <typename ElemType>
void QuickSort(vector<ElemType> &A){
QuickSortCore(A, 0, A.size()-1);
}
// Java 实现
// 随机数法,原地快排,双向扫描
public static <T extends Comparable<T>> int partition(T[] arr, int left, int right){
int i = new Random().nextInt(right-left+1)+left;
T pivot = arr[i];
arr[i] = arr[right];
arr[right] = pivot;
i = left;
int j = right;
while (i!=j){
while (arr[i].compareTo(pivot)<=0 && i<j) i++;
while (arr[j].compareTo(pivot)>=0 && i<j) j--;
if (i<j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[right] = arr[i];
arr[i] = pivot;
return i;
}
public static <T extends Comparable<T>> void quickSortCore(T[] arr, int left, int right){
if (left<right){
int i = partition(arr, left, right);
quickSortCore(left, i-1);
quickSortCore(i+1, right);
}
}
public static <T extends Comparable<T>> void quickSort(T[] arr){
quickSortCore(0, arr.length-1);
}
3.4.4 复杂度和稳定性
1)时间复杂度:O(nlogn)
平均/最好:O(nlogn) 最差:O(n^2)
2)稳定性:不稳定
3)空间复杂度
在每次递归内部的空间复杂度,取决于序列分区的方式,若通过原地交换元素分区,则为O(1),否则最多为O(n)
然因为最好/最坏情况下,需要最多O(logn)次/O(n)递归调用;
所以原地排序需要最好O(logn)/最坏O(n)的空间
非原地排序所需空间更多,不细述。
4)特别之处
比归并排序更快的原因在于,序列分区的效率更高。
3.4.5 优化
当待排序区间较小时,采用插入排序,可以进一步提高排序效率。
3.5 归并排序
归并排序(Merge sort),是将一个序列分成两个序列,分别递归地排序,然后合并两个有序序列。
适合 n 大的情况
3.5.1 时间复杂度:O(nlogn)
平均/最好/最差:O(nlogn)
3.5.2 稳定性:稳定
3.5.3 空间复杂度:O(n)
归并排序的递归过程中,每次合并时需要一个临时空间,但因为每次递归中,合并操作都在最后一步,所以任何时刻只有一个临时空间活动,故可以共同使用同一个临时空间,该空间长度为 n,空间复杂度为 O(n)
3.5.4 实现
# Python 实现
def merge_sort(array):
def m_merge(left_start, left_end, right_end):
'''将两个有序子序列合并'''
right_pos = left_end + 1
temp_pos = left_pos = left_start
while left_pos<=left_end and right_pos<=right_end:
if array[left_pos]<=array[right_pos]:
temp[temp_pos] = array[left_pos]
left_pos += 1
else:
temp[temp_pos] = array[right_pos]
right_pos += 1
temp_pos += 1
# 如果左序列还有剩余
while left_pos<=left_end:
temp[temp_pos] = array[left_pos]
left_pos += 1
temp_pos += 1
# 如果右序列还有剩余
while right_pos<=right_end:
temp[temp_pos] = array[right_pos]
right_pos += 1
temp_pos += 1
for temp_pos in range(left_start, right_end + 1):
array[temp_pos] = temp[temp_pos]
def m_sort(left, right):
'''分割序列,递归进行排序'''
if left>=right:
return
center = (left + right) // 2
m_sort(left, center)
m_sort(center+1, right)
m_merge(left, center, right)
if len(array)>=2:
temp = [0] * len(array)
m_sort(0,len(array)-1)
// C 实现
void Merge(ElemType *A, ElemType *Temp, int leftStart, int leftEnd, int rightEnd){
/*将两个有序子序列合并*/
int rightPos = leftEnd+1;
int leftPos = leftStart;
int tempPos = leftStart;
while(leftPos<=leftEnd && rightPos<=rightEnd){
if(A[leftPos]<=A[rightPos])
Temp[tempPos++] = A[leftPos++];
else
Temp[tempPos++] = A[rightPos++];
}
// 如果左序列还有剩余
while(leftPos<=leftEnd)
Temp[tempPos++] = A[leftPos++];
// 如果右边序列还有剩余
while(rightPos<=rightEnd)
Temp[tempPos++] = A[rightPos++];
for(tempPos=leftStart;tempPos<=rightEnd;tempPos++)
Temp[tempPos] = A[tempPos];
}
void MergeSortCore(ElemType *A, ElemType *Temp, int left, int right){
/*分割序列,递归进行排序*/
if(left<right){
int center = (left+right)/2;
MergeSortCore(A, left, center);
MergeSortCore(A, center+1, right);
Merge(A, Temp, left, center, right);
}
}
void MergeSort(ElemType *A, int count){
if(count>=2){
Elemtype Temp[count];
MergeSortCore(A, Temp, 0, count-1)
}
}
// Java
@SuppressWarnings("unchecked")
public static <T extends Comparable<T>> void mergeSort(T[] arr){
if (arr.length)>=2){
T[] temp = (T[])new Comparable[arr.length];
mergeSortCore(arr, temp, 0, arr.length-1);
}
}
public static <T extends Comparable<T>> void mergeSortCore(T[] arr, T[] temp, int left, int right){
if(left<right){
int center = (left+right)/2;
mergeSortCore(arr, temp, left, center);
mergeSortCore(arr, temp, center+1, right);
merge(arr, temp, left, center, right);
}
}
public static <T extends Comparable<T>> void merge(T[] arr, T[] temp, int leftStart, int leftEnd, int rightEnd){
int rightPos = leftEnd+1;
int leftPos = leftStart;
int tempPos = leftStart;
while (leftPos<=leftEnd && rihgtPos<=rightEnd){
if(arr[leftPos],compareTo(arr[rightPos])<=0)
temp[tempPos++] = arr[leftPos++];
else
temp[tempPos++] = arr[rightPos++];
}
if (leftPos<=leftEnd)
System.arraycopy(arr, leftPos, temp, tempPos, leftEnd+1-leftPos);
if (rightPos<=rightEnd)
System.arraycopy(arr, rightPos, temp, tempPos, rightEnd+1-rightPos);
System.arraycopy(temp, leftStart, arr, leftStart, rightEnd+1-leftStart);
}
3.6 计数排序
3.6.1 特点
通过计数的方法,可实现无比较的排序,从而可以突破 nlogn 的最差时间复杂度限制,实现线性时间排序。
假设输入元素均为 [0, k] 区间的整数,对于每一个元素,确认小于它的元素个数,然后放回合适的位置。
3.6.2 实现
# Python 实现
# 假设元素均为在 [0,k] 区间的正整数
def counting_sort(array, k):
counts = [0]*(k+1)
new = [0]*len(array)
for elem in array:
# counts[elem] 为元素 elem 出现的次数
counts[elem] += 1
for elem in range(1,k+1):
# counts[elem] 为小于等于元素 elem 的元素出现的次数
counts[elem] = counts[elem] + counts[elem-1]
# 输出有序序列
for elem in array:
new[count[elem]-1] = elem
count[elem] -= 1
# 赋值回原序列
array[:] = new
// C 实现
// 假设元素均为在 [0,k] 区间的正整数
void CountingSort(int *A, int n, int k){
int counts[k+1];
int new[n];
// counts[A[i]] 为元素 A[i] 出现的次数
for(int i = 0; i<n; i++) counts[A[i]]++;
for(int elem = 0; elem<=k; elem++){
// counts[elem] 为小于等于元素 elem 的元素出现的次数
counts[elem] = counts[elem] + counts[elem-1];
}
// 输出有序序列
for(i = 0; i<n; i++){
new[count[A[i]]-1]=A[i];
count[A[i]]--;
}
// 赋值回原序列
for(i = 0; i<n; i++){
A[i] = new[i];
}
}
3.6.3 时间复杂度:O(n+k)
平均时间复杂度:O(n+k)
最坏时间复杂度:O(n^2)
空间复杂度:O(n+k)
3.7 桶排序
3.7.1 特殊形式
在计数排序时,如果不追求排序的稳定性,当每个元素的出现的次数统计完毕后,就可以直接输出目标序列,此时简化后的计数排序就是桶排序的一种特殊情况。
# Python 实现
# 假设元素均在 [0,k] 区间
# 直接由计数排序转化的不稳定版本
def bucket_sort(array, k):
counts = [0]*(k+1)
for elem in array:
# counts[elem] 为元素 elem 出现的次数
counts[elem] += 1
# 此时 array 中的数值已经无用,而 counts 中记录了所有数值出现的次数
del array[:]
for elem, count in enumerate(counts):
for _ in range(count):
array.append(elem)
# 优化以后的稳定版本,计数改为直接记录元素
def bucket_sort(array, k):
buckets = [[] for i in range(k+1)]
for elem in array:
# buckets[elem] 记录了出现的所有 elem 元素
buckets[elem].append(elem)
# 此时 array 中的数值已经无用,而 buckets 中记录了所有的元素
del array[:]
for bucket in filter(None, buckets):
array.extend(bucket)
// C 实现
// 假设元素均为在 [0,k] 区间的正整数
void BucketSort(int *A, int n, int k){
int counts[k+1];
// counts[A[i]] 为元素 A[i] 出现的次数
for(int i = 0; i<n; i++) counts[A[i]]++;
for(i = 0, elem = 0; elem<=k; elem++){
for(int j=0; j<counts[elem]; j++){
A[i++] = elem;
}
}
}
// 优化以后的稳定版本,计数改为直接记录元素
typedef float ElemType;
typedef struct ListNode{
ElemType data;
struct ListNode *next;
} ListNode, *LinkedList;
LinkedList NewList(){
/*新建链表*/
dummy = (ListNode *)malloc(sizeof(ListNode));
if(!dummy) FatalError("Out of Space");
dummy->next = NULL;
return dummy;
}
void Insert(LinkedList p, ElemType key){
/*链表插入*/
node = (ListNode *)malloc(sizeof(ListNode));
if(!node) FatalError("Out of Space");
node->data = key;
node->next = p->next;
p->next = node;
}
void BucketSort(int *A, int n, int k){
ListNode *buckets[n];
// 初始化每个桶(链表)
for(int i = 0; i<n; i++) buckets_tail[i] = buckets[i] = NewList();
for(int i = 0; i<n; i++){
// buckets[A[i]] 为出现的全部元素 A[i]
Insert(buckets_tail[A[i]], A[i]);
buckets_tail[A[i]] = buckets_tail[A[i]]->next;
}
for(i = 0, elem = 0; elem<=k; elem++){
// 倒出桶内数据,并释放桶
while(buckets[elem]->next){
A[i++] = buckets[elem]->next->data;
temp = buckets[elem];
buckets[elem] = buckets[elem]->next;
free(temp);
}
free(buckets[elem]);
}
}
3.7.2 一般形式
当不满足计数排序的条件时,需要对其进行散列化,每个桶中使用链表存储多个数值。其结构类似于哈希表。
# Python 实现
# 假设元素均匀分布在 [0,1) 区间
def bucket_sort(array):
n = len(array)
# 为了简洁这里没有使用链表,使用了二维 list
buckets = [[] for i in range(n)]
for elem in array:
buckets[int(n*elem)].append(elem)
del array[:]
for bucket in filter(None, buckets):
insertion_sort(bucket)
array.extend(bucket)
// C 实现
// 假设元素均匀分布在 [0,1) 区间
typedef float ElemType;
typedef struct ListNode{
ElemType data;
struct ListNode *next;
} ListNode, *LinkedList;
LinkedList NewList(){
/*新建链表*/
dummy = (ListNode *)malloc(sizeof(ListNode));
if(!dummy) FatalError("Out of Space");
dummy->next = NULL;
return dummy;
}
void Insert(LinkedList p, ElemType key){
/*链表插入*/
node = (ListNode *)malloc(sizeof(ListNode));
if(!node) FatalError("Out of Space");
node->data = key;
node->next = p->next;
p->next = node;
}
void BucketSort(ElemType *A, int n, int k){
/* 桶排序 */
ListNode *buckets[n];
// 初始化每个桶(链表)
for(int i = 0; i<n; i++) buckets[i] = NewList();
int index;
ListNode *p;
for(j = 0; j<n; i++){
// 分配桶
index = (int)(n*A[j]);
// 按顺序插入桶
// p->next->data<=A[j] 中,“等于”时仍然后移,是为了维持稳定性
for(p = buckets[index]; p->next && p->next->data<=A[j]; p = p->next){
Insert(p, A[j]);
}
}
// 按顺序回写到原序列,并释放桶
for(i = 0, j = 0; i<n; i++){
while(buckets[i]->next){
A[j++] = buckets[i]->next->data;
temp = buckets[i];
buckets[i] = buckets[i]->next;
free(temp);
}
free(buckets[i]);
}
}
3.7.3 时间复杂度:O(n+k)
平均时间复杂度:O(n+k)
最坏时间复杂度:O(n^2)
空间复杂度:O(n+k)
3.8 基数排序
3.8.1 特点
基数排序适用于正整数排序。
首先将序列中所有的数字统一为相同位数(通过在前面补 0),然后逐位进行比较。
3.8.2 实现
def radix_sort(array, radix = 10):
# 求最大位数 digit
max_num = max(array)
digit = 0
while max_num>0:
max_num //= radix
digit += 1
# 从最后一位起,以每一位为 key 进行桶排序(基数为几就是几个桶,每个桶里无需再排序)
for i in range(1,digit):
buckets = [[] for i in range(radix)]
for elem in array:
buckets[elem%(radix**i)//(radix**(i-1))].append(elem)
del array[:]
for bucket in filter(None, buckets):
array.extend(bucket)
// C 实现
// 假设元素均为正整数
typedef float ElemType;
typedef struct ListNode{
ElemType data;
struct ListNode *next;
} ListNode, *LinkedList;
LinkedList NewList(){
/*新建链表*/
dummy = (ListNode *)malloc(sizeof(ListNode));
if(!dummy) FatalError("Out of Space");
dummy->next = NULL;
return dummy;
}
void Insert(LinkedList p, ElemType key){
/*链表插入*/
node = (ListNode *)malloc(sizeof(ListNode));
if(!node) FatalError("Out of Space");
node->data = key;
node->next = p->next;
p->next = node;
}
void DeleteList(LinkedList L){
LinkedList P,temp;
// 假设有头结点,P 初始指向头结点指向的结点
P = L->next;
// 清空头结点的指向
L->next = NULL;
// 从 P 开始逐个释放结点
while(P){
temp = P->next;
free(P);
P = temp;
}
}
void RadixSort(ElemType *A, int n, int radix){
ElemType max = A[0];
LinkedList buckets[radix];
// 初始化每个桶(链表)
for(int i = 0; i<radix; i++) buckets[i] = NewList();
// 寻找最大值
for(i=0; i<n; i++) max = max>A[i]?max:A[i];
int digit = 0;
while(max>0){
max /= radix;
digit++;
}
// 从最后一位起,以每一位为 key 进行桶排序(基数为几就是几个桶,每个桶里无需再排序)
for(int d = 1; d<digit; d++){
for(i = 0; i<n; i++;){
index = A[i]%pow(radix,d)/pow(radix,(d-1));
for(p = buckets[index]; p->next && p->next->data<=A[i]; p = p->next){
Insert(p, A[i]);
}
}
// 按顺序回写到原序列,并释放桶
for(i = 0, j = 0; i<n; i++){
while(buckets[i]->next){
A[j++] = buckets[i]->next->data;
temp = buckets[i];
buckets[i] = buckets[i]->next;
free(temp);
}
}
}
}
<推广> 本站使用 NameSilo 提供的域名服务。