/*按关键字相等的记录顺序是否变化,分为稳定和不稳定;
*按存储器不同分为内部排序和外部排序,外部是指数量很大,内存一次不能容纳全部记录,要访问外存
*按复杂度分简单排序、普通排序和基数排序;
*按依据的原则不同分为插入排序、交换排序、选择排序、归并排序和计数排序。
*排序通常需要两种操作:比较、移动记录。
*移动可以通过存储方式改变来避免:数组、链表(链表排序)、数组辅助向量指向原地址(地址排序)
*/
/* 1>平均时间快排最佳,但快排最坏情况下为冒泡;n较大时归并排序比堆排快,但辅助空间多。
* 2>简单排序包括除希尔排序外所有插入排序,冒泡和简单选择排序。直接插入排序最简单,基本有序或n小时最佳。
* 3>基数排序使用n大关键字小的情况。
* 4>稳定排序:基数排序、所有O(n^2)排序、堆排。
*/
/** 1.插入排序 **/
/*1.1 直接插入排序 O(n^2) 稳定 特殊:如果原数据为从小到大为O(n)
* 将一个记录插入到已排好序的有序表中,从右往左比较n次,原表也移动n次。
*/
void InsetSort(int a[])
{
for(int i=2;i<=n;++i)
{
int tp = a[i], j;
for(j=i-1;i>0&&tp<a[j];--j)
a[j+1] = a[j];
a[j+1] = tp;
}
}
/*1.2 折半插入排序 O(n^2)
* 通过二分思想,在数组中折半查找,但是移动还是要O(n)次
*/
/*1.3 2-路插入排序 O(n^2)
* 在折半插入排序的基础上再改进,目的是减少排序过程中的移动次数,约为n^2/8次(包含平摊除以的2)。
* 设一辅助数组d[],令d[1]=r[1]。把d[]数组看成一个循环数组,d[1]看成中间记录。
* 每次插入时只需要在d[1]前或者后移动。移动次数减少一半,但d[1]为最小或者最大时失去作用。
*/
/*1.4 表插入排序 O(n^2)
* 前面用数组保存可以二分查找但是移动需要O(n)时间,用链表保存移动O(1)时间但是不能二分查找
* 排序后数据保存在链表中,如果不要额外的空间复制到数组中。有点复杂
*/
void Arrange(int a[])
{
//根据静态链表r中各节点的指针值调整记录位置,使得数据非递减有序排序在数组中
int p = r[0].next; //p指示第一个记录的当前位置
for(int i=1; i<n; ++i) //r[1...i-1]中记录已按关键字有序排列,
{ //第i个记录在r[]中的当前位置应不小于i
while(p < i) p = r[p].next; //找到地i个记录,并用p指示其在r中当前位置
int q = r[p].next; //q指示尚未调整的表尾
if(p != i)
{
int tp = r[p]; //交换记录,使第i个记录到位
r[p] = r[i];
r[i] = tp;
r[i].next = p; //指向被移走的记录,使得以后可有while循环找回
}
p = q; //p指示尚未调整的表尾,为找第i+1个记录做准备
}
}
/*希尔(Shell)排序 又称缩小增量排序
* 它也是一种插入排序,但在时间效率上有较大的改进。时间与增量序列有关
* 增量序列有各种取法,但:应使增量序列中的值没有除1之外的公因子,并且最后增量值必须等于1
*/
void SheelInsert(int a[], int dk)
{
//对数组做一趟希尔插入排序。本算法是和一趟直接插入排序相比,做了以下修改:
// 前后记录位置的增量式dk,而不是1
for(int i=dk+1; i<=n; ++i)
if(a[i] < a[i-dk])
{
int tp = r[i];
for(int j=i-dk; j>0&&a[j]>tp; j-=dk)
a[j+dk] = a[j];
a[j+dk] = tp;
}
}
void ShellSort(int a[], int delta[], int t)
{
//按增量序列delta[0..t-1]对数组做希尔排序
for(int i=0; i<t; ++i)
ShellInsert(a,delta[i]);
}
/** 交换排序 **/
//冒泡
//快排 O(nlogn) 原地排序 不稳定 可能退化成冒泡
int partition(int *a,int p,int r){
int i = p;
for(int j=p;j<r;++j)
if(a[j] <= a[r])
swap(a[i++],a[j]);
swap(a[i],a[r]);
return i;
}
void qsort(int *a,int p,int r){
if(p < r){
int q = partition(a,p,r);
qsort(a,p,q-1);
qsort(a,q+1,r);
}
}
/** 选择排序 **/
//简单选择排序
//堆排
int size;
void HeapAdjust(int *a,int i){
int l = i<<1;
int r = i<<1|1;
int max = i;
if(l <= size && a[l] > a[max])
max = l;
if(r <= size && a[r] > a[max])
max = r;
if(max != i){
swap(a[i],a[max]);
HeapAdjust(a,max);
}
}
void build(int *a,int n){//数组为1...n,因为堆的标号是从1开始的
size = n;
for(int i=size>>1;i>=1;--i)
HeapAdjust(a,i);
}
void HeapSort(int *a,int n){
size = n;
build(a,n);
for(int i=n;i>=2;--i){
swap(a[1],a[i]);
--size;
HeapAdjust(a,1);
}
}
/** 归并排序
* O(nlogn) O(n)的空间 同快排堆排比较最大优势是稳定排序
**/
/** 基数排序
**/
*按存储器不同分为内部排序和外部排序,外部是指数量很大,内存一次不能容纳全部记录,要访问外存
*按复杂度分简单排序、普通排序和基数排序;
*按依据的原则不同分为插入排序、交换排序、选择排序、归并排序和计数排序。
*排序通常需要两种操作:比较、移动记录。
*移动可以通过存储方式改变来避免:数组、链表(链表排序)、数组辅助向量指向原地址(地址排序)
*/
/* 1>平均时间快排最佳,但快排最坏情况下为冒泡;n较大时归并排序比堆排快,但辅助空间多。
* 2>简单排序包括除希尔排序外所有插入排序,冒泡和简单选择排序。直接插入排序最简单,基本有序或n小时最佳。
* 3>基数排序使用n大关键字小的情况。
* 4>稳定排序:基数排序、所有O(n^2)排序、堆排。
*/
/** 1.插入排序 **/
/*1.1 直接插入排序 O(n^2) 稳定 特殊:如果原数据为从小到大为O(n)
* 将一个记录插入到已排好序的有序表中,从右往左比较n次,原表也移动n次。
*/
void InsetSort(int a[])
{
for(int i=2;i<=n;++i)
{
int tp = a[i], j;
for(j=i-1;i>0&&tp<a[j];--j)
a[j+1] = a[j];
a[j+1] = tp;
}
}
/*1.2 折半插入排序 O(n^2)
* 通过二分思想,在数组中折半查找,但是移动还是要O(n)次
*/
/*1.3 2-路插入排序 O(n^2)
* 在折半插入排序的基础上再改进,目的是减少排序过程中的移动次数,约为n^2/8次(包含平摊除以的2)。
* 设一辅助数组d[],令d[1]=r[1]。把d[]数组看成一个循环数组,d[1]看成中间记录。
* 每次插入时只需要在d[1]前或者后移动。移动次数减少一半,但d[1]为最小或者最大时失去作用。
*/
/*1.4 表插入排序 O(n^2)
* 前面用数组保存可以二分查找但是移动需要O(n)时间,用链表保存移动O(1)时间但是不能二分查找
* 排序后数据保存在链表中,如果不要额外的空间复制到数组中。有点复杂
*/
void Arrange(int a[])
{
//根据静态链表r中各节点的指针值调整记录位置,使得数据非递减有序排序在数组中
int p = r[0].next; //p指示第一个记录的当前位置
for(int i=1; i<n; ++i) //r[1...i-1]中记录已按关键字有序排列,
{ //第i个记录在r[]中的当前位置应不小于i
while(p < i) p = r[p].next; //找到地i个记录,并用p指示其在r中当前位置
int q = r[p].next; //q指示尚未调整的表尾
if(p != i)
{
int tp = r[p]; //交换记录,使第i个记录到位
r[p] = r[i];
r[i] = tp;
r[i].next = p; //指向被移走的记录,使得以后可有while循环找回
}
p = q; //p指示尚未调整的表尾,为找第i+1个记录做准备
}
}
/*希尔(Shell)排序 又称缩小增量排序
* 它也是一种插入排序,但在时间效率上有较大的改进。时间与增量序列有关
* 增量序列有各种取法,但:应使增量序列中的值没有除1之外的公因子,并且最后增量值必须等于1
*/
void SheelInsert(int a[], int dk)
{
//对数组做一趟希尔插入排序。本算法是和一趟直接插入排序相比,做了以下修改:
// 前后记录位置的增量式dk,而不是1
for(int i=dk+1; i<=n; ++i)
if(a[i] < a[i-dk])
{
int tp = r[i];
for(int j=i-dk; j>0&&a[j]>tp; j-=dk)
a[j+dk] = a[j];
a[j+dk] = tp;
}
}
void ShellSort(int a[], int delta[], int t)
{
//按增量序列delta[0..t-1]对数组做希尔排序
for(int i=0; i<t; ++i)
ShellInsert(a,delta[i]);
}
/** 交换排序 **/
//冒泡
//快排 O(nlogn) 原地排序 不稳定 可能退化成冒泡
int partition(int *a,int p,int r){
int i = p;
for(int j=p;j<r;++j)
if(a[j] <= a[r])
swap(a[i++],a[j]);
swap(a[i],a[r]);
return i;
}
void qsort(int *a,int p,int r){
if(p < r){
int q = partition(a,p,r);
qsort(a,p,q-1);
qsort(a,q+1,r);
}
}
/** 选择排序 **/
//简单选择排序
//堆排
int size;
void HeapAdjust(int *a,int i){
int l = i<<1;
int r = i<<1|1;
int max = i;
if(l <= size && a[l] > a[max])
max = l;
if(r <= size && a[r] > a[max])
max = r;
if(max != i){
swap(a[i],a[max]);
HeapAdjust(a,max);
}
}
void build(int *a,int n){//数组为1...n,因为堆的标号是从1开始的
size = n;
for(int i=size>>1;i>=1;--i)
HeapAdjust(a,i);
}
void HeapSort(int *a,int n){
size = n;
build(a,n);
for(int i=n;i>=2;--i){
swap(a[1],a[i]);
--size;
HeapAdjust(a,1);
}
}
/** 归并排序
* O(nlogn) O(n)的空间 同快排堆排比较最大优势是稳定排序
**/
/** 基数排序
**/