1. 程式人生 > >【演算法】常見的七種排序及其演算法優化

【演算法】常見的七種排序及其演算法優化

1、插入排序

有一個已經有序的資料序列,要求在這個已經排好的資料序列中插入一個數,但要求插入後此資料序列仍然有序,這個時候就要用到一種新的排序方法——插入排序法,插入排序的基本操作就是將一個數據插入到已經排好序的有序資料中,從而得到一個新的、個數加一的有序資料,演算法適用於少量資料的排序,時間複雜度為O(n^2)。是穩定的排序方法。插入演算法把要排序的陣列分成兩部分:第一部分包含了這個陣列的所有元素,但將最後一個元素除外(讓陣列多一個空間才有插入的位置),而第二部分就只包含這一個元素(即待插入元素)。在第一部分排序完成後,再將這個最後元素插入到已排好序的第一部分中。 插入排序的基本思想是:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止。

大概步驟如上圖,下面實現程式碼

void InsertSort(int* arr,size_t n)//插入排序
{
	assert(arr);
	for(size_t index=1;index<n;index++)
	{
		size_t tmp=arr[index];
		int pos=index-1;
		while (pos>=0&&arr[pos]>tmp)
		{
		arr[pos+1]=arr[pos];
		pos--;
		}
	
			arr[pos+1]=tmp;
		}
}
2、希爾排序 希爾排序(Shell Sort)是插入排序
的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,演算法便終止。
大概過程如上圖,程式碼如下
void ShellSort(int* arr,size_t n)//希爾排序
{
	int gap=n/3+1;
	for(size_t index=gap;index<n;index++)
	{
		size_t tmp=arr[index];
		int pos=index-gap;
		while(pos>=0&&arr[pos]>tmp)
		{
			arr[pos+gap]=arr[pos];
			pos-=gap;
		}
		arr[pos+gap]=tmp;
	}
}
3、氣泡排序

重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。

不再做過多解釋,直接上程式碼

void PopSort(int* arr,size_t n)//氣泡排序
{
	for(int i=0;i<n;i++)
	{
		for(int j=i+1;j<n;j++)
		{
			if(arr[i]>arr[i+1])
			{
				int tmp=arr[i];
				arr[i]=arr[i+1];
				arr[i+1]=tmp;
			}
		}
	}
}
4、選擇排序

每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。



程式碼如下

void selectSort(int*arr ,size_t n)
{
	int min;
	int tmp;
	for(int i=0;i<n;i++)
	{
		
		for(int j=0;j<n;j++)
		{
			min=i;
			if(arr[min]>arr[j])
			{
				min=j;
			}
		
			tmp=arr[min];
			arr[min]=arr[j];
			arr[j]=tmp;
		}
	}
}

優化的選擇排序

每次選一個最小值放最前面,選最大一個值放後面


程式碼如下

void SelectSort2(int* arr,int n)
{
	assert(arr);
	int left=0;
	int right=n-1;
    while (left<right)
    {
			int min=left;
			int max=right;
			for (int i=left;i<=right;++i)
			{
				if (arr[min]>=arr[i])
				{
					min=i;
				}
                
				if(arr[max]<=arr[i])
				{
					max=i;
				}
			
				
			}
		    swap(arr[left],arr[min]);
			if (left==max)
			{
				max=min;
			}
			swap(arr[right],arr[max]);
		    left++;
			right--;
    }

	}

5、快速排序

通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列


挖坑法塊排,選第一數做比較

void FastSort(int* arr,int low,int high)//設定一個低位一個高位,進行快速排序
{
	if(low>=high)
		return;
	int first=low;
	int last=high;
	int mid=ThreeMid(arr,first,last);
	swap(arr[mid],arr[first]);
	int key=arr[first];//用挖坑法
	while (first<last)
	{
		while(first<last&&arr[last]>=key)
		{
			--last;
		}
		arr[first]=arr[last];
		while (first<last&&arr[first]<=key)
		{
			++first;
		}
		arr[last]=arr[first];

	}
	arr[first]=key;
	FastSort(arr,low,first-1);
	FastSort(arr,first+1,high);
}
挖坑法選最後數做比較

單趟:

int PartSort2(int* arr,int first,int last)//挖坑法
{
	int left=first;
	int right=last;
	int key=ThreeMid(arr,first,last);
	swap(arr[key],arr[last]);
	int tmp=arr[last];
	while (left<right)
	{
		while (left<right&&arr[left]<=tmp)
		{
			++left;
		}
			arr[right]=arr[left];
		while(left<right&&arr[right]>=tmp)
		{
			--right;
		}
			arr[left]=arr[right];
	}
	arr[left]=tmp;
	return left;

}
遞迴
void QuickSort2(int* arr,int first,int last)
{

	if(first<last)
	{
		int key=PartSort2(arr,first,last);
		QuickSort2(arr,first,key-1);
		QuickSort2(arr,key+1,last);
	}
	
}
快排優化

當陣列數超過13個進行快排,否則插入排序

