排序小结

来源:互联网 发布:淘宝发布商品条形码 编辑:程序博客网 时间:2024/05/23 21:48

概要:
了解排序算法

这里写图片描述

各种算法的时间复杂度和空间复杂度如下:
这里写图片描述


排序详解:

一、 插入排序

直接插入排序:

将一段无序区间插入到一段有序区间(1个元素自称一个区间),每次都是取出无序区间的第一个元素插入到有序区间(在有序区间找合适自己的位置,也就是保持有序区间依然有序)。

图示直接插入过程:

这里写图片描述

代码如下:

//假设是升序template<class T>void InsertSort(T* arr,size_t N){    assert(arr && N > 0);    int front = 0;//有序区间的最后一个元素    for (; front < N - 1; ++front)    {        int end = front;        int tmp = arr[end + 1];        //插入一次        while (end >= 0)        {            if (arr[end] > tmp)            {                arr[end + 1] = arr[end];                --end;            }            else            {                break;            }        }        arr[end + 1] = tmp;//始终放在end+1的位置(-1或有序区间的任意下标)    }}

小结:

计算时间复杂度:
最坏的情况下是:O(N*N)—->对应逆序的序列
最好的情况下是:O(N)—–》已经有序

直接插入排序是一种稳定的排序方法。

希尔排序(针对直接插入排序的最坏的情况所引进):

2.希尔排序
希尔排序不是一种稳定的排序方法。

希尔排序最坏的情况是:当序列已经有序时,可能会起到反作用。

gap=gap/3 + 1;为什么要加1呢?
这样子取gap的值是因为在大数据的情况下,前几次可以尽可能的将最小的数通过移动3次左右就可以到达最前面的位置,
将大数据可以很快的移动到大量数据最后面的位置,继而不断缩小gap的值,继续重复上述过程,加1是为了使得无论是对多少数据进行排序,将gap缩小到最后都可以成为1,这样就成为了直接插入排序,但是和直接插入排序不同的是,这时进行直接插入排序的这些数,大多数已经接近有序了。所以直接插入排序的效率就会很高。

这里写图片描述

代码如下:

//对于直接排序最坏的情况而提出的template<class T>void ShellSort(T* arr,size_t N){    assert(arr && N >= 0);    int gap = N;    while (gap > 1)//最终gap肯定会为1(进行直接插入排序)    {        gap = gap / 3 + 1;//使最后的元素能在3次左右到达最前面        for (int front = 0; front < N - gap; ++front)        {            int end = front;            int tmp = arr[end + gap];            while (end >= 0)//end=0时也需要排序            {                if (arr[end] > tmp)                {                    arr[end + gap] = arr[end];                    end -= gap;                }                else                {                    break;                }            }            arr[end + gap] = tmp;        }    }}

二、选择排序

选择排序:(最简单也是效率最差的一种排序)

(优化方法:一次选两个数出来,这段区间的最大最小数,继而不断缩小区间)
时间复杂度为O(N^2)

