由于本帖只是阐述几种排序方法的原理、如何区分以及编写几种排序的简单代码,所以直接给定数组是 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);
}
其实快速排序不止这两种方法,像百度还提供了其他方法,主要是原理懂了就可以了
至此,就总结完了选择排序、冒泡排序、插入排序、快速排序,这四种排序方法,之后如果我学到了新的排序方法会继续更新的