排序方法及时间复杂度

1、插入排序——直接插入排序、希尔排序
(1)直接插入排序思路:从第1号元素开始,每个元素依次与前面的元素做比较,小的排前面,这样当比较到最后一 个元素完即完成排序。
(2)希尔排序思路:
首先以d1(0 < d1< n-1 ) 为步长, 把数组A中n个元素分为d1个组,使下标距离为d1的元素在同一组中;接着在每个组内进行直接插入排序;接着在以d2为步长(d2 < d1)在上一次基础上继续分组排序,直到dt=1。
d1一般在n/3~n/2之间。 希尔排序是不稳定的。
void ShellSearch(ElemType A[], int n){
ElemType x;
int i ,j, d;
for(d=n/2; d>=1;d/=2){ //按不同分量分组
for(i=d;i < n;i++){ //内插
x=A[i];
for(j=i-d; j>=0;j-=d){
if(x < A[j])A[j+d]=A[j];
else break;
}
A[j+d]=x;
}
}
}

2、选择排序——直接选择排序、堆排序
(1)直接选择排序: 选出最小的元素,与第0个元素交换位置,此时第0个元素成为最小的元素;接着,继续选出最小的与第1个元素交换位置,此时第1号为次小的。。。依次类推,比较到最后一个元素时,排序也完成。

void SelectSort(ElemType A[], int n){
ElemType x;
int i, j,k;
for(i=1; i<=n-1; i++){
k=i-1; //保存最小元素码下标
for(j=i; j<=n-1; j++){
if(A[j] < A[k]) k=j;
}
if(k!=i-1){ //把A[k]对调到该排序区间的第一个位置
x=A[i-1];
A[i-1]=A[k];
A[k]=x;
}
}
}

(2)堆排序
对Ri进行筛运算是在其左、右子树均为堆的基础上实现的。对Ri进行筛运算的过程可叙述为:首先把Ri的排序码Si与两个孩子中排序码较大者Sj(j=2i+1或j=2i+2)进行比较,若Si>=Sj,则以Si为根的子树成为堆,筛运算完毕,否则Ri与Rj互换位置,依次类推,直到父结点的排序码大于等于孩子结点中较大的排序码或者孩子结点为空为止。这样以Ri为根的子树就被调整为一个堆。在对Ri进行的筛运算中,小的被漏下去,大的被留下,所以把构成堆的过程形象地称为筛运算。
//筛运算
void Sift(ElemType A[], int n, int i){
ElemType x=A[i];
int j;
j=2*i+1; //左孩子
while(j<=n-1){ //当A[i]左孩子非空时执行循环
if(j < n-1&&A[j] < A[j+1]) j++; //右孩子大,把 j 改为右孩子
if(x < A[ j]){
A[i]=A[j];
i=j;
j=2*i+1; //继续往下筛
}
else break;
}
A[i]=x; //被筛点的值放入最终位置
}
//堆排序
void HeapSort(ElemType A[], int n){
ElemType x;
int i;
for(i=n/2-1;i>=0; i–) Sift(A, n, i) //建立初始堆
for(i=1; i<=n-1; i++){
x=A[0];
A[0]=A[n-i]; //将最后一个节点和树根值互换
A[n-1]=x;
Sift(A, n-i, 0); //筛A[0]结点,得到n-i个结点的堆
}
}

