堆排序以及歸併排序的理解
阿新 • • 發佈:2019-02-16
一、堆排序
堆排序的核心是把陣列看作一個完全二叉樹,通過入堆操作把整個二叉樹整理一遍,使所有的父節點都比其子節點大,如此根節點a(即陣列的第一位)就是最大的數。把根節點a與二叉樹最後一位z調換,稱為a的出堆,即a已經排序完成。再處理被換上去的z,由於所有的父節點都比子節點大,只要把根節點下面子節點較大的那一位換到根節點,而根節點上的z就重複的進行處理沉下去,直到沉到底。重複出堆的操作,就可以得到升序排列的陣列。整個操作的時間複雜度為O(Nlog(N)),空間複雜度為O(1)。
1.入堆:
從根節點開始,判斷其與父節點的大小關係,如果比父節點大,就與父節點交換,交換完成後繼續與新的父節點作比較。而根節點沒有父節點就不進行操作。
2.出堆:
將根節點與二叉樹最後一片葉交換,並且二叉樹的節點減一,即原先的根節點已經排序完成,退出比較。再把新的根節點通過與子節點比較交換一路沉下去。需要注意的是由於排好序的節點已經退出比較,所以要注意更新子節點是否存在,否則會把已經排序完成的數喚醒。
規律:畫出一個完全二叉樹並且標號(根節點標號為1)會發現任何一個父節點K的子節點為2k和2k+1,有這樣的位置關係容易比較。
3.程式碼部分:
#include<stdio.h> #include<stdlib.h> #define N 21 void enheap(int *a,int n); void outheap(int *a,int n,int count); void heap_sort(int *a,int n) { int i=0,temp=0,j=0; for(i=1;i<=n;i++) //將陣列內按照堆排序的入堆操作從前往後推,使所有的父節點都比其子節點大,如此則根節點最大。一次操作複雜度為O(log(n)) enheap(a,i); for(i=n;i>=1;i--) { temp=a[1]; a[1]=a[i]; a[i]=temp; //把根節點(即最大的數)和陣列後面未排序的數交換 outheap(a,1,i-1); //交換完成後i節點已經完成排序,不再進入比較序列,傳入引數變為i-1 } return; } void enheap(int *a,int n) { int temp=0,index=0; if(n/2<1 || a[n/2]>a[n]) //若該節點是根節點或者比父節點小則不作任何操作 return; if(a[n]>a[n/2]) //該節點比父節點大,則與父節點交換並繼續往上比較 { temp=a[n]; a[n]=a[n/2]; a[n/2]=temp; enheap(a,n/2); } return; } void outheap(int *a,int n,int count) //count數記錄目前排序未完成的個數,若超出此數則說明觸碰了已排序完成的數 { int temp,index=0; if(2*n>count) //n節點下面的已經比較過或者超出了陣列範圍 return; if(2*n+1>count || a[2*n]>=a[2*n+1]) // n節點下只有左節點或左節點不比右節點小,則n節點和其左節點比較 index=2*n; else //n節點下右節點比左節點大 index=2*n+1; if(a[n]<=a[index]) //若n節點不比子節點大,則交換後繼續比較 { temp=a[n]; a[n]=a[index]; a[index]=temp; outheap(a,index,count); //在這裡count是不變的,因為一次比較還沒有完成 } return; } int main(void) { int i=0; int a[N+1]={0}; for(i=1;i<=N;i++) a[i]=rand()%N; /*for(i=1;i<N;i++) printf("%d\n",a[i]);*/ heap_sort(a,N); for(i=1;i<=N;i++) printf("%d\n",a[i]); return 0; }
二、歸併排序:
歸併排序和快速排序在將排序陣列遞迴劃分成兩段上有一點點像,但快速排序是根據一個關鍵數將資料分成兩段,遞迴過程中是先處理大的資料再劃分處理小的資料量。而歸併排序是將所有的資料先不斷遞迴劃分,直到一個數字單元,再處理該段資料,然後返回,處理的資料量是從小到大的。而且歸併排序需要一個額外的空間。但是快速排序在最壞情況下時間複雜度會退化到O(n*n),歸併不會存在這個問題。 1.原理: 將所有的資料遞迴劃分成兩段,直到單個數據不可分。然後再把兩兩相鄰的數組合並。合併完之後得到的陣列再次合併,直到整個數組合並。 2.程式碼部分:#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1000000 int temp[N]; void merge(int *a,int *b,int n,int m) //將兩個數組合併到temp陣列中 { int i=0,j=0,count=0; while(i<n && j<m) //兩段陣列都未比較完畢 { if(a[i]<b[j]) temp[count++]=a[i++]; else temp[count++]=b[j++]; } if(i>=n) //如果a陣列先被處理完就把b陣列剩下的所有的數放入temp陣列中 while(j<m) temp[count++]=b[j++]; else //b陣列先處理完 while(i<n) temp[count++]=a[i++]; return; } void merg_sort(int *a,int n) { if(n<=1) //若已經不可再分則不處理 return; if(n>2) //若還可再分,就先處理小的 { merg_sort(a,n/2); merg_sort(a+n/2,n-n/2); } merge(a,a+n/2,n/2,n-n/2); //此處第一次處理的話就是兩個單個數合併 memcpy(a,temp,n*sizeof(int)); return; } int main(void)//測試程式碼 { int a[N]={0}; int i=0; for(i=0;i<N;i++) { a[i]=rand()%N; //printf("%d ",a[i]); } puts(""); merg_sort(a,N); for(i=0;i<N;i++) printf("%d\n",a[i]); return 0; }