C++算法系列之排序

来源:互联网 发布:水准仪测量数据 编辑:程序博客网 时间:2024/06/05 23:58

插入排序
O(N2),基本思路就是玩扑克牌的时候,从牌堆里摸牌放到手上的思路.

#include<iostream>#include<algorithm>#include<random>#include<ctime>#include<vector>#include<iterator>const int M = 1000;const int N = 1000;template<typename Iterator, typename Comparator>void insertSort(const Iterator &a, const Iterator &b){    typedef typename Iterator::value_type T;    T key;    int i, j, n = std::distance(a,b);    Iterator p,q,t;    for (p = a+1,q =p, j = 1; j <= n; j++, p++,q=p)    {        i = std::distance(a, q);        while(i > 0)        {            t = q-1;            if(Comparator()(*q, *t))            {                key = *q;                *q = *t;                *t = key;            }            i -- ;            q--;        }    }}void produceData(std::vector<int> &data){    std::default_random_engine s(std::time(0));    for ( int i = 0; i < N; i++)    {        data[i] = s() % M;    }}template<typename Iterator, typename Comparator>void checkValid(Iterator b, Iterator e){    Iterator t;    while( b != e)    {        t = b+1;        if(t == e)        {            break;        }        if(Comparator()(*t,*b))        {            std::cout << "Big Error " << std::endl;        }        b++;    }}template<typename T>class Smaller{public:    bool operator()(T& a, T&b)    {        return a>b;    }};int main(){    std::vector<int> data(N);    produceData(data);    insertSort<std::vector<int>::iterator, Smaller<int>>(std::begin(data), std::end(data));    checkValid<std::vector<int>::iterator, Smaller<int>>(data.begin(), data.end());}

归并排序
//O(NlgN),需要额外空间

template<typename Iterator>void merge(Iterator s, Iterator p, Iterator e){    typedef typename std::iterator_traits<Iterator>::value_type T;    int s1 = std::distance(s,p), s2 = std::distance(p,e);    T *L = new T(s1);    T *R = new T(s2);    std::copy(s,p, L);    std::copy(p,e,R);    int i = 0, j = 0;    Iterator tmp  = s;    while(i < s1 && j < s2)    {        if(L[i] < R[j])        {            *tmp = L[i];            i++;        }        else        {            *tmp = R[j];            j++;        }        tmp++;    }    if(i< s1)    {        *tmp = L[i];        i++;    }    if(j < s2)    {        *tmp = R[j];        j++;    }    delete []L;    delete []R;}template<typename Iterator>void mergeSort(Iterator s, Iterator e){     int n = std::distance(s,e);     if (n>1)     {         Iterator q = s;         std::advance(q, n/2);         mergeSort<Iterator>(s,q);         mergeSort<Iterator>(q,e);         merge<Iterator>(s,q,e);     }}int main(){    int a[ ] = {2,34,76,32,56,98,45};    mergeSort(a, a+7);    std::copy(a,a+7, std::ostream_iterator<int>(std::cout, " "));    std::cout << std::endl;}

快速排序O(NlgN)

