八大排序算法之交换排序

来源:互联网 发布:自动点胶机编程 编辑:程序博客网 时间:2024/05/29 14:17

在之前的两篇博客中,我们分别说了插入排序和选择排序,有兴趣的同学还可以戳链接去看看八大排序算法之选择排序、八大排序算法之插入排序。

交换排序主要说得是冒泡排序和快速排序,思想就和名字一样是用交换来实现的。

1.冒泡排序

基本思想:在要排序的数组中,对当前还没排好序的范围内的全部数,进行依次比较,以升序为例,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

稳定性:因为是冒泡,所以不会改变相同元素的相对位置。

时间复杂度:O(N^2)

这里写图片描述

冒泡排序比较简单,这里就不画图描述了,直接上代码。

void BubbleSort(int *a, int size){    assert(a);    for (int i = 0; i < size; i++)    {        for (int j = 0; j < size - i-1; j++)        {            if (a[j]>a[j + 1])                swap(a[j], a[j + 1]);        }    }}

2.快速排序

快速排序是我们最常用的排序,这里我们说三种快排的方法:左右指针法、挖坑法、前后指针法。
<1>左右指针法

基本思想:以当前的某个数为基准,然后找出第一个比它大的,第一个比它小的,然后进行交换。两个指针向中间靠近,当左右指针相等的时候,这次循环就结束了。然后不断递归缩小区间,直到所有的区间都有序。

这里写图片描述

这里写图片描述

//以一个数为基准,然后找比它大的和小的,然后交换,直到这两个指针相遇//和中间的那个数进行交换,以这个数为界,左边的都小,右边的都大int PartSort1(int *a, int left, int right)  //左右指针法{          //优化:三数取中法    //int mid = getMid(a, left, right);    //swap(a[mid], a[right]);    int key = a[right];    int begin = left;    int end = right;    while (begin < end)    {        while (begin < end && a[begin] <= key)            ++begin;        while (begin < end && a[end] >= key)            --end;        if (begin < end)            swap(a[begin], a[end]);    }    swap(a[begin], a[right]);    return begin;}

如果当前数组是个和我们排序相反的有序数组,这么这种情况是快排的最差情况,此时我们可以考虑在选择基准的时候进行优化下。在这里,最简单的优化就是三数取中法。我们在left和right找到中间的位置,然后根据这个mid进行比较,最后在按照我们上面的继续执行。

//当数组有序的时候,快排的情况最差,这个时候可以采用三数取中法int getMid(int *a, int left, int right){    int mid = left + (right - left) >> 2;    if (a[left] < a[mid])   //left  mid    {        if (a[mid] < a[right])   //left  mid  right            return mid;        else if (a[left]>a[right])   //right  left  mid              return left;        else            return right;    }    else   //mid left    {        if (a[mid] > a[right])            return mid;        else if (a[left] < a[right])            return left;        else            return right;    }}

<2>挖坑法

基本思想:以最后一个数为基准,将这个基准保存在key中,从左找大于key的,找到了就和end交换;然后从右找小于key的,找到了就交换;最后把比key大/小的都放在了合适的位置上

这里写图片描述

一趟排序的过程如上,如果你还是不明白。就直接跳过去看代码就好了。

int PartSort2(int *a, int begin, int end)   //挖坑法{    int key = a[end];    while (begin < end)    {        while (begin < end && a[begin] <= key)            ++begin;        a[end] = a[begin];        while (begin < end && a[end] >= key)            --end;        a[begin] = a[end];    }    a[begin] = key;    return begin;}

<3>前后指针法

基本思想:也是在找比key大的或者小的,但这里它是从一个方向开始找的。用两个指针分别保存大于和小于,然后找到就进行交换。

这里就不画图了,直接用代码来说明思路。

int PartSort3(int *a, int begin, int end)  //前后指针法{    int prev = begin - 1;    int cur = begin;    while (cur < end)    {        if (a[cur] >= a[end])            cur++;        if (cur != prev && a[cur] < a[end])        {            prev++;            swap(a[cur], a[prev]);            cur++;        }    }    swap(a[++prev], a[end]);    return prev;}

可以看出,上面三种排序的思路都是一样的,先找出一个基准,然后通过查找比基准大的和小的来进行比较,通过比较,完成排序,然后不断缩小区间去排序。

void QuickSort(int *a, int left,int right){    assert(a);    if (left >= right)        return;    //int mid = PartSort1(a, left, right);    //int mid = PartSort2(a, left, right);    int mid = PartSort3(a, left, right);    QuickSort(a, left, mid - 1);    QuickSort(a, mid + 1, right);}

<4>快速排序的非递归实现

有时候进行笔试或者面试的时候,可能要求你写个快排但是不能使用递归。这个时候千万不能慌,一定要想清除快排的根本是什么,然后借助合适的数据结构来完成。

在上面实现的快排中,我们可以看出递归的部分是不断缩小区间的这个范围,假设现在我们是在操作系统内部,那么每次递归的时候就是创建一个新的栈帧的时候,所以可以联想到栈来实现。也就是说,我们可以利用栈来保存每次更改的区间。其他部分的思路还是一样的,想到这里是不是有种豁然开朗的感觉?话不多说,代码奉上。

void QuickSortNonR(int *a, int left, int right){    stack<int> s;    s.push(right);    s.push(left);    while (!s.empty())    {        //每次排序的区间        int begin = s.top();          s.pop();        int end = s.top();        s.pop();        int mid = PartSort3(a, begin,end);        //类似递归的部分,将每次的区间进行压栈        if (begin < mid - 1)           {            s.push(mid - 1);            s.push(begin);        }        if (mid + 1 < right)        {            s.push(end);            s.push(mid + 1);        }    }}

对快速排序来说,两个指针分别查找相较于基准值大或者小的,所以相同元素的相对位置可能会发生改变,即快排是不稳定的。

到现在为止,常见的排序我们已经说了6种,下篇博客将会说最后的两种,并且对所有的排序算法做个总结。