归并排序就是将两个或多个有序表合并成一个有序表的过程。若将两个有序表合并成一个表则称为二路归并。
二路归并过程如下:
首先把待排的每一个元素看成一个有序表。n个元素构成n个有序表。接着两两合并,即第一个表和第二个表合并;第三个表和第四个表合并;.....。依次类推,若最后还剩一个表没有合并(即n为奇数),则直接进入下一次两路归并。此为一趟归并。然后再两两合并,直到最后合并为一个表结束。
例如:10个元素的归并排序{45 32 12 77 48 97 2 36 18 26}
初始每个元素为一个有序表{45} {32} {12} {77} {48} {97} {2} {36} {18} {26}
两两合并,完成第一趟排序 {32 45} {12 77} {48 97} {2 36} {18 26}
两两合并,完成第二趟排序 {12 32 45 77} {2 36 48 97} {18 26}
两两合并,完成第三趟排序 {2 12 32 36 45 48 77 97} {18 26}
两两合并,完成第四趟排序 {2 12 18 26 32 36 45 48 77 97}
//*****************7归并排序*******************************
void Merge(int a[],int b[],int left,int mid,int right)
{
int i=left,j=mid+1,k=left;
while(i<=mid && j<=right)
{
if(a[i]<=a[j])
b[k++] = a[i++];
else
b[k++]=a[j++];
}
while(i<=mid)
b[k++] = a[i++];
while(j<=right)
b[k++] = a[j++];
for(i=left;i<=right;i++)
a[i] = b[i];
}
void MergeSort(int a[],int b[],int left,int right)
{
int mid;
if(left>1;
MergeSort(a,b,left,mid);
MergeSort(a,b,mid+1,right);
Merge(a,b,left,mid,right);
}
}
归并排序空间复杂度---O(n) 需要辅助数组b[n]
时间复杂度分析
T(n)=O(1) if n=1
T(n)=2T(n/2)+O(n) if n>=2
利用主方法求解T(n)=O(nlgn)
最坏情况和平均情况 O(nlgn) ----->递归深度lgn
最好情况 待排有序 O(n)
稳定性分析
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序是稳定的排序算法。
关于归并排序的一个应用是求逆序对数。归并排序中的关键一步是将两个有序的数组合并成一个数组,合并的过程中,依次比较两个有序数组元素大小,以升序有序为例,
(结合下面代码理解)(left <=i <=mid mid+1<=j<=right) 如果a[i] > a[j] 说明出现了逆序对,这时在 i 指向的数组内,a[ i ] 与其后所有元素a[ i ~ mid]所有元素都是逆序的,所以逆序对是mid-i+1;下面直接看代码:
int Merge(int a[],int b[],int left,int mid,int right)
{
int i = left, j = mid + 1, k = left;
int t = 0;
while(i <= mid && j <= right)
{
if(a[i] <= a[j])
b[k++] = a[i++];
else
{
t += (mid-i+1);
b[k++] = a[j++];
}
}
while(i <= mid)
b[k++] = a[i++];
while(j <= right)
b[k++] = a[j++];
for(i = left; i <= right; i++)
a[i] = b[i];
return t;
}
int MergeSort(int a[],int b[],int left,int right)
{
int mid;
int inversion = 0;
if(left> 1;
inversion += MergeSort(a,b,left,mid);
inversion += MergeSort(a,b,mid+1,right);
inversion += Merge(a,b,left,mid,right);
}
return inversion;
}
桶排序
-----典型以空间换时间
基本思想:把区间[0 ,1)划分成n个相同大小的子区间,称为桶。将n个记录分布到各个桶中。如果有多于一个记录被分到同一个桶中,需要进行桶内排序。最后依次把各个桶中的记录列出来就得到有序序列。
显然,桶排序的思想是先预定义了很多区间,把满足区间范围的待排序数放到对应的桶中。
利用 函数的映射关系,减少了几乎所有的比较工作,把大量数据分割成基本有序的数据块(桶),然后只需要对桶中的少量数据做先进的比较排序即可。
/****************************桶排序-升序*************************/
void BucketSort(double * a,int n)
{
//链表结点描述
typedef struct Node{
double key;
struct Node * next;
}Node;
//辅助数组元素描述
typedef struct{
Node * next;
}Head;
int i,j;
Node *p,*q,*node;
Head head[10]={NULL}; //10个桶,每个桶里放置一个链表
for(i=0;ikey=a[i];
node->next=NULL;
p = q = head[(int)(a[i]*10)].next;//小数*10取整即为桶号
if(p == NULL)//桶装入第一个元素时
{
head[(int)(a[i]*10)].next=node;
continue; //进行下一次for循环
}
//直接插入排序一个一个插入
while(p){
if(node->key < p->key)
break; //如果逆序,跳到下面else,插入到p前面
q=p; //如果顺序,指针后移,再进行if判断
p=p->next;
}
if(p == NULL){
q->next=node;
}else{
node->next=p;
q->next=node;
}
}
//遍历每个桶中的元素
j=0;
for(i=0;i<10;i++){
p=head[i].next;
while(p){
a[j++]=p->key;
p=p->next;
}
}
}
void test_Bucket_sort()
{
int i;
double a[13]={0,0.13,0.25,0.18,0.29,0.81,0.52,0.52,0.83,0.52,0.69,0.13,0.16};
BucketSort(a,13);
for(i=0;i<=12;i++)
printf("%-6.2f",a[i]);
printf("\n");
}
空间复杂度 O(n)
时间复杂度
1 循环计算每个关键字的桶映射函数 O(N)
2对每个桶内的所有数据进行排序,其中第i个桶进行桶内排序时间复杂度为 O(Ni*logNi) //Ni 为第i个桶中的数据量
假设N个待排数据 M个桶 平均每个桶N/M个元素则:
T(N)= O(N)+O(M*N/M*logN/M) = O(N+C) 其中 C=Nlog(N/M)
稳定性 桶排序是稳定的,对于相同的元素都是按待排次序放入到桶中的,再从桶中收集的时候也是按原来顺序收集的,不会改变顺序。