排序演算法:
1、計數排序(rank sort)
先計算名次:
template<class T>
void Rank(T a[],int n,int r[])
{//計算a[0:n-1]中n個元素的排名
for(int i=0;i<n;i++)
r[i]=0;//初始化
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(a[j]<=a[i])
r[i]++;
else
r[j]++;
}
可以根據a的元素之間所進行的比較操作來估算程式的時間複雜性,這些比較操作是由if語句來完成的。總的比較次數為(n-1)n/2。
再按名次排序:
template <class T>
void Rearrange(T a[],int n,int r[])
{//按序重排數組a中的元素,使用附加數組u
T *u=new T[n+1];
//在u中移動到正確的位置
for(int i=0;i<n;i++)
u[r[i]]=a[i];
for(int i=0;i<n;i++)
a[i]=u[i];
delete []u;
}
在函數Rearrange執行期間移動元素的次數為2n。完成整個排序需要執行(n-1)n/2次比較操作和2n次移動操作。
另外一種重排元素的函數如下,該函數沒有使用附加數組u。
假定已經使用函數Rank計算出一個數組中每個元素的名次。
template <class T>
void Rearrange(T a[],int n,int r[])
{//原地重排數組元素
for(int i=0;i<n;i++)
//擷取應該排在a[i]處的元素
while(r[i]!=i)
{
int t=r[i];
Swap(a[i],a[t]);
Swap(r[i],r[t]);
}
}
template <class T>
inline void Swap(T& a,T& b)
{
T temp=a;a=b;b=temp;
}
此程式執行的最少交換次數為0(初始數組已經是按序排列),最大交換次數為2(n-1)。當使用本函數來代替上面的函數時,最壞情況下所需要的執行時間將增加,因為需要移動更多的元素(每次交換需要移動三次),不過程式所需要的記憶體減少了。
綜上考慮,兩種計數排序方法各有利弊。
2、選擇排序(selection sort)
template <class T>
void SelectionSort(T a[],int n)
{//對數組a[0:n-1]中的n個元素進行排序
for(int size=n;size>1;size--)
{
int j=Max(a,size);
Swap(a[j],a[size-1]);
}
}
template<class T>
int Max(T a[],int n)
{//尋找a[0;n-1]中的最大元素
int pos=0;
for(int i=1;i<n;i++)
if(a[pos]<a[i]
pos=i;
return pos;
}
每次調用max(a,size)需要執行size-1次比較,所以總的比較次數為1+2+3+...+n-1=(n-1)n/2。元素的移動次數為3(n-1)。選擇排序所需要的比較次數與按名次排序所需要的比較次數相同,但所需要的元素移動次數多出50%。
上述選擇排序函數的一個缺點是:即使元素已經按序排列,程式仍然繼續運行。為了終止不必要的迴圈,在尋找最大元素的期間,可以順便檢查數組是否已按序排列。下面程式給出了一個按照這種思想實現的選擇排序函數。在該函數中,把尋找最大元素的迴圈直接與函數SelectionSort合并在一起,而不是把它作為一個獨立的函數。
template <class T>
void SelectionSort(T a[],int n)
{//及時終止的選擇排序
bool sorted=false;
for(int size=n;!sorted && (size>1);size--)
{
int pos=0;
sorted=true;
//找最大元素
for(int i=1;i<size;i++)
if(a[pos]<=a[i])
pos=i;//如果已經按序排列,那麼就不會有else的機會,sorted也就一直為true,最紅將終止外部for迴圈。
else
sorted=false;//非未按序排列,表示需要外部for迴圈
Swap(a[pos],a[size-1]);
}
}
其最好的情況出現在數組a最初是有序數組的情形,此時外部for迴圈僅執行一次,a中元素之間的比較次數為n-1。在最壞情況下,外部for迴圈一直迴圈到size=1,執行的比較次數為(n-1)n/2。最好和最壞情況下的交換次數與上面的最佳化前的程式完全相同,但最壞情況下,最佳化後的程式可能要略微慢一些,因為它必須進行變數維護的額外工作。
綜上所述,最佳化後的選擇排序函數是最佳選擇。
3、冒泡排序(bubble sort)
template <class T>
void Bubble(T a[],int n)
{//把數組a[0:n-1]中最大元素通過冒泡移到右邊
for(int i=0;i<n-1;i++)
if(a[i]>a[i+1])
Swap(a[i],a[i+1]);
}
template <class T>
void BubbleSort(T a[],int n)
{//對數組a[0:n-1]中的n個元素進行冒泡排序
for(int i=n;i>1;i--)
Bubble(a,i);
}
元素比較次數為(n-1)n/2,與SelectionSort相同。
與選擇排序情形一樣,可以重新設計一個及時終止的冒泡排序函數。如果在一次冒泡過程中沒有發生元素互換,則說明書中已經按序排列,沒有必要再繼續進行冒泡過程。下面程式給出了一個及時終止的冒泡排序函數。
template <class T>
bool Bubble(T a[],int n)
{//把a[0:n-1]中最大元素冒泡至右端
bool swapped=false;//尚未發生交換
for(int i=0;i<n-1;i++)
if(a[i]>a[i+1])
{
Swap(a[i],a[i+1]);
swapped=true;
}
return swapped;
}
template<class T>
void BubbleSort(T a[],int n)
{//及時終止的冒泡程式
for(int i=n;i>1 && Bubble(a,i);i--);
}
在最壞情況下所執行的比較次數與原來的函數一樣,在最好的情況下比較次數為n-1。
4、插入排序(insertion sort)
template <class T>
void Insert(T a[],int n,const T &x)
{//向有序數組a[0:n-1]中插入元素x
int i;
for(i=n-1;i>=0 && x<a[i];i--)
a[i+1]=a[i];
a[i+1]=x;
}
template <class T>
void InsertionSort(T a[],int n)
{//對a[0:n-1]進行排序
for(int i=1;i<n;i++)
{
T t=a[i];
Insert(a,i,t);
}
}
在最好的情況下比較次數為n-1,而在最壞情況下比較次數為(n-1)n/2。
綜上所述,利用"附加數組重排數組元素"和"原地重排數組元素"使用的是同一個計算名次的函數Rank,比較次數為(n-1)n/2。
但移動元素的次數不一樣,"附加數組重排數組元素"需要執行2n次移動操作,而"原地重排數組元素"的最少交換次數為0,最大的交換次數為2(n-1),注意每次交換需要移動三次。
"選擇排序"總的比較次數也為(n-1)n/2,但元素移動次數為3(n-1),比計數排序所需移動次數多出50%。"及時終止的選擇排序"最好的情況是比較次數為n-1,最壞的情況下,比較次數為(n-1)n/2。
"冒泡排序"總的比較次數為(n-1)n/2,元素移動次數視具體資料而定,"及時終止的冒泡排序"在最好的情況下比較次數為n-1,最壞的情況為(n-1)n/2。
"插入排序"最好的情況下比較次數為n-1,而在最壞情況下比較次數為(n-1)n/2。