template<class T>void SelectSort(T* arr, int N){    assert(arr && N >= 0);    for (int end = N - 1; end > 0; --end)    {        //一次排序        int maxIndex = 0;        for (int i = 1; i <= end; ++i)//end为闭区间        {            if (arr[maxIndex] < arr[i])            {                maxIndex = i;            }        }        if (maxIndex != end)        {            swap(arr[maxIndex], arr[end]);        }    }}

优化:
(说是优化,但实际上时间复杂度也没有减小,而且需要注意各种问题,尴尬,不想看这种方法的可以直接忽略掉这个优化,就我自己的感受,这种优化方法纯粹是看上去有点高大上,纯粹是长见识了)

template<class T>void SelectSort(T* arr, int N){    assert(arr && N >= 0);    int begin = 0;    int end = N - 1;    for (;begin < end ;++begin, --end)    {        //一次排序        int maxIndex = begin;        int minIndex = begin;        for (int i = begin; i <= end; ++i)//end为闭区间        {            if (arr[maxIndex] < arr[i])            {                maxIndex = i;            }            if (arr[minIndex] > arr[i])            {                minIndex = i;            }        }        if (maxIndex != end)        {            swap(arr[maxIndex], arr[end]);        }        if (end == minIndex)        {            minIndex = maxIndex;        }        if (minIndex != begin)        {            swap(arr[minIndex], arr[begin]);        }    }}

堆排序:

需要注意:
升序:建大堆
降序:建小堆

堆排序的时间复杂度是:O(N*lgN)

//建大堆(升序)template<class T>void AdjustDown(T* arr, size_t N, size_t root){    int parent = root;    int child = 2 * parent + 1;    while (child < N)//这里的条件需要注意    {        if (child + 1 < N && arr[child]  < arr[child + 1])        {            ++child;        }        if (arr[child] > arr[parent])        {            swap(arr[child], arr[parent]);            parent = child;            child = 2 * parent + 1;        }        else        {            //已经是大堆,就不需要进行调堆            break;        }    }}template<class T>void HeapSort(T* arr, int N){    assert(arr);    //建堆    for (int i = (N - 2)/2; i >=  0; --i)    {        AdjustDown(arr, N, i);    }    //排序    int end = N - 1;    while (end > 0)    {        swap(arr[0], arr[end]);        AdjustDown(arr, end, 0);        --end;    }}

交换排序:

冒泡排序:

//冒泡排序template<class T>void BubbleSort(T* arr, size_t N){    assert(arr);    int end = N - 1;    while (end > 0)    {        bool tag = true;        //冒一次泡        for (int i = 0; i < end; ++i)        {            if (arr[i] > arr[i + 1])            {                tag = false;                swap(arr[i], arr[i + 1]);            }        }        if (tag == true)        {            break;//已经有序,不需要再冒泡了        }        --end;    }}

快速排序:

1.左右指针法:

这里写图片描述

这里写图片描述

代码实现如下:

template<class T>int SortPart(T* arr,int begin,int end){    int left = begin;    int right = end;    int key = arr[end];    while (left < right)    {        while (arr[left] <= key && left < right)        {            ++left;        }        while (arr[right] >= key && left < right)        {            --right;        }        if (left != right)        {            swap(arr[left], arr[right]);        }    }    swap(arr[left], arr[end]);//将key放在合适的位置    return left;//将key的位置返回}//快排(递归实现,注意函数参数的形式,需要以区间的形式出现)template<class T>void QuickSort(T* arr,int begin,int end){    assert(arr);    if (begin >= end)    {        return;    }    int div = SortPart(arr,begin,end);//闭区间    QuickSort(arr, begin, div - 1);    QuickSort(arr, div + 1, end);}

2.挖坑法:

这里写图片描述

template<class T>int SortPart2(T* arr, int begin, int end)//闭区间{    assert(arr);    T key = arr[end];//end是第一个坑    while (begin != end)    {        //begin找大于key的值        while (begin != end && arr[begin] <= key)        {            ++begin;        }        if (begin != end)        {            arr[end] = arr[begin];            --end;        }        //end找小于key的值        while (begin != end && arr[end] >= key)        {            --end;        }        if (begin != end)        {            arr[begin] = arr[end];            ++begin;        }    }    if (begin == end)    {        arr[begin] = key;    }    return begin;}//快排(递归实现,注意函数参数的形式,需要以区间的形式出现)template<class T>void QuickSort(T* arr,int begin,int end){    assert(arr);    if (begin >= end)    {        return;    }    int div = SortPart2(arr,begin,end);//闭区间    QuickSort(arr, begin, div - 1);    QuickSort(arr, div + 1, end);}

3.前后指针法:

这里写图片描述

//前后指针法//template<class T>int SortPart3(int* arr, int begin, int end)//闭区间{    assert(arr);    int cur = begin;    int prev = cur - 1;    int key = arr[end];    while (cur < end)    {        //cur找小于key的值        while (cur < end && arr[cur] >= key )        {            ++cur;        }        if (cur < end && ++prev != cur)        {            swap(arr[prev], arr[cur]);        }        ++cur;    }    ++prev;    swap(arr[prev], arr[end]);    return prev;    }//快排(递归实现,注意函数参数的形式,需要以区间的形式出现)template<class T>void QuickSort(T* arr,int begin,int end){    assert(arr);    if (begin >= end)    {        return;    }    int div = SortPart3(arr, begin, end);//闭区间    QuickSort(arr, begin, div - 1);    QuickSort(arr, div + 1, end);}

快排的性能分析:
快速排序是一种快速的分而治之的算法,它是已知的最快的排序算法,
其时间复杂度是O(N*lgN)。
但是要知道,这里的时间复杂度和我们之前说的还是不一样的,因为我们都知道,时间复杂度一般算的都是最坏情况下的。而有两个是例外:1.哈希算法 2.快排

快速排序最坏的情况是:每次找的基准值都是该序列中的最小或者最大值,这种情况下的时间复杂度是O(N*N),每次划分左右区间时必然会有一个区间为空,这样的话,可能会造成递归的深度较大,
当排序的区间特别大时,有可能会造成栈溢出。
但每次选出的基准值都是最大或最小值的概率太小了,所以这样情况就忽略不看了。
上面的情况是一种极端的分析,但是每次选出的基准值是该序列中的接近最大值或者接近最小值的还是很有可能的,所以我们就想到了一种方法:

优化1:
三数取中法:
就是在三个数中每次取出的那个数都是中间的那个,这样可以很好的解决了上面的那种情况。

快速排序最好的情况:
最好的情况就是每次划分后形成的左右子序列的个数相等或者相差不大,这样的情况的时间复杂度是O(N*lgN)。

优化2:

当划分的子序列很小的时候(一般认为小于13个元素左右时),这时如果我们使用快速排序反而不如使用直接插入排序高效,因为快排对区间的划分就像一棵二叉树,当区间元素个数小于13时我们如果使用快速排序就等于增加了二叉树的高度,而我们上面的排序是通过递归来实现的,也就是增加了几层栈帧,但是可能就因为这几层栈帧从而导致栈溢出。
所以当划分的区间个数小于13时最好采用直接插入排序。

#include<cassert>//优化---》1.三数取中法   2。当区间较小时,使用快排算法//三数取中法int GetMid(int* arr, int left, int right){    assert(arr);    int mid = (left ^ right) >> 1 + (left & right);    if (arr[left] < arr[right])//left < right    {        if (arr[left] > arr[mid])        {            return left;        }        else if (arr[mid] < arr[right])//left <= mid  left < right        {            return mid;        }        else//mid>right  mid > left        {            return right;        }    }    else//left > right    {        if (arr[left] < arr[mid])        {            return left;        }        else if (arr[right] > arr[mid])//left > mid   left > right        {            return right;        }        else        {            return mid;        }    }}//左右指针法template<class T>int SortPart1(T* arr,int begin,int end){    int left = begin;    int right = end;    int midIndex = GetMid(arr, begin, end);//优化    swap(arr[midIndex], arr[end]);    int key = arr[end];    while (left < right)    {        while (arr[left] <= key && left < right)        {            ++left;        }        while (arr[right] >= key && left < right)        {            --right;        }        if (left != right)        {            swap(arr[left], arr[right]);        }    }    swap(arr[left], arr[end]);//将key放在合适的位置    return left;//将key的位置返回}//挖坑法template<class T>int SortPart2(T* arr, int begin, int end)//闭区间{    assert(arr);    int key = GetMid(arr, begin, end);//优化    swap(arr[key], arr[end]);    T key = arr[end];//end是第一个坑    while (begin != end)    {        //begin找大于key的值        while (begin != end && arr[begin] <= key)        {            ++begin;        }        if (begin != end)        {            arr[end] = arr[begin];            --end;        }        //end找小于key的值        while (begin != end && arr[end] >= key)        {            --end;        }        if (begin != end)        {            arr[begin] = arr[end];            ++begin;        }    }    if (begin == end)    {        arr[begin] = key;    }    return begin;}//前后指针法//template<class T>int SortPart3(int* arr, int begin, int end)//闭区间{    assert(arr);    int cur = begin;    int prev = cur - 1;    int key = GetMid(arr, begin, end);//优化    swap(arr[key], arr[end]);    while (cur < end)    {        //cur找小于key的值        while (cur < end && arr[cur] >= key )        {            ++cur;        }        if (cur < end && ++prev != cur)        {            swap(arr[prev], arr[cur]);        }        ++cur;    }    ++prev;    swap(arr[prev], arr[end]);    return prev;    }//优化:当区间小于13时---》选择直接插入排序//快排(递归实现,注意函数参数的形式,需要以区间的形式出现)template<class T>void QuickSort(T* arr,int begin,int end){    assert(arr);    if (begin >= end)    {        return;    }    if (end - begin + 1 > 13)    {        int div = SortPart1(arr, begin, end);//闭区间        QuickSort(arr, begin, div - 1);        QuickSort(arr, div + 1, end);    }    else    {        InsertSort(arr,end - begin + 1);    }}

我们都知道,一般的递归函数都可以借助栈来实现非递归函数,因为递归需要借助栈帧来保存数据,而栈可以利用其特性(后进先出)就正好代替了栈帧来保存数据,所以我们可以借助栈来实现非递归。

//左右指针法template<class T>int SortPart1(T* arr,int begin,int end){    int left = begin;    int right = end;    int midIndex = GetMid(arr, begin, end);//优化    swap(arr[midIndex], arr[end]);    int key = arr[end];    while (left < right)    {        while (arr[left] <= key && left < right)        {            ++left;        }        while (arr[right] >= key && left < right)        {            --right;        }        if (left != right)        {            swap(arr[left], arr[right]);        }    }    swap(arr[left], arr[end]);//将key放在合适的位置    return left;//将key的位置返回}//快速排序的非递归实现void QuickSortR(int* arr, int left, int right){    if (left >= right)    {        return;    }    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 div = SortPart1(arr, begin, end);        if (begin < div - 1)        {            s.push(div - 1);            s.push(begin);        }        if (div + 1 < end)        {            s.push(end);            s.push(div + 1);        }    }}

归并排序

思想简述:
将一个大区间不断的通过求其中间数而划分为两个区间(区间的个数不大于1),直到划分为区间的数据个数为1时就可以停止了。
将区间个数划分为1时就可以合并了,将最后划分的这两个区间合并为一个区间(合并时注意将区间的数据放在合适的位置),还需要注意一个问题就是需要将两个小区间的数据合并在一个辅助数组tmp中,而不是原区间,因为这会覆盖掉原区间的数据。

最后再将合并好的数据拷回原空间。

这里写图片描述

void _Merge(int* arr,int* tmp,int begin1,int end1,int begin2,int end2){    int begin = begin1;    int index = begin1;    //循环结束时:至少有一个区间的数据已经合并完了    while (begin1 <= end1 && begin2 <= end2)    {        if (arr[begin1] <= arr[begin2])        {            tmp[index++] = arr[begin1++];        }        else        {            tmp[index++] = arr[begin2++];        }    }    while (begin1 <= end1)    {        tmp[index++] = arr[begin1++];    }    while (begin2 <= end2)    {        tmp[index++] = arr[begin2++];    }    //将数据拷回原空间    while (begin <= end2)    {        arr[begin] = tmp[begin];        begin++;    }}//归并排序void _MergeSort(int* arr,int* tmp, int left, int right){    assert(arr);    if (left >= right)    {        return;    }    int mid = ((left ^ right) >> 1) + (left & right);//需要特别注意移位运算符的优先级在这里最低    _MergeSort(arr, tmp, left, mid);    _MergeSort(arr, tmp, mid + 1, right);    _Merge(arr, tmp, left, mid, mid + 1, right);}void MergeSort(int* arr, int N){    assert(arr);    int* tmp = new int[N];    int left = 0;    int right = N - 1;    _MergeSort(arr, tmp,0, right);    delete[] tmp;}

当要对磁盘文件里的一个大文件进行排序:

这里有一个快排和归并的应用:
这里写图片描述

原创粉丝点击