*快排延伸-自省排序

此节是学有余力的人去看,如果没时间,不看也没关系,只要知道代码就可以了!

自省排序的思路是自我侦测和反省,快速排序如果递归深度太深,其算法的效率可能被大幅度削弱,这就需要借助其他的算法进行排序了,所以我们需要把之前所有算法都要使用上,至于思想大家可以去搜搜资料,这个算法的代码过长,所以实现起来很麻烦,但是算法思路我之前都已经讲过,里面有些难以理解的地方,这只是一个代码分享的过程,没必要追究如何得到的这个结论,我只在适当地方进行注释。我改变的代码在第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();
//}

运行结果如下:

这个代码是完全复制之前的,所以有些函数是已经没有用处的,至于有些东西和我们排序算法有区别的地方需要自己去理解了,因为这是适用于所有情况的排序算法,太长也很正常,之前的排序总有不适用的情况,需要自己去解决了哦!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值