最近在复习数据结构与算法,参考了武汉大学李春葆老师的《数据结构教程》一书,因为自己之前也在慕课平台上听过老师的课,觉得讲得通俗易懂,挺适合自己的。以下是自己整理的排序类算法,直接从vim里复制过来的,因为白天在图书馆没连接校园网,就直接在vim里写了所有的笔记。谢谢。
typedef int KeyType;
typedef struct
{
KeyType key; // 元素关键字的值
} RecType;
// 直接插入排序 (这里按升序进行排序)
// 假设一个带排序的数组有n个元素, 将这个数组中的n个元素划分有序区和无序区两组元素
// 设a[0]-a[i-1]为有序区元素, a[i]-a[n-1]为无序区元素
// 初始化时, 有序区只有一个元素, 则一定是有序的
// 每次从无序区中取出第一个元素与有序区中的元素从后向前依次比较
// 直到在有序区中找到一个元素的关键字值小于等于当前元素,则将无序区中取出的这一个元素插入其后
// 在此过程中有序区中的所有元素也需要不断向后移动一个位置
void InsertSort(RecType R[], int n)
{
int i, j;
RecType tmp; /*作为临时变量暂存每次从无序区中取出的第一个元素,
而且有序区元素移动的过程中会将无序区的第一个元素覆盖,也需要tmp将其保存*/
for (i = 1; i < n; i++)
{
if (R[i].key < R[i-1].key)
{
tmp = R[i];
j = i-1;
do
{
R[j+1] = R[j];
j--;
} while(j >= 0 && tmp.key < R[j].key);
// 退出循环的两种情况:
// 1.有序区中所有元素均比当前tmp中保存的无序区第一个元素大, 则j最终变为-1
// 2.有序区中某个元素要小于等于当前tmp中保存的无序区第一个元素
R[j+1] = tmp;
}
}
}
// 折半插入排序算法(二分插入排序)
// 同直接插入排序算法相同, 将带排的n个数组元素划分为有序区和无序区两组
// 不同的是采用折半查找方式将无序区的第一个元素与有序区的元素进行比较
// 减少了元素比较的次数
// 同时每次在有序区中找到一个合适位置插入时, 集中移动元素
// 而不是同直接插入排序一样,比较一个有序区元素, 向后移动一个元素位置
void BinInsertSort(RecType R[], int n)
{
int i, j;
int high = 0, mid, low;
RecType tmp;
for (i = 1; i < n; i++)
{
if (R[i].key < R[i-1].key)
{
low = i-1;
tmp = R[i];
while (low <= high)
{
mid = (low + high) / 2;
if (tmp.key < R[mid].key)
high = mid - 1;
else
low = mid + 1;
}
}
for (j = i-1; j >= high + 1; j--)
{
R[j+1] = R[j];
}
R[high + 1] = tmp;
}
}
// 希尔排序
// 是一种分组排序的方法, 它将待排序的n个数组元素按一个增量d1(d < n)划分成d1个组
// 对d1个组内元素使用直接插入排序, 这里的d1也可以理解成组内元素的间隔数
// 所有距离为d的倍数的元素放在同一个组中
// 然后接着取第二个增量d2, 在各组内继续使用直接插入排序,
// 取t次后, 当dt=1时,即将n个元素放在一个组中使用直接插入排序,得到最终排序结果
void ShellSort(RecType R[], int n)
{
int i, j;
RecType tmp;
int d = n/2; // 增量取一个初值
while (d >= 1)
{
for (i = d; i < n; i++)
{
// 组内使用直接插入排序
tmp = R[i];
j = i - d; // 相当于每组内有序区的元素
while (j >= 0 && tmp.key < R[j].key)
{
R[j+d] = R[j];
j = j-d;
}
R[j+d] = tmp;
}
d = d / 2;
}
}
// 冒泡排序算法(递增)
// 将一个包含有n个元素的待排数组从前往后或者从后往前依次取出一个元素
// 将该元素与它前面的所有元素(如果是从后往前)或者与它后面的所有元素(如果是从前往后)两两比较
// 若前一个元素大于后一个元素则交换两个元素
// 经过一次冒泡排序后, 产生一个全局有序的元素, 可以往前冒泡,也可以往后冒泡
void swap(RecType &x, RecType &y)
{
RecType tmp = x;
x = y;
y = tmp;
}
// 从后往前冒泡排序
void BubbleSort1(RecType R[], int n)
{
int i, j;
for (i = 0; i < n - 1; i++)
{
for (j = n-1; j > i; j--)
{
if (R[j].key < R[j-1].key)
swap(R[j], R[j-1]);
}
}
}
// 从前往后冒泡排序
void BubbleSort2(RecType R[], int n)
{
int i, j;
for (i = n -1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
if (R[j].key > R[j+1].key)
swap(R[j], R[j+1]);
}
}
}
// 改进的冒泡排序算法, 假设第i趟元素比较过程中没有发生元素交换
// 则不再需要第i+1趟排序比较了,可以直接结束排序算法
void OptimizedBubbleSort(RecType R[], int n)
{
int i, j;
bool exchange; // 判断元素是否发生交换的变量
for (i = 0; i < n-1; i++)
{
exchange = false; // 每一趟冒泡排序开始时并没有发生元素交换
for (j = n - 1; j > i; j--)
{
if (R[j].key < R[j-1].key)
{
swap(R[j], R[j-1]);
exchange = true; // swap交换两个元素后表明元素发生了交换, 则将exchange置为true
}
}
if (!exchange) // 每完成一个元素的冒泡排序后判断该趟有没有元素发生交换,
// 若没有, 则可以直接结束算法
return;
}
}
// 快速排序算法(递增)
// 对于一个包含n个元素的待排数组, 将其看做是一个无序区
// 从中任意取一个元素作为基准元素, 一般取第一个元素作为基准
// 定义两个变量i, j分别指向当前无序区的第一个元素和最后一个元素
// 然后从两端向中间依次扫描元素,遇到当前基准元素逆序的元素, 则交换它们
// i在此过程中向后推进, j在此过程中向前推进, 直到i=j时, 则将基准元素放在i(j)的位置上
// 完成一次快速排序, 此时该基准元素的左侧所有元素均小于该基准元素,
// 右侧所有元素均大于该基准元素
// 接着使用递归算法对该基准元素的左侧和右侧的所有元素使用快速排序
// 递归算法的出口是当数组中只有一个待排元素或者没有元素
int OneQuickSort(RecType R[], int s, int t)
{
int i = s, j = t;
RecType tmp = R[i];
while (i < j)
{
// 这里基准元素取的是第一个元素, 元素和自己的值是等的,不需要再次比较, 则从后向前比较
while (j > i && R[j].key >= tmp.key)
{
j--;
}
R[i] = R[j];
while (i < j && R[i].key <= tmp.key)
{
i++;
}
R[j] = R[i];
}
R[i] = tmp;
return i;
}
void QuickSort(RecType R[], int s, int t)
{
int i;
if (s < t) // 至少存在两个元素的情况
{
i = OneQuickSort(R, s, t);
QuickSort(R, s, i-1); // 左侧所有元素调用递归排序
QuickSort(R, i+1, t); // 右侧所有元素调用递归排序
}
}
void foreach(RecType R[], int n)
{
int i = 0;
for (; i < n; i++)
{
printf("%d ", R[i].key);
}
printf("\n");
}
用于测试的主函数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
RecType R[10];
int i;
srand(time(NULL));
for (i = 0; i < 10; i++)
{
R[i].key = rand()%11;
}
printf("快速排序前元素的顺序: ");
foreach(R, 10);
QuickSort(R, 0, 10);
printf("快速排序后元素的顺序: ");
foreach(R, 10);
return 0;
}