C++算法系列之排序

插入排序
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的前面,则为稳定排序,否则为不稳定排序.

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

猜你喜欢

转载自blog.csdn.net/jxhaha/article/details/78678382
今日推荐