八大排序
在处理大批量数据时,有序化的数据可以在很大程度上提高算法效率。
直接插入排序 先总结一下数据结构的八大排序,分别是插入排序中的直接插入排序,希尔排序,交换排序中的起泡排序,快速排序,选择排序中的直接选择排序,堆排序,以及归并排序和基数排序。
如何评价排序的优劣呢?除了正确,易读和容错(自动检错,报错并通过与用户对话来纠错)以外,性能是一个重要指标。
性能分析
算法性能是指运行一个算法所需要的时间长短和内存多少,他们分别称为时间复杂性和空间复杂性。
时间复杂性分析
1)有些计算机需要用户提供程序运行时间的上限。一旦达到这个上限,程序将被强制结束。
2)一个正在开发的程序可能需要一个令人满意的实时响应。
选择什么样的时间单位(程序步)来度量算法运行时间呢?对少量的输入,算法瞬间就运行完了。所以对算法性能的评价总是对大的输入量而言的。
假设输入量是n,算法运行时间是n的函数T(n),我们研究当很大时,T(n)是什么级别。这里就用到了大O记法:如果存在正常数c和n0,使得当n≥n0时,T(n)≤ c*f(n),则记为T(n)=O(f(n))。
空间复杂性分析
算法所需空间包括固定部分和变动部分。固定部分与输入量或规模无关,主要包括程序码空间和常量,变量和对象的定长所占的空间。变动部分与输出量有关,主要包括递归栈空间和中间处理所需空间。如果用P表示算法,S(P)表示空间需求,那么S(P)=c(固定部分)+Sp(变动部分)。算法的空间复杂性分析重点是变动部分Sp。
稳定性
此外,如果一种排序实施前后,关键码相同的任意两个数据元素其前后次序没有发生变化,那么这个排序方法就被称作是稳定的,否则就是不稳定的。
1.直接插入排序
template
void InsertSort(T* pa, int n)
{
T temp;
for(int i = 1; i < n; i++)
{
temp = pa[i];
int j = i;
while(j >= 1 && temp < pa[j-1])
{
pa[j] = pa[j-1];
j--;
}
pa[j] = temp;
}
}
原理:从待排序集的第1个数据元素开始,依次选择数据元素,与有序子集的数据元素依次从后往前进行比较,选择插入位置。
稳定性:稳定。
2.希尔排序
template
void ShellSort(T* pa, int n)
{
T temp;
int gap = n/2;
while(gap)
{
for(int start = gap; start < 2*gap && start < n; start ++)
for(int i = start; i < n; i += gap)
{
temp = pa[i];
int j = i;
while(j >= gap && temp < pa[j - gap])
{
pa[j] = pa[j - gap];
j -= gap;
}
pa[j] = temp;
}
gap = gap/2;
}
}
原理:以增量为步长划分子序列,即同一子序列的数据元素,其下标步长等于增量。对每个子序列实施直接插入排序。不断缩小增量,当增量为1时,所有数组元素都在一个子序列中,成为有序集。
通俗来讲,增量即为数组中元素下表的差值,假设步长为4,及a[0],a[4],a[8]…为一个子序列。实行直接插入排序后,将增量缩小为一半,直至增量缩小为1。
稳定性:不稳定
3.起泡排序
template
void BubbleSort(T* pa, int n)
{
T temp;
int i = 0;
while(i < n - 1)
{
int last = n - 1;
for(int j = n - 1; j > i; j --)
if(pa[j] < pa[j-1])
{
temp = pa[j - 1];
pa[j - 1] = pa[j];
pa[j] = temp;
last = j;
}
i = last;
}
}
原理:把数组分为左右两个半区,左半区为有序子集,右半区为无序子集。开始时,左半区为空。在无序子集中,从后往前,两两相邻元素比较,逆序则交换。最后交换的位置成为有序子集的上界。直到一趟起泡排序中没有发生交换,排序停止。
稳定性:稳定。
4.快速排序
template //给数组分区
void Partition(T* pa, int low, int high)
{
int i = low, j = high;
T temp = pa[i];
while(i != j)
{
while(pa[j] >= temp && j > i)
j --;
if(j > 1)
pa[i ++] = pa[j];
while(pa[i] <= temp && i
void QuickSort(T* pa, int low, int high)
{
if(low >= high)
return;
int m = Partition(pa, low, high);
QuickSort(pa, low, m - 1);
QuickSort(pa, m + 1,high);
}
template
void QuickSort(T* pa, int n)
{
QuickSort(pa, 0, n - 1);
}
原理:取无序子集中的第一个数据元素作为基准,将无序子集分为左右两个半区,左半区不大于基准,右半区不小于基准;然后对左右半区重复上述操作,知道各半区元素个数为1.
稳定性:不稳定,主要是划分算法Partition造成的。
5.直接选择排序
template
void SelectSort(T* pa, int n)
{
T temp;
for(int i = 0; i <= n - 1; i ++)
{
int min = i;
for(int j = i + 1; j < n; j ++)
if(pa[j] < pa[min])
min = j;
if(min != i);
{
temp = pa[i];
pa[i] = pa[min];
pa[min] = temp;
}
}
}
原理:将数组分为左右两个半区,左半区为有序子集,右半区为无序子集。开始时,有序子集为空。在无序子集中,选出最小元素,与无序子集第一个元素交换,再将第一个元素并入有序子集中。重复上述操作。
稳定性:稳定。
6.堆排序
template
void BuildHeap(T* pa, int size) //建大根堆
{
for(int i = size/2 - 1; i >= 0; i--)
PercolateDown(pa, i, size); ////将下标[i, size)范围内的元素调整为堆
}
template
void PercolateDown(T* pa, int pos, int size) //将下标[pos, size)范围内的元素调整为堆
{
int p = pos,c = 2*p + 1;
T temp = pa[p];
while(c < size)
{
if(c + 1 < size && pa[c + 1] > pa[c])
++ c;
if(temp >= pa[c])
break;
else
{
pa[p] = pa[c];
p = c;
c = 2*p + 1;
}
}
pa[p] = temp;
}
template
void HeapSort(T* pa, int n)
{
T temp;
BuildHeap(pa, n);
for(int i = n - 1; i > 0; i --)
{
temp = pa[0];
pa[0] = pa[i];
pa[i] = temp;
PercolateDown(pa, 0, i);
}
}
原理:
1)将数组分为左右两个半区,左半区为有序子集,右半区为无序子集。开始时,有序子集为空。
2)将无序子集创建为大根堆。
3)将堆化为无序子集首位数据元素交换,将交换后的尾元素并入有序子集,然后把缩小的无序子集调整为大根堆。
4)重复步骤3)n-2次。
稳定性:不稳定。
7.归并排序
template
void Merge(T* ini,T* merge, int s, int m, int e) //二路归并
{
int i = s, j = m + 1, k = s;
while(i <= m && j <= e)
if(ini[i] < ini[j])
merge[k ++] = ini[i ++];
else
merge[k ++] = ini[j ++];
if(i <= m)
while(i <= m)
merge[k ++] = int[i ++];
else
while(j <= e)
merge[k ++] = ini[j ++];
}
template
void MergePass(T* ini,T* merge, int len, int size) //一趟归并
{
int i = 0;
while(i + 2*len - 1 <= size - 1)
{
Merge(ini, merge, i, i + len - 1, i + 2 * len - 1);
i = i + 2*len;
}
if(i + len <= size - 1)
Merge(ini, merge, i, i + len - 1, size - 1);
else
while(i <= size - 1)
merge[i ++] = int[i];
}
template
void MergeSort(T* pa, int n) //迭代的归并排序
{
T* merge = new T[n];
int len = 1;
while(len < n)
{
MergePass(pa, merge, len, n);
len *= 2;
MergePass(merge, pa, len, n);
len *= 2;
}
delete[]merge;
}
原理:
1)归并(一般指二路归并):将两个有序表合成一个新的有序表。包含关键码的原始数组ini分为左右两个有序分区(归并段)[s,m]和[m+1,e],将他们按序归并,一个归并段存储到一个辅助数组(merge)中。
2)迭代归并:包含关键码的原始数组ini按长度len划分为几个连续的归并段,每一个归并段都有序,用二路归并将相邻归并段合成一个长度为2len的归并段并存入辅助数组,这个过程称为一趟归并。重复上述步骤。
①剩下一个长度为len的归并段和一个长度不足len的归并段,继续调用二路归并。
②只剩下一个长度为len或不足len的归并段,直接移至辅助数组merge。
稳定性:稳定。
8.基数排序
include
void RadixSort(int* pa, int n)
{
queue Q[10];
int base = 1, flag = 1, k;
while(flag)
{
for(int i = 0; i < n; i ++)
{
k = (pa[i]/base) % 10;
Q[k].push(pa[i]);
}
base *= 10;
flag = 0;
i = 0;
for(k = 0; k < 10; k ++)
while(!Q[k].empty())
{
pa[i++] = Q[k].front();
Q[k].pop();
if(pa[i - 1]/base != 0 && flag == 0)
flag = 1;
}
}
}
原理:采用“分配”和“收集”技术,从关键码的低位到高位进行比较。有十个队列作为分配用的”箱子“,编号0~9。遵照先进先出原则,从个位开始排序,到十位,百位,以此类推。
稳定性:稳定。