int randomNumber(int p, int q){    return p + (int)(std::rand()%(q-p));//返回一个[p,q)之间的数字}template<typename Iterator>Iterator partition(Iterator s, Iterator e, typename std::iterator_traits<Iterator>::value_type n)//返回一个序列,使得大于n的在左边,小于n的在右边{    Iterator p = s, q = e;    typedef typename std::iterator_traits<Iterator>::value_type T;    for(;;p++)    {        for(;p!=q && *p>=n;p++);        if(p==q)break;        for(;p!=--q && *q<n;);        if(p==q)break;        std::swap(*p,*q);    }    return p;}template<typename Iterator>Iterator randomizedPartition(Iterator s, Iterator e)//随机抽取一个元素,作为主元,用它来重新划分序列{    int n = std::distance(s, e), i;    Iterator p = s, q = e;    i = randomNumber(0, n);    std::advance(p, i);    std::cout << n << "  " << i  << "  " << *p<< std::endl;    return partition<Iterator>(s, e, *p);}template<typename Iterator>void quickSort(Iterator s, Iterator e){    long n  = std::distance(s,e);//计算出序列的长度    if(n > 1)//只有序列超过两个元素的时候,才进行处理,1个元素没有左右之分    {        Iterator p = randomizedPartition(s,e);//得到一个随机的元素作为主元        quickSort(s,p);// 先排左侧序列        quickSort(p,e);//再排右侧序列    }}int main(){    int a[ ] = {2,34,76,32,56,98,45};    quickSort(a,a+sizeof(a)/sizeof(int));    std::copy(a,a+sizeof(a)/sizeof(int), std::ostream_iterator<int>(std::cout, " "));    std::cout << std::endl;}

归并和快排的代码有点相似,都是利用分治策略来进行问题的求解的.
分治策略大体上分为3步,即分解, 解决, 合并
归并排序和快排也是按照这三个步骤进行的,不过二者有点差异.归并排序在分解的时候,不做任何处理,问题的分解没有利用到原问题的信息,一路分解到子问题规模足够小(也就是剩下一个元素的时候),一个元素默认有序,因此子问题直接解决,最后调用合并有序序列的方法将结果进行合并.
快速排序在分解的时候,就进行了对问题的解决(对序列按主元进行了重新划分),问题分解的时候利用到了原问题的信息.
归并排序是按照自底向上的顺序进行排序
快速排序是按照自顶向下的顺序进行排序

堆排序
O(NlgN)
首先明白堆的性质,操作对象是个数组,可被视为一个几乎完全的二叉树.
对于所有非根节点,当A[parent[i]] >= A[i],视为最大堆
A[parent[i]] <= A[i], 视为最小堆.
堆排序首先需要创建堆,创建堆的意思就是调整数组的元素位置,使得数组的元素符合堆的样子,即对数组A, 有A[parent[i]] >= A[i] 或者A[parent[i]] <= A[i].
调整的方式是自下而上,就从所有的非叶子节点开始调整,保证从非叶子节点开始往下都是堆有序的(即所有非叶子节点的子节点i, 满足A[parent[i]] >= A[i] OR A[parent[i]] <= A[i])
堆排序就更简单了,将堆顶元素和数组最后一个元素交换,然后再调整堆顶节点,使得交换后的堆恢复堆有序.不断交换直至完全有序为止.

template<typename Iterator>void downHeap(Iterator s,  int i, int n){    Iterator p = s, left = s, index = s;    std::advance(p, i);    typedef typename std::iterator_traits<Iterator>::value_type T;    T min = *p;    int offset;    std::cout << *p << " " << i << " " << n << std::endl;    if(2*i + 1 < n)    {        std::advance(left, 2*i+1);        if(min > *left)        {            min = *left;            offset = 2 *i +1;            index = left;        }    }    if(2*i +2 < n)    {        left++;        if(min > *left)        {            offset = 2 *i +2;            min = *left;            index = left;        }    }    if(min != *p)    {        std::swap(*p, *index);        downHeap(s, offset, n);    }}template<typename Iterator>void upHeap(Iterator s, int i){    typedef typename std::iterator_traits<Iterator>::value_type T;    Iterator p  =  s, q = s;    std::advance(p, i);    T k =*p;    int parentIndex = (i-1)/2;    std::advance(q, parentIndex);    if (parentIndex >= 0)    {        if(*q < k)        {            std::swap(*q, *p);            if (parentIndex > 0)            {                upHeap(s, parentIndex);            }        }    }}template<typename Iterator>void makeheap(Iterator s, Iterator e){    int n = std::distance(s,e);    for(int i = (n-1)/2; i >= 0; i --)    {        downHeap(s, i, n);    }}template<typename Iterator>void heapSort(Iterator s, Iterator e){    int n = std::distance(s, e);    Iterator m = e;    while (s!=e)    {          std::swap(*s, *(--e));          downHeap(s, 0, std::distance(s,e));    }}int main(){    int a[ ] = {2,34,76,32,56,98,45};    makeheap(a,a+7);    heapSort(a,a+7);    std::copy(a,a+sizeof(a)/sizeof(int), std::ostream_iterator<int>(std::cout, " "));    std::cout << std::endl;}

优先队列
这东西可以被理解成一个包裹了heap 的壳子,它的核心就是一个heap. 不过它可以限定了内置heap的大小.定义了出队和入队操作. 出队和入队事实上就是堆的插入和删除操作
这玩意再统计TopK里面很有用.
具体如下,首先弄一个最小堆
堆顶元素是最小值.
当读入元素小于K时,就入队.
当读入元素大于K时,比较该元素与堆顶元素大小.如果比堆顶小,舍弃;如果大于堆顶元素,删除堆顶元素,将该元素入队

template<typename T>class PrioQueue{private:    std::vector<T> heap;    int heapSize;public:    PrioQueue():heapSize(0){}    void enQueue(T e)    {        heapSize ++;        heap.push_back(e);        upHeap(heap.begin(), heapSize-1);    }    T deQue()    {        assert(heapSize > 0);        std::swap(heap[0], heap[heap.size()-1]);        downHeap(heap.begin(), 0, heap.size()-1);        heapSize --;        T top  = heap[heapSize];        heap.erase(heap.end()-1);        return top;    }    T top()    {        return heap[0];    }    void print()    {        std::copy(heap.begin(), heap.end(), std::ostream_iterator<T>(std::cout, " "));        std::cout << std::endl;    }};int main(){    int a[ ] = {2,34,76};    makeheap(a,a+3);     std::copy(a,a+sizeof(a)/sizeof(int), std::ostream_iterator<int>(std::cout, " "));    std::cout << std::endl;    heapSort(a,a+3);    std::copy(a,a+sizeof(a)/sizeof(int), std::ostream_iterator<int>(std::cout, " "));    std::cout << std::endl;    PrioQueue<int> q;    q.enQueue(kk);    q.print();    q.deQueue();    q.print();}
//STL定义了优先队列的结构,这里简单列一下用法struct Node{    std::string szName;    int  priority;    Node(int nri, const std::string &pszName)    {        szName = pszName;        priority = nri;    }};struct NodeCmp{    bool operator()(const Node &na, const Node &nb)    {        if (na.priority != nb.priority)            return na.priority <= nb.priority;        else            return na.szName >= nb.szName;    }};int main(){    std::priority_queue<Node, std::vector<Node>, NodeCmp> m;    m.push(Node(5, "ss"));    m.push(Node(3, "aa"));    m.push(Node(1, "bb"));}

基于比较的排序次数最坏情况下至少需要nlgn次比较.
假设比较1,2,3. 我们以二叉树分支表示,顶点位置记成1:2(拿1和2比较),小于进左边分支,大于进右边分支.这样叶子节点就是最终能到3个元素的全排列.全排列是n的阶乘.高度h 的满二叉树最多有2的h次方个叶子.所以 n!<=2的h次方(公式). 因为任何一次从顶点到根的路径就是一个排序过程. 所以最坏排序情况肯定在其中一条路径上.所做的比较次数 也就是h.
n!<=2的h次方,两边取对数h>=lg(n!)>=nlgn.

稳定排序, 假设待排序的序列有两个相同的元素A,B, 其中A,B的值是一样的,排序前A 在B前面,排序后A 还在B的前面,则为稳定排序,否则为不稳定排序.

插入排序,归并排序和堆排序都是稳定排序.快排则不是稳定排序

原创粉丝点击