数据结构中涉及到的排序算法有很多种类,根据实现原理不同大致可分为以下几类,本文将依次介绍其算法思想并给出具体的代码。
- 插入排序
- 直接插入排序
- 希尔排序
- 交换排序
- 冒泡排序
- 快速排序
- 选择排序
- 简易选择排序
- 堆排序
- 归并排序和基数排序
- 归并排序
- 基数排序
一、插入排序
1.1直接插入排序
void InsertSort(ElemType A[],int n){
int i,j;
ElemType tmp;
for(i = 1;i < n;i++){
if(A[i] < A[i-1]){//若A[i]小于其前驱
tmp = A[i];
for(j = i - 1;i > 0 && A[j] > tmp;--j)//较大元素往后移动
A[j+1] = A[j];
A[j+1] = tmp;
}
}
}
算法思想: 每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成。
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度: 最好时间复杂度
O
(
n
)
O(n)
O(n),最坏时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
注:当待排序序列已经基本有序时,其时间复杂度最小,接近
O
(
n
)
O(n)
O(n).当待排序序列为逆序时时间复杂度最大,需要进行
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)次比较。
是否稳定: 稳定
特点: 每次排序后都确定一个有序序列,但是最后一趟排序开始前元素的最终位置都没有确定。
1.2 希尔排序
void ShellSort(ElemType A[],int n){
int i,j,dk;
ElemType tmp;
for(dk = n/2;dk > 0;dk = dk/2){
for(i = dk;i < n;++i){
if(A[i]<A[i-dk]){
tmp = A[i];
for(j = i-dk;j>=0 && A[j]>tmp;j-=dk)
A[j+dk]=A[j];
A[j+dk]=tmp;
}
}
}
}
算法思想: 因为直接插入排序当待排序表基本有序和数据量不大时效率较高,希尔排序基于这两点对直接插入排序进行了优化。其基本思想是,将待排序表根据一定间隔划分为若干子表,先对数据量较小的子表进行排序,当子表有序后缩小间隔来扩大单个子表规模,直到间隔为1时退变到直接插入排序。因为每一步都在上一步排序的基础上进行,所以排序效率较高。
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度: 约为
O
(
n
1.3
)
O(n^{1.3})
O(n1.3) ,最坏情况下为
O
(
n
2
)
O(n^2)
O(n2)
是否稳定: 不稳定
二、交换排序
2.1 冒泡排序
void swap(ElemType &a,ElemType &b){
ElemType tmp;
tmp = a;
a = b;
b = tmp;
}
/*冒泡排序*/
void BubbleSort(ElemType A[],int n){
int i,j;
bool flag;
for(i = 0; i < n-1;i++){
flag=false;
for(j = n-1;j > i;j--){
if(A[j] < A[j-1]){
swap(A[j-1],A[j]);
flag=true;
}
}
if(flag==false)
return;
}
}
算法思想: 从后往前两两比较相邻元素的值,若为逆序则交换他们,直到序列比较完,我们称之为第一趟排序。结果是将序列中最小元素交换到序列表中的第一个位置,即关键字最小的元素如气泡一样逐渐浮出水面.
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度: 最好时间复杂度为
O
(
n
)
O(n)
O(n),最坏时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),平均时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
当初始序列有序时,显然第一趟冒泡后flag依然为false,从而直接跳出循环,比较次数为n-1,移动次数为0。当初始序列为逆序时,需要进行n-1趟排序,第i趟需要进行n-i次比较。则总比较次数为
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1),总交换次数为
3
n
(
n
−
1
)
2
\frac{3n(n-1)}{2}
23n(n−1)。
是否稳定: 稳定
2.2快速排序
/*快速排序*/
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);
}
}
算法思想: 快速排序的基本思想是分治法,即在待排序表中任取一个元素pivot作为枢轴,通过一趟排序将待排序表划分为两部分,使得左边的元素都小于枢轴,右边的元素都大于枢轴,则此时pivot放在了最终位置上,这个过程称为一趟快速排序。然后分别递归的对两个子表重复上述过程,直至每部分内只有一个元素或为空为止。
空间复杂度: 最好情况下为
O
(
log
2
n
)
O(\log_2n)
O(log2n),最坏情况下为
O
(
n
)
O(n)
O(n),平均情况下为
O
(
log
2
n
)
O(\log_2n)
O(log2n).
时间复杂度: 最好情况下为
O
(
n
log
2
n
)
O(n\log_2n)
O(nlog2n),最坏情况下为
O
(
n
2
)
O(n^2)
O(n2),平均情况下为
O
(
n
log
2
n
)
O(n\log_2n)
O(nlog2n).
是否稳定: 不稳定
注:在快速排序中,并不产生有序子序列,但第i趟排序后会至少有i个元素放到最终位置上。
三、选择排序
3.1简单选择排序
void SelectSort(ElemType A[],int n){
int i,j,min;
for(i = 0;i < n-1;i++){
min=i;
for(j=i+1;j < n;j++){
if(A[j]<A[min]) min=j;
}
if(min!=i) swap(A[i],A[min]);
}
}
算法思想: 每一趟(如第i趟)在后面n-i-1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下一个就不用再选了。
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
是否稳定: 不稳定
3.2 堆排序
/*堆排序*/
void HeadAdjust(ElemType A[],int k,int len){
int i;
ElemType head=A[k];//暂存子树根节点
for(i = 2*k;i <= len;i*=2){
if(i < len && A[i] < A[i+1])//右孩子较大
i++;
if(head>=A[i]) break;
else{
A[k]=A[i];
k=i;
}
}
A[k]=head;
}
void BuildMaxHeap(ElemType A[],int len){
for(int i = len/2;i > 0;i--){
HeadAdjust(A,i,len);
}
}
void HeapSort(ElemType A[],int len){
BuildMaxHeap(A, len);
for(int i = len;i > 0;i--){
swap(A[i],A[1]);
HeadAdjust(A, 1, i-1);
}
}
算法思想: 不断构造大根堆,将每次构造后选出的最大元素放到尾部以此实现排序。
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度: 最好,最坏和平均情况下时间复杂度为
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n)
是否稳定: 不稳定
注:
1、堆排序适合关键词较多的情况,如在一亿个元素中选出前100个最大值。
2、堆的定义是递归的,因此在大根堆中次大值一定在第二层。
3、插入时不用上浮不用和邻居节点比较,也不用重新检查生成的新子树是否需要调整。
四、归并排序和基数排序
4.1 归并排序
void Merge(ElemType A[],int low,int mid,int high){
int i,j,k;
for(int k = low;k <= high;k++)
B[k]=A[k];
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
if(B[i]<B[j])
A[k]=B[i++];
else
A[k]=B[j++];
}
while(i<=mid) 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);
}
}
算法思想: 基于分治法将待排序表切分成左右两部分,直到每部分只剩下一个元素,然后两两合并。
空间复杂度:
O
(
n
)
O(n)
O(n)
时间复杂度:
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n),每趟归并的时间复杂度为
O
(
n
)
O(n)
O(n),一共需要进行
l
o
g
2
n
log_2n
log2n趟归并。
是否稳定: 稳定