几种排序算法的认识
所有排序算法總結(jié):冒泡排序,快速排序,插入排序,歸并排序,堆排序,shell排序,選擇排序
算法排序的穩(wěn)定性是指:關(guān)鍵碼相同的記錄排序前后相對位置不發(fā)生改變。舉個例子:
待排序數(shù)組:int a[] ={1, 2, 2, 3, 4, 5, 6};
在快速排序的隨機選擇比較子(即pivot)階段:
若選擇a[2](即數(shù)組中的第二個2)為比較子,,而把大于等于比較子的數(shù)均放置在大數(shù)數(shù)組中,則a[1](即數(shù)組中的第一個2)會到pivot的右邊, 那么數(shù)組中的兩個2非原序(這就是“不穩(wěn)定”)。
若選擇a[1]為比較子,而把小于等于比較子的數(shù)均放置在小數(shù)數(shù)組中,則數(shù)組中的兩個2順序也非原序。
1. 冒泡排序
很簡單的排序,外層循環(huán)是n-1趟,內(nèi)層循環(huán)是n-1次兩兩比較。主要思路:從底部往上冒泡,通過無序區(qū)中相鄰記錄關(guān)鍵字間的比較和位置的交換,使關(guān)鍵字最小的記錄如氣泡一般逐漸往上“漂浮”直至“水面”。
就是在每一趟內(nèi)層循環(huán)完畢之后,最小的那個值會像氣泡一樣上浮到第一個位置(從小到大排序),這樣循環(huán)執(zhí)行n-1趟,每一趟都是從最后一個值開始進(jìn)行兩兩比較,把每趟中的最小的值往上浮。(注意內(nèi)層循環(huán)的終止條件是j>i,因為i之前是已經(jīng)放置好的有序的最小值)
代碼:
static void bubblesort(int* A,int n){if(A==NULL) return;for (int i=0;i<n-1;++i){for (int j=n-1;j>i;--j){if(A[j]<A[j-1]){int temp=A[j];A[j]=A[j-1];A[j-1]=temp;}}cout<<"第"<<i+1<<"趟冒泡排序:"<<endl;for (int i=0;i<n;++i){cout<<A[i]<<" ";}cout<<endl;} }?
2. 歸并排序(穩(wěn)定,效率高,采用遞歸)
思路:基本思路就是將數(shù)組分成二組A,B,如果這二組組內(nèi)的數(shù)據(jù)都是有序的,那么就可以很方便的將這二組數(shù)據(jù)進(jìn)行排序。問題是如何讓這二組組內(nèi)數(shù)據(jù)有序呢?
可以將A,B組各自再分成二組。依次類推,當(dāng)分出來的小組只有一個數(shù)據(jù)時,可以認(rèn)為這個小組組內(nèi)已經(jīng)達(dá)到了有序,然后再合并相鄰的二個小組就可以了。這樣通過先遞歸的分解數(shù)列,再合并數(shù)列就完成了歸并排序。具體過程參考下圖:
代碼(先用遞歸分解數(shù)組,然后用mergearray合并):
//將有二個有序數(shù)列a[first...mid]和a[mid...last]合并。 void mergearray(int a[], int first, int mid, int last, int temp[]) {int i = first, j = mid + 1;//i為分出的第一個數(shù)組的第一個位置,j為分出來的第二個數(shù)組的第一個位置int m = mid,n = last;//m為分出的第一個數(shù)組的末尾,n為分出來的第二個數(shù)組的末尾int k = 0;while (i <= m && j <= n){if (a[i] <= a[j])temp[k++] = a[i++];elsetemp[k++] = a[j++];}while (i <= m)temp[k++] = a[i++];while (j <= n)temp[k++] = a[j++];for (i = 0; i < k; i++)a[first + i] = temp[i]; } void mergesort(int a[], int first, int last, int temp[]) {if (first < last){int mid = (first + last) / 2;mergesort(a, first, mid, temp); //左邊有序mergesort(a, mid + 1, last, temp); //右邊有序mergearray(a, first, mid, last, temp); //再將二個有序數(shù)列合并 } }bool MergeSort(int a[], int n) {int *p = new int[n];if (p == NULL)return false;mergesort(a, 0, n - 1, p);delete[] p;return true; }?
3.?堆排序(轉(zhuǎn)http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html)1.堆
??堆實際上是一棵完全二叉樹,其任何一非葉節(jié)點滿足性質(zhì):
? Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
? 即任何一非葉節(jié)點的關(guān)鍵字不大于或者不小于其左右孩子節(jié)點的關(guān)鍵字。
? 堆分為大頂堆和小頂堆,滿足Key[i]>=Key[2i+1]&&key>=key[2i+2]稱為大頂堆,滿足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱為小頂堆。由上述性質(zhì)可知大頂堆的堆頂?shù)年P(guān)鍵 字肯定是所有關(guān)鍵字中最大的,小頂堆的堆頂?shù)年P(guān)鍵字是所有關(guān)鍵字中最小的。
2.堆排序的思想
?? 利用大頂堆(小頂堆)堆頂記錄的是最大關(guān)鍵字(最小關(guān)鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。
??? 其基本思想為(大頂堆):
??? 1)將初始待排序關(guān)鍵字序列(R1,R2....Rn)構(gòu)建成大頂堆,此堆為初始的無序區(qū);
??? 2)將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(qū)(R1,R2,......Rn-1)和新的有序區(qū)(Rn),且滿足R[1,2...n-1]<=R[n];?
??? 3)由于交換后新的堆頂R[1]可能違反堆的性質(zhì),因此需要對當(dāng)前無序區(qū)(R1,R2,......Rn-1)調(diào)整為新堆,然后再次將R[1]與無序區(qū)最 后一個元素交換,得到新的無序區(qū)(R1,R2....Rn-2)和新的有序區(qū)(Rn-1,Rn)。不斷重復(fù)此過程直到有序區(qū)的元素個數(shù)為n-1,則整個排 序過程完成。
??? 操作過程如下:
???? 1)初始化堆:將R[1..n]構(gòu)造為堆;
???? 2)將當(dāng)前無序區(qū)的堆頂元素R[1]同該區(qū)間的最后一個記錄交換,然后將新的無序區(qū)調(diào)整為新的堆。
??? 因此對于堆排序,最重要的兩個操作就是構(gòu)造初始堆和調(diào)整堆,其實構(gòu)造初始堆事實上也是調(diào)整堆的過程,只不過構(gòu)造初始堆是對所有的非葉節(jié)點都進(jìn)行調(diào)整。
????下面舉例說明:
???? 給定一個整形數(shù)組a[]={16,7,3,20,17,8},對其進(jìn)行堆排序。
??? 首先根據(jù)該數(shù)組元素構(gòu)建一個完全二叉樹,得到
?然后需要構(gòu)造初始堆,則從最后一個非葉節(jié)點開始調(diào)整,調(diào)整過程如下:20和16交換后導(dǎo)致16不滿足堆的性質(zhì),因此需重新調(diào)整
這樣就得到了初始堆。
即每次調(diào)整都是從父節(jié)點、左孩子節(jié)點、右孩子節(jié)點三者中選擇最大者跟父節(jié)點進(jìn)行交換(交換之后可能造成被交換的孩子節(jié)點不滿足堆的性質(zhì),因此每次交換之后要重新對被交換的孩子節(jié)點進(jìn)行調(diào)整)。有了初始堆之后就可以進(jìn)行排序了。此時3位于堆頂不滿堆的性質(zhì),則需調(diào)整繼續(xù)調(diào)整
這樣整個區(qū)間便已經(jīng)有序了。 從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,為了從R[1...n]中選擇最大記錄,需比較n-1次,然后 從R[1...n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經(jīng)在前面的n-1次比較中已經(jīng)做過,而樹形選擇排序恰好利用樹形的 特點保存了部分前面的比較結(jié)果,因此可以減少比較次數(shù)。對于n個關(guān)鍵字序列,最壞情況下每個節(jié)點需比較log2(n)次,因此其最壞情況下時間復(fù)雜度為 nlogn。堆排序為不穩(wěn)定排序,不適合記錄較少的排序。 測試程序 /*堆排序(大頂堆) 2011.9.14*/#include <iostream>
#include<algorithm>
using namespace std;
void HeapAdjust(int *a,int i,int size) //調(diào)整堆
{
int lchild=2*i; //i的左孩子節(jié)點序號
int rchild=2*i+1; //i的右孩子節(jié)點序號
int max=i; //臨時變量
if(i<=size/2) //如果i是葉節(jié)點就不用進(jìn)行調(diào)整
{
if(lchild<=size&&a[lchild]>a[max])
{
max=lchild;
}
if(rchild<=size&&a[rchild]>a[max])
{
max=rchild;
}
if(max!=i)
{
swap(a[i],a[max]);
HeapAdjust(a,max,size); //避免調(diào)整之后以max為父節(jié)點的子樹不是堆
}
}
}
void BuildHeap(int *a,int size) //建立堆
{
int i;
for(i=size/2;i>=1;i--) //非葉節(jié)點最大序號值為size/2
{
HeapAdjust(a,i,size);
}
}
void HeapSort(int *a,int size) //堆排序
{
int i;
BuildHeap(a,size);
for(i=size;i>=1;i--)
{
//cout<<a[1]<<" ";
swap(a[1],a[i]); //交換堆頂和最后一個元素,即每次將剩余元素中的最大者放到最后面
//BuildHeap(a,i-1); //將余下元素重新建立為大頂堆
HeapAdjust(a,1,i-1); //重新調(diào)整堆頂節(jié)點成為大頂堆
}
}
int main(int argc, char *argv[])
{
//int a[]={0,16,20,3,11,17,8};
int a[100];
int size;
while(scanf("%d",&size)==1&&size>0)
{
int i;
for(i=1;i<=size;i++)
cin>>a[i];
HeapSort(a,size);
for(i=1;i<=size;i++)
cout<<a[i]<<"";
cout<<endl;
}
return 0;
} 4.快速排序 思路:主要patition函數(shù)的編寫,即先選一個值作為哨兵,然后把小于該哨兵的數(shù)字放在其左邊(后稱左邊序列),然后把大于哨兵的數(shù)字放在右邊(后稱右邊序列)。該函數(shù)有幾種實現(xiàn)方法,請參見http://www.cnblogs.com/mfryf/archive/2012/08/06/2625300.html 該函數(shù)用small指針來表示左邊序列的最后一個位置,當(dāng)發(fā)現(xiàn)某個值A(chǔ)[i]小于哨兵時,只需先對該small指針后移一位(移到右邊序列的第一位),然后將A[small]和A[i]互換即可,換完之后,small指針仍然指向左邊序列最后一個位置。 所以說,數(shù)組A,在進(jìn)行一輪快速排序的時候,實際上有一個哨兵和三個序列,左邊序列,右邊序列,待排序列。 具體如圖所示: 5.直接插入排序(最好O(n),最壞O(n2))參考博文 思路:把一個序列分為兩個區(qū),前部分是已排好的序列sorted,而后部分則是待排序列wait_sort,插入排序就是選擇wait_sort中的第一個元素,直接插入到sorted序列中。 假設(shè)要插入元素a[i],則插入的步驟分為3步:1>在sorted序列中找到要插入元素的位置j。2>將位置j之后的序列往后移動一位,給帶插入的a[i]空出一個位置。3>把元素a[i]插入到sorted序列中。
設(shè)數(shù)組為a[0…n-1]。
1.????? 初始時,a[0]自成1個有序區(qū),無序區(qū)為a[1..n-1]。令i=1
2.????? 將a[i]并入當(dāng)前的有序區(qū)a[0…i-1]中形成a[0…i]的有序區(qū)間。
3.????? i++并重復(fù)第二步直到i==n-1。排序完成。
?
下面給出嚴(yán)格按照定義書寫的代碼(由小到大排序):
void Insertsort1(int a[], int n) {int i, j, k;for (i = 1; i < n; i++){//為a[i]在前面的a[0...i-1]有序區(qū)間中找一個合適的位置for (j = i - 1; j >= 0; j--)if (a[j] < a[i])break;//如找到了一個合適的位置if (j != i - 1){//將比a[i]大的數(shù)據(jù)向后移int temp = a[i];for (k = i - 1; k > j; k--)a[k + 1] = a[k];//將a[i]放到正確位置上a[j+1] = temp;}} }
這樣的代碼太長了,不夠清晰。現(xiàn)在進(jìn)行一下改寫,將搜索和數(shù)據(jù)后移這二個步驟合并。即每次a[i]先和前面一個數(shù)據(jù)a[i-1]比較,如果a[i] > a[i-1]說明a[0…i]也是有序的,無須調(diào)整。否則就令j=i-1,temp=a[i]。然后一邊將數(shù)據(jù)a[j]向后移動一邊向前搜索,當(dāng)有數(shù)據(jù)a[j]<a[i]時停止并將temp放到a[j + 1]處。
void Insertsort2(int a[], int n) {int i, j;for (i = 1; i < n; i++)if (a[i] < a[i - 1]){int temp = a[i];for (j = i - 1; j >= 0 && a[j] > temp; j--)a[j + 1] = a[j];a[j + 1] = temp;} }?
?
轉(zhuǎn)載于:https://www.cnblogs.com/fightformylife/p/4335390.html
總結(jié)
- 上一篇: 数学之美笔记(二十)
- 下一篇: 【流量劫持】躲避 HSTS 的 HTTP