排序记录区
内部排序方法
顺序表:记录之间的次序关系由其存储位置决定,实现排序需要移动记录
链表排序:记录之间的次序关系由指针指示,实现排序不需要移动记录,仅需修改指针即可
地址排序:待排序记录本身存储在一组地址连续的存储单元内,同时另设一个指示各个记录存储位置的地址向量,在排序过程中不移动记录本身,而移动地址向量中那些记录的 “地址” ,在排序结束之后再按照地址向量中的值调整记录的存储位置
#define MAXSIZE 10 // 顺序表的最大长度
typedef struct {
int key; // 关键字项
int value; // 其他数据项
} RedType; // 记录类型
typedef struct {
RedType r[MAXSIZE+1]; // r[0] 闲置或用做哨兵单元
int length; // 顺序表长度
} SQList; // 顺序表类型
void InsertSort(SQList* L) {
for (int i = 2; i < MAXSIZE + 1; i++)
{
if (L->r[i].key < L->r[i-1].key)
{
L->r[0] = L->r[i];
for (int j = 1; j < i; j++)
{
if (L->r[0].key < L->r[j].key)
{
L->r[i] = L->r[j];
L->r[j] = L->r[0];
L->r[0] = L->r[i];
}
}
}
}
L->r[0].key = L->r[0].value = 0;
printf("InsertSort Success\n");
}
时间复杂度
最好情况:比较次数 = n-1 ,记录不需移动
最坏情况:
关键字比较次数 KCN
K C N = ∑ i = 2 n i = ( n + 2 ) ( n − 1 ) 2 ≈ n 2 2 KCN = \sum_{i=2}^{n}i = \frac{(n+2)(n-1)}{2} ≈ \frac{n^2}{2} KCN=i=2∑ni=2(n+2)(n−1)≈2n2
记录移动次数 RMN
R M N = ∑ i = 2 n ( i + 1 ) = ( n + 4 ) ( n − 1 ) 2 ≈ n 2 2 RMN = \sum_{i=2}^{n}(i+1) = \frac{(n+4)(n-1)}{2} ≈ \frac{n^2}{2} RMN=i=2∑n(i+1)=2(n+4)(n−1)≈2n2
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度
算法特点
void BInsertSort(SQList* L) {
for (int i = 2; i < MAXSIZE + 1; i++)
{
int low = 1;
int high = i - 1;
L->r[0] = L->r[i]; // 将待插入的记录暂存到监视哨中
while (low <= high) // 在 r[low~high] 中折半查找插入的位置
{
int m = (low + high) / 2; // 折半
if (L->r[0].key < L->r[m].key)
{
high = m - 1; // 插入点在前一子表
}
else
{
low = m + 1; // 插入点在后一子表
}
}
for (int j = i - 1; j >= high + 1; j--)
{
L->r[j + 1] = L->r[j]; // 记录后移
}
L->r[high + 1] = L->r[0]; // 将原 r[i] 插入到正确位置
}
L->r[0].key = L->r[0].value = 0;
printf("BInsertSort Success\n");
}
时间复杂度
空间复杂度
算法特点
void ShellInsert(SQList* L, int step) {
for (int i = step + 1; i < MAXSIZE + 1; i++)
{
if (L->r[i].key < L->r[i - step].key) // 将 L->r[i] 插入有序增量子表
{
L->r[0] = L->r[i]; // 暂存 L->r[i] 于 L->r[0]
int j = 0;
for (j = i - step; j > 0 && L->r[0].key < L->r[j].key; j -= step)
{
L->r[j + step] = L->r[j]; // 记录后移,直到找到插入位置
}
L->r[j + step] = L->r[0]; // 将原 r[i] 插入到正确位置
}
}
}
void ShellSort(SQList* L, int dt[], int num) {
for (int i = 0; i < num; i++)
{
ShellInsert(L, dt[i]);
}
printf("ShellSort Success\n");
}
时间复杂度
空间复杂度
算法特点
#include
#include
#define MAXSIZE 10
void InsertSort(SQList);
void BInsertSort(SQList);
void ShellInsert(SQList);
void ShellSort(SQList);
void PrintList(SQList);
typedef struct {
int key;
int value;
} RedType;
typedef struct {
RedType r[MAXSIZE + 1];
int length;
} SQList;
int main() {
int keys[MAXSIZE] = { 2,4,6,8,5,3,7,9,10,1 };
SQList L;
SQList Copy;
L.r[0].key = L.r[0].value = 0;
L.length = 0;
for (int i = 1; i < MAXSIZE + 1; i++)
{
L.r[i].key = L.r[i].value = keys[i-1];
L.length++;
}
Copy = L;
InsertSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
BInsertSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
int dt[] = { 5,3,1 };
ShellSort(&L, dt, 3);
PrintList(L);
L = Copy;
printf("****************\n");
}
void InsertSort(SQList* L) {
for (int i = 2; i < MAXSIZE + 1; i++)
{
if (L->r[i].key < L->r[i-1].key)
{
L->r[0] = L->r[i];
for (int j = 1; j < i; j++)
{
if (L->r[0].key < L->r[j].key)
{
L->r[i] = L->r[j];
L->r[j] = L->r[0];
L->r[0] = L->r[i];
}
}
}
}
L->r[0].key = L->r[0].value = 0;
printf("InsertSort Success\n");
}
void BInsertSort(SQList* L) {
for (int i = 2; i < MAXSIZE + 1; i++)
{
int low = 1;
int high = i - 1;
L->r[0] = L->r[i];
while (low <= high)
{
int m = (low + high) / 2;
if (L->r[0].key < L->r[m].key)
{
high = m - 1;
}
else
{
low = m + 1;
}
}
for (int j = i - 1; j >= high + 1; j--)
{
L->r[j + 1] = L->r[j];
}
L->r[high + 1] = L->r[0];
}
L->r[0].key = L->r[0].value = 0;
printf("BInsertSort Success\n");
}
void ShellInsert(SQList* L, int step) {
for (int i = step + 1; i < MAXSIZE + 1; i++)
{
if (L->r[i].key < L->r[i - step].key)
{
L->r[0] = L->r[i];
int j = 0;
for (j = i - step; j > 0 && L->r[0].key < L->r[j].key; j -= step)
{
L->r[j + step] = L->r[j];
}
L->r[j + step] = L->r[0];
}
}
}
void ShellSort(SQList* L, int dt[], int num) {
for (int i = 0; i < num; i++)
{
ShellInsert(L, dt[i]);
}
printf("ShellSort Success\n");
}
void PrintList(SQList L) {
for (int i = 1; i < MAXSIZE + 1; i++)
{
printf("%d ", L.r[i].key);
}
printf("\n");
}
void BubbleSort(SQList* L) {
int flag = 0; // flag 标记某一趟排序是否发生交换
int index = MAXSIZE;
while ((flag == 0) && (index > 1))
{
flag = 1; // flag 置为 1 ,如果本趟没有发生交换,则不会执行下一趟排序
for (int i = 1; i < index; i++)
{
if (L->r[i].key > L->r[i+1].key)
{
flag = 0; // flag 置为 0 ,表示本趟排序发生了交换
L->r[0] = L->r[i]; // 交换前后两个记录
L->r[i] = L->r[i + 1];
L->r[i + 1] = L->r[0];
}
}
index--;
}
L->r[0].key = L->r[0].value = 0;
printf("BubbleSort Success\n");
}
时间复杂度
最好情况(初始序列为正序):只需进行一趟排序,在排序过程中进行 n-1 次关键字间的比较,且不移动记录
最坏情况(初始序列为逆序):需进行 n-1 趟排序
总的关键字比较次数 KCN
K C N = ∑ i = n 2 ( i − 1 ) = n ( n − 1 ) 2 ≈ n 2 2 KCN = \sum_{i=n}^{2}{(i-1)} = \frac{n(n-1)}{2} ≈ \frac{n^2}{2} KCN=i=n∑2(i−1)=2n(n−1)≈2n2
总的记录移动次数 RMN(每次交换都要移动 3 次记录)
R M N = 3 ∑ i = n 2 ( i − 1 ) = 3 n ( n − 1 ) 2 ≈ 3 n 2 2 RMN = 3\sum_{i=n}^{2}{(i-1)} = \frac{3n(n-1)}{2} ≈ \frac{3n^2}{2} RMN=3i=n∑2(i−1)=23n(n−1)≈23n2
平均情况下, K C N = n 2 4 , R M N = 3 n 2 4 KCN = \frac{n^2}{4},RMN = \frac{3n^2}{4} KCN=4n2,RMN=43n2
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度
算法特点
int Partition(SQList* L, int low, int high) {
L->r[0] = L->r[low]; // 用子表的第一个记录做枢轴记录
int key = L->r[low].key; // 枢轴记录关键字保存在 key 中
while (low < high) // 从表的两端交替地向中间扫描
{
while ((low < high) && (L->r[high].key >= key))
{
high--;
}
L->r[low] = L->r[high]; // 将比枢轴小的记录移动到低端
while ((low < high) && (L->r[low].key <= key))
{
low++;
}
L->r[high] = L->r[low]; // 将比枢轴大的记录移动到高端
}
L->r[low] = L->r[0]; // 枢轴记录到位
return low; // 返回枢轴位置
}
void QSort(SQList* L, int low, int high) {
if (low < high)
{
int middle = Partition(L, low, high); // 将 L->r[low···high] 一分为二,midddle 是枢轴位置
QSort(L, low, middle - 1); // 对左子表递归排序
QSort(L, middle + 1, high); // 对右子表递归排序
}
}
void QuickSort(SQList* L) {
QSort(L, 1, MAXSIZE);
printf("QuickSort Success\n");
}
时间复杂度
最好情况:每一趟排序后都能将记录序列均匀地分割成两个长度大致相等的子表,类似折半查找
最坏情况:在待排序序列已经排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个记录的子序列
必须经过 n-1 趟才能将所有记录定位,而且第 i 趟需要经过 n-i 次比较
总的关键字比较次数 KCN
K C N = ∑ i = 1 n − 1 ( n − i ) = n ( n − 1 ) 2 ≈ n 2 2 KCN = \sum_{i=1}^{n-1}{(n - i)} = \frac{n(n-1)}{2} ≈ \frac{n^2}{2} KCN=i=1∑n−1(n−i)=2n(n−1)≈2n2
时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
空间复杂度
算法特点
#include
#include
#define MAXSIZE 10
void BubbleSort(SQList);
int Partition(SQList);
void QSort(SQList);
void QuickSort(SQList);
void PrintList(SQList);
typedef struct {
int key;
int value;
} RedType;
typedef struct {
RedType r[MAXSIZE + 1];
int length;
} SQList;
int main() {
int keys[MAXSIZE] = { 2,4,6,8,5,3,7,9,10,1 };
SQList L;
SQList Copy;
L.r[0].key = L.r[0].value = 0;
L.length = 0;
for (int i = 1; i < MAXSIZE + 1; i++)
{
L.r[i].key = L.r[i].value = keys[i-1];
L.length++;
}
Copy = L;
BubbleSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
QuickSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
}
void BubbleSort(SQList* L) {
int flag = 0;
int index = MAXSIZE;
while ((flag == 0) && (index > 1))
{
flag = 1;
for (int i = 1; i < index; i++)
{
if (L->r[i].key > L->r[i+1].key)
{
flag = 0;
L->r[0] = L->r[i];
L->r[i] = L->r[i + 1];
L->r[i + 1] = L->r[0];
}
}
index--;
}
L->r[0].key = L->r[0].value = 0;
printf("BubbleSort Success\n");
}
int Partition(SQList* L, int low, int high) {
L->r[0] = L->r[low];
int key = L->r[low].key;
while (low < high)
{
while ((low < high) && (L->r[high].key >= key))
{
high--;
}
L->r[low] = L->r[high];
while ((low < high) && (L->r[low].key <= key))
{
low++;
}
L->r[high] = L->r[low];
}
L->r[low] = L->r[0];
return low;
}
void QSort(SQList* L, int low, int high) {
if (low < high)
{
int middle = Partition(L, low, high);
QSort(L, low, middle - 1);
QSort(L, middle + 1, high);
}
}
void QuickSort(SQList* L) {
QSort(L, 1, MAXSIZE);
printf("QuickSort Success\n");
}
void PrintList(SQList L) {
for (int i = 1; i < MAXSIZE + 1; i++)
{
printf("%d ", L.r[i].key);
}
printf("\n");
}
void SelectSort(SQList* L) {
for (int i = 1; i < MAXSIZE; i++) // 从 L->r[low···high] 中选择关键字最小的记录
{
int index = i;
for (int j = i + 1; j < MAXSIZE + 1; j++)
{
if (L->r[j].key < L->r[index].key)
{
index = j; // index 指向此趟排序中关键字最小的记录
}
}
if (index != i)
{
L->r[0] = L->r[i]; // 交换 r[i] 与 r[index]
L->r[i] = L->r[index];
L->r[index] = L->r[0];
}
}
printf("SelectSort Success\n");
}
时间复杂度
最好情况(正序):不移动
最坏情况(逆序):移动 3 ( n − 1 ) 3(n-1) 3(n−1) 次
总的关键字比较次数 KCN
K C N = ∑ i = 1 n − 1 ( n − i ) = n ( n − 1 ) 2 ≈ n 2 2 KCN = \sum_{i=1}^{n-1}{(n - i)} = \frac{n(n-1)}{2} ≈ \frac{n^2}{2} KCN=i=1∑n−1(n−i)=2n(n−1)≈2n2
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度
算法特点
树形选择排序(Tree Selection Sort)(锦标赛排序(Tournament Sort)):按照锦标赛的思想进行选择排序的方法
时间复杂度
堆排序(Heap Sort):将待排序的记录 r[ 1 ··· n ] 看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序的序列中选择关键字最大(或最小)的记录
堆:n 个元素的序列 k 1 , k 2 , ⋅ ⋅ ⋅ , k n {k_1,k_2,···,k_n} k1,k2,⋅⋅⋅,kn 满足以下条件时才称之为堆
k i ≥ k 2 i 且 k i ≥ k 2 i + 1 或 k i ≤ k 2 i 且 k i ≤ k 2 i + 1 ( 1 ≤ i ≤ ⌊ n 2 ⌋ ) k_i ≥ k_{2i} 且 k_i ≥ k_{2i + 1} \quad 或 \quad k_i ≤ k_{2i} 且 k_i ≤ k_{2i + 1} \quad (1 ≤ i ≤ ⌊\frac{n}{2}⌋) ki≥k2i且ki≥k2i+1或ki≤k2i且ki≤k2i+1(1≤i≤⌊2n⌋)
若将和此序列对应的一维数组(以一维数组做此序列的存储结构)看成是一个完全二叉树,则堆实质上是满足以下性质的完全二叉树
堆排序利用大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得当前无序的序列中选择关键字最大(或最小)的记录变得简单
void HeapAdjust(SQList* L, int s, int m) {
L->r[0] = L->r[s];
for (int i = 2 * s; i <= m; i *= 2) // 沿 key 较大的孩子结点向下筛选
{
if ((i < m) && (L->r[i].key < L->r[i+1].key)) // i 为 key 较大的记录的下标
{
i++;
}
if (L->r[0].key >= L->r[i].key) // L->r[0] 应插入在位置 s 上
{
break;
}
L->r[s] = L->r[i];
s = i;
}
L->r[s] = L->r[0];
}
void CreatHeap(SQList* L) {
for (int i = MAXSIZE / 2; i > 0; i--)
{
HeapAdjust(L, i, MAXSIZE);
}
}
void HeapSort(SQList* L) {
CreatHeap(L); // 把无序序列 L->r[1···MAXSIZE] 建成大根堆
for (int i = MAXSIZE; i > 1; i--)
{
L->r[0] = L->r[1]; // 将堆顶记录和当前未经排序子序列 L->r[1···i] 中最后一个记录互换
L->r[1] = L->r[i];
L->r[i] = L->r[0];
HeapAdjust(L, 1, i - 1); // 将 L->r[1···i-1] 重新调整为大根堆
}
printf("HeapSort Success\n");
}
时间复杂度
设有 n 个记录的初始序列所对应的完全二叉树的深度为 h ,建初堆时,每个非终端结点都要自上而下进行 “筛选”
由于第 i 层上的结点数小于等于 2 i − 1 2^{i-1} 2i−1 ,且第 i 层结点最大下移的深度为 h − i h-i h−i ,每下移一层要做两次比较
总的关键字比较次数 KCN
K C N = ∑ i = h − 1 1 2 i − 1 ∗ 2 ( h − i ) = ∑ i = h − 1 1 2 i ( h − i ) = ∑ j = 1 h − 1 2 h − j ∗ j ≤ 2 n ∑ j = 1 h − 1 j 2 j ≤ 4 n KCN = \sum_{i=h-1}^{1}{2^{i-1}*2(h - i)} = \sum_{i=h-1}^{1}{2^{i}(h - i)} = \sum_{j=1}^{h-1}{2^{h-j} * j} ≤ 2n\sum_{j=1}^{h-1}{\frac{j}{2^{j}}} ≤ 4n KCN=i=h−1∑12i−1∗2(h−i)=i=h−1∑12i(h−i)=j=1∑h−12h−j∗j≤2nj=1∑h−12jj≤4n
n 个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋ + 1 ⌊log2n⌋+1 ,则重建堆时关键字总的比较次数不超过
2 ( ⌊ l o g 2 ( n − 1 ) ⌋ + ⌊ l o g 2 ( n − 2 ) ⌋ + 4 + l o g 2 2 ) < 2 n ( ⌊ l o g 2 n ⌋ ) 2(⌊log_2{(n-1)}⌋ + ⌊log_2{(n-2)}⌋ + 4 + log_2{2}) < 2n(⌊log_2n⌋) 2(⌊log2(n−1)⌋+⌊log2(n−2)⌋+4+log22)<2n(⌊log2n⌋)
最坏情况:时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
平均性能接近于最坏性能
空间复杂度
#include
#include
#define MAXSIZE 10
void SelectSort(SQList);
void HeapAdjust(SQList);
void CreatHeap(SQList);
void HeapSort(SQList);
void PrintList(SQList);
typedef struct {
int key;
int value;
} RedType;
typedef struct {
RedType r[MAXSIZE + 1];
int length;
} SQList;
int main() {
int keys[MAXSIZE] = { 2,4,6,8,5,3,7,9,10,1 };
SQList L;
SQList Copy;
L.r[0].key = L.r[0].value = 0;
L.length = 0;
for (int i = 1; i < MAXSIZE + 1; i++)
{
L.r[i].key = L.r[i].value = keys[i-1];
L.length++;
}
Copy = L;
SelectSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
HeapSort(&L);
PrintList(L);
L = Copy;
printf("****************\n");
}
void SelectSort(SQList* L) {
for (int i = 1; i < MAXSIZE; i++)
{
int index = i;
for (int j = i + 1; j < MAXSIZE + 1; j++)
{
if (L->r[j].key < L->r[index].key)
{
index = j;
}
}
if (index != i)
{
L->r[0] = L->r[i];
L->r[i] = L->r[index];
L->r[index] = L->r[0];
}
}
printf("SelectSort Success\n");
}
void HeapAdjust(SQList* L, int s, int m) {
L->r[0] = L->r[s];
for (int i = 2 * s; i <= m; i *= 2)
{
if ((i < m) && (L->r[i].key < L->r[i+1].key))
{
i++;
}
if (L->r[0].key >= L->r[i].key)
{
break;
}
L->r[s] = L->r[i];
s = i;
}
L->r[s] = L->r[0];
}
void CreatHeap(SQList* L) {
for (int i = MAXSIZE / 2; i > 0; i--)
{
HeapAdjust(L, i, MAXSIZE);
}
}
void HeapSort(SQList* L) {
CreatHeap(L);
for (int i = MAXSIZE; i > 1; i--)
{
L->r[0] = L->r[1];
L->r[1] = L->r[i];
L->r[i] = L->r[0];
HeapAdjust(L, 1, i - 1);
}
printf("HeapSort Success\n");
}
void PrintList(SQList L) {
for (int i = 1; i < MAXSIZE + 1; i++)
{
printf("%d ", L.r[i].key);
}
printf("\n");
}
归并排序(Merging Sort):将两个或两个以上的有序表合并成一个有序表的过程
2-路归并:将两个有序表合并成一个有序表的过程
算法思想
void Merge(RedType R[], RedType T[], int low, int mid, int high) {
int i = low;
int j = mid + 1;
int k = low;
while (i <= mid && j <= high )
{
if (R[i].key <= R[j].key)
{
T[k++] = R[i++];
}
else {
T[k++] = R[j++];
}
}
while (i <= mid)
{
T[k++] = R[i++];
}
while (j <= high)
{
T[k++] = R[j++];
}
}
void MSort(RedType R[], RedType T[], int low, int high) {
if (low != high)
{
int mid = (low + high) / 2;
RedType S[MAXSIZE + 1];
MSort(R, S, low, mid);
MSort(R, S, mid + 1, high);
Merge(S, T, low, mid, high);
}
else {
T[low] = R[low];
}
}
void MergeSort(SQList* L) {
MSort(L->r, L->r, 1, MAXSIZE);
printf("MergeSort Success\n");
}
时间复杂度
空间复杂度
算法特点
外部排序由两个相对独立的阶段组成
2-路平衡归并:每一趟从 m 个归并段得到 ⌈ m 2 ⌉ ⌈\frac{m}{2}⌉ ⌈2m⌉ 个归并段
一般情况下
外 部 排 序 所 需 总 的 时 间 = 内 部 排 序 ( 产 生 初 始 归 并 段 ) 所 需 的 时 间 ( m ∗ t I S ) + 外 存 信 息 读 写 的 时 间 ( d ∗ t I O ) + 内 部 归 并 所 需 的 时 间 ( s ∗ u t m g ) 外部排序所需总的时间 = 内部排序(产生初始归并段) 所需的时间 (m*t_{IS}) + \\ 外存信息读写的时间 (d * t_{IO}) + \\ 内部归并所需的时间 (s * ut_{mg}) 外部排序所需总的时间=内部排序(产生初始归并段)所需的时间(m∗tIS)+外存信息读写的时间(d∗tIO)+内部归并所需的时间(s∗utmg)
t I S t_{IS} tIS :为得到一个初始归并段进行内部排序所需时间的均值
t I O t_{IO} tIO :进行一次外存读/写时间的均值
u t m g ut_{mg} utmg :对 u u u 个记录进行内部归并所需时间
m m m :经过内部排序之后得到的初始归并段的个数
s s s :归并的趟数
d d d :总的读/写次数
一般情况下,对 m m m 个初始归并段进行 k-路 平衡归并时,归并的趟数
s = ⌈ l o g k m ⌉ s = ⌈log_km⌉ s=⌈logkm⌉
为了减少归并趟数 s ,可以从以下两个方面进行改进