目錄
2.1 直接插入排序(Straight Insertion Sort)
1 基本定義
內排序:整個排序過程在內存中完成
排序的穩定性
- 存在多個具有相同排序碼的記錄
- 排序後這些記錄的相對次序保持不變
排序算法的時間代價:記錄的比較和移動次數
2 插入排序
2.1 直接插入排序(Straight Insertion Sort)
發撲克牌的時候我喜歡一邊摸牌,一邊理牌。這個過程就類似於直接插入排序。
基本操作:將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數+1的有序表。
2.1.1 栗子
本來有牌45
抽到了一張34,放45的前面
抽到了一張78,放45的後面
抽到了一張12,依次跟78,45,34比較,最後放在45的前面
其他同理
2.1.2 代碼實現
template<class Record>
void ImprovedInsertSort(Record Array[], int n){//Array爲待排數組,n爲數組長度
Record TempRecord; //臨時變量
//將待排元素一個個取出,存到臨時變量裏
for(int i=1; i<n; i++){
TempRecord = Array[i];
//記錄i往前與序列中的元素比較
int j = i - 1;
//將大於等於記錄i的記錄後移(j=-1時,即原序列無元素,記錄i爲第一個元素)
while(j >= 0 && (TempTecord < Array[j])){
Array[j+1] = Array[j];
j = j - 1;
}
//此時j後面就是記錄i的位置
Array[j+1] = TempRecord
}
}
2.1.3 算法分析
2.2 Shell排序
基於直接插入排序的兩個性質
- 序列本身有序的情況下,時間代價爲O(n)
- 對於短排序,直接插入排序比較有效
Shell排序算法思想
- 將序列轉化爲若干小序列,在這些小序列內進行插入排序
- 逐漸壯大小序列的規模,減少序列個數,使待排序序列逐漸處於更有序的狀態
- 最後對全體記錄進行一次直接插入排序
2.2.1 栗子——增量每次除以2遞減
選擇增量爲4的兩個元素爲一組(4 = 待排序列總數 / 2)
採用直接插入排序
選擇增量爲2的4個元素爲一組,採用直接插入排序
最後一輪,增量爲1,對全部記錄進行一次直接插入排序
2.2.2 代碼實現
template<class Record>
void ShellSort(Record Array[], int n){//Array[]爲待排序數組,n爲數組長度
int i, delta;
//增量delta每次除以2遞減,直到delta=1
for(delta = n/2; delta>0; delta /=2)
for(i = 0; i<delta; i++)
//分別對delta個子序列進行插入排序
//&傳Array[i]的地址,數組總長度爲n-i
//i=0,數組長爲n,delta=n/2
ModInsSort(&Array[i],n-i,delta);
//如果增量序列不能保證最後一個delta間隔爲1
//可以安排下面這個掃尾性質的插入排序
//ModInsSort(Array, n, 1)
}
針對增量而修改的插入排序算法
template<class Record>
void ModInsSort(Record Array[], int n, int delta){
int i, j;
//對子序列中第i個記錄,尋找合適的插入位置
//以下標爲delta的記錄開始處理,其前面的元素下標爲0
for(i = delta; i < n; i += delta)
//j以delta爲步長向前尋找逆序對進行調整
for(j = i; j >= delta; j -= delta){
if(Array[j] < Array[j-delta])//逆序對
swap(Array,j,j-delta);//交換
else break;
}
}
2.2.3 算法分析
2.2.4 增量選擇
(1)增量不互質
選取的增量之間不互質,導致重複排序。如
45與34'第一輪在同一子序列裏
第二輪還在同一子序列裏
(2)增量互質
3 選擇排序
3.1 直接選擇排序
依次選出剩下的未排序的記錄中的最小記錄。
過程類似於打擂臺。
3.1.1 栗子
第一回合
第一個站在擂臺上的是45,34去跟45打,34贏了。
78去跟34打,78輸了,目前站在擂臺的還是34。
12去跟34打,精彩,34輸了,12領先。
34'去跟12打,輸了。32去跟12打,輸了。29去跟12打,輸了。64去跟12打,還是輸。
第一回合,獲勝的是12!其他同理。
3.1.2 代碼實現
template<class Record>
void SelectSort(Record Array[], int n){
//依次選出第i小的記錄,即剩餘記錄中最小的那個
for(int i=0; i<n-1; i++){
//首先假設記錄i就是最小的
int smallest = i;
//開始向後掃描所有剩餘記錄
for(int j=i+1; j<n; j++)
//如果發現更小的記錄,記錄它的位置
if(Array[j] < Array[smallest])
smallest = i;
swap(Array,i,smallest);//將smallest賦值給Array[i]
}
}
3.1.3 算法分析
3.2 堆排序
基於最大堆來實現。而堆的本質是一棵用數組表示的完全二叉樹。
3.2.1 栗子
(1)建立最大堆
將已經存在的N個元素按最大堆的要求存放在一個一維數組中。
(2)找到最大值就將其刪除。即將堆裏的最後元素與堆頂元素交換
(3)最大值78出堆,堆的規模-1。剩餘的元素從根開始調整。
3.2.2代碼實現
template<class Record>
void sort(Record Array[], int n){
int i;
//建堆
MaxHeap<Record> max_heap = MaxHeap<Record>(Array,n,n);
//算法操作n-1次,最小元素不需要出堆
for(i=0; i<n-1; i++)
//依次找出剩餘記錄中的最大記錄,即堆頂
max_heap.RemoveMax();
}
3.2.3 算法分析
此外可以改寫堆排序算法,發現逆序對直接交換。
4 交換排序
4.1 冒泡排序
不斷比較相鄰的記錄,不滿足排序要求的就交換相鄰記錄,直到所有記錄都已經排好序。
4.1.1 栗子
從最後開始。發現逆序對就交換。
發現逆序對32 29
交換位置
發現逆序對34’ 29
交換位置
......第一輪比較結束後,選出了最小元素12
直接選擇排序中沒有這麼多次的交換,站在擂臺上的數不一定是最小數。
4.1.2 代碼實現
template<class Record>
void BubbleSort(Record Array[], int n){
bool NoSwap; //用來標識是否發生了交換
int i, j;
for(i = 0; i<n-1; i++){//比較n-1輪
NoSwap = true; //標識初始爲true
for(j = n-1; j>i;j--){
if(Array[j] < Array[j-1]){//判斷是否爲逆序對
swap(Array,j,j-1);//交換逆序對
NoSwap = false; //發生了交換,標誌爲false
}
}
if(NoSwap) //這一輪的元素都是排好序的,沒發生交換,直接退出冒泡排序
return;
}
}
4.1.3 算法實現
4.2 快速排序
- 選擇軸值pivot(核心)
- 將序列劃分成兩個子序列L和R,使得L中所有記錄都小於或等於pivot,R中記錄都大於pivot
- 對子序列L和R遞歸進行快速排序
- 基於分治策略
4.2.1 軸值的選擇(核心)
儘可能使得L,R長度相等
- 選擇首元素或尾元素作爲軸值(完全正序或完全逆序的輸入就會比較糟糕)
- 隨機選擇
- 選擇平均值(代價)
4.2.2 栗子
一次的分割過程
選擇32作爲軸值
然後把最右端的元素64填充進去,然後最右邊的位置就空出來了
接着從左邊開始看,將左邊的元素25與軸值32做比較。
25<32,不是逆序元素,i繼續往右走指向34,34>32,34填充到最右邊的位置。此時左邊空出了一個位置。
此時左邊空出了一個位置。移動右邊的j,從右邊找一個逆序元素29填入左邊的空位。
此時右邊空出了一個位置。從左邊找一個逆序元素45填入右邊的空位。
風水輪流轉,左邊有空位了。繼續移動右邊的j,從右邊找一個逆序元素12填入左邊的空位
最終
具體過程描述:
- 選擇軸值並存儲軸值(上述例子取中間記錄作爲軸值)
- 最後一個元素放到軸值位置
- 初始化下標 i, j,分別指向頭尾
- 遞增直到遇到比軸值大的元素,將此元素覆蓋到 j 的位置;j 遞減直到遇到比軸值小的元素,將此元素覆蓋到 i 的位置
- 重複上一步直到 i==j,將軸值放到 i 的位置,完畢
4.2.3 代碼實現
template<class Record>
void QuickSort(Record Array[], int left, int right){
//Array[]爲待排序數組,left,right分別爲數組兩端
if(right <= left) //如果只有0個或1個記錄,就不需要排序
return;
int pivot = SelectPivot(left,right); //選擇軸值
swap(Array,pivot,right); //軸值放到數組末端
pivot = Partition(Array,left,right); //分割後軸值正確
QuickSort(Array,left,pivot-1); //左子序列遞歸快排
QuickSort(Array,pivot+1,right); //右子序列遞歸快排
}
int SelectPivot(int left,int right){
//選擇軸值,參數left,right分別表示序列的左右端下標
return(left+right)/2; //選中間記錄作爲軸值
}
分割函數
template<class Record>
int Partition(Record Array[],int left,int right){
//分割函數,分割後軸值已到達正確位置
int l = left; //l爲左指針
int r = right; //r爲右指針
Record TempRecord = Array[r];//保存軸值
while(l != r){//l,r不斷向中間移動,直到相遇
//l指針向右移動,直到找到一個大於軸值的記錄
while(Array[l] <= TempRecord && r>l)
l++;
if(l<r)//未相遇,將逆置元素換到右邊空位
Array[r] = Array[l];
r--; //r指針向左移動一步
//r指針向左移動,直到找到一個小於軸值的記錄
while(Array[r] >= TempRecord && r>l){
r--;
if(l < r){ //未相遇,將逆置元素換到左空位
Array[l] = Array[r];
l++; //l指針向右移動一步
}
}//end while
Array[l] = TempRecord;//將軸值回填到分界位置上
return l; //返回分界位置l
}
4.2.4 算法分析
你們以爲這就結束了?天真!
學習自:
張銘《數據結構》