几种排序方法详解(选择排序、冒泡排序、插入排序、快速排序)

由于本帖只是阐述几种排序方法的原理、如何区分以及编写几种排序的简单代码,所以直接给定数组是 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. 冒泡排序法

原理

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 每一轮对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。第一轮结束,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤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. 插入排序法

原理
插入排序法通过把数组中的元素插入到适当的位置来进行排序:

  1. 先假设第一个元素有序,则第一轮从第二个元素开始,作为待插入的数,往前依次比较,看往哪里插
  2. 第二轮把下一个元素(第三个)插入到其对应于已排序元素的排序位置
  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. 先假设第一个元素为轴值,自右向左找一个比轴值小的数交换,再自左向右找一个比轴值大的数交换,再重复自右向左找一个比轴值小的数交换,自左向右找一个比轴值大的数交换,直到轴值左边没有比其大的数存在,右边也没有比其小的数存在,则第一轮结束。原来的一组数据被划分为以轴值为界的两组新数据
  2. 第二轮:取上一轮轴值左边的一组新数据,重复1的操作;取上一轮轴值右边的一组新数据,重复1的操作,则把最初的一组数据分成了四部分,这样便产生一个递归的思想
  3. 一直重复操作,直到数据被分的不可再分为止。

形象排序

原始数据: |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);
}

其实快速排序不止这两种方法,像百度还提供了其他方法,主要是原理懂了就可以了

至此,就总结完了选择排序、冒泡排序、插入排序、快速排序,这四种排序方法,之后如果我学到了新的排序方法会继续更新的

转载于:https://www.cnblogs.com/yuzilan/p/10626201.html

你可能感兴趣的:(几种排序方法详解(选择排序、冒泡排序、插入排序、快速排序))