一、简单概述
首先要解释几个术语:
稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
不稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
非原地排序:需要利用额外的数组来辅助排序。
排序所用的结构:
#define MAXSIZE 10
typedef struct
{
int r[MAXSIZE + 1];/*用于存储要排序数组,r[0]用作哨兵或临时变量*/
int length;
}list;
算法比较
二、冒泡排序
基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程剩下的排序,直至整个序列有序为止。
排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。
代码:
void Bubble_sort(list* l)
{
for (int i = 1; i < l->length; i++)
{
for (int j = l->length - 1; j >= i; j--)/*从后往前*/
{
if (l->r[j] > l->r[j + 1])
swap(l->r[j], l->r[j + 1]);
}
}
}
改进代码
如果说在一遍循环中,没有任何数据交换,这就说明此序列已经有序。根据这一点可以增加一个flag作为标记。
void Bubble_sort2(list* l)
{
bool flag = true;
for (int i = 1; i < l->length&&flag; i++)
{
flag = false;
//没有任何数据交换,这就说明此序列已经有序,
for (int j = l->length - 1; j >= 1; j--)
{
if (l->r[j] > l->r[j + 1])
{
swap(l->r[j], l->r[j + 1]);
flag = true;
}
}
}
}
三、选择排序
简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-i+ 1个记录中选出关键字最小的记录,并和第i (1≤ i ≤n)个记录交换之。
void select_sort(list* l)
{
for (int i = 1; i < l->length; i++)
{
int min = i; /*将当前下标定义为最小值下标 */
for (int j = i + 1; j <= l->length; j++)
/*循环之后的数据 */
{
if (l->r[min] > l->r[j])
min = j;
}
if (min != i)/*若不等于,找到最小值*/
swap(l->r[i], l->r[min]);
}
}
四、直接插入排序
直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
代码
void insert_sort(list* l)
{
int i, j;
for ( i = 2; i <= l->length; i++)
{
if (l->r[i] < l->r[i - 1]) /*需将L->r[i]插入有序子表*/
{
l->r[0] = l->r[i];
for ( j = i - 1; l->r[j] > l->r[0]; j--)
l->r[j + 1] = l->r[j];//记录后移
l->r[j + 1] = l->r[0];
}
}
}
五、希尔排序
基本思想:算法先将要排序的一组数按某个增量d分成若干组,每组中
记录的下标相差d。对每组中全部元素进行排序,然后再用一个较小的增量
对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
一组,排序完成。
代码
void shell_short(list* l)
{
for (int dk = l->length / 2; dk >= 1; dk = dk / 2)
{//增量序列
for (int i = dk + 1; i <= l->length; i++)
{
if (l->r[i] < l->r[i - dk])
{
l->r[0] = l->r[i];//暂存在l->r[0]
int j;
for (j = i - dk; j > 0 && l->r[0] < l->r[j]; j-=dk)
{//记录后移寻找查找位置
l->r[j + dk] = l->r[j];
}
l->r[j+dk] = l->r[0];//插入
}
}
}
}
算法中巧妙地地方在于,同时对所有数据进行希尔排序,增量为循环量,第二个循环依次访问一组中的全部元素进行插入排序。
六、归并排序
基本思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
算法代码
void merge(int SR[],int TR[],int i,int m,int n)
{ /*将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] */
int j, k, l;
for (j = m + 1, k = i; i <= m && j <= n; k++)
{//j为中间分割点
if (SR[i] < TR[j])
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
if (i <= m)
{
for (l = 0; l <= m - i; l++)
TR[k + l] = SR[i + l];
}
if (j <= n)
{
for (l = 0; l <= n - j; l++)
TR[k + l] = SR[j + l];
}
}
递归实现
void msort(int SR[],int TR1[],int s,int t)
//把SR[s...t],归并排序为TR1[s...t]
{
int m;
int TR2[MAXSIZE + 1];
if (s == t)
TR1[s] = SR[s];
else
{
m = (s + t) / 2;
msort(SR, TR2, s, m);
msort(SR, TR2, m + 1, t);
merge(TR1, TR2, s, m, t);
/*将TR2[s..m]和TR2 [m+1..t],归并到TR1[s..t]*/
}
}
非递归写法
void merge_pass(int SR[], int TR[], int s, int n)
{/*将SR[]中相邻长度为s的子序列两两归并到TR[] */
int i = 1;
while (i <= n - 2 * s + 1)
{
merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
if (i < n - s + 1)/*归并最后两个序列*/
{
merge(SR, TR, i, i + s - 1, n);
}
else//只剩下单个子序列
{
for (int j = i; j <= n; j++)
TR[j] = SR[j];
}
}
void meger_sort2(list* l)
{
int* TR = (int*)malloc(l->length * sizeof(int));
int k = 1;
while (k < l->length)
{
merge_pass(l->r, TR, k, l->length);
k = k * 2;
merge_pass(TR, l->r, k, l->length);
k = k * 2;
}
}
七、快速排序
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
排序流程
- 首先设定一个分界值,通过该分界值将数组分成左右两部分。
- 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
- 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
void quicksort(int left, int right, list* l)
{
if (left >= right)
return;
int i = left;
int j = right;
int base = l->r[left];/*取最左为基准数*/
while (i < j)
{
while (l->r[j] >= base && i < j)
j--;
while (l->r[i] <= base && i < j)
i++;
if (i < j)
swap(l->r[i], l->r[j]);
}
l->r[left] = l->r[i];
l->r[i] = base;
quicksort(left, i - 1, l);
quicksort(i + 1, right, l);
}
八、堆排序
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。
每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。反之每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
基本思想:将待排序的序列构造成一个大顶堆。 此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
/*已知L->r[s. .m]中记录的关键字除L->r[s]之外均满足堆的定义*/
/*本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆*/
void head_adjust(list* l, int s, int m)
{
int temp = l->r[s];
int j;
for ( j = 2 * s; j <= m; j *= 2)
{//沿关键字较大的孩子节点向下
if (j < m && l->r[j] < l->r[j + 1])
j++;
if (temp >=l->r[j])
break;//temp该插入的位置
l->r[s] = l->r[j];
s = j;
}
l->r[s] = temp;//插入
}
void head_sort(list* l)
{
for (int i = l->length / 2; i > 0; i--)
head_adjust(l, i, l->length);
for (int i = l->length; i > 1; i--)
{
swap(l->r[1], l->r[i]);
head_adjust(l, 1, i - 1);//调整1-(i-1)重新为大顶堆
}
}