分治法---歸併排序,改進的歸併排序,自然合併排序

要做算法題作業,關於自然合併排序的,不太清楚自然合併排序和歸併排序有什麼區別,在網上查找到下面的資料,很詳細,趕緊貼在下面和大家分享~~~

思想:將待排序元素分成大小大致相同的兩個子集,分別對兩個子集合進行排序,最終將排好序的子集合併成所要求的排好序的集合。T(n)最好情況=O(nlogn)    T(n)最壞情況=O(nlogn)    S(n)=O(n)

程序實例1:

#include<iostream>
using namespace std;
//合併數組中的兩端有序序列到一個新的數組,並複製回來排好序的結果到原數組
static void merge(int array[], int p, int q, int r)
{
  int i,k;
  int begin1,end1,begin2,end2;
  int* temp = new int [r-p+1]; //申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
  begin1= p;     end1 = q; //設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
  begin2 = q+1;  end2 = r;
 
  k = 0;

  //比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
  while((begin1 <= end1)&&( begin2 <= end2))  

 {
   if(array[begin1]<array[begin2])
   {
    temp[k] = array[begin1];  begin1++;
   }
   else
   {
    temp[k] = array[begin2];  begin2++;
    }
   k++;  
  }
 
  while(begin1<=end1) //若第一個序列有剩餘,直接拷貝出來粘到合併序列尾
  {
   temp[k++] = array[begin1++];
  }
  while(begin2<=end2) //若第二個序列有剩餘,直接拷貝出來粘到合併序列尾
  {
   temp[k++] = array[begin2++];
  }
  for (i = 0; i < (r - p +1); i++) //將排序好的序列拷貝回數組中
   array[p+i] = temp[i];
  delete[] (temp);
 }

//歸併算法的核心代碼
void merge_sort(int array[], unsigned int first, unsigned int last)
{
 int mid = 0;
 if(first<last)
 {
  mid = (first+last)/2;
  merge_sort(array, first, mid);
  merge_sort(array, mid+1,last);
  merge(array,first,mid,last);
 }

}

int main()
{
   
    int a[]={2,26,45,76,97,36,26,87,4,3,1,5,9,8,6,7,10,65,43,85};
    merge_sort(a, 0, 19);
    for(int i=0;i<20;i++)
    cout<<a[i]<<endl;
    system("pause");
    return 0;
}

歸併排序可以改進,依次兩兩合併,四四合並,…………,直到合併結束。

程序實例2:(改進的歸併排序)

#include<iostream>
using namespace std;
/**********************************************************
算法說明:
本算法是歸併算法的一種改進,省去了遞歸的過程
***********************************************************/
//將c數組中的l~m,m~r段合併到d數組從l到r對應位置
void Merge(int c[],int d[],int l,int m, int r)
{
int i=l,j=m+1,k=l;   //這裏都是l
while((i<=m)&&(j<=r))
if(c[i]<=c[j]) d[k++]=c[i++];
else d[k++]=c[j++];
if(i>m)for(int q=j;q<=r;q++)d[k++]=c[q];
else   for(int q=i;q<=m;q++)d[k++]=c[q];
}

//對於不同的長度s,歸併趟算法如下
void MergePass(int x[],int y[],int s,int n)
{
int i=0;
while(i<=n-2*s)
{
Merge(x,y,i,i+s-1,i+2*s-1);
i=i+2*s;              
}    
if(i+s<n)
Merge(x,y,i,i+s-1,n-1);
else
for(int j=i;j<=n-1;j++)
y[j]=x[j];
}
//歸併的主算法
void Mergesort(int a[],int n)
{
     int *b=new int[n];
     int s=1;
     while(s<n)
     {
     MergePass(a,b,s,n);
     s+=s;
     MergePass(b,a,s,n);                 // 由於MergePass存在一種賦值的情況保證最終的a是變換後的,即排好序的
     s+=s;
     }
     delete []b;
}

