表排序
表排序使用场景
当我们的排序对象不再是整数或者其他基本类型时,比如是一个含有多个数据元素的结构体,想象成一本书或者一个视频,其所占用的内存就很大,在其他的排序算法中总会涉及数据的频繁交换,但是交换一个庞大的结构体就会很不方便,此时我们可以采用表排序
算法概述
定义一个table表,用于装结构体对象数组info[]的指针(即数组下标索引),然后对索引以对象数组为比较对象进行table数组的排序
,每次的比较对象是==info [ table[ i ] ] ==,因为当前table的值即相应info数组的索引,对table数组的排序可以采用插入排序、选择排序或者冒泡排序等基本排序方法
最后按照排序好的table[] 索引数组输出info对象数组
代码实现
void table_sort(Info *info,int n)
{
int table[n];
for(int i=0;i<n;i++) //定义table表
table[i]=i;
for(int i=1;i<n;i++) //对table表进行选择排序
{
for(int j=i-1;j>=0;j--)
{
int t=table[i]; //将当前需要插入到已排好序的数组中的数据保存下来
if(info[table[j]].key<info[table[i]].key)
{
for(int k=i;k>j+1;k--) //从当前元素的下一个元素依次将元素往后移动一位
{
table[k]=table[k-1];
}
table[j+1]=t;
break;
}
else if(j==0) //如果当前元素比前面排好序的所有元素都要小,进行不同的插入操作
{
for(int k=i;k>j;k--)
{
table[k]=table[k-1];
}
table[j]=t;
}
}
for(int i=0;i<n;i++)
cout<<table[i]<<" ";
cout<<endl;
}
for(int i=0;i<n;i++)
{
cout<<info[table[i]].key<<" "<<info[table[i]].name<<endl;
} //按照排好序的table表输出info对象数组
}
物理排序
当我们用表排序将table排好序后,输出的时候是依赖于table表输出的,如果要正直的实现info对象数组的排序,即可以直接根据i从0-n输出info数组,就需要在表排序的基础上进行物理排序
可以看出n个数字的排列由若干个独立的环组成
依赖每个环单独地排序,便可将info正确的排好序
表排序与物理排序
归并排序
归并排序算法 思想
归并排序详情
个人觉得这篇文章写得很好,可以看看这个的讲解
递归实现代码
//有序子列的归并
//L=左边起始位置,R=右边起始位置,RightEnd=右边终点位置
//为了保证函数传入的参数足够小,但是又必须足够,传入这三个参数,剩下的通过计算推理得到
void Merge(ElementType A[],ElementType TmpA[],int L,int R,int RightEnd)
{
int LeftEnd=R-1, //左边终点的位置,假设左右两个子列是挨着的
Tmp=L, //存放结果数组的初始位置
NumElements=RightEnd-L+1; //存放结果数组元素个数
while(L<=LeftEnd&&R<=RightEnd)
{
if(A[L]<A[R]) TmpA[Tmp++]=A[L++];
else TmpA[Tmp++]=A[R++];
}
while(L<=LeftEnd) TmpA[Tmp++]=A[L++];
while(R<=RightEnd) TmpA[Tmp++]=A[R++];
for(int i=0;i<NumElements;i++,RightEnd--)
{
A[RightEnd]=TmpA[RightEnd]; //最后将TmpA数组中的元素导回到A数组中
}
}
void Msort(ElementType A[],ElementType TmpA[],int L,int RightEnd)
{
if(L>=RightEnd) return; //当子序列中没有元素时不再进行递归排序
int Center=(L+RightEnd)/2;
Msort(A,TmpA,L,Center);
Msort(A,TmpA,Center+1,RightEnd);
Merge(A,TmpA,L,Center+1,RightEnd);
}
//统一函数接口(所有排序函数的接口都统一为 排序名字_sort)
void Merge_sort(ElementType A[],int n)
{
ElementType *TmpA;
TmpA=(ElementType *)malloc(n*sizeof(ElementType));
if(TmpA!=NULL)
{
Msort(A,TmpA,0,n-1);
free(TmpA); //养成好的习惯,用完一块内存就释放掉
}
else cout<<"空间不足"<<endl;
}
程序中TmpA数组在最外面声明,并且每次调用递归函数都传入数组头指针,避免了将该数组在merge函数中声明的情况,那样会导致每次递归调用都在执行数组的开辟和释放
非递归实现代码
递归算法虽然简单容易理解,但是会使用额外的堆栈,程序执行效率不是很高,所以会想采用非递归算法
思路:
为了实现不需要每次归并都定义一个数组,我们采用两个数组来回导的方式,这样额外的空间复杂度只需要O(n)
/* 归并排序 - 循环实现 */
/* 这里Merge函数与递归版本的Merge不太一样,不需要最后一步将TmpA导回A */
void Merge(ElementType A[],ElementType TmpA[],int L,int R,int RightEnd)
{
int LeftEnd=R-1, //左边终点的位置,假设左右两个子列是挨着的
Tmp=L, //存放结果数组的初始位置
NumElements=RightEnd-L+1; //存放结果数组元素个数
while(L<=LeftEnd&&R<=RightEnd)
{
if(A[L]<A[R]) TmpA[Tmp++]=A[L++];
else TmpA[Tmp++]=A[R++];
}
while(L<=LeftEnd) TmpA[Tmp++]=A[L++];
while(R<=RightEnd) TmpA[Tmp++]=A[R++];
}
// length = 当前有序子列的长度
void Merge_pass( ElementType A[], ElementType TmpA[], int N, int length )
{ /* 两两归并相邻有序子列 */
int i, j;
for ( i=0; i <= N-2*length; i += 2*length )
Merge( A, TmpA, i, i+length, i+2*length-1 ); //保证最后的结果一定是存在TmpA数组中的
if ( i+length < N ) /* 归并最后2个子列*/ //最后剩下的子序列可能是两个,也可能是单独的一个,需要单独讨论
Merge( A, TmpA, i, i+length, N-1);
else /* 最后只剩1个子列*/
for ( j = i; j < N; j++ ) TmpA[j] = A[j];
}
void Merge_Sort( ElementType A[], int N )
{
int length;
ElementType *TmpA;
length = 1; /* 初始化子序列长度*/
TmpA = (ElementType *)malloc( N * sizeof( ElementType ) );
if ( TmpA != NULL ) {
while( length < N ) {
Merge_pass( A, TmpA, N, length ); //每次循环执行两次,先将A导入TmpA,再将TmpA导入A
length *= 2;
Merge_pass( TmpA, A, N, length ); //最后一次归并如果归并结果就在A里面,这一步就只是简单的复制了一下数组而已
length *= 2;
}
free( TmpA );
}
else printf( "空间不足" );
}
归并排序优点和缺点
优点:
归并排序稳定并且始终能保证时间复杂度为O(nlogn)
缺点:
唯一不好的缺点就是在程序中需要在两个数组中来回导,不适合用于内排序,基本上内排序都不会想用归并排序,归并排序更适合外排序