目录
引言:本博客将逐个分析插入排序,冒泡排序,归并排序,堆排序,快速排序,计数排序,基数排序,桶排序等主流排序算法
二.时间复杂度为n*logn的算法-归并排序,堆排序,快速排序
三.针对特殊输入时间复杂度为n的算法-计数排序,基数排序,桶排序
引言:本博客将逐个分析插入排序,冒泡排序,归并排序,堆排序,快速排序,计数排序,基数排序,桶排序等主流排序算法
(首先排序的意思是是将一组"无序"的记录序列调整为"有序"的记录序列,接下来的算法统一以完成升序为目标)
一.时间复杂度为n*n的算法-插入排序与冒泡排序
1.插入排序
(1).什么是插入排序?
扑克牌斗地主什么的玩过吧,多数人都是这么去整理手牌的:最开始手上有0张牌,此时可以看作已经是有序的,接下来每发给我一张牌,我就把这张牌插入到我已有牌中的正确位置上,这样我手上的牌就一直是有序的,最后也是有序的.
(2).插入排序实现思路
我们的目标是让手中的牌升序排列,因为手上已有的牌已经是升序了,所以对于新来的牌x,我们只需要将它不断的与未比较的牌里最大的那张y比较,比y大或等于则x牌就该插在这儿,比y小则y牌后移,x继续与未比较的牌中最大的比较,因为已经是升序故而就是y牌前一张,
(3).模版代码
void insert_sort(int* a,int len){ //排序[begin,end)内的数字为升序
for(int i=1;i<len;i++){
int key=a[i];
int j=i-1;
while(j>=0&&a[j]>key){ //大于key的全部后移一位
a[j+1]=a[j];
j--;
}
a[j+1]=key; //插入到该位置
}
}
2.冒泡排序
(1).什么是冒泡排序
用来排序的一个n*n时间复杂度算法
(2).冒泡排序实现思路
实现思路正如它的名字,冒泡,不断的比较相邻的两个数字,将二者中较小的那个放到前面,这样子的比较会做大约n*n/2次,故而时间复杂度是n*n
(3).模版代码
void bubble_sort(int* a,int len){ //排序指针a后len个数字为升序
for(int i=0;i<len-1;i++){
for(int j=len-1;j>i;j--){ //将小的数字往前冒
if(a[j]<a[j-1]){
int t=a[j-1];
a[j-1]=a[j];
a[j]=t;
}
}
}
}
二.时间复杂度为n*logn的算法-归并排序,堆排序,快速排序
1.归并排序
(1).什么是归并排序?
利用归并的思想,将当前无序的n个数字不断递归二分,直到区间大小为一时,此时自然已经是有序的了,然后递归回溯,将两个已经有序的区间归并.最终整个区间都有序化.
(2).归并排序实现思路
使用递归来实现,两个函数,一个函数名为merge_sort,如模版代码中所写,思维很简单,l<r时也就是r-l>0时也就是区间内至少有两个元素时,将区间二分,递归继续进行merge_sort,到最深处返回回来后,对这两个子区间执行merge函数,merge函数就是用于将两个区间合并,由于两个区间都已经是有序的,此时我们进行排序就可以建立两个指针pos1,pos2,分别分别指向两区间的开始处,然后不断的将两指针之间较小的那个放进一个新的temp数组然后后移此指针.最后将temp数组覆盖到对应区间的a数组中,即可完成合并.
(3).模版代码
void merge(int *a,int l,int r,int mid){
int temp[r-l],tempnum=0;
int pos1=l,pos2=mid+1;
while(pos1<=mid&&pos2<=r){
if(a[pos1]<=a[pos2]) temp[tempnum++]=a[pos1++];
else temp[tempnum++]=a[pos2++];
}
while(pos1<=mid) temp[tempnum++]=a[pos1++];
while(pos2<=r) temp[tempnum++]=a[pos2++];
for(int i=0;i<tempnum;i++) a[l+i]=temp[i];
}
void merge_sort(int *a,int l,int r){ //排序a数组中[l,r]区间内的数为升序
if(l<r){
int mid=(l+r)/2;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
merge(a,l,r,mid);
}
}
2.堆排序
(1).什么是堆排序
一种n*logn的排序算法,利用堆这种数据结构对数据的高效管理特性.
(2).实现思路
建立一个小根堆,将数字依次压进堆中,再依次取出,取出时就是有序的了,关于大根堆STL中有priority_queue可用,不过这种基础数据结构最好自己也敲几次
(3).模版代码
void heap_sort(int *a,int l,int r){
priority_queue<int,vector<int>,greater<int> > que;
for(int i=l;i<=r;i++) que.push(a[i]);
for(int i=l;i<=r;i++){
a[i]=que.top();
que.pop();
}
}
只做上面这个算法好像太过偷懒了....而且用stl填充核心的程序算什么算法,于是心虚的我加上了下面这个自己实现的:
#include <queue>
#include <stdio.h>
using namespace std;
const int maxn=100001;
class myheap_max{
private:
int heap[maxn];
int heap_size;
public:
void init(){
for(int i=0;i<maxn;i++) heap[i]=0;
heap_size=0;
}
void clear(){
for(int i=0;i<heap_size;i++) heap[i]=0;
heap_size=0;
}
void myswap(int *t1,int *t2){ int temp=*t1,*t1=*t2,*t2=temp; } //交换函数,相当于swap
int parent(int pos){ return (pos-1)/2; } //获得pos结点的父亲节点
int left_children(int pos){ return pos*2+1; } //获得pos结点的左子节点
int right_children(int pos){ return pos*2+2; }//获得pos结点的右子节点
int size(){ return heap_size; }
bool empty(){ return heap_size==0; }
void push(int t){ //push操作
int now=heap_size++;
do{
int pa=parent(now);
if(heap[pa]>=t) break;
heap[now]=heap[pa];
now=pa;
}while(now);
heap[now]=t;
}
int top(){ //top操作
return heap[0];
}
void adjust_max(int pos){ //调整使pos结点下的子树成为一个大根堆
int l=left_children(pos),r=right_children(pos),largest;
if(l<heap_size&&heap[l]>heap[pos]) largest=l;
else largest=pos;
if(r<heap_size&&heap[r]>heap[largest]) largest=r;
if(largest!=pos){
myswap(heap+largest,heap+pos);
adjust_max(largest);
}
}
void pop(){ //pop操作
heap[0]=heap[--heap_size];
adjust_max(0);
}
};
void heap_sort(int *a,int l,int r){
myheap_max que;
que.init();
for(int i=l;i<=r;i++) que.push(a[i]);
for(int i=r;i>=l;i--){
a[i]=que.top();
que.pop();
}
}
int main(){
int a[10]={3,5,1,2,6,8,10,9,4,7};
heap_sort(a,0,9);
for(int i=0;i<10;i++) printf("%d ",a[i]);
printf("\n");
return 0;
}
3.快速排序
(1).什么是快速排序?
一个最常见的用于排序的n*logn算法,c++中sort底层就是快速排序.
(2).实现思路
在n个未排序数字中选取一个数作为"基数",我选择左边界l上的数,令l=左边界,r=右边界,r向左找小于基数的数字,l向右找大于l的数然后找到的这两个数字对换,循环进行这个过程,最后 r>l 时自然n个数就都被处理完了,left左边的全部小于等于基数,右边的全部大于等于基数,然后再对左边和右边分别快排.
(3).模版代码
void quick_sort(int *a,int l,int r){
if(l>=r) return;
int key=a[l],left=l,right=r;
while(left<right){
while(a[right]>=key&&left<right) right--;
while(a[left]<=key&&left<right) left++;
if(left<right){
int t=a[left];
a[left]=a[right];
a[right]=t;
}
}
a[l]=a[left];
a[left]=key;
quick_sort(a,l,left-1);
quick_sort(a,left+1,r);
}
三.针对特殊输入时间复杂度为n的算法-计数排序,基数排序,桶排序
1.计数排序(输入数据区间小适用)
(1).什么是计数排序
这是一种针对特殊输入理论上可以达到n时间复杂度的算法.这个特殊输入就是待排序数字皆在一个大小为k的数值区间内,而这个k越小,计数排序越高效,因为实际上计数排序的时间复杂度为O(n+k),不过由于说好了是针对特殊输入的故而k因较小而被忽略.
(2).计数排序实现思路
计数排序的思路正如其名-计数,计算每个数字的出现次数,因为我们知道所有这n个待排序数字都是在某一区间上的,比如[l,r],故而我们可以定义这么一个数组count,大小正好为r-l+1,最开始全部赋值为0,这样我们就可以把输入中每一个可能出现的数字对应到数组中对应的一位,就像输入一个3时我就把count[3]+1,这样最后每一个count[i]的值就代表着 i 在这n个数字中出现过几次.
光说肯定不好理解,现在我们来看看对于最简单的一组数字 3,2,3,1,5,2 ,我事先告诉你数字最大为6,故而最开始count数组如下
count 0 1 2 3 4 5 6
0 1 2 2 0 1 0 (就是每个i在这6个数字中的出现次数对吧)
最后一步自然就是读取count数组构建排序结果了,如代码,以一个pos即时保存当前排序构建到哪儿了,从0到k读count数组,读到count[i]不为0就往排序结果中加count[i]个i,然后pos也加count[i],继续往后读.
什么的是计数排序最简单的思路,我实现的模版代码做了一点优化,支持自定义输入数据区间.
(3).模版代码
const int tmin=0; //输入数据最小界
const int tmax=1000001; //输入数据最大界
const int k=tmax-tmin+1;//区间大小
int count[k]; //这里count[i]存的是输入数据中i+tmin出现的次数
void jishu_sort(int *a,int l,int r){
for(int i=0;i<k;i++) count[i]=0;
for(int i=l;i<=r;i++) count[a[i]-tmin]++;
int pos=0;
for(int i=0;i<k;i++){
if(count[i]){
for(int j=0;j<count[i];j++) a[pos+j]=i+tmin;
pos+=count[i];
}
}
}