目录
插入排序
代码:
//插入排序
void InsertSort(int* pa, int n)
{
for (int i = 1; i < n; i++)
{
int end = i - 1;
int tmp = pa[i];
while (end >= 0)
{
if (tmp < pa[end])
{
pa[end + 1] = pa[end];
}
else
{
break;
}
end--;
}
pa[end + 1] = tmp;
}
}
注:插入排序是从后往前比较插入,每次选后一个和前面的数据比较,如果小于前面数据,则把前面的数据往后挪,再继续往前找前一个继续比较。
插入排序时间复杂度:
最好情况是O(N),当数组正好是有序时每次排序只需要选后一个和前一个进行比较,如果大于则终止循环。
最坏情况是O(N^2),数组完全逆序的情况下,每一次选新的数据都要和前面所有数据比较,然后一个一个的去挪动他们。
希尔排序
//希尔排序
void ShellSort(int* pa, int n)
{
for (int gap = n / 2; gap > 0; gap /= 2)
{
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = pa[i + gap];
while (end >= 0)
{
if (tmp < pa[end])
{
pa[end + gap] = pa[end];
}
else
{
break;
}
end -= gap;
}
pa[end + gap] = tmp;
}
}
}
希尔排序时间复杂度是 O (n^ (1.3-2)) ,空间复杂度为常数阶 O (1) 。 希尔排序没有时间复杂度为 O (n*logn) 的快速排序算法快 ,因此对中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O (n^2) 复杂度的算法快得多。
选择排序
//选择排序
void SelectSort(int* pa, int n)
{
//第一种方法
/*for (int i = 0; i < n - 1; i++)
{
int min = i;
int tmp = pa[i];
for (int j = i + 1; j < n; j++)
{
if (pa[j] < tmp)
{
tmp = pa[j];
min = j;
}
}
Swap(&pa[i], &pa[min]);
}*/
//优化之后的第二种方法
int lefti = 0, righti = n - 1;
while (lefti < righti)
{
int mini = lefti, maxi = lefti;
for (int i = lefti + 1; i <= righti; i++)
{
if (pa[i] < pa[mini])
{
mini = i;
}
if (pa[i] > pa[maxi])
{
maxi = i;
}
}
Swap(&pa[mini], &pa[lefti]);
/*if (lefti == maxi)
{
maxi = mini;
}*/
Swap(&pa[maxi], &pa[righti]);
lefti++;
righti--;
}
}
选择排序的时间复杂度是:最好和最坏都是O(N^2),不管数组是有序还是无序,都要选出最大的和最小的。
堆排序排序
//向下调整 - 建大堆
void Adjusedown(int* pa, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && pa[child + 1] > pa[child])
{
child++;
}
if (pa[child] > pa[parent])
{
Swap(&pa[child], &pa[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* pa, int n)
{
//建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
Adjusedown(pa, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&pa[0], &pa[end]);
Adjusedown(pa, end, 0);//调整堆
end--;
}
}
堆排序的时间复杂度是:O(N*logN),建堆的时间复杂度是N,调整堆的时间复杂度是logN。
冒泡排序
//冒泡排序
void BubbleSort(int* pa, int n)
{
for (int i = 0; i < n - 1; i++)
{
int falg = 0;
for (int j = 1; j < n - i; j++)
{
if (pa[j - 1] > pa[j])
{
falg = 1;
Swap(&pa[j - 1], &pa[j]);
}
}
if (falg == 0)
{
break;
}
}
}
快速排序
Hoare版本
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
代码:
//三数取中: 三个数比较大小取中间值
int GetMidNumi(int* pa, int lefti, int righti)
{
int midi = (lefti + righti) / 2;
if (pa[lefti] < pa[midi])
{
if (pa[midi] < pa[righti])
{
return midi;
}
else if(pa[righti] > pa[lefti])
{
return lefti;
}
else
{
return righti;
}
}
else
{
if (pa[midi] > pa[righti])
{
return midi;
}
else if(pa[righti] > pa[lefti])
{
return lefti;
}
else
{
return righti;
}
}
}
//快速排序
//Hoare版本
void HoareQuickSort(int* pa, int lefti, int righti)
{
if (lefti >= righti)
return;
int begin = lefti, end = righti;
//随机选keyi
/*int randi = lefti + (rand() % (righti - lefti));
if (randi != lefti)
Swap(&pa[lefti], &pa[randi]);*/
//三数取中
int midi = GetMidNumi(pa, lefti, righti);
if (midi != lefti)
Swap(&pa[midi], &pa[lefti]);
int keyi = lefti;
while (lefti < righti)
{
//找小
while (lefti < righti && pa[righti] >= pa[keyi])
righti--;
//找大
while (lefti < righti && pa[lefti] <= pa[keyi])
lefti++;
Swap(&pa[lefti], &pa[righti]);
}
Swap(&pa[keyi], &pa[lefti]);
HoareQuickSort(pa, begin, lefti - 1);
HoareQuickSort(pa, righti + 1, end);
}
挖坑法
代码:
void HoleQuickSort(int* pa, int lefti, int righti)
{
if (lefti >= righti)
return;
int begin = lefti, end = righti;
//三数取中
int midi = GetMidNumi(pa, lefti, righti);
if (midi != lefti)
Swap(&pa[midi], &pa[lefti]);
int key = pa[lefti];
while (lefti < righti)
{
//找小
while (lefti < righti && pa[righti] >= key)
righti--;
pa[lefti] = pa[righti];
//找大
while (lefti < righti && pa[lefti] <= key)
lefti++;
pa[righti] = pa[lefti];
}
pa[lefti] = key;
HoareQuickSort(pa, begin, lefti - 1);
HoareQuickSort(pa, righti + 1, end);
}
前后指针版
前后指针就是把大的往前推,小的往后走。
代码:
//前后指针法
void pointerQuickSort(int* pa, int lefti, int righti)
{
if (lefti >= righti)
return;
//三数取中
int midi = GetMidNumi(pa, lefti, righti);
if (midi != lefti)
Swap(&pa[midi], &pa[lefti]);
int keyi = lefti;
int prev = lefti;
int cur = lefti + 1;
while (cur <= righti)
{
if (pa[cur] < pa[keyi] && ++prev != cur)
{
Swap(&pa[cur], &pa[prev]);
}
cur++;
}
Swap(&pa[prev], &pa[keyi]);
keyi = prev;
pointerQuickSort(pa, lefti, keyi - 1);
pointerQuickSort(pa, keyi + 1, righti);
}
小区间优化
上面这三种方法都有一个共同的特点就是它们都是通过递归来完成排序的,每一次递归就要创建一层函数栈帧,当递归次数过多时会出现栈溢出的请况,换句话说就是空间被用完了,出现程序崩溃的请况。
小区间优化就是当数据低于某个数值范围时直接使用插入排序,可以大大降低最后几层的递归所消耗的函数栈帧创建的空间。
上面的三种快速排序方法都可以使用小区间优化。
非递归快速排序
//非递归快排
void QuickSortNonR(int* pa, int lefti, int righti)
{
ST st;
StackInit(&st);
StackPush(&st, righti);
StackPush(&st, lefti);
while (!StackEmpty(&st))
{
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
//三数取中
int midi = GetMidNumi(pa, begin, end);
if (midi != begin)
Swap(&pa[midi], &pa[begin]);
int keyi = begin;
int prev = begin;
int cur = begin + 1;
while (cur <= end)
{
if (pa[cur] < pa[keyi] && ++prev != cur)
{
Swap(&pa[cur], &pa[prev]);
}
cur++;
}
Swap(&pa[prev], &pa[keyi]);
keyi = prev;
if (keyi + 1 < end)
{
StackPush(&st, end);
StackPush(&st, keyi + 1);
}
if (begin < keyi - 1)
{
StackPush(&st, keyi - 1);
StackPush(&st, begin);
}
}
StackDestory(&st);
}
归并排序
递归实现归并排序
void _MergeSort(int* pa, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = (begin + end) / 2;
_MergeSort(pa, begin, mid, tmp);
_MergeSort(pa, mid + 1, end, tmp);
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (pa[begin1] < pa[begin2])
{
tmp[i++] = pa[begin1++];
}
else
{
tmp[i++] = pa[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = pa[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = pa[begin2++];
}
memcpy(pa + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
非递归实现归并排序
//非递归归并排序
//第一种写法:全部归并,再一把拷贝回去
void MergeSortNonR(int* pa, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + gap * 2 - 1;
int j = i;
//修正下标
if (end1 >= n)
{
end1 = n - 1;
begin2 = n;
end2 = n - 1;
}
if (begin2 >= n)
{
begin2 = n;
end2 = n - 1;
}
else if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (pa[begin1] < pa[begin2])
{
tmp[j++] = pa[begin1++];
}
else
{
tmp[j++] = pa[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = pa[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = pa[begin2++];
}
}
memcpy(pa, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
}
//第二种写法:归并一部分,拷贝一部分
void MergeSortNonR(int* pa, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + gap * 2 - 1;
int j = i;
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (pa[begin1] < pa[begin2])
{
tmp[j++] = pa[begin1++];
}
else
{
tmp[j++] = pa[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = pa[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = pa[begin2++];
}
//归并一部分,拷贝一部分
memcpy(pa + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
计数排序
//计数排序
void CountSort(int* pa, int n)
{
int max = pa[0], min = pa[0];
for (int i = 1; i < n; i++)
{
if (pa[i] > max)
{
max = pa[i];
}
if(pa[i] < min)
{
min = pa[i];
}
}
int range = max - min + 1;
int* CountA = calloc(range, sizeof(int));
if (CountA == NULL)
{
perror("calloc fail\n");
return;
}
for (int i = 0; i < n; i++)
{
CountA[pa[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (CountA[i]--)
{
pa[j++] = i + min;
}
}
free(CountA);
}
排序算法复杂度及稳定性
稳定性:是指在排序时有两个值相同的数据a[i]和a[j],a[i]和a[j]相等,在排完序之后a[i]仍然在a[j]之前,说明这个算法是稳定的,如果在之后则说明是不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:是指能够处理极大量 数据 的 排序算法 。通常来说,外排序处理的数据不能一次装入 内存 ,只能放在读写较慢的 外存储器 (通常是 硬盘 )上。外排序通常采用的是一种"排序- 归并 "的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。而后在归并阶段将这些临时文件组合为一个大的有序文件,也即排序结果。