排序
内部排序:在内存中进行排序
插入排序
直接插入排序
将序列分为两半,前一半已经有序,后一半等待排序,将i存到0上,与后面元素依次比较
void InsSort(RecordType r[], int length) {
int i, j;
for (i = 2; i <= length; ++i) {
r[0] = r[i];//因为0不用,把i上元素存到0上
for (j = i - 1; r[0].key < r[j].key; --j)//因为用的小于号,因此算法是稳定排序
r[j + 1] = r[j];
r[j + 1] = r[0];//j+1位置上为空位置
}
}
//时间复杂度O(n^2)
//空间复杂度O(1)
//稳定排序
因为前面是有序排列,因此可以用折半查找法找到位置
折半插入
void BinSort(RecordType r[], int length) {
int i, j;
int low, high, mid;
for (i = 2; i <= length; ++i) {
r[0] = r[i];
low = 1;
high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (r[0].key < r[mid].key)
high = mid - 1;
else
low = mid + 1;
}
for (j = i - 1; j >= low; --j)
r[j + 1] = r[j];
r[low] = r[0];
}
}
shell排序
- 选取stride,即每间
- 隔s个数取为一组(可能两个以上在同一组里),在组内进行插入排序
- 每一轮缩小stride,取为一半
static void ShellInsert(RecordType r[], int length, int delta) {
int i, j;
for (i = 1 + delta; i <= length; ++i) // 1+delta为第一个子序列的第二个元素的下标
if (r[i].key < r[i - delta].key) {
r[0] = r[i];//i号元素存到0号上
for (j = i - delta; j > 0 && r[0].key < r[j].key; j -= delta)//将组内前面的所有元素向后移位置
r[j + delta] = r[j];
r[j + delta] = r[0];
}
}
void ShellSort(RecordType r[], int length) {
for (int delta = length / 2; delta >= 1; delta /= 2)//递减delta
ShellInsert(r, length, delta);
}
交换排序
冒泡排序
用swapped标记减少循环轮数
void BubbleSort(RecordType r[], int length) {
int i, j;
RecordType x;
bool swapped = true;
for (i = 1; i <= length - 1 && swapped; ++i) {
swapped = false;
for (j = 1; j <= length - i; ++j)
if (r[j + 1].key < r[j].key) {
x = r[j];
r[j] = r[j + 1];
r[j + 1] = x;
swapped = true;
}
}
}
快速排序(插空法)
三值取中法求得参考值(pivot)
-
通过pivot函数取得p
-
取得pivot作a[0](在一维数组中取出来)
-
从左向右找到一个比p小的值置为low,插到空位(第一次是插到[0],后面都是pivot的位置),递增i
-
从右向左找到一个比p大的值为high,插到空位,递减j
-
若low和high相遇,将p插到空位,完成排序
-
快排最好的情况:p每轮都在中间位置
最坏的情况:数列已经有序,退化为冒泡排序,p每轮都在第一个位置
static int Pivot(RecordType r[], int low, int high) {
RecordType x = r[low];
while (low < high) {
while (low < high && r[high].key >= x.key)
--high;
if (low < high) {
r[low] = r[high];
++low;
}
while (low < high && r[low].key < x.key)
++low;
if (low < high) {
r[high] = r[low];
--high;
}
}
r[low] = x;
return low;
}
//pivot函数将数组分为两部分,左边全部小于p,右边全部大于p,并且返回low值作为pivot
static void _QuickSort_(RecordType r[], int low, int high) {
if (low < high) {
int pivot = Pivot(r, low, high);
_QuickSort_(r, low, pivot - 1);
_QuickSort_(r, pivot + 1, high);
}
}
// 科里化,wrapper,可以在调用函数的时候减少传参
void QuickSort(RecordType r[], int length) {
_QuickSort_(r, 1, length);
}
快速排序(交换法)
选择排序
-
选择排序:在每一轮中,选择未排序部分的最小(或最大)元素,然后将其与未排序部分的第一个元素交换位置。这个过程重复进行,直到整个数组被排序。
-
插入排序:从数组的第二个元素开始,将每个元素与前面已排序的元素进行比较,找到合适的位置插入,直到所有元素都被插入到正确的位置。
简单选择
void SelectSort(RecordType r[], int length) {
int k, j, i;
RecordType x;
for (i = 1; i < length; ++i) {//外层循环遍历未排序部分
k = i;
for (j = i + 1; j <= length; ++j)
if (r[j].key < r[k].key)
k = j;//找到最小值
if (k != i) {
x = r[i];
r[i] = r[k];
r[k] = x;//完成swap
}
}
}
树形排序:在树中完成排序,每排一个将其置为无穷大,
堆排序
- 将一个原始序列(一维数组)视为一个堆,初始化为大顶堆(如何创建?)
- 交换最后一个堆首和队尾的元素,重构大顶堆
- 重复交换,直到只剩根节点
初始化堆的方法:从第一个非叶结点开始,比较左右子树,将大的值一路上溯到根结点
/*
* 假设r[k..m]是以r[k]为根的完全二叉树,且分别以r[2k]和r[2k+1]为根的左、右子树为大根堆,
* 调整r[k],使整个序列r[k..m]满足堆的性质
*/
//上滤函数(上滤为大顶堆)
static void sift(RecordType r[], int k, int m) {//m为最后一个索引
RecordType t = r[k]; // 暂存"根"记录r[k]
bool finished = false;
int i = k; //i是子树的根节点序号
int j = 2 * i; //初始时,j是i的左孩子序号
//沿着i的孩子j,往下筛选
while (j <= m && !finished) {
if (j < m && r[j].key < r[j + 1].key) //j+1是i右孩子的序号,判断右子是否大于左子
++j; //若存在右子树,且右子树根的关键字大,则沿右分支"筛选"
if (t.key >= r[j].key)
finished = true; //如果父节点大,筛选完毕
else {
r[i] = r[j]; //子树的根节点被其值最大的那个孩子覆盖
i = j; //以j为子树的根,进行下一趟筛选
j = 2 * i;
}
}
//i已经不再是原来的i,而是原来的某个深度的孩子的序号
r[i] = t; //原来的根r[k]填入到恰当的位置
}
//从无序数组中建一个堆,length为数组的长度
static void create_heap(RecordType r[], int length) {
/*
* 自第[n/2]个记录开始进行筛选建堆
* 从这个记录开始往前(序号变小)都是非叶节点。当然了,此后的一定都是叶节点
*/
for (int i = length / 2; i >= 1; --i)
sift(r, i, length);//在所有非叶节点中完成上滤
}
//对r[1..n]进行堆排序,执行本算法后,r中记录按关键字由大到小有序排列
void HeapSort(RecordType r[], int length) {
RecordType b;
create_heap(r, length);
for (int i = length; i >= 2; --i) {
//将堆顶记录和堆中的最后一个记录互换
b = r[1];
r[1] = r[i];
r[i] = b;
//进行调整,使r[1..i-1]变成堆
sift(r, 1, i - 1);
}
}