此节是学有余力的人去看,如果没时间,不看也没关系,只要知道代码就可以了!
自省排序的思路是自我侦测和反省,快速排序如果递归深度太深,其算法的效率可能被大幅度削弱,这就需要借助其他的算法进行排序了,所以我们需要把之前所有算法都要使用上,至于思想大家可以去搜搜资料,这个算法的代码过长,所以实现起来很麻烦,但是算法思路我之前都已经讲过,里面有些难以理解的地方,这只是一个代码分享的过程,没必要追究如何得到的这个结论,我只在适当地方进行注释。我改变的代码在第465行。代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include<stdbool.h>
#include<stddef.h>//NULL
#include<time.h>
//交换函数
void Swap(int* a, int* b)
{
int p = *a;
*a = *b;
*b = p;
}
//直接插入排序(升序)
void InsertSort1(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
//我们把需要比较的数放入tmp中,然后先与第i个数据比较,依次往前进行比较插入
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
//如果end=-1我们不能进行数组越界,所以要进行+1操作
arr[end + 1] = tmp;
}
}
//希尔排序
void ShellSort1(int* arr, 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 = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
//直接选择排序
void SelectSort1(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int max, min;
max = min = begin;
for (int i = begin + 1; i <= end; i++)
{
//我们从开始位置后面的数据进行遍历,原因不用解释
if (arr[i] < arr[min])
{
min = i;
}
if (arr[i] > arr[max])
{
max = i;
}
}
if (max == begin)
{
max = min;
}
Swap(&arr[min], &arr[begin]);
Swap(&arr[max], &arr[end]);
begin++;
end--;
}
}//堆的结构
//能存储的数据有很多种
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;//有效数据的个数
int capacity;//容量
}HP;
//堆的初始化
void HPInit(HP* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
//堆的销毁
void HPDesTory(HP* php)
{
//判断是否为NULL
if (php->arr)
{
free(php->arr);
}
php->arr = NULL;
php->size = php->capacity = 0;
}
//向上调整算法
void AdjustUp(HPDataType* arr, int child)
{
HPDataType parent = (child - 1) / 2;
while (child > 0)
{
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
//或者可以加个continue,之后把else去掉,只留break;
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//插入数据
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
//增容
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * (newcapacity));
//增容失败
if (tmp == NULL)
{
perror("realloc fail!\n");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
//第二个传的参数是php->size,因为下标这个时候最大已经是size,有size+1个数据,我们还没有执行++的操作
AdjustUp(php->arr, php->size);
++php->size;
}
//判空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;
while (child < n)
{
//先找最小的
if (child + 1 < n && arr[child] > arr[child + 1])
{
child++;
}
if (arr[parent] > arr[child])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//删除栈顶数据
void HPpop(HP* php)
{
assert(!HPEmpty(php));
Swap(&php->arr[0], &php->arr[php->size - 1]);
--php->size;
AdjustDown(php->arr, 0, php->size);
}
//取堆顶元素
HPDataType HPTop(HP* php)
{
assert(!HPEmpty(php));
return php->arr[0];
}
//向下调整算法建堆
void HeapSort(HPDataType* arr, int n)
{
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDown(arr, i, n);
}
//堆排序
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
//向上调整算法建堆
void HeapSort01(HPDataType* arr, int n)
{
for (int i = 0; i < n; i++)
{
AdjustUp(arr, i);
}
//堆排序
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
//冒泡排序
void BubbleSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
int exchange = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
exchange = 1;
Swap(&arr[j], &arr[j + 1]);
}
}
if (exchange == 0)
{
break;
}
}
}
typedef int STDataType;
//栈的定义
typedef struct Stack
{
STDataType* arr;//所存储的数据
int size;//有效数据的个数
int capacity;//所能存储的数据个数
}ST;
//栈的初始化
void SLInit(ST* st)
{
assert(st);
st->arr = NULL;
st->size = st->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));
//我们这个元素的扩容是在数组上进行操作的,所以我们要这样写
if (tmp == NULL)
{
//增容失败
perror("realloc fail!\n");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
ps->arr[ps->size++] = x;
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->size == 0;
//有效数据为0个时,栈就会为空
}
//出栈
void StackPop(ST* ps)
{
assert(!StackEmpty(ps));
//这个!一定要加上去,如果为true,则栈中没有数据
ps->size--;
}
//取栈顶元素
STDataType STTop(ST* ps)
{
assert(!StackEmpty(ps));
return ps->arr[ps->size - 1];
}
//销毁栈
void STDestroy(ST* ps)
{
//如果栈本来就没申请空间,就不需要销毁了
//所以我们要加个判断条件
if (ps->arr != NULL)
{
//也可以写为ps->capacity!=0或ps->capacity
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
}
bool isValid(char* s)
{
ST st;
//初始化
SLInit(&st);
char* pi = s;
//用于遍历字符串
while (*pi != '\0')
{
if (*pi == '(' || *pi == '[' || *pi == '{')
{
//入栈
StackPush(&st, *pi);
//记得把这个int改为char
}
else
{
//判断栈是否为空
if (StackEmpty(&st))
{
//如果为空,直接返回为false
//相当于出栈没有了
return false;
}
//可以加个else语句
//如果前面条件不成立,直接就跳出了这个函数了
//取栈顶元素
char top = STTop(&st);
//记得出栈,否则之后取的都是一个元素
StackPop(&st);
//记得pi要解引用,之前我也写错了
if ((top == '(' && *pi != ')') || (top == '[' && *pi != ']') || (top == '{' && *pi != '}'))
{
//先返回false的,只有满足所有条件才能返回true
return false;
}
}
pi++;
//记得调整pi,否则会死循环
}
//如果直接返回的话,就没办法销毁链表
bool ret = StackEmpty(&st) ? true : false;
//销毁链表
STDestroy(&st);
return ret;
}
//lomuto前后指针法
int _QuickSort(int* arr, int left, int right)
{
int prev = left;
int cur = prev + 1;
//key也可以不创建,之后又不会发生改变,只是方便理解而已
int key = left;
while (cur <= right)
{
if (arr[cur] < arr[key] && ++prev != cur)
{
//我们如果把一个位置的数进行交换是没必要的
//并且如果把++prev放到前面就会先执行该语句会出错
Swap(&arr[cur], &arr[prev]);
}
++cur;
}
Swap(&arr[prev], &arr[key]);
return prev;
}
快速排序
//void QuickSort(int* arr, int left, int right)
//{
// if (left >= right)
// {
// return;
// }
// //找基准值并进行排序
// int key = _QuickSort(arr, left, right);
// //把数组分为三部分: [left,key-1] key [key+1,right]
// QuickSort(arr, left, key - 1);
// QuickSort(arr, key + 1, right);
//}
// 之前没有在代码中实现这个函数,所以在这里添上
// 取栈顶元素
STDataType StackTop(ST* ps)
{
assert(!StackEmpty(ps));
return ps->arr[ps->size - 1];
}
非递归版本的快速排序
//void QuickSort(int* arr, int left, int right)
//{
// ST st;
// SLInit(&st);
// //先让right和left入栈,防止栈为空
// StackPush(&st, right);
// StackPush(&st, left);
// while (!StackEmpty(&st))
// {
// //取栈顶元素
// int begin = StackTop(&st);
// //要出栈
// StackPop(&st);
// int end = StackTop(&st);
// StackPop(&st);
// //对[begin,end]使用双指针法
// int prev = begin;
// int cur = prev + 1;
// int key = begin;
// while (cur <= end)
// {
// if (arr[cur] < arr[key] && ++prev != cur)
// {
// Swap(&arr[prev], &arr[cur]);
// }
// ++cur;
// }
// Swap(&arr[key], &arr[prev]);
// //记得加上这句话,之前递归的是直接返回了prev,而这里不行,我刚刚也没发现问题
// key = prev;
// //数组分为以下三部分:[begin,prev-1]prev [prev+1,end]
// //先入右子数组
// //从右至左开始入
// if (prev + 1 < end)
// {
// StackPush(&st, end);
// StackPush(&st, prev + 1);
// }
// //再入左子数组
// if (key - 1 > begin)
// {
// StackPush(&st, key - 1);
// StackPush(&st, begin);
// }
// }
// STDestroy(&st);
//}
//自省排序
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{
if (left >= right)
{
return;
}
//数组长度小于16的小数组视为插入排序,简单递归次数
if (right - left + 1 < 16)
{
InsertSort1(a + left, right - left + 1);
return;
}
//当深度超过2*logN,则改为堆排序
if (depth > defaultDepth)
{
HeapSort(a + left, right - left + 1);
return;
}
//其他情况我们直接用上一讲讲过的排序方法
depth++;
int begin = left;
int end = right;
//随机选key
int randi = left + (rand() % (right - left));
Swap(&a[left], &a[randi]);
int prev = left;
int cur = prev + 1;
int key = left;
while (cur <= right)
{
if (a[cur] < a[key] && ++prev != cur)
{
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[prev], &a[key]);
key = prev;
//[begin,key-1] [key,key] [key+1,end]
IntroSort(a, begin, key - 1, depth, defaultDepth);
IntroSort(a, key + 1, end, depth, defaultDepth);
}
//快速排序
void QuickSort(int* a, int left, int right)
{
int depth = 0;
int logn = 0;
int N = right - left + 1;
//这个i要从1开始,不然就死循环
//我也是找了20分钟才发现的!
for (int i = 1; i < N; i *= 2)
{
logn++;
}
//防止快速排序退化
IntroSort(a, left, right, depth, logn * 2);
}
//测试
int main()
{
int a[] = { 4,3,8,2,1,9,0,7,6,5 };
int n = sizeof(a) / sizeof(a[0]);
printf("排序之前: ");
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
QuickSort(a, 0, n - 1);
printf("\n排序之后: ");
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
return 0;
}
// 测试排序的性能对⽐
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
//int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
//a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort1(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort1(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort1(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort01(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
//int begin6 = clock();
//MergeSort(a6, N);
//int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
//printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
//free(a6);
free(a7);
}
//int main()
//{
// TestOP();
//}
运行结果如下:
这个代码是完全复制之前的,所以有些函数是已经没有用处的,至于有些东西和我们排序算法有区别的地方需要自己去理解了,因为这是适用于所有情况的排序算法,太长也很正常,之前的排序总有不适用的情况,需要自己去解决了哦!