void QuickSort2(int* arr,int first,int last)
{

	if(first<last)
	{
		if ((last-first)>=13)
		{
			int key=PartSort2(arr,first,last);
			QuickSort2(arr,first,key-1);
			QuickSort2(arr,key+1,last);
		}
		else
			InsertSort(arr,sizeof(arr)/sizeof(arr[0]));
		
	}
	
}
前後指標法,陣列前後個一個指標,同時進行排序
int PartSort(int* arr,int left,int right)
{	
    int mid=ThreeMid(arr,left,right);
	swap(arr[mid],arr[right]);
	int key=arr[right];
	int first=left;
	int last=right-1;
	while(first<last)
	{
		while (first<last&&arr[first]<=key)
		{
			++first;
		}
		while (first<last&&arr[last]>=key)
		{
			--right;
		}
		swap(arr[first],arr[last]);
	}
	if(arr[first]>arr[right]);
	swap(arr[first],arr[right]);
	return first;
}
void QuickSort3(int* arr,int left,int right)
{
	while (left<right)
	{
		int mid=PartSort(arr,left,right);
		QuickSort3(arr,left,mid-1);
		QuickSort3(arr,mid+1,right);
	
	}
}


6、堆排序

堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在陣列的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。

建堆

void AdjustDown(int* arr,int index,int n)
{
	int parent=index;
	int child=2*parent+1;
	while (child<n)
	{
		if(child<n&&arr[child]<arr[child+1])
			child++;
	    if (child<n&&arr[child]>arr[parent])
		{
			swap(arr[parent],arr[child]);
			parent=child;
			child=2*parent+1;
		}
		else
			break;
	}
	child=parent;
}
堆排序
void HeapSort(int* arr,int n)
{
	for(int i=n/2-1;i>=0;--i)
	{
      AdjustDown(arr,i,n);
	}
	for(int j=n-1;j>0;--j)
	{
		swap(arr[j],arr[0]);
        AdjustDown(arr,0,j);
	}
	if(arr[0]>arr[1])
	swap(arr[0],arr[1]); 
}
7、歸併排序

歸併過程為:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此迴圈下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的演算法我們通常用遞迴實現,先把待排序區間[s,t]以中點二分,接著把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。



單趟排序

void Merge(int* arr,int* tmp,int first,int mid,int last)//歸併
{
	int i=first;int j=mid+1;int k=first;
	while (i!=mid+1&&j!=last+1)
	{
		if(arr[i]>arr[j])
			tmp[k++]=arr[j++];
		else
			tmp[k++]=arr[i++];
	}
	while(i!=mid+1)
	{
		tmp[k++]=arr[i++];
	}
	while (j!=last+1)
	{
		tmp[k++]=arr[j++];
	}
	for(i=first;i<=last;i++)
		arr[i]=tmp[i];
}
遞迴歸併
void MergeSort(int* arr,int* tmp,int first,int last)//歸併排序
{
	int mid;
	if (first<last)
	{
		mid= first+((last-first)>>1);          
		MergeSort(arr,tmp,first,mid);
		MergeSort(arr,tmp,mid+1,last);
		Merge(arr,tmp,first,mid,last);
	}
}
三數求中法
int ThreeMid(int* arr,int left,int right)//找首位,末尾和中點中中間的數
{
	int mid=left+((right-left)>>1);
	while (left<right)
	{
		if(arr[left]<arr[right])
		{
			if(arr[mid]<arr[left])
				return left;
			else if(arr[right]<arr[mid])
				return right;
			else
				return mid;
		}
		if(arr[left]>arr[right])
		{
			if(arr[mid]<arr[right])
				return right;
			else if(arr[mid]>arr[left])
				return left;
			else
				return mid;
		}
	}
	return mid;
}
三數中中間值進行比較避免了用最後一位比較時最後一位是最大值或用第一位比較時第一位是最小值,降低了時間複雜度。

測試程式碼

int main()
{
	int arr[]={9,3,5,1,4,7,8,0};
	int tmp[8];
	//int arr[]={2,3,6,5,4,13,56};
	cout<<"原陣列  : ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	InsertSort(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"插入排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	ShellSort(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"希爾排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	PopSort(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"氣泡排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	selectSort(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"選擇排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	FastSort(arr,0,sizeof(arr)/sizeof(arr[0])-1);
	cout<<"快速排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	SelectSort2(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"選擇排序2:";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	HeapSort(arr,sizeof(arr)/sizeof(arr[0]));
	cout<<"堆排序:   ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	QuickSort2(arr,0,sizeof(arr)/sizeof(arr[0])-1);
	cout<<"快速排序2: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	MergeSort(arr,tmp,0,sizeof(arr)/sizeof(arr[0])-1);
	cout<<"歸併排序: ";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	QuickSort2(arr,0,sizeof(arr)/sizeof(arr[0])-1);
	cout<<"快速排序3:";
	Print(arr,sizeof(arr)/sizeof(arr[0]));
	system("pause");                
	return 0;
}


測試用例

int arr[]={9,3,5,1,4,7,8,0};




測試用例

int arr[]={2,3,6,5,4,13,56};



歡迎補充