由於本帖只是闡述幾種排序方法的原理、如何區分以及編寫幾種排序的簡單代碼,所以直接給定數組是 a[ ]={6,2,8,5,1},需要把以上5個數字按升序排列
1. 選擇排序法
(如果不想看解釋分析,直接往后拉看代碼)
實質:
第一輪:通過對比數組中前一個元素和后一個元素的大小,先找到數組中最小的數字,並且記錄它的下標。如果標記的下標不是第一個元素的下標,則交換兩個元素。
第二輪:從第二個元素開始(因為第一個元素已經是第一輪排好的“有序”元素),對比數組中前一個元素和后一個元素的大小,遍歷尋找數組中第二小的數字,並記錄它的下標。如果標記的下標不是第二個元素的下標,則交換兩個元素。
第三輪:從第三個元素開始(因為第一和第二個元素已經是第一、二輪排好的“有序”元素),對比數組中前一個元素和后一個元素的大小,遍歷尋找數組中第三小的數字,並記錄它的下標。如果標記的下標不是第三個元素的下標,則交換兩個元素。
第四輪:從第四個元素開始(因為第一、第二、第三個元素已經是第一、二、三輪排好的“有序”元素),對比數組中前一個元素和后一個元素的大小,遍歷尋找數組中第四小的數字,並記錄它的下標。如果標記的下標不是第四個元素的下標,則交換兩個元素。
第五輪:沒有第五輪(因為一共就五個數,排好四個數,自然就全部排好了,再多排一輪浪費了)
形象排序:
原始數據: |6 2 8 5 1
第一輪: 1 |2 8 5 6
第二輪: 1 2 |8 5 6
第三輪: 1 2 5 |8 6
第四輪: 1 2 5 6 |8
具體解釋:
先假設第一個數字6為最小的數字,那么記錄它的下標(index=0)
第一輪中:
第一次:先比較6和2(即標記的數和后面一個元素比較),發現2更小,則記錄2的下標(index=1)
第二次:比較2和8(標記的數和后面一個元素比較),發現還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和5(標記的數和更后面的數比較),發現還是2小,則下標還是2的下標不變(index=1)
第四次:比較2和1(標記的數和更后面的數比較),發現1比2小,則標記改為1的下標(index=4)
最后:index並不等於第一個元素的下標(0),則交換第一個元素和被標記的元素
第一個元素已經有序,則從第二個元素開始(並且把標記index初始化為1,代表第二個元素2)
第二輪中:
第一次:先比較2和8,還是2小,則下標還是2的下標不變(index=1)
第二次:比較2和5,還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和6,還是2小,則下標還是2的下標不變(index=1)
最后:index等於第二個元素的下標(1),則不用交換第二個元素和被標記的元素
第一、二個元素(1和2)已經有序,則從第三個元素開始(並且把標記index初始化為2,代表第三個元素8)
第三輪中:
第一次:先比較8和5,發現5比8小,則標記改為5的下標(index=3)
第二次:比較5和6,還是5小,則下標還是5的下標不變(index=3)
最后:index並不等於第三個元素的下標(2),則交換第三個元素和被標記的元素
第一、二、三個元素(1和2和5)已經有序,則從第四個元素開始(並且把標記index初始化為3,代表第四個元素8)
第四輪中:
第一次:先比較8和6,發現6比8小,則標記改為6的下標(index=4)
最后:index並不等於第四個元素的下標(3),則交換第四個元素和被標記的元素
代碼1(每輪都輸出,看怎么變化):
#include <iostream>
using namespace std;
void swap(int &a,int &b);//聲明一個交換函數
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=0;i<4;++i)//只要進行4輪比較就可以了
{
int index=i;//運行到第幾輪就初始化index為第幾個元素的下標
for(int j=i+1;j<5;++j)//從被標記的后一個數開始遍歷
if(a[index]>a[j])//找到最小元素的下標
index=j;
if(index!=i) swap(a[index],a[i]);//交換
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
}
return 0;
}
void swap(int &a,int &b)//交換函數的定義
{
int t;
t=a;
a=b;
b=t;
}
代碼2(排序完再輸出,直接看結果):
#include <iostream>
using namespace std;
void swap(int &a,int &b){int t=a;a=b;b=t;};//想縮短代碼,直接聲明和定義合在一起了
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=0;i<4;++i)//只要進行4輪比較就可以了
{
int index=i;//運行到第幾輪就初始化index為第幾個元素的下標
for(int j=i+1;j<5;++j)//從被標記的后一個數開始遍歷
if(a[index]>a[j])//找到最小元素的下標
index=j;
if(index!=i) swap(a[index],a[i]);//交換
}
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
return 0;
}
2. 冒泡排序法
原理:
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 每一輪對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最后一對。第一輪結束,最后的元素應該會是最大的數。
- 針對所有的元素重復以上的步驟n-1輪,分別倒序排好倒數第二大、倒數第三大……的元素。直到沒有任何一對數字需要比較。
形象排序:
原始數據: 6 2 8 5 1
第一輪: 2 6 5 1 |8
第二輪: 2 5 1 |6 8
第三輪: 2 1 |5 6 8
第四輪: 1 |2 5 6 8
具體解釋:
第一輪中:
第一次:比較第一個和第二個元素(即6和2),發現6比2大,則交換6和2(目前數字排列為 2 6 8 5 1)
第二次:比較第二個和第三個元素(即6和8),發現6比8小,則保持原樣(目前數字排列為 2 6 8 5 1)
第三次:比較第三個和第四個元素(即8和5),發現8比5大,則交換8和5(目前數字排列為 2 6 5 8 1)
第四次:比較第四個和第五個元素(即8和1),發現8比1大,則交換8和1(目前數字排列為 2 6 5 1 8)
最后:這樣,第一輪就把最大的元素放到了最右邊
最后一個元素已經有序,則第二輪不用比較最后一個元素和倒數第二個元素的大小(目前數字排列為 2 6 5 1 |8)
第二輪中:
第一次:比較第一個和第二個元素(即2和6),發現2比6小,則保持原樣(目前數字排列為 2 6 5 1 |8)
第二次:比較第二個和第三個元素(即6和5),發現6比5大,則交換6和5(目前數字排列為 2 5 6 1 |8)
第三次:比較第三個和第四個元素(即6和1),發現6比1大,則交換6和1(目前數字排列為 2 5 1 6 |8)
最后:這樣,第二輪就把倒數第二大的元素放到了倒數第二的位置
倒數兩個元素已經有序,則第三輪不用比較倒數第三個元素和倒數第二個元素的大小(目前數字排列為2 5 1 |6 8)
第三輪中:
第一次:比較第一個和第二個元素(即2和5),發現2比5小,則保持原樣(目前數字排列為 2 5 1 |6 8)
第二次:比較第二個和第三個元素(即5和1),發現5比1大,則交換5和1(目前數字排列為 2 1 5 |6 8)
最后:這樣,第三輪就把倒數第三大的元素放到了倒數第三的位置
倒數三個元素已經有序,則第四輪不用比較倒數第四個元素和倒數第三個元素的大小(目前數字排列為2 1 |5 6 8)
第四輪中:
第一次:比較第一個和第二個元素(即2和1),發現2比1大,則交換2和1(目前數字排列為 1 2 |5 6 8)
最后:這樣,第四輪就把倒數第四大的元素放到了倒數第四的位置,所有的元素就按升序排好了
代碼1(每輪都輸出,看怎么變化):
#include <iostream>
using namespace std;
void swap(int &a,int &b);//聲明一個交換函數
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=0;i<4;++i)//只要進行4輪比較就可以了
{
for(int j=0;j<5-i-1;++j)//每輪進行5-i-1次比較
if(a[j]>a[j+1]) swap(a[j],a[j+1]);//如果前一個比后一個大就交換
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
}
return 0;
}
void swap(int &a,int &b)//交換函數的定義
{
int t;
t=a;
a=b;
b=t;
}
代碼2(排序完再輸出,直接看結果):
#include <iostream>
using namespace std;
void swap(int &a,int &b){int t=a;a=b;b=t;};
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=0;i<4;++i)//只要進行4輪比較就可以了
{
for(int j=0;j<5-i-1;++j)//每輪進行5-i-1次比較
if(a[j]>a[j+1]) swap(a[j],a[j+1]);//如果前一個比后一個大就交換
}
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
return 0;
}
3. 插入排序法
原理:
插入排序法通過把數組中的元素插入到適當的位置來進行排序:
- 先假設第一個元素有序,則第一輪從第二個元素開始,作為待插入的數,往前依次比較,看往哪里插
- 第二輪把下一個元素(第三個)插入到其對應於已排序元素的排序位置
- 對於數組中的每個元素重復2步驟。即把第四個元素插入到適當位置,然后是第5個元素,等等。
形象排序:
原始數據: 6| 2 8 5 1
第一輪: 2 6| 8 5 1
第二輪: 2 6 8| 5 1
第三輪: 2 5 6 8| 1
第四輪: 1 2 5 6 8|
具體解釋:
假設第一個元素6是有序的,並且定義待插入的數int inserter=a[i],和定義下標index=i-1,用此下標來讓插入點與對應數比較
因為第一個數假設是有序的,則從第二個數開始作為待插入的數(inserter=a[1])
第一輪中:
第一次:把inserter與第一個元素比較(即2與6),發現2比6小,則把第一個元素后挪一個位置(目前數字排列為 6 6| 8 5 1)
最后:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 6| 8 5 1)
前面兩個元素已經有序,則第二輪把第三個元素插到有序元素中的適當位置,則實現前三個元素有序(目前數字排列為 2 6| 8 5 1)
第二輪中:(保存第三個元素inserter=a[2])
第一次:把inserter與第二個元素比較(即8與6),發現8比6大,則把第二個元素不做后挪(目前數字排列為 2 6 8| 5 1)
第二次:由於8比6大,所以肯定比2大,所以不需要再比了
最后:把inserter中保留的待插入的數插入到相應位置(對於本題,則還是原位置)(目前數字排列為 2 6 8| 5 1)
前面三個元素已經有序,則第三輪把第四個元素插到有序元素中的適當位置,則實現前四個元素有序(目前數字排列為 2 6 8| 5 1)
第三輪中:(保存第四個元素inserter=a[3])
第一次:把inserter與第三個元素比較(即5與8),發現5比8小,則把第三個元素后挪一個位置(目前數字排列為 2 6 8 8| 1)
第二次:把inserter與第二個元素比較(即5與6),發現5比6小,則把第二個元素后挪一個位置(目前數字排列為 2 6 6 8| 1)
第三次:把inserter與第一個元素比較(即5與2),發現5比2大,則把第一個元素不做后挪(目前數字排列為 2 6 6 8| 1)
最后:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 5 6 8| 1)
前面四個元素已經有序,則第四輪把第五個元素插到有序元素中的適當位置,則實現前五個元素有序(目前數字排列為 2 5 6 8| 1)
第五輪中:(保存第五個元素inserter=a[4])
第一次:把inserter與第四個元素比較(即1與8),發現1比8小,則把第四個元素后挪一個位置(目前數字排列為 2 5 6 8 8|)
第二次:把inserter與第三個元素比較(即1與6),發現1比6小,則把第三個元素后挪一個位置(目前數字排列為 2 5 6 6 8|)
第三次:把inserter與第二個元素比較(即1與5),發現1比5小,則把第二個元素后挪一個位置(目前數字排列為 2 5 5 6 8|)
第四次:把inserter與第一個元素比較(即1與2),發現1比2小,則把第一個元素后挪一個位置(目前數字排列為 2 2 5 6 8|)
最后:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 1 2 5 6 8|),完成排序
代碼1(每輪都輸出,看怎么變化):
#include <iostream>
using namespace std;
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=1;i<5;++i)//只要進行4輪比較就可以了
{
int inserter=a[i];
int index=i-1;//插入點初始化是inserter前面的一個元素
while(index>=0 && inserter<a[index])//尋找插入點
{
a[index+1]=a[index];
--index;//符合插入條件,插入點在往前移
}
a[index+1]=inserter;//插入
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
}
return 0;
}
代碼2(排序完再輸出,直接看結果):
#include <iostream>
using namespace std;
int main()
{
int a[]={6,2,8,5,1};
for(int i=0;i<5;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
for(int i=1;i<5;++i)//只要進行4輪比較就可以了
{
int inserter=a[i];
int index=i-1;//插入點初始化是inserter前面的一個元素
while(index>=0 && inserter<a[index])//尋找插入點
{
a[index+1]=a[index];
--index;//符合插入條件,插入點在往前移
}
a[index+1]=inserter;//插入
}
for(int k=0;k<5;++k)
cout<<a[k];
cout<<endl;
return 0;
}
4. 快速排序法
原理:
通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
- 先假設第一個元素為軸值,自右向左找一個比軸值小的數交換,再自左向右找一個比軸值大的數交換,再重復自右向左找一個比軸值小的數交換,自左向右找一個比軸值大的數交換,直到軸值左邊沒有比其大的數存在,右邊也沒有比其小的數存在,則第一輪結束。原來的一組數據被划分為以軸值為界的兩組新數據
- 第二輪:取上一輪軸值左邊的一組新數據,重復1的操作;取上一輪軸值右邊的一組新數據,重復1的操作,則把最初的一組數據分成了四部分,這樣便產生一個遞歸的思想
- 一直重復操作,直到數據被分的不可再分為止。
形象排序:
原始數據: |6| 2 8 5 1
第一輪: |1| 2 5 | 6 |8|
第二輪: 1 ||2| 5 | 6 | 8
第三輪: 1 | 2 | 5 | 6 | 8
具體解釋:
第一輪中:(先假設第一個元素6為軸值)
第一次:自右向左找一個比軸值(即6)小的數交換,正巧右邊的第一個數就比6小,則交換6和1(目前數字排列為 1 2 8 5 6)
第二次:自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,找左邊第三個數;左邊第三個數為8,比6大,則交換6和8(目前數字排列為 1 2 6 5 8)
第三次:再自右向左找一個比軸值(即6)小的數交換,右邊第一個數為8,不比6小,則找右邊第二個數;右邊第二個數為5,比6小,則交換6和5(目前數字排列為 1 2 5 6 8)
第四次:再自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,則找左邊第三個數;左邊第三個數為5,不比6大,則找左邊第四個數,結果第四個書就是軸值本身,則一輪循環停止(目前數字排列為 1 2 5 6 8)
最后:這樣,第一輪就把最初的一組元素{ 6 2 8 5 1 }分為兩組元素{ 1 2 5 }和{ 8 }(6為軸值,經歷這幾次遍歷,便已經固定其正確位置了,以后不需要再考慮這個元素)
第二輪中:
先考慮第一輪軸值(即6)左邊的數據 { 1 2 5 }:
第二輪中左邊新數據的第一輪:(先假設新數據的第一個元素1為新的軸值)自右向左找一個比軸值(即1)小的數交換,右邊第一個數為5,不比1小,則找右邊第二個數;右邊第二個數為2,不比1小,則找右邊第三個數,結果右邊第三個數就是軸值本身,則循環停止(目前數字排列為 1 2 5 ),同樣的循環已經固定軸值(即1)的位置
同時,軸值1的左邊沒有數據,即分到了不可再分的地步,那么遞歸結束,而軸值1的右邊還有數據 { 2 5 },則繼續確立新的軸值為2,再進行如上操作,直到分到不可以再分,則遞歸終止,最后可以確保第一輪的軸值(即6)左邊的新數據 { 1 2 5 }每個都被固定,則左邊數據的遞歸結束。
再考慮第一輪軸值(即6)右邊的數據 { 8 }:
已經被分到不可再分,則它的位置就已經被確定了,右邊數據的遞歸結束。
最終整組數據就排列完畢
代碼1(按我如上分析的快速排序法):
#include <iostream>
using namespace std;
void qsort(int[],int,int);//聲明排序函數
void swap(int &a,int &b){ int t=a;a=b;b=t;}//直接定義交換函數
int main()
{
int a[]={6,2,8,5,1};
int len=sizeof(a)/sizeof(int);//計算數組中元素的個數
for(int i=0;i<len;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
qsort(a,0,len-1);//調用排序函數
for(int i=0;i<len;++i)
cout<<a[i];
cout<<endl;
}
void qsort(int a[],int left,int right)
{
int index=left;//初始化軸值的下標為要排序數組的第一個元素
int pivot=a[left];//記錄軸值初始化為一組數據的第一個元素
int l=left,r=right;//因為要從右向左,從左向右遍歷,所以定義l,r作為可移動的指向判斷數的下標,可以想成移動的指針
//而left、right則為每個函數的最左最右的固定下標值
while(l<r)//這個循環是用於,當一個數據不可再分的時候就停止遞歸用的,
{ //比如{ 8 },它不可再分(即已經固定),它的l和它的r相等,不滿足循環條件,即停止遞歸
for(;l<r && a[r]>pivot;--r);//因為要從右向左遍歷,如果右邊的數字比軸值大,則r往前移一位,再比較
swap(a[r],a[index]);
index=r;//因為每次是交換a[r]與a[index]的值,所以要求index每次交換完要變為相應的下標值
for(;l<r && a[l]<=pivot;++l);//因為要從左向右遍歷,如果左邊的數字比軸值小,則l往后移一位,再比較
swap(a[l],a[index]);
index=l;//因為每次是交換a[l]與a[index]的值,所以要求index每次交換完要變為相應的下標值
}
if(left<index-1) qsort(a,left,index-1);//如果上一輪軸值前面的新數組可以再分,則重復調用函數進行遞歸
if(right>index+1) qsort(a,index+1,right);//如果上一輪軸值后面的新數組可以再分,則重復調用函數進行遞歸
}
代碼2(快速排序法的其他實現方法):
#include <iostream>
using namespace std;
void qsort(int[],int,int);//聲明排序函數
void swap(int &a,int &b){ int t=a;a=b;b=t;}//直接定義交換函數
int main()
{
int a[]={6,2,8,5,1};
int len=sizeof(a)/sizeof(int);//計算數組中元素的個數
for(int i=0;i<len;++i)
cout<<a[i];//打印原數組
cout<<endl<<endl;//空一行
qsort(a,0,len-1);//調用排序函數
for(int i=0;i<len;++i)
cout<<a[i];
cout<<endl;
}
void qsort(int a[],int left,int right)
{
int pivot=a[right],l=left,r=right;
while(l<r)
{
swap(a[l],a[r]);
while(l<r && a[r]>pivot) --r;
while(l<r && a[l]<=pivot) ++l;
}
swap(a[left],a[r]);
if(left<r-1) qsort(a,left,r-1);
if(r+1<right) qsort(a,r+1,right);
}
其實快速排序不止這兩種方法,像百度還提供了其他方法,主要是原理懂了就可以了
至此,就總結完了選擇排序、冒泡排序、插入排序、快速排序,這四種排序方法,之后如果我學到了新的排序方法會繼續更新的