前言
- 数据
Item[1:len]
Item[0] -->不存储数据 (功能:哨兵节点
OR暂存数据
)
内部排序
排序过程在内存中实现
插入排序
查找位置,移动元素,插入
直接插入排序
/**
* 直接插入排序 顺序存储,链式存储
* 0:i-1 查找位置 j+1--->顺序查找
* j+1:i-1 数据移动
*
*/
void InsertSort(Item *data, int len) {
int i, j;
for (i = 2; i < len + 1; i++) {
data[0] = data[i];
for (j = i - 1; data[0].value < data[j].value; j--) {
data[j + 1] = data[j];
}
data[j + 1] = data[0];
}
}
稳定
时间复杂度:O(n2)
空间复杂度:O(1)
最好:有序 O(n) 无查找,移动
最坏:逆序 O(n2)
折半插入排序
/**
* 折半插入排序 顺序存储
* 0:i-1 查找位置 j+1--->顺序查找
* j+1:i-1 数据移动
*
*/
void BinaryInsertSort(Item *data, int len) {
int low, high, mid;
int i, j;
for (i = 2; i < len + 1; i++) {
data[0] = data[i];
low = 1;
high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (data[mid].value > data[0].value)
high = mid - 1;
else
low = mid + 1;
}
for (j = i - 1; j >= low; j--)
data[j + 1] = data[j];
data[low] = data[0];
/* data[high + 1] = data[0];*/
}
}
稳定
时间复杂度:O(n2)
空间复杂度:O(1)
希尔排序
/**
* 希尔排序(不稳定) 顺序存储
* 步长选取 d:
*
* d1=n/2 d2=d1/2
*
* 最坏O(n^2) 一般O(n^1.3)
*
*/
void ShellSort(Item *data, int len) {
int j;
for (int d = len / 2; d >= 1; d = d / 2) {
for (int i = d + 1; i <= len; ++i) {
if (data[i].value < data[i - d].value) {
data[0] = data[i];//暂存作用,非哨兵
for (j = i - d; j > 0 && data[0].value < data[j].value; j -= d)
data[j + d] = data[j];
data[j + d] = data[0];
}
}
}
}
不稳定
时间复杂度:O(n2) O(n1.3)
空间复杂度:O(1)
交换排序
一趟排序过程通过不断的交换,确定其中一个元素的位置
冒泡排序
/**
* 冒泡排序 顺序存储,链式存储
* 一趟冒泡一个数据到达最终位置(n-1次冒泡)
*
*/
void BubbleSort(Item *data, int len) {
for (int i = 1; i < len; i++) {
_Bool flag = FALSE;
for (int j = len; j > i; j--) {
//查找逆序进行交换
if (data[j - 1].value > data[j].value) {
data[0] = data[j];
data[j] = data[j - 1];
data[j - 1] = data[0];
flag = TRUE;
}
}
if (flag == FALSE)
return;
}
}
稳定
时间复杂度:O(n2)
空间复杂度:O(1)
最好:有序 O(n) 无查找,移动
最坏:逆序 O(n2)
快速排序
依赖划分,构建二叉树
/**
* 快速排序 顺序存储,链式存储 不稳定
* 一次划分过程,一个元素到位
* Partition 划分
* QuickSortImpl
*/
void QuickSort(Item *data, int len) {
QuickSortImpl(data, 1, len);
}
void QuickSortImpl(Item *data, int low, int high) {
if (low < high) {
int mid = Partition(data, low, high);
QuickSortImpl(data, low, mid - 1);
QuickSortImpl(data, mid + 1, high);
}
}
int Partition(Item *data, int low, int high) {
data[0] = data[low];
while (low < high) {
while (low < high && data[high].value >= data[0].value)
high--;
data[low] = data[high];
while (low < high && data[low].value <= data[0].value)
low++;
data[high] = data[low];
}
data[low] = data[0];
return low;
}
不稳定
时间复杂度:O(nlog2n)
空间复杂度:O(log2n)
最坏:有序,逆序
树的高度max 时间复杂度–> O(n2) 空间复杂度—> O(n)
选择排序
每次选择出当前未排序状态下的最小OR最大
直接选择排序
/**
* 直接排序 顺序存储,链式存储 不稳定
* 每次选择一个最小的数据
*/
void SelectSort(Item *data, int len) {
for (int i = 1; i < len; ++i) {
int min = i;
for (int j = i + 1; j <= len; j++) {
if (data[j].value < data[min].value)
min = j;
}
if (min != i) {
data[0] = data[i];
data[i] = data[min];
data[min] = data[0];
}
}
}
不稳定
时间复杂度:O(n2)
空间复杂度:O(1)
堆排序
完全二叉树的顺序存储
/**
* 堆排序 顺序存储,链式存储 不稳定
*
* @param data
* @param len
*/
void HeapSort(Item *data, int len) {
BuildMaxHeap(data, len);
for (int i = len; i > 1; i--) {
data[0] = data[i];
data[i] = data[1];
data[1] = data[0];
AdjustDown(data, 1, i - 1);
}
}
void BuildMaxHeap(Item *data, int len) {
for (int i = len / 2; i > 0; i--) {
AdjustDown(data, i, len);
}
}
void AdjustDown(Item *data, int parent, int len) {
data[0] = data[parent];
int i;
for (i = parent * 2; i <= len; i = i * 2) {
if (i < len && data[i].value < data[i + 1].value)
i++;
if (data[0].value >= data[i].value)
break;
else {
data[parent] = data[i];
parent = i;
}
}
data[parent] = data[0];
}
不稳定
时间复杂度:O(nlog2n)
空间复杂度:O(1)
归并排序
/**
* 归并排序
*/
void MergeSort(Item *data, int len) {
MergeSortImpl(data, 1, len);
}
void MergeSortImpl(Item *data, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
MergeSortImpl(data, low, mid);
MergeSortImpl(data, mid + 1, high);
Merge(data, low, mid, high);
}
}
/**
* 合并 i:mid mid+1:high
*/
void Merge(Item *data, int low, int mid, int high) {
Item *buffer = (Item *) malloc((high + 1) * sizeof(Item));
// memset(buffer, 0, (high + 1) * sizeof(Item));
for (int i = low; i <= high; ++i) {
buffer[i] = data[i];
}
int i = low, j = mid + 1;
int k = i;
for (; i <= mid && j <= high; k++) {
if (buffer[i].value <= buffer[j].value)
data[k] = buffer[i++];
else
data[k] = buffer[j++];
}
while (i <= mid)
data[k++] = buffer[i++];
while (j <= high)
data[k++] = buffer[j++];
free(buffer);
buffer=NULL;
}
稳定
k时间复杂度:O(nlog2n)
空间复杂度:O(n)
基数排序
数字有d位 ,每位采用r进制
一趟排序需要分配r个队列
进行d趟分配和收集,一趟分配需要O(n),一趟收集需要O®
稳定
时间复杂度:O(d(n+r))
空间复杂度:O( r )
外部排序
处理大批量数据
首先,根据内存缓冲区大小,将外存上含n个记录的文件分成若干长度为h的子文件,依次读入内存,并利用内部排序算法进行排序,使得初始归并段有序,并将有序的初始归并段写回外存
然后,对初始归并段进行逐趟归并,使归并段逐渐由小到大,直至得到整个有序文件为止。
外部排序时间:内部排序时间+外村读写时间+内部归并时间
文件记录:n 归并段长:h
归并段数:r= ⌈n/h⌉
采用m路归并算法:
归并趟数: ⌈logmr⌉
整体文件I/O:⌈logmr⌉ +1
减少IO时间:
- 增加归并路数
- 减少归并段数
失败树
目的:增加归并路数
前因
S趟归并需要比较的次数:
S(n-1)(m-1)=⌈logmr⌉ (n-1)(m-1)
因此不能盲目增加归并路数m!!!
失败树
增加m并且使比较时间不会因此增加
比较次数:
⌈logmr⌉ (n-1) ⌈log2m⌉ = (n-1) ⌈log2r⌉
与m无关,故此时增加归并路数,对比较次数无影响
过程
but: 怎加m 会对内存缓冲区的个数有要求。
置换选择排序
减小r的数目 获取长度不等的归并段
待排序文件:17,23,46,53,11,9,78,8,56,36,21,96
输出文件FO | 工作区WA | 输入文件FI |
---|---|---|
17,23,46 | 53,11,9,78,8,56,36,21,96 | |
17 | 23,46,53 | 11,9,78,8,56,36,21,96 |
17,23 | 46,53,11 | 9,78,8,56,36,21,96 |
17,23,46 | 53,11,9 | 78,8,56,36,21,96 |
17,23,46,53 | 11,9,78 | 8,56,36,21,96 |
17,23,46,53,78 | 11,9,8 | 56,36,21,96 |
17,23,46,53,78 # | 11,9,8 | 56,36,21,96 |
8 | 11,9,56 | 36,21,96 |
8,9 | 11,56,36 | 21,96 |
8,9,11 | 56,36,21 | 96 |
8,9,11,21 | 56,36,96 | |
… | … | … |
8,9,11,21,36,56,96# |
两个初始归并段:
17,23,46,53,78
8,9,11,21,36,56,96
最佳归并树
结合哈夫曼树 (m阶哈夫曼树:树中节点的度数为m或0)
a. 通过置换选择排序获取了初始归并段
b. 构造成m阶哈夫曼树
n0+nm -1=度数=nm * m
(m-1)nm =n0-1
nm=(n0-1)/(m-1)
nm整数
补充段数目:(n0’-1)%(m-1)
n0=初始归并段数目+ 补充段数目
例. 三叉树T中有6个叶节点的权分别为2,3,4,5,6,7,T的带权外部路径长度最小为: 46 (构造3阶哈夫曼树)
补充:5%2=1 补充1个段
13+18+15=46
最小:
根
6,7
4,5
0,2,3