下文只涉及到内排序的内容,外排序的内容后续再更新。
内排序
插入排序
直接插入排序
直接插入排序是插入排序算法中的一种,采用的方法是:在添加新的记录时,使用顺序查找的方式找到其要插入的位置,然后将新记录插入。
void InsertSort(RecType R[], int n) //对R[0..n-1]按递增有序进行直接插入排序
{
int i, j;
RecType tmp;
for (i = 1; i < n; i++)
{
if (R[i].key < R[i - 1].key) //反序时
{
tmp = R[i];
j = i - 1;
do //找R[i]的插入位置
{
R[j + 1] = R[j]; //将关键字大于R[i].key的记录后移
j--;
} while (j >= 0 && R[j].key > tmp.key);
R[j + 1] = tmp; //在j+1处插入R[i]
}
}
}
直接插入排序算法的平均时间复杂度为:O(n2) 是稳定的排序算法。
若元素初始状态接近正序,选用直接插入排序比较好。
希尔排序
n个元素分为d组(d=n/2)相距d个位置的元素分到一组。组内一般采用直接插入排序算法。(组间必须有序,组内可以无序)
d越小时越接近有序,当d=1时基本有序。
void ShellSort(RecType R[], int n) //希尔排序算法
{
int i, j, d;
RecType tmp;
d = n / 2; //增量置初值
while (d > 0)
{
for (i = d; i < n; i++) //对所有组采用直接插入排序
{
tmp = R[i]; //对相隔d个位置一组采用直接插入排序
j = i - d;
while (j >= 0 && tmp.key < R[j].key)
{
R[j + d] = R[j];
j = j - d;
}
R[j + d] = tmp;
}
d = d / 2; //减小增量
}
}
希尔排序算法的平均时间复杂度为:O(n1.3) 是不稳定的排序算法。
交换排序
冒泡排序
起泡排序,别名“冒泡排序”,该算法的核心思想是将无序表中的所有记录,通过两两比较关键字,得出升序序列或者降序序列。通过一趟趟的比较,一个个的“最小值”被找到并移动到相应位置,直到检测到表中数据已经有序,或者比较次数等同于表中含有记录的个数,排序结束,这就是起泡排序。
void BubbleSort(RecType R[],int n)
{
int i,j;
for (i = 0;i < n - 1;i++)
{
for (j = n - 1;j > i;j--) //比较,找出本趟最小关键字的记录
if (R[j].key < R[j-1].key) //如果元素是反序
{
swap(R[j],R[j-1]); //交换两元素
}
}
}
改进的冒泡排序算法
void BubbleSort1(RecType R[], int n)
{
int i, j;
bool exchange;
for (i = 0; i < n - 1; i++)
{
exchange = false; //一趟前exchange置为假
for (j = n - 1; j > i; j--) //归位R[i],循环n-i-1次
if (R[j].key < R[j - 1].key) //相邻两个元素反序时
{
swap(R[j], R[j - 1]); //交换两元素
exchange = true; //一旦有交换,exchange置为真
}
if (!exchange) //本趟没有发生交换,中途结束算法
return;
}
}
冒泡排序算法的平均时间复杂度为:O(n2) 是稳定的排序算法。
快速排序
int partition(RecType R[],int s,int t) //一趟划分
{
int i=s,j=t;
RecType tmp=R[i]; //以R[i]为基准
while (i<j) //从两端交替向中间扫描,直至i=j为止
{ while (j>i && R[j].key>=tmp.key)
j--; //从右向左扫描,找一个小于tmp.key的R[j]
R[i]=R[j]; //找到这样的R[j],放入R[i]处
while (i<j && R[i].key<=tmp.key)
i++; //从左向右扫描,找一个大于tmp.key的R[i]
R[j]=R[i]; //找到这样的R[i],放入R[j]处
}
R[i]=tmp;
return i;
}
void QuickSort(RecType R[],int s,int t) //对R[s..t]的元素进行快速排序
{ int i;
RecType tmp;
if (s<t) //区间内至少存在两个元素的情况
{ count++;
i=partition(R,s,t);
DispList(R,10); //调试用
QuickSort(R,s,i-1); //对左区间递归排序
QuickSort(R,i+1,t); //对右区间递归排序
}
}
快速排序算法的平均时间复杂度为:O(nlog2n) 是不稳定的排序算法。
快速排序算法的平均空间复杂度为:O(log2n)
初始数据若有序,则不适合采用快速排序。
选择排序
简单选择排序
void SelectSort(RecType R[],int n)
{
int i,j,k;
for (i=0;i<n-1;i++) //做第i趟排序
{
k=i;
for (j=i+1;j<n;j++) //在当前无序区R[i..n-1]中选key最小的R[k]
if (R[j].key<R[k].key)
k=j; //k记下目前找到的最小关键字所在的位置
if (k!=i) //交换R[i]和R[k]
swap(R[i],R[k]);
}
}
简单选择排序算法的平均时间复杂度为:O(n2) 是不稳定的排序算法。
对于含有n个元素简单选择排序关键字的比较次数与记录的初始排序次序无关,总是n(n-1)/2。
堆排序
void sift(RecType R[],int low,int high)
{
int i=low,j=2*i; //R[j]是R[i]的左孩子
RecType temp=R[i];
while (j<=high)
{
if (j<high && R[j].key<R[j+1].key) //若右孩子较大,把j指向右孩子
j++; //变为2i+1
if (temp.key<R[j].key)
{
R[i]=R[j]; //将R[j]调整到双亲结点位置上
i=j; //修改i和j值,以便继续向下筛选
j=2*i;
}
else break; //否则,筛选结束
}
R[i]=temp; //被筛选结点的值放入最终位置
}
void HeapSort(RecType R[],int n)
{
int i;
for (i=n/2;i>=1;i--) //循环建立初始堆,调用sift算法 n/2 次
sift(R,i,n);
for (i=n;i>=2;i--) //进行n-1趟完成推排序,每一趟堆排序的元素个数减1
{ swap(R[1],R[i]); //将最后一个元素与根R[1]交换
sift(R,1,i-1); //对R[1..i-1]进行筛选,得到i-1个节点的堆
}
}
堆排序算法的平均时间复杂度为:O(nlog2n) 是不稳定的排序算法。
堆排序的执行时间不受初始数据状态影响。
归并排序
自底向上的二路排序算法
void Merge(RecType R[],int low,int mid,int high)
{
RecType *R1;
int i=low,j=mid+1,k=0; //k是R1的下标,i、j分别为第1、2段的下标
R1=(RecType *)malloc((high-low+1)*sizeof(RecType)); //动态分配空间
while (i<=mid && j<=high) //在第1段和第2段均未扫描完时循环
if (R[i].key<=R[j].key) //将第1段中的记录放入R1中
{
R1[k]=R[i];
i++;k++;
}
else //将第2段中的记录放入R1中
{
R1[k]=R[j];
j++;k++;
}
while (i<=mid) //将第1段余下部分复制到R1
{
R1[k]=R[i];
i++;k++;
}
while (j<=high) //将第2段余下部分复制到R1
{
R1[k]=R[j];
j++;k++;
}
for (k=0,i=low;i<=high;k++,i++) //将R1复制到R中
R[i]=R1[k];
}
void MergePass(RecType R[],int length,int n) //对整个排序序列进行一趟归并
{
int i;
for (i=0;i+2*length-1<n;i=i+2*length) //归并length长的两相邻子表
Merge(R,i,i+length-1,i+2*length-1);
if (i+length-1<n-1) //余下两个子表,后者长度小于length
Merge(R,i,i+length-1,n-1); //归并这两个子表
}
void MergeSort(RecType R[],int n) //自底向上的二路归并算法
{
int length;
for (length=1;length<n;length=2*length)//进行log2n趟归并
MergePass(R,length,n);
}
归并排序算法的平均时间复杂度为:O(nlog2n) 是稳定的排序算法。
归并排序算法的空间复杂度为:O(n)
二路归并排序是我们所学过的最耗费内存的(空间复杂度最高)的排序算法。
常见内排序方法的性能总结
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
直接插入排序 | O(n2) 排序趟数与关键字初始次序无关(n-1趟) | O(1) | 稳定 |
折半插入排序 | O(n2)仅减少了比较元素的次数,未改变移动次数 | O(1) | 稳定 |
希尔排序 | 最优可达O(n1.3) | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(1) | 稳定 |
快速排序 | O(nlog2n) | O(log2n) | 不稳定 |
简单选择排序 | O(n2) 比较次数始终是n(n-1)/2;排序趟数与关键字初始次序无关 | O(1) | 不稳定 |
堆排序 | O(nlog2n) | O(1) | 不稳定 |
二路归并排序 | O(nlog2n) | O(n) | 稳定 |
基数排序 | O( d*(n+r) ) 元素移动次数(排序趟数)与关键字初始次序无关 | O(r) | 稳定 |
Comments NOTHING