排序算法的稳定性
若待排序表中有两个元素 Ri 和 Rj,其对应的关键字 ki=kj,且在排序前 Ri 在 Rj 前面,若使用某排序算法后,Ri 仍然在 Rj 前面。则称这个排序算法是稳定的,否则称排序算法不稳定。
算法的稳定性是算法的性质,并不能衡量一个算法的优劣
时空复杂度决定内部排序算法的性能
插入排序
插入排序每次将一个待排序的序列插入到一个前面已经排好序的子序列当中
直接插入排序
初始 L[1] 是一个已经排好序的子序列
对于元素 L[i] ( L[2] ~ L[n] ) 插入到前面已经排好序的子序列当中:
-
查找出 L[i] 在 L[1...i-1] 中的插入位置 k
-
将 L[k...i-1] 中的所有元素全部向后移一个位置
-
将 L[i] 复制到 L[k]
void InsertSort(ElemType A[], int n){
int i, j;
for(i=2, i<n, i++){
A[0] = A[1];
for(j=i-1; A[0].key<A[j].key; j--)
A[j+1] = A[j];
A[j+1] = A[j];
}
}
def insert_sort(L):
length = len(L)
for i in range(1, length):
a = L[i]
for j in range(i, -1, -1):
if L[j-1] > a: L[j] = L[j-1]
else: break
L[j] = a
时间复杂度:,空间复杂度: ,稳定,顺序存储和链式存储
折半插入顺序
void BInsertSort(ElemType A[], int n){
int i, j;
int low, high, mid;
for(i=2;i<n;i++){
A[0] = A[i];
# 折半查找
low = 1; high = i-1;
while(low<=high){
mid = (low + high) / 2;
if(A[mid].key > A[0].key)
high = mid - 1;
else
low = mid + 1
}
# 移动
for(j=i-1; j>=high; i--)
A[j+1] = A[j];
A[high + 1] = A[0];
}
}
def b_insert_sort(L):
length = len(L)
for i in range(1, length):
a = L[i]
# 折半查找
low = 0
high = i-1
while low <= high:
mid = (low + high) // 2
if L[mid] > a:
high = mid - 1
else:
low = mid + 1
# 移动
for j in range(i, high, -1):
L[j] = L[j-1]
L[high+1] = a
时间复杂度:,空间复杂度: ,稳定,顺序存储
希尔排序
先将排序表分割成d个形如 L[i, i+d, i+2d, ..., i+kd] 的“特殊”子表,分别进行直接插入排序,当整个表中的元素已呈“基本有序时”,在对全体记录进行一次直接插入排序
void SheelSort(ElemType A[], int n){
for(int dk=n/2; dk>=1; dk=dk/2){
for(int i=dk+1; i<n; ++i){
if(A[i].key < A[i-dk].key){
A[0] = A[i];
for(j=i-dk; j>0&&A[0].key<A[j].key, j-=dk)
A[j+dk] = A[j];
A[j+dk] = A[0]
}
}
}
}
def shell_sort(L):
length = len(L)
dk = length // 2
while dk >= 1:
for i in range(dk, length):
if L[i] < L[i-dk]:
a = L[i]
for j in range(i, -1, -dk):
if L[j-dk] > a: L[j] = L[j-dk]
else: break
L[j] = a
dk = dk // 2
时间复杂度 ,空间复杂度 ,不稳定,顺序存储
交换排序
冒泡排序
假设待排序表长为 n,从后往前(从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换他们直到序列比较结束
一次冒泡会将一个元素放置到它最终的位置上
void BubbleSort(ElemType A[], int n){
// 从后往前
/*
for(int i=0; i<n-1; i++){
bool flag = False;
for(int j=n-1; j>i; j--){
if(A[j-1].key > A[j].key){
swap(A[j-1], A[j]);
flag = true;
}
}
if(flag==false)
return;
}
*/
// 从前往后
for(int i=n-1; i>0; i--){
bool flag=false;
for(int j=0; j<i; j++){
if(A[j].key > A[j+1].key){
swap(A[j], A[j+1]);
flag=true;
}
}
if(flag==false)
return;
}
}
def bubble_sort(L):
n = len(L)
# 从前往后
for i in range(n-1, 0, -1):
flag = False
for j in range(i):
if L[j] > L[j+1]:
L[j], L[j+1] = L[j+1], L[j]
flag = True
if not flag: return
# 从后往前
# for i in range(n-1):
# flag = False
# for j in range(n-1, i, -1):
# if L[j] < L[j-1]:
# L[j], L[j-1] = L[j-1], L[j]
# flag = True
# if not flag: return
时间复杂度 ,空间复杂度 ,稳定,顺序存储和链式存储
快速排序
在待排序表 L[1...n] 中任取一个元素 pivot 作为基准,通过一趟排序将待排序表划分为具有如下特点的两部分
一次划分会将一个元素 pivot 放置到它最终的位置上
Partition
初始化标记 low 为划分部分第一个元素的位置,high为最后一个元素的位置,然后不断地移动两标记并交换元素
-
high向前移动找到第一个比 pivot 小的元素
-
low 向后移动找到第一个比 pivot 大的元素
-
交换当前两个位置的元素
-
继续移动标记,执行 1 2 3 的过程,直到 low 大于等于 high 为止
int Partition(ElemType A[], int low, int high){
ElemType pivot = A[low];
while(low < high){
while(low < high && A[high] >= pivot)
high --;
A[low] = A[high];
while(low < high && A[low] <= pivot)
low ++;
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(ElemType A[], int low, int high){
if(low < high){
int pivotpos = Partition(A, low, high);
QuickSort(A, low, pivotpos-1);
QuickSort(A, pivotpos+1, high);
}
}
def partition(L, low, high):
a = L[low]
while low < high:
while high > low and L[high] >= a:
high -= 1
L[low] = L[high]
while low < high and L[low] <= a:
low += 1
L[high] = L[low]
L[low] = a
return low
def quick_sort(L, low, high):
if low < high:
mid = partition(L, low, high)
quick_sort(L, low, mid-1)
quick_sort(L, mid+1, high)
最好、平均时间复杂度 ,最坏时间复杂度
最好、平均空间复杂度 ,最坏空间复杂度
选择排序
直接选择排序
每一趟在后面 n-i+1 (i=1,2, ..., n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第 i 个元素,直到 n-1趟做完,待排序元素只剩下1个
一趟排序会将一个元素放置在最终的位置上
void SelectSort(ElemType A[], int n){
for(int i=0; i<n-1;i++){
int min = i;
for(int j=i+1; j<n; j++){
if(A[j] < A[min])
min = j;
}
if(min != i)
swap(L[i], L[min]);
}
}
def select_sort(L):
length = len(L)
for i in range(length-1):
min_index = i
for j in range(i+1, length):
if L[j] < L[min_index]:
min_index = j
if min_index != i:
L[min_index], L[i] = L[i], L[min_index]
时间复杂度 ,空间复杂度 ,不稳定,顺序存储和链式存储
堆排序
堆
n 个关键字序列 L[1...n] 称为堆,当且仅当该序列满足:
-
若 L[i] <= L[2i] 且 L[i] <= L[2i+1], 则称该堆为小根堆
-
若 L[i] >= L[2i] 且 L[i] >= L[2i+1], 则称该堆为大根堆
在排序过程中将 L[1...n] 视为一棵完全二叉树的顺序存储结构
堆的初始化
大根堆
对所有具有双亲节点含义编号从大到小 ( ~ 1)做出如下调整
-
若孩子节点皆小于双亲节点,则该节点的调整结束
-
若存在孩子节点大于双亲节点,则将最大的孩子节点与双亲节点交换,并对该孩子节点进行1 2 ,直到出现 1 或到叶节点为止
void BuildMaxHeap(ElemType A[], int len){
for(int i=len/2; i>0; i--)
AdjustDown(A, i, len);
}
void AdjustDown(Elemtype A[], int k, int len){
A[0] = A[k];
for(int i=2*k; i<=len; i*=2){
if(i<len && A[i]<A[i+1])
i++;
if(A[0]>=A[i])
break;
else{
A[k] = A[i];
k = i;
}
}
A[k] = A[0];
}
def build_max_heap(heap, length):
# 初始化最大堆
for i in range(length//2, -1, -1):
adjust_down(heap, i, length)
def adjust_down(heap, parent, length):
# 向下调整
child = parent * 2 + 1
while child < length:
if child < length - 1 and L[child] < L[child+1]:
child += 1
if heap[parent] >= heap[child]:
break
heap[parent], heap[child] = heap[child], heap[parent]
parent, child = child, child * 2 + 1
初始建堆
堆排序
不断地输出堆顶元素,并向下调整
void HeapSort(ElemType A[], int len){
BuildMaxHeap(A, len);
for(int i=len; j>1; i--){
swap(A[i], A[1]);
AdjustDown(A, 1, i-1);
}
}
def heap_sort(heap, length):
build_max_heap(heap, length)
for i in range(length-1, -1, -1):
heap[i], heap[0] = heap[0], heap[i]
adjust_down(heap, 0, i-1)
时间复杂度 ,空间复杂度 ,不稳定,顺序存储(链式存储)
堆的插入
将新节点放置在末端然后进行向上调整
void AdjustUp(ElemType A[], int len){
A[0] = A[k];
int i=k/2;
while(i>0 && A[i]<A[0]){
A[k] = A[i];
k=i;
i=k/2
}
A[k] = A[0];
}
归并排序
// 合并两个有序线性表
ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType));
void Merge(ElemType A[], int low, int mid, int high){
for(int k=low;k<=high;k++)
B[k] = A[k];
for(int i=low,j=mid+1,k=low;i<=low&&j<=high;k++){
if(B[i]<B[j])
A[k] = B[i++];
else
A[k] = B[j++];
}
while(i<=low)
A[k++] = B[i++];
while(j<=high)
A[k++] = B[j++]
}
//归并排序
void MergeSort(ElemType A[], int low, int high){
if(low < high){
int mid = (low + high) / 2;
MergeSort(A, low, mid);
MergeSort(A, mid+1, high);
Merge(A, low, mid, high);
}
}
def merge_sort(L):
if len(L) <= 1:
return L
mid = len(L) // 2
left = merge_sort(L[:mid])
right = merge_sort(L[mid:])
return merge(left, right)
def merge(a, b):
c = []
i, j = 0, 0
while i < len(a) and j < len(b):
if a[i] < b[j]:
c.append(a[i])
i += 1
else:
c.append(b[j])
j += 1
while i < len(a):
c.append(a[i])
i += 1
while j < len(b):
c.append(b[j])
j += 1
return c
时间复杂度 ,空间复杂度 ,稳定,顺序存储和链式存储
基数排序
不基于比较
借助“分配”和“收集”两种操作对单逻辑关键字进行排序,分为最高位优先 (MSD) 和最低位优先(LSD)。
内部排序算法比较和应用
应用考虑因素:
元素数目、元素大小、关键字结构及分布、稳定性、存储结构、辅助空间
-
若 n 较小时(n<=50),可采用直接插入排序或简单选择排序
若 n 较大时,则采用快排、堆排或归并排序
-
若 n 很大,记录关键字位数较少且可分解,采用基数排序
-
当文件的 n 个关键字随机分布时,任何借助于“比较”的排序,至少需要 的时间
-
若初始基本有序,则采用直接插入或冒泡排序
-
当记录元素比较大,应避免大量移动的排序算法,尽量采用链式存储