1.排序的概念及常见的排序算法
1.1排序的概念
- 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
- 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
- 内部排序:数据元素全部放在内存中的排序。
- 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.2常见排序算法
2.常见排序算法的实现
2.1直接插入排序
2.1.1基本思想
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的 大 小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有 序序列。
2.1.2代码实现
void InsertSort(int* a, int n) {
//[0,end]的区间有序 把end+1的数向前插入
for (int i = 0; i < n - 1; i++) {
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else {
break;
}
}
a[end + 1] = tmp;
}
}
2.2希尔排序
2.2.1基本思想
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
2.2.2代码实现
void ShellSort(int* a, int n) {
//希尔分为预排序和插入排序,预排序是分组写
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
2.3选择排序
2.3.1基本思想
在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。
2.3.2代码实现
这里的代码是对选择排序的优化版本,它可以同时选出最大值和最小值。
//选择排序
void SelectSort(int* a, int n) {
//遍历选出最小的和最大的数的下标,再进行交换最小的换到最左边,最大的换到最右边
int begin = 0,end=n-1;
while (begin < end)
{
int min = begin, max = begin;//每次循环后缩小区间
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[min])
{
min = i;
}
if (a[i] > a[max])
{
max = i;
}
}
//swap(&a[begin], &a[min]);
防止最大的下标和开始一样,最小的和end一样,导致重复两次置换数组元素
//if (max == begin)
//{
// max = min;
//}
//swap(&a[end], &a[max]);
swap(&a[end], &a[max]);
if (min == end)
{
min = max;
}
swap(&a[begin], &a[min]);
begin++;
end--;
}
Print(a, n);
}
2.4堆排序
2.4.1基本思想
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
2.4.2代码实现
//堆排序
void AdjustDown(HPDataType* a, int parent,int n) {
int child = parent * 2 + 1;
while (child<n)
{
//选出左右孩子里小的那个,child+1要小于n,因为可能只有一个孩子
if ((child+1)<n && a[child+1] < a[child])
{
child++;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n) {
//升序建大堆,降序建小堆
int i = 0;
for (i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, i, n);
}
int end = n - 1;//end记录最后一个节点的下标,把第一个节点和最后一个节点换位置然后向下调整
while (end>0)
{
swap(&a[0], &a[end]);
AdjustDown(a, 0, end);
end--;
}
}
2.5冒泡排序
2.5.1基本思想
冒泡排序是在数组中对相邻两个元素进行对比,依次循环知道所有数据有序。属于最简单的排序算法之一。
2.5.2代码实现
//冒泡排序
void BubbleSort(int* a, int n) {
for (int j = 0; j < n; j++)
{
for (int i = 0; i < n-j-1; i++)
{
if (a[i] > a[i + 1])
{
swap(&a[i], &a[i + 1]);
}
}
}
Print(a, n);
}
2.6快速排序
2.6.1基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2.6.2Hoare版本的快速排序
//经典快排
void PartSort1(int* a, int left, int right) {
int begin, end;
begin = left, end = right;
if (left >= right)
return;//返回条件
int keyi = left;
while (left < right)
{
while(left<right&&a[right] >= a[keyi])//R找小,R必须先走,必须要有left<right的判断条件要不right和left相遇无法判断
{
right--;
}
while(left<right&&a[left] <= a[keyi])//L找大
{
left++;
}
swap(&a[left], &a[right]);
}
swap(&a[left], &a[keyi]);
keyi = left;
// [begin,keyi]keyi[keyi+1,end]
PartSort1(a, begin, keyi);
PartSort1(a, keyi + 1, end);
}
2.6.3双指针法快速排序
void PartSort3(int* a, int left, int right) {
int prev = left,cur=left+1;
if (left >= right)
return;
int keyi = left;
while (cur < right +1)
{
if (a[cur]<a[keyi])
{
prev++;
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[prev], &a[keyi]);
keyi = prev;
//[left,keyi]
PartSort3(a, left, keyi);
PartSort3(a, keyi+1, right);
}
2.6.4非递归版快速排序
借用数据结构栈来进行堆递归时栈帧的模拟,每一次把区间的端点值入到栈中,先入右端点再入左端点,出栈的时候先出左端点再出右端点。之后再以keyi为界限把区间再度二分。依次循环直到栈为空。
void QuickSortNonR(int* a, int left, int right) {
//栈中存放区间端点,先入右端点后入左端点。
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while (!StackEmpty(&st))
{
int begin=StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int cur = begin + 1, prev = begin, keyi = begin;
while (cur <=end)
{
if (a[cur] < a[keyi])
{
prev++;
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[prev], &a[keyi]);
keyi = prev;
//[begin,keyi]keyi[keyi+1,end]
if (keyi + 1 < end)
{
StackPush(&st, end);
StackPush(&st, keyi + 1);
}
if (keyi > begin)
{
StackPush(&st, keyi);
StackPush(&st, begin);
}
}
StackDestroy(&st);
}
2.7归并排序
2.7.1基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
2.7.2代码实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin == end)
return;
int mid = (begin + end) / 2;
// [begin, mid] [mid+1, end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
// 归并
int begin1 = begin,end1 = mid;
int begin2 = mid + 1,end2 = end;
int i = begin;
// 依次比较,取小的尾插tmp数组
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a+begin, tmp + begin, sizeof(int) * (end - begin + 1));
}