内排序和外排序中的“内外”指的是内存和外存。
基本概念
- 记录(record ):结点
- 关键码(key ):序号
- 排序码(sort key ):作为排序依据的一个或多个域,不一定是关键码
- 序列(sequence):线性表
- 排序(sort )将序列中的记录按照排序码排列起来,使序列不增或不减。
- 内排序,在内存中进行的排序
- 正序
逆序
稳定性:排序码相同时保持原来的相对次序。
衡量标准:
- 时间代价:记录的比较和移动次数
- 空间代价:占用的存储空间
- 算法本身繁杂程度
插入排序
直接插入排序
- 和抓牌一样
代码
//直接插入排序算法
template <class Record>
void ImprovedInsertSort(Record Array[],int n)
{
Record TempRecord;
for(int i = 1;i < n;i++)
{
TempRecord = Array[i];
if(Array[i] < Array[0]) //如果是最小值,先行处理
{
for(int j = i - 1;j >= 0;j--)
Array[j+1] = Array[j];
Array[0] = Array[i];
}
else
{
for(int j = i - 1;j >= 0;j--)
{
if(Array[i] >= Array[j]) //找到插入的位置
{
for(int k = i - 1;k > j;k--)
Array[k+1] = Array[k];
Array[j+1] = TempRecord;
break; //跳出循环
}
}
}
}
}
算法分析
- 稳定。保持稳定性方法:排序码相同时直接靠后放
- 时间复杂度O(n2),平均复杂度O(n2),在最好情况下Θ(n)
- 空间复杂度O(1)
- 对于短序列,插入算法比较简单有效
Shell排序
- 将序列变成很多小序列进行排序
- 逐步扩大小序列规模,直到整个序列
- 可调节步长
代码
//Shell排序算法
template <class Record>
void ShellSort(Record Array[],int n)
{
int i,delta;
for(i = 0;i < delta;i++)
ModInsSort(&Array[i],n - i,delta); //传地址
ModInsSort(Array,n,1);
}
class template<Record>
void ModInsSort(Record Array[],int n,int delta)
{
for(int i = delta;i < n;i += delta)
{
for(int j = i;j >= delta;j -= delta)
{
if(Array[j] < Array[j - delta])
swap(Array,j,j - delta);
else break;
}
}
}
算法分析
- 不稳定
- 空间代价Θ(1)
- 增量每次除以2时,时间代价是Θ(n2)
- 适当选取增量序列,可以使时间代价接近Θ(n)
- Hibbard增量序列{2k−1},时间代价Θ(n3/2)
选择排序
直接选择排序
- 选出剩下未排序记录中的最小记录,然后直接和数组的第i个记录交换。
代码
template<class Record>
void SelectSort(Record Array[],int n)
{
for(int i = 0;i < n;i++)
{
int Smallest = i; //先标记i最小
for(int j = i;j < n;j ++)
if(Compare::It(Array[j],Array[Smallest]))
Smallest = j; //找到剩下的记录中最小的,并且标记
swap(Array,i,Smallest); //替换
}
}
算法分析
- 不能保证稳定性(
\? ) - 时间代价Θ(n2):比较次数Θ(n2),交换次数n−1
- 空间代价Θ(1)
堆排序
- 最大值堆
- 建堆
- 每次删除堆顶,按顺序放入数组尾部
代码
//最大值堆排序
template <class Record>
void sort(Record Array[],int n)
{
MaxHeap<Record> max_heap=MaxHeap<Record>(Aray,n,n);
for(int i = 0;i < n;i++)
{
max_heap.RemoveMax();
}
}
算法分析
- 时间复杂度:Θ(nlogn)。1次建堆
Θ(n) ,n次删除堆顶Θ(logi) - 空间代价Θ(1)
交换排序
冒泡排序
- 不停比较相邻的记录,不满足要求就交换。
代码
//冒泡排序算法
template <class Record>
void BubbleSort(Record Array[],int n)
{
for(int i = 0;i < n;i++)
for(int j = i + 1;j < n;j++)
{
if(Array[j] < Array[i])
swap(Array,i,j);
}
}
算法分析
- 稳定
- 空间代价O(1)
- 时间代价Θ(n2)
快速排序
- 1962年Tony Hoare提出
- 基于分治法的排序:快速、归并
- 选择轴值(pivot )
- 将序列划分为L和R两个子序列,L中的记录都小于等于轴值,R的记录大于轴值
递归,对子序列L、R分别进行快速排序
过程
- 选择轴值并存储
- 将最后一个元素放在轴值的位置
- 初始化下标i,j,分别指着头尾
- i递增直到遇到比轴值大的元素,把这个元素放到
j 的位置;
j递增直到遇到比轴值小的元素,把这个元素放到i 的位置; - 重复直到i==j,把轴值放回到i位置。
代码
//快速排序算法
template<class Record>
void QuickSort(Record Array[], int left,int right)
{
if(right <= left)
return;
int pivot = SelectPivot(left,right); //选择轴值
swap(Array,pivot,right); //把右边最后一个元素调到轴值的位置
pivot = Partition(Array,left,right); //分割一次
QuickSort(Array,left,pivot-1); //递归,对两个子序列继续分割
Quicksort(pivot+1,right);
}
int SelectPivot(left,right)
{
return (left+right)/2;
}
//partition分割算法
template <class Record>
int Partition(Record Array,int left,int right)
{
int i = left;
int j = right;
Record TempRecord = Array[j]; //这个Arrray[j]是交换过来的轴值
while(i != j) //直到i,j相等时才停止
{
while(Array[i] <= TempRecord && i < j) //找到左边大于轴值的i
i++;
if(i < j) //如果两头还没相遇,逆置元素调到右边空位
{
Array[j] = Array[i];
j--;
}
while(Array[j] >= TempRecord && i < j) //找到右边小于轴值的j
j--;
if(i < j) //如果两头还没相遇,逆置元素调到左边空位
{
Array[i] = Array[j];
i++;
}
Array[i] = TempRecord; //轴值归位
return i; //返回轴值
}
}
算法分析
- 最差情况
- 时间代价
Θ(n2) - 空间代价Θ(n)
- 时间代价
- 最佳情况
- 时间代价Θ(nlogn)
- 空间代价Θ(logn)
- 平均情况
- 时间代价Θ(nlogn)
- 空间代价Θ(logn)
- 不稳定
- 优化方法
- 轴值的选择,如RQS
- 小子串不递归
- 消除递归
归并排序
- 简单地将原始序列划分为两个子序列
- 分别对每个子序列进行递归
- 归并,即将排好序的子序列合并成一个序列。
代码
//归并排序算法
template <class Record>
void MergeSort(Record Array[],Record TempArray,int left,int right)
{
int middle;
if(left<right)
{
middle = (left+right)/2;
MergeSort(Array,TempArray,left,middle);
MergeSort(Array,TempArray,middle+1,right);
Merge(Array,TempArray,left,right,middle);
}
}
template <class Record>
void Merge(Record Array[],Record TempArray[],int left,int right,int middle)
{
int i,j,index1,index2;
for(int j = left;j<right;j++)
TempArray[j] = Array[j];
index1 = left;
index2 = middle + 1;
i = left;
while(index1 < middle && index2 < right)
{
if(TempArray[index1] <= TempArray[index2])
Array[i++] = TempArray[index1++];
else
Array[i++] = TempArray[index2++];
}
while(index1 <=(?) middle)
Array[i++] = TempArray[index1++];
while(index2 <= right)
Array[i++] = TempArray[index2++];
}
优化——R.Sedgewick算法
算法分析
- 空间代价:Θ(n)
- 时间代价Θ(nlogn),最大、最小、平均都是Θ(nlogn)
- 稳定
分配排序和基数排序
- 不需要进行两个记录之间的比较
- 需要事先知道序列中记录的一些具体情况
桶式排序
- 需要事先知道序列中记录在小区间[0,m)内
- 适合大量重复数值
- 将相同值扔到同一个桶里,最后按照桶的顺序依次取出
- 数据结构:只需要额外加两个和桶个数相当的两个数组
- 数组长度n,区间
[0,m) ,时间复杂度Θ(m+n) - 空间复杂度Θ(m+n)
- 稳定
基数排序
- 当桶式排序中的m很大时,可以进行拆分,比如多位数拆分成多个一位数
- 设序列长度
n ,排序码可以拆分成d个子排序码 - 设子排序码有优先级,比如多位数
- 分为高位优先法(MSD)和低位优先法(LSD)
- 高位优先法:对最高位进行桶式排序,序列分成若干个桶;在每个桶里,对次高位进行桶式排序,继续划分序列;…;最后将所有的桶收到一起。分、分、分…收的过程,空间代价较大。
排序算法的时间代价
方法 | 时间复杂度 | ||||
---|---|---|---|---|---|
直接插入 | |||||
shell |