經典排序總結

冒泡排序

#include <stdio.h>

void BubbleSort(int a[],int n)
{
	int change=1;
	for (int i=n-1;i>0&&change==1;i--)
	{
		change=0;
		for (int j=0;j<i;j++)
		{
			if (a[j]>a[j+1])
			{
				int tmp;
				tmp=a[j];
				a[j]=a[j+1];
				a[j+1]=tmp;
				change=1;
			}
		}
	}

}
最壞的情況爲逆序時,比較1+2+3.....+n-1次,時間複雜度爲O(n^2);增添標誌位進行優化,避免不必要的操作!

選擇排序

基本操作爲選擇最小的交換

void SelctionSort(int a[],int n)
{
	for (int i=0;i<n;i++)
	{
		int min=i;
		for (int j=i+1;j<n;j++)
		{
			if (a[min]>a[j])
			{
				min=j;
			}
		}
		int tmp;
		tmp=a[i];
		a[i]=a[min];
		a[min]=tmp;
	}
}

每次都從j=i+1開始選擇出最小數的下標,然後將最小數與i交換。

1最大的特點是交換次數相當少

2無論數據好壞,比較次數都是一樣的。爲1+2+3....+n-1;時間複雜度仍然爲O(n^2)

儘管時間複雜度與冒泡排序相當,但簡單排序還是優與冒泡排序

直接插入排序:PS:總把這個選擇排序搞混!!

基本操作爲將一個記錄插入到已經排好的有序表中,將後續元素插入到合適位置

void InserSort(int a[],int n)
{
	for (int i=2;i<n;i++)
	{
		if (a[i]<a[i-1])
		{
			a[0]=a[i];
			for (int j=i-1;a[j]>a[i];j--)//跳出循環時,j指向正確位置的前一個位置;
			{
				a[j+1]=a[j];
			}
		a[j+1]=a[i];
		}
	}
}
算法要求一個哨崗位,a[0]作爲哨崗,使尋找插入位置的循環可以正確跳出

對於數組而言 若數組a[0]也是要加入排序的數據,則也要開闢個存儲空間,保存待插入元素,

a[j]>tmp&&j>=0

void InsertSort(int a[],int n)
{
	for (int i=1;i<n;i++)
	{
		if (a[i]<a[i-1])
		{
			int tmp=a[i];
			for (int j=i-1;a[j]>tmp&&j>=0;j--)//跳出循環時,j指向正確位置的前一個位置;
			{
				a[j+1]=a[j];
			}
		a[j+1]=tmp;
		}
	}
}

時間複雜度仍然爲O(O^2)同樣的的時間複雜度,直接插入發比簡單選擇和冒泡排序的性能要好。

1.在記錄本身就是基本有序的時候,我們只需要少量的插入操作,就可以完成整個記錄的集的排序工作,此時插入效率很高

2.在記錄比較少時,直接插入法的優勢也比較明顯.

爲了利用直接插入法的這兩個優勢 希爾發明了希爾排序

希爾排序:爲了利用插入排序的當元素基本有序時候的優勢

採用跳躍分割的策略,將相距摸個增量的記錄組成一個字序列,這樣才能保證在子序列內分別進行直接插入排序後得到的結果是基本有序而不是局部有序.

void ShellSort(int a[],int n)
{
	int add=n;
	do 
	{
		add=add/3+1;
		for (int i=add;i<n;i++)
		{
			if (a[i]<a[i-add])
			{
				int tmp=a[i];
				for (int j=i-add;j>=0&&tmp<a[j];j-=add)
				{
					a[j+add]=a[j];
				}
				a[j+add]=tmp;
			}
		}
	} while (add>1);
}

do while(add>1)這個循環很關鍵,控制了add增量等於一時就是最後一次循環,對比直接插入法發現,希爾算法就是分別對增量子集進行插入排序,並逐漸縮小增量,最後一次排序時是用直接插入法對基本有序的序列進行排序,

其時間複雜度 突破了前人的O(n^2), 爲:O(n^(3/2));

堆排序

堆排序是對簡單選擇排序的一種改進,在進行選擇排序時,每次都要從後續序列中選出最小的元素來插入,顯然,這樣的比較重複了很多次,堆排序就改正了這樣一個缺點。

1.如何由一個無序序列構建一個堆。

2如果在輸出堆頂元素後,調整剩餘元素成爲一個新的堆.

所謂的將待排序列構建成爲一個大頂堆,其實就是從下往上,從右往左,將每個非終端結點當做根節點,將其調整爲大頂堆。

void HeapAdjust(int a[],int s,int n)
{
	int tmp=a[s];
	for (int j=2*s;j<n;j*=2)
	{
		if (j<n&&a[j]<a[j+1])
		{
			++j;
		}
		if (tmp>=a[j])
		{
			break;
		}
		else
		{
			a[s]=a[j];//
			s=j;//很精妙的兩句話!! 注意理解
		}
	}
	a[s]=tmp;
}
void HeapSort(int a[],int n)
{
	for (int i=n/2;i>0;i--)
	{
		HeapAdjust(a,i,n);
	}
	for (i=n;i>0;i--)
	{
		int tmp;
		tmp=a[1];
		a[1]=a[i];
		a[i]=tmp;
		HeapAdjust(a,1,i-1);
	}
}

