ONE 选择排序
- 简单选择排序
顾名思义,简单选择排序就是每一趟(如第i趟)从待排序列选取最小的一个元素放到第 i 个位置,直到第n-1趟做完。
//简单选择排序
void SelectSort(vector<int> &buf)
{
int n = buf.size();
for (int i = 0; i < n - 1; i++)
{
int min = i; //记录最小元素位置
for (int j = i + 1; j < n; j++) //在buf[i]到buf[n-1]中选择最小元素
{
if (buf[j] < buf[min])
{
min = j; //更新最小元素的位置
}
}
if (min != i)
{
swap(buf[i], buf[min]); //与第i个位置交换
}
}
}
- 堆排序
堆排序是一种树形选择排序方法。它的特点是:在排序过程中,将L[1...n]看成一棵完全二叉树的顺序存储结构,利用二叉树中双亲结点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的元素。
堆的定义如下:n个关键字序列L[1...n]称为堆,当且仅当该序列满足:
- L(i)<=L(2i) 且 L(i)<=L(2i+1) 或
- L(i)>=L(2i) 且 L(i)>=L(2i+1)
满足 1 的堆称为小根堆,满足 2 的堆称为大根堆。
另外,堆经常被用来实现优先级队列,优先级队列在操作系统的作业调度和其他领域有广泛应用。
堆排序的关键是构造初始堆。对初始序列建堆,就是一个反复筛选的过程。n个结点的完全二叉树,最后一个结点是第【n/2】结点的孩子。所以,从第【n/2】结点开始,对第【n/2】结点为根的子树进行筛选(比如大根堆:若根结点的关键字小于左右子女中关键字较大者, 则交换),使该子树成为堆。之后向前依次对各个结点为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不是,将左右子结点中较大值与之交换,交换后可能破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整堆的方法建堆,直到根结点。
过程如图所示:
建立大根堆的算法:
//调整函数
void AdjustDown(vector<int> &buf, int k, int len)
{
buf[0] = buf[k]; //暂存入buf[0]
for (int i = 2 * k; i <= len; i *= 2) //沿key较大的子节点向下筛选
{
if (i < len&&buf[i] < buf[i + 1])
{
i++; //取key较大的子节点的下标
}
if (buf[0] >= buf[i])
{
break; //筛选结束
}
else
{
buf[k] = buf[i]; //将buf[i]调整到双亲结点上
k = i; //修改 k 值,以便继续向下筛选
}
}
buf[k] = buf[0]; //被筛选节点的值放入最终位置
}
//建立大根堆
void BuildMaxHeap(vector<int> &buf,int len)
{
for (int i = len / 2; i > 0; i--) //从 n/2 到 1 ,反复调整堆
{
AdjustDown(buf, i, len);
}
}
接下来是具体排序操作,首先由于堆本身性质,堆顶元素就是最大值。输出堆顶元素后(通常是把堆顶元素与堆底元素互换位置,然后使堆结点数 - 1),此时根节点已经不满足大根堆性质,继续调用调整函数使其再次满足大根堆性质,再互换堆顶元素,如此重复,直到堆中只剩下一个元素为止。
堆排序算法:
//堆排序
void HeapSort(vector<int> &buf, int len)
{
BuildMaxHeap(buf, len); //初始建堆
for (int i = len; i > 1; i--) //n-1趟的交换和建堆过程
{
swap(buf[i], buf[1]); //输出堆顶元素(和堆底元素交换)
AdjustDown(buf, 1, i - 1); //把剩余的元素整理成为堆
}
}
堆排序算法的时间复杂度是O(nlog2n),空间复杂度为O(n),是一种很高效的排序算法。
TWO 归并排序
- 2--路归并排序
归并排序与前面的基于交换,选择等排序的思想不一样,“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。假定待排序表含有n个记录,则可以看做n个有序的子表,每个表长度为1,然后两两合并,得到[n/2]个长度为2或1的有序表,再两两归并,....如此重复,直到合并成为一个长度为n的有序表为止,这种排序算法称为2-路归并排序。
首先写将两个有序表归并成为一个有序表的算法void Merge():
//合并算法
//表buf的两段buf[low...mid]和buf[mid+1...high]各自有序,将它们合并为一个有序表
vector<int> buf_copy(20); //辅助数组
void Merge(vector<int> &buf, int low, int mid, int high)
{
int i, j, k;
for (int k = low; k <= high; k++)
{
buf_copy[k] = buf[k]; //将buf数组全部复制到buf_copy
}
for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++)
{
if (buf_copy[i] <= buf_copy[j]) //比较buf_copy数组左右两段中的元素
{
buf[k] = buf_copy[i++]; //将较小值复制到buf中
draw(buf, 0);
}
else
{
buf[k] = buf_copy[j++];
draw(buf, 0);
}
}
while (i <= mid)
{
buf[k++] = buf_copy[i++]; //若前半段未检测完,则剩余部分全部复制进buf
draw(buf, 0);
}
while (j <= high)
{
buf[k++] = buf_copy[j++]; //若后半段未检测完,则剩余部分全部复制进buf
draw(buf, 0);
}
}
最后是归并排序的具体代码,其效果是反复递归,使得原始序列被不断从中间分割,直到成为1个元素的数列,再依次调用Merge()函数进行归并。
void MergeSort(vector<int> &buf, int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2; //从中间划分成两个子序列
MergeSort(buf, low, mid); //对左侧子序列进行递归调用
MergeSort(buf, mid + 1, high); //对右侧子序列进行递归调用
Merge(buf, low, mid, high); //归并
}
}
- 多路归并排序
归并排序多用于外部排序,之前说过了,外部排序的定义是:
外部排序:指在排序期间元素无法全部同时存放于内存中,必须在排序过程中根据需求不断在内外存之间移动的排序。
外部排序通常采用归并排序方法,它包括两个相对独立的阶段:首先,根据内存缓冲区大小,将外存上含n个记录的文件分成若干长度为h的子文件,依次读入内存并利用有效的内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写回外存,通常称这些有序子文件为“归并段”或“顺串”,然后,对这些归并段进行逐趟归并,使归并段(有序的子文件)逐渐由小到大,直至得到整个有序文件为止。
额,这个其实我没怎么学,只知道概念,没用过,就不细谈了,以后用到再学习补充。
Three 非比较排序
非比较排序算法不通过比较两个数的大小来排序。因此可以突破基于比较的排序算法的时间下限。当然,非比较排序有其局限性,所以并不常用。
(断电了,以后再补)
- 计数排序
计数排序有几种变化,先从最简单的讲起。
最初的计数排序就是单纯的记录数据然后排序。其过程是:
- 遍历一趟待排序列buf,找出其中元素的最大值max
- 建立一个大小等于(max+1)的数组 buf_s ,全体赋值为0
- 遍历一趟buf,若 buf[i] = num,则 buf_s [num] 加 1
- 最后遍历一趟buf_s,若buf_s[i]>0,则输出i,同时buf_s[i]--
具体代码为:
//计数排序 [1.0]
void CountSort(vector<int>&buf)
{
int max = buf[0]; //定义max
for (int i = 0; i < buf.size(); i++) //遍历待排序列找到最大值max
{
if (buf[i] > max)
{
max = buf[i];
}
}
vector<int> buf_s(max + 1, 0); //创建一个大小为max+1的数组buf_s
for (int i = 0; i < buf.size(); i++) //遍历待排序列,录入buf_s数组
{
buf_s[buf[i]]++;
}
for (int i = 0, t = 0; i < max + 1; i++) //遍历buf_s存入buf数组
{
for (int j = buf_s[i]; j > 0; j--, t++)
{
buf[t] = i;
}
}
}
但是这样存在一些问题,比如,若待排序列为{995,996,997,998,999},则需要一个大小为1000的数组。因此做出如下改进:第一次遍历的同时获得max和min,建立数组为max-min+1,第二趟录入数据时,下标需减去一个min的偏移量。具体代码类似一,不再赘述。
- 桶排序
- 基数排序