插入排序
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的前面,则为稳定排序,否则为不稳定排序.
插入排序,归并排序和堆排序都是稳定排序.快排则不是稳定排序