3、交换排序——气泡排序和快速排序
(1)(1)气泡排序(冒泡排序)
首先将A[n-1]元素的排序码同A[n-2]元素的排序码进行比较,若A[n-1] < A[n-2],则交换两元素位置,使轻者上浮,重者下沉,接着比较A[n-2]元素的排序码同A[n-3]元素的排序码,依次类推,直到A[1]和A[0]比较。这样A[0]成为最小;然后比较A[n-1]~A[1]进行第二次排序;重复n-1遍后整个冒泡排序完成。
(2)快速排序(划分排序)
是对冒泡排序的改进,冒泡排序是相邻元素比较,而快排是从中间往两边比较,这样总的移动次数大大减少。
过程:从待排区间选一基准元素(通常取第一个元素,若不是第一个则与第一个交换),通过两端往中间比较,大的换到后面,小的换到前面;当所有元素换过之后,把基准元素交换到两个区间的交界处,这也是该基准元素的最终位置;然后再对两个区间进行同样的快速排序过程,当一个区间为空或只有一个元素时,结束快排。
void quickSort(int A[],int s,int t){
int i=s,j=t;
int standard=A[s]; //保存基准值
while(i<=j){
while(i<=j&&A[i] < A[s])i++;
while(i<=j&&A[j]>A[s])j–;
if(i < j){
int temp=A[i];
A[i]=A[j];
A[j]=temp;
i++;
j–;
}
}
if(s!=j){ //把基准值交换到交界处
A[s]=A[j];
A[j]=standard;
}
if(s < j-1)quickSort(A,s,j-1);
if(j+1 < t)quickSort(A,j+1,t);
}
把基准元素看作根结点,上述排序过程对应一棵二叉搜索树。这是一种不稳定的排序方法。
4、归并排序
(1)归并:将两个或者多个有序表合并成为一个有序表的过程。若将两个个有序表合并成一个有序表则称为二路归并,适合内排序也适合外排序。
二路归并算法:
void TwoMerge(ElemType A[], ElemType R[], int s, int m, int t ){ //将A数组两个相邻的有序表A[s]~A[m]和A[m+1]~A[t]归并为R数组中的一个有序表R[s]~R[t]
int i, j, k;
i=s; j=m+1; k=s; //给每个有序表指针赋初值
while(i<=m&&j<=t){ //两个表都非空
if(A[i] < A[j]){
R[k]=A[i]; i++; k++;
} //把较小的先赋值给R数组
else{
R[k]=A[j]; j++; k++;
}
}
while(i<=m){
R[k]=A[i]; i++; k++;
} //其中一个为空,把另一个剩下的元素都搬到R数组
while(j<=t){
R[k]=A[j]; j++; k++;
}
}
(2)归并排序:利用归并操作把一个无序表排列成一个有序表的过程。利用二路归并操作则称为二路归并排序。
过程:首先,把每个元素看成一个有序表,接着相邻两个表归并,若剩一个表,则直接进入下一轮归并;然后再两个表两两归并,如此进行下去直到得到一个长度为n的有序表。
一趟二路归并排序的算法描述:
void MergePass(ElemType A[], ElemType R[], int n; int len){ //把数组A[n]中每个长度为len的有序表两两归并到数组R[n]
int p=0; //p为每一对待合并表的第一个元素下标,初始为0
while(p+2*len-1<=n-1){ //两两归并长度为len的有序表
TowMerge(A, R,p,p+len-1,p+2*len-1);
p+=2*len;
}
if(p+len-1<=n-1) //归并最后两个长度不等的有序表
TwoMerge(A, R, p,p+len-1, n-1);
else
for(int i=p; i<=n-1; i++)
R[i]=A[i]; //把剩下最后一个有序表复制到R中
}
二路归并排序算法
void MergeSort(ElemType A[], int n){
ElemType* R=new ElemType[n];
int len=1;
while(len < n){
MergePass(A, R, n,len); //A归并到R
len*=2;
MergePass(R, A, n,len); //R归并到A
len*=2;
}
delect [] R;
}
时间复杂度O(n*lbn), 空间复杂度O(n)。二路归并排序是稳定的。

5、各种内排序方法的比较
(1)时间复杂度:直接插入、直接选择和冒泡这3中简单排序方法属于第一类,其时间复杂度为O(n^2);快速、归并、堆排序这3种属于第二类,其时间复杂度为O(lbn);希尔排序介于两者中间。最好情况下,直接插入和冒泡最好;平均情况下,快排最好;最坏情况下,归并和堆排序最好。
(2)空间复杂度:归并O(n);快排O(lbn);其他O(1)
(3)稳定性:稳定——直接插入、冒泡和归并;不稳定——直接选择、希尔、快排、堆排序
(4)数据较少时用简单排序合适,数据多用改进算法合适。

猜你喜欢

转载自blog.csdn.net/u012794505/article/details/79599402