基本概念
穩定性:對於序列中相等的元素a , b,且a在b的前面,若排序之後,a依然在b的前面,則該排序算法是穩定的,否則算法是不穩定的。
時間複雜度:直白的說就是算法才做的次數。
空間複雜度:指執行算法所需要的輔助空間(存儲空間)。若存儲空間的大小與數據的個數無關,則空間複雜度爲O(1)。
說明
爲方便講清楚代碼,先定義一個用於排序的順序結構和一個交換函數。
注意:這裏數字的起始位置的下標設置爲1
#define MAXSIZE 10 /* 要排序數組的元素個數,可根據需要改變 */
typedef struct
{
int r [MAXSIZE +1] /* 用於存儲排序數組,r[0]用作哨兵或臨時變量 */
int length;
}SqList;
// 也可以使用vector替代
#include<vector>
vector<int> r(MAXSIZE+1, 0);
viod swap(SqList *L, int i , int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
冒泡排序
冒泡算法的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄爲止。時間複雜度爲O(n^2)
void BubbleSort(SqList* L)
{
int i, j;
flag = True;
for(i = 1; i< L->length && flag; i++)
{
flag = False;
for(j = L->length -1; j >= i; j--)
{
if(L->r[j] > L->r[j+1])
{
swap(L, j, j+1);
flag = True;
}
}
}
}
選擇排序
選擇排序就是通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i(1<= i <=n)個記錄交換。
時間複雜度爲O(n^2)。儘管選擇排序與冒泡排序的時間複雜度一樣,但是在性能上還是優於冒泡排序的。
void SelectSort(SqList* L)
{
int i,j, min;
for(i = 1;i< L->length;i++)
{
min = i;
for(j = i + 1;j <= L->length;i++)
{
if(L->r[min] > L->r[j])
min = j;
}
if(i != min)
swap(L, i, min);
}
}
插入排序
插入排序是將一個記錄插入到已經排好順序的有序表中,從而得到一個新的、記錄數增加1的有序表。
時間複雜度同樣爲O(n^2), 但是優於選擇排序和冒泡排序。
void InsertSort(SqList* L)
{
int i,j;
for(i = 2; i <= L->length; i++)
{
if(L->r[i] < L->r[i-1])
{
L->r[0] = L->r[i]; /* 設置哨兵 */
for( j = i -1;L->r[j] > L->r[0]; j--) /* 找出在已排序的序列中的位置 */
L->r[j+1] = L->r[j];
L->r[j+1] = L->r[0];
}
}
}
希爾排序
希爾排序是對插入排序的優化,基本思想是:將序列分割成若干個子序列,然後對這些子序列分別進行直接插入排序,當整個序列都基本有序時,注意只是基本有序時,再對全體記錄進行一次直接插入排序。
所謂基本有序就是小的關鍵字基本在前面,大的基本在後面,不大不小的在中間。
void ShellSort(SqList* L)
{
int i, j;
int increment = L->length;
do
{
increment = increment/3 + 1;
for(i = increment+1; i< L->length;i++)
{
if(L->r[i] < L->r[i- increment])
{
L->r[0] = L->r[i];
for(j = i-increment; j>0 && L->r[0] < L->r[j]; j-= increment)
L->r[j+increment] = L->r[j];
L->r[j+increment] = L->r[0];
}
}
}
while(increment > 1);
}
堆排序
堆排序就是將待排序的序列構成一個大頂堆。此時整個序列的最大值就是堆頂的根節點。將它與堆數組的末尾元素交換,此時末尾元素就是最大值,然後將剩餘的n-1個序列重新構造成一個堆,這樣就會得到n個元素的次小值。如此反覆執行,就可以得到一個有序序列。
實現堆排序需要解決兩個問題:
1.如何由一個無序序列構建一個最大堆?
2.如果在輸出對頂元素後,調整剩餘元素成爲一新的堆?
時間複雜度O(nlogn)
/* 對順序表L 進行堆排序 */
void HeapSort(Sqort* L)
{
int i ;
for(i = L->length/2;i > 0 ;i++) /*將待排序的序列構建成一個大頂堆*/
{
HeapAdjust(L, i, L->length);
}
for(i = L->length;i>1; i--) /* 逐步將每個最大堆的根節點與末尾元素交換,並且在調整其成爲大頂堆。 */
{
swap(L, l,i);
HeapAdjust(L,1,i-1);
}
}
void HeapAdjust( SqList* L, int s, int m)
{
int temp, j;
temp = L->r[s];
for(j = 2*s; j <= m; j*=2)
{
if(j < m &&L->r[j] < L->r[j+1])
j++;
if(temp >= L->r[j])
break;
L->r[s] = L->r[j];
s = j;
}
L->r[s] = temp;
}
歸併排序
歸併排序的原理是:假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度爲1,然後兩兩歸併,得到[n/2]個長度爲1或者2 的有序子序列;再兩兩歸併,如此重複,直至得到長度爲n的有序序列爲止。
時間複雜度O(nlogn),空間複雜度爲O(n + logn)
void MergeSort(SqList* L)
{
MSort(L->r, L->r, 1, L->length);
}
void MSort( int SR[], int TR1[], int s, int t)
{
int m;
int TR2[MAXSIZE + 1];
if(s == t)
TR1[s] = SR[s];
else
{
m = (s+t) /2; /*將SR[s .. t]平分爲SR[s..m] 和SR[m+1 .. t] */
MSort(SR,TR2, s, m); /* 遞歸將SR[s..m] 歸併爲有序的TR2[s..m] */
MSort(SR, TR2, m+1, t); /* 遞歸將SR[m+1 .. t] 歸併爲有序的TR2[m+1 .. t] */
Merge((TR2, TR1,s, m, t); /*將TR2[s..m]和TR2[m+1 .. t]歸併到TR1[s..t] */
}
}
void Merge(int SR[], int TR[], int i, int m, int n)
{
int j,k,x;
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)
{
for( x = 0; x<= m-i; x++)
TR[k+x] = SR{i+x];
}
if(j <= n)
{
for(x = 0; x<=n-j;n++)
TR[k+x] = SR[j+x];
}
}
快速排序
快速排序的基本思想是:通過一趟排序將待排序分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小, 則可分別對這兩部分記錄繼續進行排序,已達到整個序列有序的目的。
時間複雜度O(nlogn)
void QuickSort(SqList *L)
{
QSort(L, 1,L->length);
}
void QSort(SqList* L, int low, int high)
{
int pivot;
if(low < high)
{
pivot = Partition(L,low,high);
QSort(L,low,pivot-1);
QSort(L,pivot+1,high);
}
}
int Partition(SqList* L, int low, int high)
{
int pivotkey;
pivotkey = L-> r[low];
while(low<high)
{
while(low < high&&L->r[high} >= pivotkey)
high--;
swap(L,low,high);
while(low<high && L->r[low]<=pivotkey)
low++;
swap(L,low, high);
}
return low;
}