數組中a[0]不能存儲元素,因爲這樣會打破父結點和左右孩子結點的2i和2i+1的關係

自下而上調整大頂堆:循環n/2到1實現了非根結點自下而上,從右到左的調整,遍歷每個結點時,比較左右孩子的大小,若右孩子大,則j++;若此時根結點已經是最大,則退出循環,若不是,則將較大的賦值給跟結點,s=j,根結點的位置發生變化。

關於時間複雜度:無論是最好,最壞和平均時間複雜度均爲O(nlogn)。這在性能上遠遠超過了冒泡,簡單選擇,直接插入的時間複雜度了.充分利用了完全二叉樹的深度是log2n+1的性質。

第i次取堆頂記錄重建需要O(logi)的時間,(因爲完全二叉樹的某個結點到根結點的距離爲log2i+1,)需要取n-1次,每次取的記錄與最後一個元素進行交換 時間複雜度均爲O(nlogn)!又一次大突破!!

適合數據量大的排序:由於初始建堆所需的次數比較多,因此,它不適合排序序列個數較少的情況。


歸併排序

void Merge(int SR[],int TR[],int i,int m,int n)
{
	int j,k,l;
	for (j=m+1,k=i;i<=m&&j<=n;k++)//每次將兩個區域對比,擇小插入,循環結束後至少一個區域已經全部插入,後續不需要比較直接全部插入,(後面兩個循環)。
	{
		if (SR[i]<SR[j])
		{
			TR[k]=SR[i++];
		}
		else
		{
			TR[k]=SR[j++];
		}
	}
	if (i<=m)//若i....m還有元素沒有插入
	{
		for (l=0;l<=m-i;l++)
		{
			TR[k+l]=SR[i+l];
		}

	}
	if (j<=n)
	{
		for (l=0;l<=n-j;l++)//一定要'='
		{
			TR[k+l]=SR[j+l];
		}
	}
	
}
void Msort(int SR[],int TR1[],int s,int t)//將SR【s...t】歸併排序爲TR[s...t]
{
	int m;
	int TR2[1000];
	if (s==t)
	{
		TR1[s]=SR[t];
	}
	else
	{
		m=(s+t)/2;
		Msort(SR,TR2,s,m);
		Msort(SR,TR2,m+1,t);
		Merge(TR2,TR1,s,m,t);//將TR2歸併爲TR1
	}
}

Merge爲歸併函數,將兩個各自有序的數列歸併成一個有序序列,Msort爲遞歸主題函數,將SR【】歸併排序爲TR【】!!!

蛋疼問題:循環中改變了循環條件,居然沒看出來

Merge時間複雜度爲o(n),由完全二叉樹的深度可知,整個歸併排序需要進行logn2次,所以總的時間複雜度爲o(nlogn);

排序中存在比較,是一種穩定的排序方法,

空間複雜度:由於歸併排序在歸併過程中需要與原記錄序列同樣數量的存儲空間存放歸併排序結果,以及歸併時深度爲log2n的棧空間,因此空間複雜度爲O(n+logn)

也就是說歸併排序是一種比較佔用內存,但卻效率高且穩定的算法.


快速排序:

void swap(int &a,int &b)//數組可以不用取地址符,單個整形數一定要用
{
	int tmp;
	tmp=a;
	a=b;
	b=tmp;
}
int Partition(int a[],int low,int high)
{
	int pivotkey;
	pivotkey=a[low];
	while (low<high)
	{
		while (low<high&&a[high]>=pivotkey)
		{
			high--;
		}
		swap(a[low],a[high]);
		while (low<high&&a[low]<=pivotkey)
		{
			low++;
		}
		swap(a[low],a[high]);
	}
	return low;
}   
void Qsort(int a[],int low,int high)
{
	int pivot;
	if(low<high)
	{
		pivot=Partition(a,low,high);
		Qsort(a,low,pivot-1);
		Qsort(a,pivot+1,high);
	}
}
在最優的情況下,快速排序算法的詩句複雜度爲O(nlogn).

在最壞的情況下,待排序列爲正序或者逆序,每次劃分只得到一個比上一次少一個記錄的子序列,注意另一個爲空,需要n-1次分割,且第i次劃分需要經過n-i次關鍵字的比較才能找到第i個記錄,也就是樞軸的位置,比較次數爲1+...+..n-1次,時間複雜度爲O(n^2);

可惜的是,由於關鍵字的比較和交換式跳躍進行的,因此,快速排序是一種不穩定的排序方法.

快速排序還有優化的方案 ,以後再敲......


發佈了30 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章