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的前面,则为稳定排序,否则为不稳定排序.
插入排序,归并排序和堆排序都是稳定排序.快排则不是稳定排序
- 排序算法系列之基本排序算法
- 排序算法系列---希尔排序(C++)
- 排序算法系列----堆排序(C++)
- 排序算法系列----归并排序(C++)
- 排序算法系列之基数排序
- 算法系列之归并排序
- C++算法系列之排序
- 七大排序算法系列之冒泡排序
- 七大排序算法系列之希尔排序
- 七大排序算法系列之快速排序
- 七大排序算法系列之归并排序
- 七大排序算法系列之堆排序
- 排序算法系列之希尔排序
- 排序算法系列之插入排序
- 排序算法系列之冒泡排序
- 排序算法系列之选择排序
- 排序算法系列之合并排序
- 排序算法系列之堆排序
- JavaScript
- Oracle查询表字段和类型
- 2018秋招面经——C++后端
- 设置eclipse关联源码
- shell中的${},##, %% , :- ,:+, ? 的使用
- C++算法系列之排序
- 运行 Anbox
- rn学习个人问题汇总
- percona-toolkit 基本使用
- composer切换国内镜像
- 【Deep Learning】Fast R-CNN
- 一些好的网站记录(PART 1)
- 堆和栈的区别
- 小白使用react——表单上传多个文件功能和下载文件功能