【算法】排序算法及其应用总结

来源:互联网 发布:linux zip 编辑:程序博客网 时间:2024/06/05 08:51

2017/11/21
更新基数排序
添加在博客最后面


2017/11/13

最近重温了几大排序算法,包括冒泡排序、插入排序、选择排序、随机快排、归并排序、堆排序、桶排序(后续还会补充)。

另外添加了几个与排序相关的扩展应用。

比如求小和问题、逆序对问题,可以用归并排序解决。
最大差值问题可以用桶排序解决。
后面具体解释一下每个问题为什么这样做,以及如何做。


0、交换函数

void myswap(int &a, int &b){    a = a^b;    b = a^b;    a = a^b;}void myswap1(int &a, int &b){    int temp = a;    a = b;    b = temp;}void myswap2(vector<int>&a,int i, int j){    int temp = a[i];    a[i] = a[j];    a[j] = temp;}

1、冒泡排序

时间复杂度:O(N^2)
额外空间复杂度:O(1)
是否可实现稳定性:是

从前依次向后比较,碰到比其大的就进行交换,直到到达此次比较的末尾,则得到此次比较的全局最大值,比较限界的末尾向前滑动。然后重复此过程。

eg:

5 3 1 9 7第一次排序:3 5 1 9 7  (5比3大,交换)3 1 5 9 7  (5比1大,交换)3 1 5 9 7  (5比9小,不交换)3 1 5 7 9  (9比7大,交换)此时,全局最大值9已经到达末尾。除去9,从第一个3再进行第二次比较即可。

代码:

class Bubble{public:    void BubbleSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = a.size() - 1; i > 0; i--)        {            for (int j = 0; j < i; j++)            {                if (a[j]>a[j + 1])                    myswap(a[j], a[j + 1]);            }        }    }};

2、插入排序

时间复杂度:O(N^2)
额外空间复杂度:O(1)
是否可实现稳定性:是

每次从未排好序的数列中拿第一个p,往已排好序的数列中插入,插入的过程是从已排好序的数列末尾向前依次比较,如果p比其小,则交换后继续往前比较,否则,不交换,此次排序结束。类似摸扑克牌。

eg:

1 3 7 9 51 3 7 9 已排好序,将5插入:1 3 7 5 9  (9比5大,交换)1 3 5 7 9  (7比5大,交换)1 3 5 7 9  (5比3大,不交换)

代码:

class Insert{public:    void InsertSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 1; i < a.size(); i++)        {            for (int j = i - 1; j >= 0; j--)            {                if (a[j + 1] < a[j])                    myswap(a[j], a[j + 1]);            }        }    }};

3、选择排序

时间复杂度:O(N^2)
额外空间复杂度:O(1)
是否可实现稳定性:否

从未排好序的数列中找到最小的值,将这个值放在已排好序的数列末尾,即与未排好序的首个值交换即可。找最小值只要依次比较即可。

eg:

5 3 1 9 7排序:1 3 5 9 7  (找到1是最小值,与5交换)1 3 5 9 7  (找到3是最小值,与3交换)1 3 5 9 7  (找到5是最小值,与5交换)1 3 5 7 9  (找到7是最小值,与9交换)此时,未排序数列只剩一个数值,而这个数值定是全局最大值,此时排序已完成。

代码:

