算法的穩定性:
概念
判斷方法
生成N個隨機數:
//創建一個隨機數組,arr保存生成的數據,n爲數組元素的數量,min爲生成數據的最小值,max表示生成數據的最大值,數組中生成的數都不重複。
int CreateData(int arr[],int n,int min,int max)
{
int i,j,flag;
srand(time(NULL));//取時間做隨機數種子,這樣種子酒不會重複,產生的隨機數也就不會重複。
if((max-min+1)<n) //最大數與最小數之差小於產生數組中元素的數量,生成數據不成功,因爲數組中的元素都是不重複的。
return 0;
for(i=0;i<n;i++)
{
do
{
arr[i]=(max-min+1)*rand()/(RAND_MAX+1)+min;
flag=0;
for(j=0;j<i;j++)
{
if(arr[i]==arr[j])
flag=1;
}
}while(flag);
}
return 1;
}
上面的函數需要包含頭文件:#include<stdlib.h>,#include <time.h>。1.冒泡排序算法:
void Bubble(int a[],int length)
{
int i,j,tmp;
/*外層循環式控制循環次數,之所以i<length-1,是因爲,
當前length-1個數都排好序後,最後一個數不用在排序了*/
for(i=0; i<length-1; i++)
{
/*內層循環是進行逐個比較,若滿足大於或小於關係則交換,之所以j<length-1-i,
是因爲當前已經有i個數已經排好序了,故當前只需要比較前length-1-i個數就好了。*/
for(j=0; j<length-1-i; j++)
{
//若滿足關係,則交換。
if(a[j] > a[j+1])
{
tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
}
int bubbleSort(int arr[],int len)
{
int i,j,tmp,flag=0;//增加標誌變量flag判斷本次掃描是否有交換。
if(len < 0 )
return -1;
for(i=0;i<len-1;i++)
{
for(j=0;j<len-1-i;j++)
{
if(arr[j]<arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag=1;//本次掃描有交換,就將flag置1
}
}
if(0 == flag)//如果本次掃描flag爲0則本次掃描沒有發生交換,那麼數組已然有序,break退出,不用在掃描了
break;
else
flag = 0;//否則將flag置爲默認的0值。繼續下一次掃描(冒泡排序操作)。
}
return 0;
}
算法分析
比較:n(n-1)/2=O(n^2)
移動:3n(n-1)/2=O(n^2)
冒泡排序畢竟是一種效率低下的排序方法,在數據規模很小時,可以採用。數據規模比較大時,最好用其它排序方法。
算法穩定性
2.選擇排序:
還有一種解釋就是每一趟從待排序的數據元素中選出最小的一個元素,順序放在已排好序的數列的最後,直到全部待排序的數據元素排完直接選擇排序和直接插入排序類似,都將數據分爲有序區和無序區,所不同的是直接插入排序是將無序區的第一個元素直接插入到有序區以形成一個更大的有序區,而直接選擇排序是從無序區選一個最小的元素直接放到有序區的最後。
bool SelectSort(int arr[],int len)
{
if(NULL == arr || len<=0)
return false;
int i,j,tmp,minIndex;
for(i=0; i<len-1; i++)
{
minIndex = i;
for(j=i+1; j<len; j++)
{
if(arr[j]<arr[minIndex])
minIndex = j;
}
if(minIndex != i)
{
tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
return true;
}
3.直接插入排序:
bool InsertSort(int arr[],int len)
{
if(NULL == arr || len<=0)
return false;
int i,j,currentInsertValue;
for(i=1; i<len; i++)
{
currentInsertValue = arr[i];
for(j=i-1; j>=0; j--)
{
if(currentInsertValue < arr[j])
{
arr[j+1] = arr[j]
}
else
break;
}
arr[j+1] = currentInsertValue;
}
return true;
}
4.快速排序:
#include <iostream>
using namespace std;
/*進行一趟快速排序,將第一個元素作爲基準。返回此趟拍完序的基準應該處的位置
此函數還進行一趟排序處理,將大於基準的元素都放入基準元素的右方,小於基準的元素放入基準的左方。*/
int Position(int arr[],int left,int right)
{
//取最左邊的元素爲基準元素
int base = arr[left];
while(left<right)
{
//找到右邊第一個小於基準的數將他放入左邊
while(left<right && base<=arr[right])
right--;
arr[left] = arr[right];
//找到左邊第一個大於基準的數,將它放入右邊
while(left<right && base>=arr[left])
left++;
arr[right] = arr[left];
}
//最後將基準值放入該插入的位置,並返回此位置
arr[left] = base;
return left;
}
bool QuickSort(int arr[],int left,int right)
{
if(NULL==arr || right<=left)
return false;
int pos;
if(left<right)
{
//以基準元素將數組分爲兩部分,獲得基準元素在數組中的位置
pos = Position(arr,left,right);
//遞歸排序數組左部分
QuickSort(arr,left,pos-1);
//遞歸排序數組右部分
QuickSort(arr,pos+1,right);
}
return true;
}
int main()
{
int arr[] = {5};
int len = sizeof(arr)/sizeof(int);
int i;
for(i=0; i<len; i++)
cout<<arr[i]<<" ";
cout<<endl;
if(QuickSort(arr,0,len-1))
{
for(i=0; i<len; i++)
cout<<arr[i]<<" ";
cout<<endl;
}
return 0;
}
快速排序算法分析:
快速排序的時間主要耗費在劃分操作上,對長度爲 k 的區間進行劃分,共需 k-1 次關鍵字的比較。
最壞時間複雜度:最壞情況是每次劃分選取的基準都是當前無序區中關鍵字最小(或最大)的記錄,劃分的結果是基準左邊的子區間爲空(或右邊的子區間爲空),而劃分所得的另一個非空的子區間中記錄數目,僅僅比劃分前的無序區中記錄個數減少一個。因此,快速排序必須做 n-1 次劃分,第 i 次劃分開始時區間長度爲 n-i-1, 所需的比較次數爲 n-i(1<=i<=n-1), 故總的比較次數達到最大值 Cmax =n(n-1)/2=O(n^2) 。如果按上面給出的劃分算法,每次取當前無序區的第 1 個記錄爲基準,那麼當文件的記錄已按遞增序(或遞減序)排列時,每次劃分所取的基準就是當前無序區中關鍵字最小(或最大)的記錄,則快速排序所需的比較次數反而最多。
最好時間複雜度:在最好情況下,每次劃分所取的基準都是當前無序區的“中值”記錄,劃分的結果與基準的左、右兩個無序子區間的長度大致相等。總的關鍵字比較次數爲 O(n×lgn)。
用遞歸樹來分析最好情況下的比較次數更簡單。因爲每次劃分後左、右子區間長度大致相等,故遞歸樹的高度爲 O(lgn), 而遞歸樹每一層上各結點所對應的劃分過程中所需要的關鍵字比較次數總和不超過 n,故整個排序過程所需要的關鍵字比較總次數C(n)=O(n×lgn) 。因爲快速排序的記錄移動次數不大於比較的次數,所以快速排序的最壞時間複雜度應爲 O(n^2 ),最好時間複雜度爲 O(n×lgn)。
基準關鍵字的選取:在當前無序區中選取劃分的基準關鍵字是決定算法性能的關鍵。 ①“三者取中”的規則,即在當前區間裏,將該區間首、尾和中間位置上的關鍵字比較,以三者之中值所對應的記錄作爲基準,在劃分開始前將該基準記錄和該區的第1 個記錄進行交換,此後的劃分過程與上面所給的 Partition 算法完全相同。 ② 取位於 low 和 high 之間的隨機數k(low<=k<=high), 用 R[k] 作爲基準;選取基準最好的方法是用一個隨機函數產生一個位於 low 和 high 之間的隨機數k(low<=k<=high), 用 R[k] 作爲基準 , 這相當於強迫 R[low..high] 中的記錄是隨機分佈的。用此方法所得到的快速排序一般稱爲隨機的快速排序。隨機的快速排序與一般的快速排序算法差別很小。但隨機化後,算法的性能大大提高了,尤其是對初始有序的文件,一般不可能導致最壞情況的發生。算法的隨機化不僅僅適用於快速排序,也適用於其他需要數據隨機分佈的算法。
平均時間複雜度:儘管快速排序的最壞時間爲 O(n^2 ), 但就平均性能而言,它是基於關鍵字比較的內部排序算法中速度最快的,快速排序亦因此而得名。它的平均時間複雜度爲 O(n×lgn)。
空間複雜度:快速排序在系統內部需要一個棧來實現遞歸。若每次劃分較爲均勻,則其遞歸樹的高度爲 O(lgn), 故遞歸後所需棧空間爲 O(lgn) 。最壞情況下,遞歸樹的高度爲 O(n), 所需的棧空間爲 O(n) 。
穩定性:
5.歸併排序:
來看歸併排序,其的基本思路就是將數組分成二組A,B,如果這二組組內的數據都是有序的,那麼就可以很方便的將這二組數據進行排序。如何讓這二組組內數據有序了?
//將有序的兩個序列first到mid 和mid到last 合併
void MergeArray(int arr[],int first, int mid,int last,int tmp[])
{
int i = first;
int j = mid + 1;
int k = 0;
while(i<=mid && j<=last)
{
if(arr[i] < arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
while(i<=mid)
tmp[k++] = arr[i++];
while(j<=last)
tmp[k++] = arr[j++];
for(i=0; i<k; i++)
arr[first+i] = tmp[i]; //注意這裏是 arr[first+i] = tmp[i];不是arr[i] = tmp[i];
}
//合併平排序的核心函數
void MergeSortCore(int arr[],int first, int last,int tmp[])
{
int mid;
if(first < last)
{
mid = (first+last)/2;
MergeSortCore(arr,first,mid,tmp);//排序前半序列
MergeSortCore(arr,mid+1,last,tmp);//排序後半序列
MergeArray(arr,first,mid,last,tmp);//合併前半和後半序列
}
}
//在此函數中調用MergeSortCore函數
bool MergeSort(int arr[],int len)
{
if(NULL == arr || len<=0)
return false ;
int* tmp = new int[len];
if(!tmp)
return false ;
MergeSortCore(arr,0,len-1,tmp);
delete[] tmp;
return true ;
}
1、穩定性
歸併排序是一種穩定的排序。
2、存儲結構要求
可用順序存儲結構。也易於在鏈表上實現。
3、時間複雜度
對長度爲n的文件,需進行 趟二路歸併,每趟歸併的時間爲O(n),故其時間複雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。
4、空間複雜度
需要一個輔助向量來暫存兩有序子文件歸併的結果,故其輔助空間複雜度爲O(n),顯然它不是就地排序。
注意:
若用單鏈表做存儲結構,很容易給出就地的歸併排序
歸併算法將兩個有序的數組合併到一個數組中並使之有序,這兩個數組並不一定相同大小,但需要一個額外的數組存放歸併結果。算法比較兩個數組相同位置的元素,將小的放入結果數組中,如此往復,如果其中一個先到達末尾,則將另外一個剩下部分放入結果數組中。
歸併排序將數組不斷劃分, 第一次分成兩半, 第二次分成四份, 如此直到得到只有一個元素的數組返回, 假定一個元素是有序的, 然後將兩個數據項歸併到兩個元素的有序數組中, 再次返回, 將這一對兩個元素的數組歸併到一個四個元素的數組中, 返回最外層的時候, 這個數組將會有兩個分別有序的子數組, 再次歸併則完成排序.