排序分为直接插入类排序、交换类排序、选择类排序和归并排序。
注:为了满足每个函数只有一个数组首地址、一个数组长度的要求,我们会对某些函数进行封装。
直接插入类排序:在一个已排好序的记录子集的基础上,每一步将下一个待排序的记录有序的插入到已排好序的记录子集中,直到将所有的待排记录全部插入为止。
直接插入排序:通俗点说,就是把第i个元素插入到前i-1个有序集合并使得插入之后的集合有序。具体做法就是将第i个元素依次与前i-1个元素进行比较,并且将前面的元素依次后移,直到找到一个合适的位置(如果是增序则应插入第一个小于或等于待插入元素的后面)。依次遍历所有元素。
// 直接插入排序
template <typename T>
void InsertSort(T *data, int len)
{
if (len <= 0)
return;
for (int i = 1; i < len; ++i)
{
int j = i - 1;
T temp = data[i];
while (j >= 0 && temp < data[j])
{
data[j + 1] = data[j];
j--;
}
data[j + 1] = temp;
}
}
折半查找排序:与直接插入排序类似,但不同的是,在直接插入排序中,寻找待插入元素(第i个元素)的位置时,使用的顺序查找。然而,已知待插入元素前i-1个元素有序(从插入类排序定义可知),为了提高查找速度,此时可以使用二分查找。
// 折半插入排序
template <typename T>
void BinInsertSort(T *data, int len)
{
if (len <= 0)
return;
for (int i = 1; i < len; ++i)
{
int temp = data[i];
int low = 0, high = i - 1;
// 与直接插入不同点
while (low <= high)
{
int mid = (low + high) / 2;
if (temp < data[mid])
high = mid - 1;
else
low = mid + 1;
}
// 元素依次后移
for (int j = i - 1; j >= low; --j)
{
data[j + 1] = data[j];
}
data[low] = temp;
}
}
希尔排序:希尔排序又称增量缩小排序法,是一种基于插入思想的排序方法。先将待排序记录分割成若干个(具体个数与增量有关)子序列,然后对这若干个序列进行直接插入排序。其中,增量缩小为1时,希尔排序就是复杂度很小的直接插入排序。
// 一次增量为d的直接排序
template <typename T>
void ShellSortPart(T *data, int len, int d)
{
for (int i = d; i < len; ++i)
{
int j = i - d;
T temp = data[i];
while (j >= 0 && temp < data[j])
{
data[j + d] = data[j];
j = j - d;
}
data[j + d] = temp;
}
}
// 希尔排序,封装上述函数
template <typename T>
void ShellSort(T *data, int len)
{
if (len <= 0)
return;
int d[] = { 5, 3, 2, 1 }; // 增量序列
int len2 = sizeof(data) / sizeof(data[0]);
for (int i = len2; i > 0; --i)
{
ShellSortPart(data, len, d[i]);
}
}
交换类排序:基于交换的排序法是一类通过交换不符合要求(前后顺序不符合排序要求)的元素对。
冒泡排序:从头到尾扫描记录序列(也可从尾到头扫描),比较相邻的两个记录,如果逆序,则交换两个记录。则一次扫描就可以将最大元素(降序排序则为最小元素)交换到最后一位。接下来第二趟,扫描前(i-1)个元素。直到所有元素有序。
// 冒泡排序
template <typename T>
void BubbleSort(T* data, const int len)
{
if (len <= 0)
return;
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
if (data[j] > data[j + 1])
{
mySwap(data[j], data[j + 1]);
}
}
}
}
冒泡排序初级改进:在冒泡排序过程中,如果有一次比较没有发生交换,则数组已经有序。例如对序列{8,6,9,1,2,3,4},遍历三次数组后,序列变为{1,2,3,4,6,8,9},数组已经有序,则在下一次遍历,数组中没有发生交换,此时可以直接退出排序。
// 冒泡排序初级改进
template <typename T>
void PrimBubbleSort(T *data, const int len)
{
if (len <= 0)
return;
bool flag = true;
for (int i = 0; i < len - 1 && flag; i++)
{
flag = false;
for (int j = 0; j < len - i - 1; j++)
{
if (data[j] > data[j + 1])
{
mySwap(data[j], data[j + 1]);
flag = true;
}
}
}
}
冒泡排序再改进:在上述过程中,即使序列已经有序,也需要再遍历一次,才能退出程序。按照同样的思路,可以记录交换发生的最后位置。例如,如果现在有五个元素没有排好顺序的话,但是在这五个元素比较时,如果有两个发生了交换,而后面的没有发生交换,即后面的已经有序,下次排序就可以排序到记录的最后位置。
// 冒泡排序再改进
template <typename T>
void AdvBubbleSort(T *data, const int len)
{
if (len <= 0)
return;
int lastIndex = 0, tempIndex = len - 1;
for (int i = 0; i < len - 1; i++)
{
lastIndex = tempIndex;
for (int j = 0; j < lastIndex; j++)
{
if (data[j] > data[j + 1])
{
mySwap(data[j], data[j + 1]);
tempIndex = j;
}
}
if (lastIndex == tempIndex)
break;
}
}
快速排序:太经典了,不讲算法,直接上程序。
// 快速排序
template <typename T>
void QuickSortPart(T *data, int left, int right)
{
if (left > right)
return;
int low = left, high = right;
while (low < high)
{
while (low < high && data[low] < data[high])
high--;
if (low < high)
mySwap(data[low], data[high]);
while (low < high && data[low] < data[high])
low++;
if (low < high)
mySwap(data[low], data[high]);
}
QuickSortPart(data, left, low - 1);
QuickSortPart(data, high + 1, right);
}
// 快速排序,封装上述函数
template <typename T>
void QuickSort(T *data, int len)
{
if (len <= 0)
return;
QuickSortPart(data, 0, len - 1);
}
选择类排序:每一趟排序再len - i - 1个记录中选取最小的记录作为有序序列中第i个记录。
简单选择排序:第i趟简单选择排序是指通过n-1次比较,从n-i-1个记录中选出最小的记录(如果是增序的话),并与第i个记录交换。直到所有记录排序完成为止。
// 简单选择排序
template <typename T>
void SelectSort(T *data, int len)
{
if (len <= 0)
return;
for (int i = 0; i < len - 1; ++i)
{
int k = i;
int min = data[i];
for (int j = i; j < len; ++j)
{
if (min > data[j])
{
min = data[j];
k = j;
}
}
if (k != i)
mySwap(data[i], data[k]);
}
}
堆排序:把待排序的元素看成一颗完全二叉树,然后调整这些节点使其符合大根堆性质,然后交换堆顶元素与最后一个元素。此时最大元素在数组末尾,再调整其余未排序节点为大根堆,重复执行使得所有节点有序。
// 调整堆,从root节点到index节点调整
template <typename T>
void AdjHeap(T *data, int root, int index)
{
int j = 2 * root + 1; //最后一个有孩子节点的节点位置
T temp = data[root];
while (j <= index)
{
if (j < index)//找出左孩子节点与右孩子节点中较大的一个
{
if (data[j] < data[j + 1])
{
j++;
}
}
if (temp < data[j])//找出根节点与左右孩子节点中较大的一个
{
data[(j - 1) / 2] = data[j];
j = j * 2 + 1;
}
else
{
break;
}
}
data[(j - 1) / 2] = temp;
}
// 堆排序
template <typename T>
void HeapSort(T *data, int len)
{
if (len <= 0)
return;
int i = 0, index = len - 1;
//初始化堆
for (i = (index - 1) / 2; i >= 0; i--)
{
AdjHeap(data, i, index);
}
//交换最大值与最后一个元素,并调整堆
for (i = index; i>0; i--)
{
mySwap(data[0], data[i]);
AdjHeap(data, 0, i - 1);
}
}
归并类排序:归并就是将两个或两个以上的有序序列合并成一个有序序列。假设初始序列含有n个记录,首先将这n个记录看成n个有序的子序列,即每个子序列含有1个元素,即每个子序列有序,然后让这n个子序列两两归并,再次归并基础上,再进行两两归并…
// 合并有序子序列
template <typename T>
void Merge(T *data, int first, int mid, int last, T *temp)
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (data[i] < data[j])
temp[k++] = data[i++];
else
temp[k++] = data[j++];
}
while (i <= m)
temp[k++] = data[i++];
while (j <= n)
temp[k++] = data[j++];
for (i = 0; i < k; ++i)
data[i + first] = temp[i];
}
// 部分归并排序
template <typename T>
void MergeSortPart(T *data, int first, int last, T *temp)
{
if (first >= last)
return;
int mid = (first + last) / 2;
MergeSortPart(data, first, mid, temp);
MergeSortPart(data, mid + 1, last, temp);
Merge(data, first, mid, last, temp);
}
// 归并排序,封装上述函数
template <typename T>
void MergeSort(T *data, int len)
{
if (len <= 0)
return;
T *temp = new T[len]();
MergeSortPart(data, 0, len - 1, temp);
}
努力提高可读性,努力提高健壮性,努力符合编程规范。但是水平有限,能力不够,如果您能发现程序中的错误并愿意提出来,本人不胜感激。