class Select{public:    void SelectSort1(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 0; i < a.size() - 1;i++)        {            for (int j = i + 1; j < a.size();j++)            {//边比较边交换                if (a[j] < a[i])                    myswap(a[j], a[i]);            }        }    }    void SelectSort2(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 0; i < a.size() - 1; i++)        {//找到最小值的下标再交换            int temp = i;            for (int j = i + 1; j < a.size(); j++)                temp = a[j] < a[temp] ? j : temp;            myswap1(a[i], a[temp]);            //此处有问题,i有可能等于temp,此时用异或交换有问题了            //如果不用异或交换,正常交换还是对的。        }    }};

4、随机快速排序

时间复杂度:O(Nlog(N))
额外空间复杂度:O(log(N))
是否可实现稳定性:否

1、将数列划分为3个区域,小于区、等于区、大于区。

2、随机选取一个值,作为比较的基准,将其与数列末尾交换。

3、设置小于区指针less(起始时less=-1,因为此时小于区还不存在)
设置大于区指针more(起始时more==基准值的位置,因为此时大于区也不存在)

4、取指针l进行遍历,依次比较数列:

5、比这个值小的,放在小于区末尾,即a[++less]与a[l++]交换,并且l指针++向前移动。

6、等于这个值的,小于区、大于区的指针都不需要移动,遍历指针l++即可。

7、比这个值大的,放在大于区开头,即a[–more]与a[l]交换,此时的l指针并不移动。

8、当l指针到达大于区的位置时,比较结束。

9、此时的less+1是等于区首位置,more是等于区末位置。

10、基准值与大于区第一个值进行交换,这样,数列完成了小于区、等于区、大于区的分区。

11、然后递归在小于区、大于区进行上述过程即可。

eg:

5 3 1 9 7第一次排序:随机选取5作为基准值,7 3 1 9 5  (5与末尾7交换)9 3 1 7 5  (7比5大,与9交换)1 3 9 7 5  (9比5大,与1交换)1 3 9 7 5  (1比5小,与1交换)1 3 9 7 5  (3比5小,与3交换)此时,到达大于区位置,停止比较。将大于区第一个值9与5交换。1 3 5 7 9

代码:

class Quick{public:    void QuickSort(vector<int>&a)    {        if (a.size() < 2)            return;        QuickSort(a, 0, a.size() - 1);    }    void QuickSort(vector<int>&a, int l, int r)    {        if (l < r)        {            srand((int)time(NULL));            int temp = rand() % (r - l + 1);            myswap1(a[l + temp], a[r]);            //myswap2(a, l + temp, r);            vector<int>p = pattern(a, l, r);            QuickSort(a, l, p[0] - 1);            QuickSort(a, p[0] + 1, r);        }    }    vector<int> pattern(vector<int>&a,int l,int r)    {        vector<int>p;        int less = l - 1;        int more = r;        while (l<more)        {            if (a[l] < a[r])                myswap1(a[++less], a[l++]);            //myswap2(a, ++less, l++);            else if (a[l]>a[r])                myswap1(a[--more], a[l]);                //myswap2(a, --more, l);            else                l++;        }        myswap1(a[r], a[more]);        //myswap2(a, r, more);        p.push_back(less + 1);        p.push_back(more);        return p;    }};

5、归并排序

时间复杂度:O(Nlog(N))
额外空间复杂度:O(N)
是否可实现稳定性:是

二划分和合并的过程。

1、首先划分划分划分,一直划分到不能划分,即每个组都只有一个数值。

2、然后合并,合并的过程就是每个二划分排序的过程。

3、在合并的时候,开辟一个辅助数组,其大小等于这两个合并数列的大小。

4、设置两个指针分别指向每个数列的首部,然后比较得到其中较小的值,并将这个值放入辅助数组中。然后取出小值的那个数列的指针可以继续向前走,与另一个数列的指针所指向的值继续比较。

5、这样比较完成后,如果两个数列中有个数列的数值有剩余,即其指针没有走到末尾,则将这个数列直接赋到辅助数组末尾即可。

6、然后将辅助数组中的值拷贝回原数组中刚才合并的那两个数列的位置上。

eg:

3 7        5 9合并:设置辅助数组:3  (5比3大,3放入数组中)3 5  (7比5大,5放入数组中)3 5 7   (9比7大,7放入数组中)3 5 7 9  (数列1结束,直接将数列2放入辅助数组末尾,即9放入数组中)完成一次合并排序。下一次,3 5 7 9 可以和 1 4 6 8进行合并排序。

代码:

class Merge{public:    void MergeSort(vector<int>&a)    {        if (a.size() < 2)            return;        MergeSort(a, 0, a.size() - 1);    }    void MergeSort(vector<int>&a, int l, int r)    {        if (l == r)            return;        int mid = l + (r - l) / 2;        MergeSort(a, l, mid);        MergeSort(a, mid + 1, r);        merge(a, l, mid, r);    }    void merge(vector<int>&a, int l, int mid, int r)    {        vector<int>help(r - l + 1);        int i = 0;        int p1 = l;        int p2 = mid + 1;        while (p1<=mid && p2<=r)            help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];        while (p1 <= mid)            help[i++] = a[p1++];        while (p2 <= r)            help[i++] = a[p2++];        for (i = 0; i < help.size(); i++)            a[l + i] = help[i];    }};

6、堆排序

时间复杂度:O(Nlog(N))
额外空间复杂度:O(1)
是否可实现稳定性:否

主要依据一个大根堆。

1、首先建立大根堆。建立大根堆的过程就是依次插入比较,当插入到i时,比较其是否比父节点(i-1)/2的值大,如果大,则交换两个节点,i节点更新为其父节点(i-1)/2,继续比较。直到该节点不再大于父节点,表示此时的大根堆已经完成。继续下一个节点的插入建立。

2、当建立好大根堆后,就可以开始排序。首先设置一个size值,用来表示未排序的范围。将堆顶与未排序的末尾交换,则此时未排序的末尾与已排好序的数列已经形成有序。交换后,堆顶不再是最大元素,需要进行调整使其保持大根堆。排序的过程就是堆顶与未排序的末尾不断交换,交换后调整使其仍为大根堆。

3、在交换后如何调整使其仍为大根堆,首先找到此时i的左节点,如果左节点未超过未排序范围size,则进入循环,如果右节点存在,且右节点的值比左节点的值大,则i与其右节点交换,否则与左节点交换。交换后i更新为交换后的子节点,继续与其子节点比较,直到到达size。

eg:

这里写图片描述

代码:

class Heap{public:    void HeapSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 1; i < a.size(); i++)            HeapInsert(a, i);        int size = a.size();        myswap1(a[0], a[--size]);        while (size>0)        {            Heapify(a, 0, size);            myswap1(a[0], a[--size]);        }    }    void Heapify(vector<int>&a,int index,int size)    {        int left = 2 * index + 1;        while (left<size)        {            int largest = left + 1 < size && a[left]<a[left + 1] ? left + 1 : left;            largest = a[largest] > a[index] ? largest : index;            if (index == largest)                break;            myswap1(a[largest], a[index]);            index = largest;            left = 2 * index + 1;        }    }    void HeapInsert(vector<int>&a,int index)    {        while (a[index] > a[(index - 1) / 2])        {            myswap(a[index], a[(index - 1) / 2]);            index = (index - 1) / 2;        }           }};

7、桶排序

时间复杂度:O(N)
额外空间复杂度:O(N)
是否可实现稳定性:是

桶排序不是基于比较的排序,其排序与数的个数无关,但对于数据的位数和范围有限制。

1、首先基于数据的范围创建相应大小的辅助数组help。即遍历找到数组的最大值,则辅助数组的大小即为最大值+1,且初始化为0。

2、辅助数组的每个位置i放置原数组中i的个数。

3、遍历辅助数组,根据位置i中的值,依次向原数组填入help[i]个i值。

eg:

5 3 1 9 7 5得到最大值9,则辅助数组大小为10.0 0 0 0 0 0 0 0 0 0  (初始化为0)0 1 0 1 0 2 0 1 0 1  (1有1个,3有1个,5有两个,7、9各有一个)填入原数组,1 3 5 5 7 9

代码:

class Bucket{public:    void BucketSort(vector<int>&a)    {        if (a.size() < 2)            return;        int imax = INT_MIN;        for (int i = 0; i < a.size(); i++)            imax = a[i]>imax ? a[i] : imax;        vector<int>buckets(imax + 1, 0);        for (int i = 0; i < a.size(); i++)            buckets[a[i]]++;        int j = 0;        for (int i = 0; i < buckets.size();i++)        {            while (buckets[i]-->0)                a[j++] = i;        }    }};

8、总代码

1、头文件mysort.h

#include <iostream>#include <vector>#include<time.h> #include <math.h>#include <stdlib.h>using namespace std;/*2017/11/11冒泡排序、插入排序、选择排序、随机快排2017/11/12归并排序、堆排序、桶排序*/#define Random(x) (rand() % x)void myswap(int &a, int &b){    a = a^b;    b = a^b;    a = a^b;}void myswap1(int &a, int &b){    int temp = a;    a = b;    b = temp;}void myswap2(vector<int>&a,int i, int j){    int temp = a[i];    a[i] = a[j];    a[j] = temp;}class Bubble{public:    void BubbleSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = a.size() - 1; i > 0; i--)        {            for (int j = 0; j < i; j++)            {                if (a[j]>a[j + 1])                    myswap(a[j], a[j + 1]);            }        }    }};class Insert{public:    void InsertSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 1; i < a.size(); i++)        {            for (int j = i - 1; j >= 0; j--)            {                if (a[j + 1] < a[j])                    myswap(a[j], a[j + 1]);            }        }    }};class Select{public:    void SelectSort1(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 0; i < a.size() - 1;i++)        {            for (int j = i + 1; j < a.size();j++)            {//边比较边交换                if (a[j] < a[i])                    myswap(a[j], a[i]);            }        }    }    void SelectSort2(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 0; i < a.size() - 1; i++)        {//找到最小值的下标再交换            int temp = i;            for (int j = i + 1; j < a.size(); j++)                temp = a[j] < a[temp] ? j : temp;            myswap1(a[i], a[temp]);            //此处有问题,i有可能等于temp,此时用异或交换有问题了            //如果不用异或交换,正常交换还是对的。        }    }};class Quick{public:    void QuickSort(vector<int>&a)    {        if (a.size() < 2)            return;        QuickSort(a, 0, a.size() - 1);    }    void QuickSort(vector<int>&a, int l, int r)    {        if (l < r)        {            srand((int)time(NULL));            int temp = rand() % (r - l + 1);            myswap1(a[l + temp], a[r]);            //myswap2(a, l + temp, r);            vector<int>p = pattern(a, l, r);            QuickSort(a, l, p[0] - 1);            QuickSort(a, p[0] + 1, r);        }    }    vector<int> pattern(vector<int>&a,int l,int r)    {        vector<int>p;        int less = l - 1;        int more = r;        while (l<more)        {            if (a[l] < a[r])                myswap1(a[++less], a[l++]);            //myswap2(a, ++less, l++);            else if (a[l]>a[r])                myswap1(a[--more], a[l]);                //myswap2(a, --more, l);            else                l++;        }        myswap1(a[r], a[more]);        //myswap2(a, r, more);        p.push_back(less + 1);        p.push_back(more);        return p;    }};class Merge{public:    void MergeSort(vector<int>&a)    {        if (a.size() < 2)            return;        MergeSort(a, 0, a.size() - 1);    }    void MergeSort(vector<int>&a, int l, int r)    {        if (l == r)            return;        int mid = l + (r - l) / 2;        MergeSort(a, l, mid);        MergeSort(a, mid + 1, r);        merge(a, l, mid, r);    }    void merge(vector<int>&a, int l, int mid, int r)    {        vector<int>help(r - l + 1);        int i = 0;        int p1 = l;        int p2 = mid + 1;        while (p1<=mid && p2<=r)            help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];        while (p1 <= mid)            help[i++] = a[p1++];        while (p2 <= r)            help[i++] = a[p2++];        for (i = 0; i < help.size(); i++)            a[l + i] = help[i];    }};class Heap{public:    void HeapSort(vector<int>&a)    {        if (a.size() < 2)            return;        for (int i = 1; i < a.size(); i++)            HeapInsert(a, i);        int size = a.size();        myswap1(a[0], a[--size]);        while (size>0)        {            Heapify(a, 0, size);            myswap1(a[0], a[--size]);        }    }    void Heapify(vector<int>&a,int index,int size)    {        int left = 2 * index + 1;        while (left<size)        {            int largest = left + 1 < size && a[left]<a[left + 1] ? left + 1 : left;            largest = a[largest] > a[index] ? largest : index;            if (index == largest)                break;            myswap1(a[largest], a[index]);            index = largest;            left = 2 * index + 1;        }    }    void HeapInsert(vector<int>&a,int index)    {        while (a[index] > a[(index - 1) / 2])        {            myswap(a[index], a[(index - 1) / 2]);            index = (index - 1) / 2;        }           }};class Bucket{public:    void BucketSort(vector<int>&a)    {        if (a.size() < 2)            return;        int imax = INT_MIN;        for (int i = 0; i < a.size(); i++)            imax = a[i]>imax ? a[i] : imax;        vector<int>buckets(imax + 1, 0);        for (int i = 0; i < a.size(); i++)            buckets[a[i]]++;        int j = 0;        for (int i = 0; i < buckets.size();i++)        {            while (buckets[i]-->0)                a[j++] = i;        }    }};

2、main.cpp

#include <iostream>#include "sort_mydefine.h"using namespace std;/*2017/11/11冒泡排序、插入排序、选择排序、随机快排2017/11/12归并排序、堆排序、桶排序*/#if 1void main(){    vector<int>a = { 1, 1, 6, 4, 5, 7, 2, 14, 6, 14};    //Bubble b;//冒泡排序    //b.BubbleSort(a);      //Insert b;//插入排序    //b.InsertSort(a);    //Select b;//选择排序    //b.SelectSort2(a);    //Quick b;//随机快排    //b.QuickSort(a);    //Merge b;//归并排序    //b.MergeSort(a);    //Heap b;//堆排序    //b.HeapSort(a);    Bucket b;//堆排序    b.BucketSort(a);    for (int i = 0; i < a.size(); i++)        cout << a[i] << " ";    cout << endl;    system("pause");}#else#endif

9、应用

1、小和问题
如果每个数字左边比其小的数的相加之和称为小和。
求一个数列重所有的小和之和。
见博客。

2、逆序对问题
求一个数列中所有顺序为逆序的对数有几个。
逆序为大的值在小的值之前。
见博客。

3、最大差值问题。
求一个排好序的数列中,相邻数之间的差值最大的那个差值是多少。
见博客。

4、比较器的使用
见博客。

10、基数排序

时间复杂度:O(RN)
额外空间复杂度:O(N)
是否可实现稳定性:是

基数排序,是按照每一位上的数字进行排序的。首先找出数组中最大的那个数,获取这个数有多少位,最大数有多少位就要进行多少次遍历。

1、首先按照个位数上的数字进行排序,将数字依次放入对应的0-9的桶中。

2、然后出桶,这里要注意,出桶顺序在整个循环中要保持一致,比如先进先出,或者先进后出,只能选一种方式出桶。出桶后复制回原数组。

3、对此时的数组根据十位数的数字进行排序,依次入桶出桶,复制回原数组。

4、所有位数遍历结束,原数组已经排好序。

eg:

100 023 015 005 031个位数排序:桶:  0    1    2   3   4   5   6  7  8  9 入桶:100  031     023     015                          005出桶:100 031 023 015 005十位数排序:桶:  0    1     2    3   4   5   6  7  8  9 入桶:100  015  023   031       005出桶:100 005 015 023 031百位数排序:桶:  0     1    2   3   4   5   6  7  8  9 入桶:005  100     015     023     031       出桶:005 015 023 031 100排序结束

然后进行优化,不需要准备0-9的10个桶,只需要准备一个桶bucket,其大小等于要排序的数组大小即可。

1、如何做,准备一个count数组,大小为10,存放当每位数0-9上各有多少个数。然后对count数组进行累加操作count[i] += count[i - 1],这样做完之后,count中最大的数一定是原排序数组的大小。这样做的原因是记录存放在桶bucket的位置。

2、然后反向遍历原排序数组(反向的原因是数字从大到小的顺序向前放入bucket),对每一个数num,获取此次遍历位数上的数字p,找到count[p],说明加上num前面有count[p]个数,所以将num放入桶bucket[count[p]-1]中,然后count[p]–。

3、最后再把bucket桶中的数复制回原数组。

4、然后进行下一次位数上的遍历。

再解释一下为什么用count累加和逆序遍历数组。

1、准备一个桶,每一次填充是填充在对应累计的count个数中,我们已经关注的低位里面最大的那个数(第一次关注个位,第二次关注十位),比如第一次填充中,此时比较值是个位数,count[5]=8,我们访问到了个位数为5的某个数假设为15好了,我们可以认为15是前面累计8个数里面最大的,然后放在buket[8-1]的位置,然后count[5]–,我们接下来遇到了25好了,注意我们只关注个位数目前,所以这个也是前面7个数最大的,(因为目前只关注个位,所以15和25都是最大的),然后count[7-1]放25,count[5]–,等于6,如果count[4]==6,说明个位为5的已经没有了,接下来个位为4的就是最大的了.

2、好,接下来是第二次,我们开始关注十位了,所以我们现在找到的是个位和十位一起最大的数,接着刚才的例子,假设还有一个14好了,那么按照我们的规则,只看14,15,25,这三个值,目前的排序是14,25,15,

3、我们关注十位的时候,从后往前,因为从后往前查找的过程中,保证了是在前面排序的基础上从大往小找,因为我们是要填充count[i]对应的个数里面最大的那个,假设count[1]为3好了,从后往前,我们先找到是15,15放在bucket[3-1]的位置,count[1]–,再找到14,放在buket[2-1]的位置,满足找到的是已经关注低位的最大的数.

4、相反,如果我们从前往后找,那么先找到的是14,放在buket[3-1]的位置,count[1]–,再找到15,放在buket[2-1]的位置,那么.15就在14的前面了.也可以这么简单的理解,,就是我们每次往桶里对应放的时候,都是从后往前放(对应的index是一直–的),那么自然就是先放大的了,先前排序的过程中,能够保障,已经关注的低位中,较大的在后面,所以我们要从后往前遍历.

代码:

class Radix{public:    void RadixSort(vector<int>&a)    {        if (a.size() < 2)            return;        radix(a, 0, a.size() - 1, maxbits(a));    }       void radix(vector<int>&a,int start,int end,int digits)    {        vector<int>bucket(end - start + 1);//准备一个桶,长度是原数组大小        int num;//获取一个数x在位数digit上的数字        for (int d = 1; d <= digits;d++)        {            vector<int>count(10,0);//记录每个0-9上各有多少个数,初始化为0            for (int i = start; i <= end;i++)            {                num = getnum(a[i], d);//获得a[i]在d位上的数字                count[num]++;//这个数字个数增1            }            for (int i = 1; i < count.size();i++)//规定入桶的顺序,从后面往前入                 count[i] += count[i - 1];//累加和            for (int i = end; i >= start;i--)//从后面往前出            {                num = getnum(a[i], d);                bucket[count[num] - 1] = a[i];                count[num]--;            }            for (int i = 0; i < bucket.size(); i++)                a[start + i] = bucket[i];        }    }    int getnum(int x, int digit)//获取一个数x在位数digit上的数字    {        return int(x / pow(10, digit - 1)) % 10;    }    int maxbits(vector<int>a)//获取数组最大值的位数    {        int imax = INT_MIN;        for (int i = 0; i < a.size(); i++)            imax = max(imax, a[i]);        int digit = 0;        while (imax > 0)        {            digit++;            imax = imax / 10;        }        return digit;    }};