每次用排序算法的时候都是用的冒泡法,这里整理下排序算法,用于以后应用。
C++库里带的函数为sort(start,end,compare);qsort(...);
最近都在用sort进行排序,用于vector的排序十分有效。qsort没怎么用过,等以后了解过再贴。
参考链接:
1)http://blog.csdn.net/likefrank/article/details/2974949
2)http://blog.csdn.net/hguisu/article/details/7776068
3)http://blog.csdn.net/xiazdong/article/details/8462393
4)http://blog.chinaunix.net/uid-25906157-id-3318529.html
5)http://www.cnblogs.com/eniac12/p/5329396.html--喜欢这篇文章里面的图,强推。
6)http://www.cnblogs.com/eniac12/p/5332117.html--记录非比较方法,接链接5)
7)http://blog.csdn.net/hguisu/article/details/7776068#t7
先上图,各排序算法的空间复杂度和稳定性情况:表来自链接1)
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n+r) | B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(n) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
因为冒泡,交换,选择和插入排序复杂度类似,都是在小型数据应用较好,所以在此不详细记录。
主要对基数,Shell(希尔),快速,归并,堆排序进行讨论说明
//代码参考链接及书《C和C++程序员面试秘籍》
一、基数排序
当时学语言和数据结构的时候并没有第一次听说过这种算法。参考链接3)4)5)
不过这种算法只适用于整数的排序,浮点数的排序,需要重新映射
思想:它是一张非比较的算法,根据位的高低进行排序,所以先按个位数进行排序,再根据十位数进行排序......以此类推
//find the max number
int find_max(int a[],int len)
{
int max=a[0];
for(int i=1;i<len;++i)
{
if(max<a[i])
max=a[i];
}
return max;
}
int digit_number(int number)
{
int digit=0;
while(number!=0)
{
number/=10;
digit++;
}
return digit;
}
//return kth number
int kth_digit(int number,int kth)
{
number/=pow(10,kth);
return number%10;
}
void radix_sort(int a[],int len)
{
int **temp=new int *[10];
int count[10];
int max=find_max(a,len);
int maxDigit=digit_number(max);
for(int i=0;i<10;++i)
{
temp[i]=new int[len];
memset(temp[i],0,sizeof(int)*len);
}
for(int i=0;i<maxDigit;++i)
{
memset(count,0,sizeof(int)*10);
for(int j=0;j<len;++j)
{
int xx=kth_digit(a[j],i);
temp[xx][count[xx]]=a[j];
count[xx]++;
}
int index=0;
for(int j=0;j<10;++j)
{
for(int k=0;k<count[j];++k)
{
a[index++]=temp[j][k];
}
}
}
}
二、希尔排序
插入排序的高速尔稳定的改进版本。
将数据分成不同组,先对每一组进行排序,再对所有的元素进行插入排序。
希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
void shell_sort(int a[],int len)
{
int j;
for(int h=len/2;h>0;h=h/2)
{
for(int i=h;i<len;++i)
{
int temp=a[i];
j=i-h;
while(j>=0&&temp<a[j])
{
a[j+h]=a[j];
j-=h;
}
a[j+h]=temp;
}
}
}
三、快速排序
一般选择序列最左边的值作为支点数据,将序列分成2部分,一部分大于另一部分小于,再对两边的数据进行递归排序。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
- 从序列中挑出一个元素,作为"基准"(pivot).
- 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
- 对每个分区递归地进行步骤1~3,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
void quick_sort(int a[],int low,int high)
{
int pivot;
if(low<high)
{
pivot=a[low];
int i=low;
int j=high;
while(i<j)
{
while(i<j&&a[j]>=pivot)
--j;
if(i<j)
a[i++]=a[j];
while(i<j&&a[i]<pivot)
i++;
if(i<j)
a[j--]=a[i];
}
a[i]=pivot;
quick_sort(a,low,i-1);
quick_sort(a,i+1,high);
}
}
四、归并排序
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
void Merge(int *r,int *rf, int i, int m, int n)
{
int j,k;
for(j=m+1,k=i; i<=m && j <=n ; ++k){
if(r[j] < r[i]) rf[k] = r[j++];
else rf[k] = r[i++];
}
while(i <= m) rf[k++] = r[i++];
while(j <= n) rf[k++] = r[j++];
//print(rf,n+1);
}
void MergeSort(int *r, int *rf, int length)
{
int len = 1;
int *q = r ;
int *tmp ;
while(len < length)
{
int s = len;
len = 2 * s ;
int i = 0;
while(i+ len <length)
{
Merge(q, rf, i, i+ s-1, i+ len-1 ); //对等长的两个子表合并
i = i+ len;
}
if(i + s <=length)
{
Merge(q, rf, i, i+ s-1 , length -1); //对不等长的两个子表合并
}
tmp = q; q = rf; rf = tmp; //交换q,rf,以保证下一趟归并时,仍从q 归并到rf
}
}
五、堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。
我们可以很容易的定义堆排序的过程:
- 创建一个堆
- 把堆顶元素(最大值)和堆尾元素互换
- 把堆的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
- 重复步骤2,直到堆的尺寸为1
/**
* 已知H[s…m]除了H[s] 外均满足堆的定义
* 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,
*
* @param H是待调整的堆数组
* @param s是待调整的数组元素的位置
* @param length是数组的长度
*
*/
void HeapAdjust(int H[],int s, int length)
{
int tmp = H[s];
int child = 2*s+1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)
while (child < length) {
if(child+1 <length && H[child]<H[child+1]) { // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
++child ;
}
if(H[s]<H[child]) { // 如果较大的子结点大于父结点
H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点
s = child; // 重新设置s ,即待调整的下一个结点的位置
child = 2*s+1;
} else { // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
break;
}
H[s] = tmp; // 当前待调整的结点放到比其大的孩子结点位置上
}
//print(H,length);
}
/**
* 初始堆进行调整
* 将H[0..length-1]建成堆
* 调整完之后第一个元素是序列的最小的元素
*/
void BuildingHeap(int H[], int length)
{
//最后一个有孩子的节点的位置 i= (length -1) / 2
for (int i = (length -1) / 2 ; i >= 0; --i)
HeapAdjust(H,i,length);
}
/**
* 堆排序算法
*/
void HeapSort(int H[],int length)
{
//初始堆
BuildingHeap(H, length);
//从最后一个元素开始对序列进行调整
for (int i = length - 1; i > 0; --i)
{
//交换堆顶元素H[0]和堆中最后一个元素
int temp = H[i]; H[i] = H[0]; H[0] = temp;
//每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
HeapAdjust(H,0,i);
}
}
上面内容来源于别人的博客加以整理。了解排序算法的过程。