1. 程式人生 > >堆排序以及歸併排序的理解

堆排序以及歸併排序的理解

一、堆排序

       堆排序的核心是把陣列看作一個完全二叉樹,通過入堆操作把整個二叉樹整理一遍,使所有的父節點都比其子節點大,如此根節點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;
}