int main()
{   
    int a[]={3,2,1,6,5,4};
    for(int i=0;i<6;i++)
    cout<<a[i]<<endl;
    Mergesort(a,6);
    for(int i=0;i<6;i++)
    cout<<a[i]<<endl;
    system("pause");
    return 0;   
}

擴展:自然和並排序

  自然合併排序是合併排序算法的一種改進。

 

  自然合併排序:對於初始給定的數組,通常存在多個長度大於1的已自然排好序的子數組段.例如,若數組a中元素爲{4,8,3,7,1,5,6,2},則自然排好序的子數組段有{4,8},{3,7},{1,5,6},{2}.用一次對數組a的線性掃描就足以找出所有這些排好序的子數組段.然後將相鄰的排好序的子數組段兩兩合併,構成更大的排好序的子數組段({3,4,7,8},{1,2,5,6}).繼續合併相鄰排好序的子數組段,直至整個數組已排好序。

程序實例:

#include<iostream>
using namespace std;
#include<vector>

static void merge(int array[], int p, int q, int r)
{
  int i,k;
  int begin1,end1,begin2,end2;
  int* temp = new int [r-p+1]; //申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
  begin1= p;     end1 = q; //設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
  begin2 = q+1;  end2 = r;
 
  k = 0;

  //比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
  while((begin1 <= end1)&&( begin2 <= end2)) 
 {
   if(array[begin1]<array[begin2])
   {
    temp[k] = array[begin1];  begin1++;
   }
   else
   {
    temp[k] = array[begin2];  begin2++;
    }
   k++; 
  }
 
  while(begin1<=end1) //若第一個序列有剩餘,直接拷貝出來粘到合併序列尾
  {
   temp[k++] = array[begin1++];
  }
  while(begin2<=end2) //若第二個序列有剩餘,直接拷貝出來粘到合併序列尾
  {
   temp[k++] = array[begin2++];
  }
  for (i = 0; i < (r - p +1); i++) //將排序好的序列拷貝回數組中
   array[p+i] = temp[i];
  delete[] (temp);
 }

void Nature_Merge(int a[],int n)
{
   vector<int> v;
  
   v.push_back(0);                 //首作爲分割點
                       
   for(int i=0;i<n-1;i++)          //中間的分割點          
   if(a[i]>a[i+1]) v.push_back(i);
  
   v.push_back(n-1);               //尾分割點
  
//   for(int j=0;j<v.size();j++)     //輸出測試分割點是否正確,可註釋掉
//   cout<<v[j]<<endl;    
//   cout<<v.size()<<"ok"<<endl;
   int s=1;
  
   for(int group=v.size()-1;group!=1;group=(group%2==0?group/2:group/2+1))
   {    
        int count=group/2;                  //合併次數  例如:5組合並需要兩次,4組合並兩次     
        //進行第一次合併
        int p,q,r;

        p=0;q=s;r=2*s;
        if(r>v.size()-1) r=v.size()-1;
        merge(a,v[p],v[q],v[r]);
       
        //進行接下來的合併
        for(int j=1;j<count;j++)
        {
        p=r;q=p+s;r=q+s;
        if(r>v.size()-1) r=v.size()-1;
        merge(a, v[p]+1, v[q],v[r]);                      
        }    
        s+=s;       
   }  
}

 
int main()
{
int a[]={4,3,2,1};
Nature_Merge(a,4);   
for(int i=0;i<4;i++)
cout<<a[i]<<endl;
system("pause");
return 0;   
}
總結:自然歸併排序思想簡單,但是實現的時候還有很多細節需要考慮,比如需要歸併次數是分組次數除以二取下限。還有就是r的控制,每次都要保證在v.size()-1範圍內不能超出。實現時候可能需要不斷測試,最終成功。
 

參考網站:http://blog.163.com/guchonglin-6/blog/static/5752753120099247170200/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章