排序算法

一、冒泡排序

个人理解:
冒泡排序是最基础的排序算法之一,其思想就是把一个最大值排到最后面,通过不断排出最大值,来达到“冒泡”的效果。

代码思想:
重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

复杂度和稳定性分析:
时间复杂度O(n^2)
空间复杂度O(1)
不稳定

代码:

//冒泡排序
void BubbleSort(int *arr, int len)
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - i - 1; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
			}
		}
	}
}

二、选择排序

个人理解:
选择排序也是最基础的排序算法之一,其基本思想是选择一个最小值(或者最大值),然后把它直接放到该放的地方。

代码思想:
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

复杂度和稳定性分析:
时间复杂度O(n^2)
空间复杂度O(1)
不稳定

代码:

//选择排序
void SelectSort(int *arr, int len)
{
	for (int i = 0; i < len - 1; ++i)
	{
		int min = i;

		for (int j = i; j < len; ++j)
		{			
			if (arr[min] > arr[j])
			{
				min = j;
			}
		}

		Swap(&arr[i], &arr[min]);
	}
}

三、直接插入排序

个人理解:
直接插入排序本质上是把要排序的数组拆分成一个一个独立的元素,通过不断“插入”后面的元素来实现排序。

代码思想:
每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。

复杂度和稳定性分析:
时间复杂度O(n^2)
空间复杂度O(1)
稳定

代码:

//直接插入排序
void InsertSort(int *arr, int len)
{
	for (int i = 1; i < len; ++i)
	{
		int tmp = arr[i];

		int j = i - 1;	
		while (arr[j] > tmp && j >= 0)
		{
			arr[j + 1] = arr[j];
			j--;
		}
		
		arr[j + 1] = tmp;
	}
}

四、希尔排序

个人理解:
希尔排序又称为“缩小增量排序”,是直接插入排序的优化算法,通过分组的形式先隔开进行直接排序,通过并组的方式进行整体的排序,这种方法的优点在于,假设A<B,C<D,那么A<D的概率就会增加,数组会趋于有序,会减少交换的次数。

代码思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1( < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

复杂度和稳定性分析:
时间复杂度 O(n^1.3) ~~ O(n^1.5)
空间复杂度O(1)
不稳定

图解希尔排序:
在这里插入图片描述

代码:

//希尔排序
void Shell(int *arr, int len, int d)
{
	for (int i = d; i < len; ++i)
	{
		int tmp = arr[i];

		int j = i - d;
		while (arr[j] > tmp && j >= 0)
		{
			arr[j + d] = arr[j];
			j -= d;
		}

		arr[j + d] = tmp;
	}
}
void ShellSort(int *arr, int len)
{
	int d[4] = { 7, 5, 3, 1 };//必须互质

	for (int i = 0; i < 4; ++i)
	{
		Shell(arr, len, d[i]);
	}
}

五、堆排序

个人理解:
堆排序我个人认为,其实跟利用堆结构的选择排序差别不大,利用堆结构使数组本身大致有序,达到优化的作用。

代码思想:
创建一个堆之后,把根节点与最后一个元素互换,在不计入最后一个元素的情况下,根据新的结构改变堆,依次类推,最后达到排序的效果。

复杂度和稳定性分析:
时间复杂度O(nlogn)
空间复杂度O(1)
不稳定

图解堆排序:
在这里插入图片描述
代码:

//堆排序
void Adjust(int *arr, int len, int root)
{
	int j = 2 * root + 1;

	while (j < len)
	{
		if (j + 1 < len && arr[j] < arr[j + 1])
		{
			j += 1;
		}

		if (arr[j] > arr[root])
		{
			Swap(&arr[j], &arr[root]);
		}

		root = j;
		j = 2 * root + 1;
	}
}
void CreateHeap(int *arr, int len)
{
	for (int root = (len - 2) / 2; root >= 0; --root)
	{
		Adjust(arr, len, root);
	}
}
void HeapSort(int *arr, int len)
{
	CreateHeap(arr, len);

	for (int i = len; i > 0; --i)
	{
		Swap(&arr[0], &arr[i - 1]);
		Adjust(arr, i - 1, 0);
	}
}

六、快速排序

个人理解:
快速排序其实就是一个分组方法的变种,以一个基准为界,把数组中的数据元素分为两组,然后再在两组中套用此方法,达到最终数组有序的目的。

代码思想:
递归- - -选择一个基数,把比基数小的都放在左边,比基数大的放在右边,以基数为界,把数组分为两个部分,递归重复上述过程,直到两边只剩一个数据元素不用排序为止。

复杂度和稳定性分析:
时间复杂度O(nlogn)
空间复杂度O(logn)
不稳定

图解快速排序:
在这里插入图片描述

代码:

//快速排序
int OneQuick(int *arr, int head, int tail)
{
	int i = head,
		j = tail;

	int tmp = arr[i];

	while (i < j)
	{
		while (tmp <= arr[j] && i < j)
		{
			j--;
		}

		if (i >= j)
		{
			break;
		}

		arr[i] = arr[j];
		i++;

		while (tmp >= arr[i] && i < j)
		{
			i++;
		}

		if (i >= j)
		{
			break;
		}

		arr[j] = arr[i];
		j--;
	}

	arr[i] = tmp;

	return i;
}

void Quick(int *arr, int head, int tail)
{
	int i = OneQuick(arr, head, tail);

	if (i - head > 1)
	{
		Quick(arr, head, i - 1);
	}

	if (tail - i > 1)
	{
		Quick(arr, i + 1, tail);
	}
}

void QuickSort(int *arr, int len)
{	
	Quick(arr, 0, len - 1);
}

七、快速排序的优化

非递归
非递归的快速排序实际上是采用了栈的结构来用循环代替递归,把本来需要递归来处理划分出来的两个组的边界值存储在栈中,通过入栈出栈的方式来依次处理,达到消除递归的效果,这里代码引用了自己写的栈。

//快速排序
int OneQuick(int *arr, int head, int tail)
{
	int i = head,
		j = tail;

	int tmp = arr[i];

	while (i < j)
	{
		while (tmp <= arr[j] && i < j)
		{
			j--;
		}

		if (i >= j)
		{
			break;
		}

		arr[i] = arr[j];
		i++;

		while (tmp >= arr[i] && i < j)
		{
			i++;
		}

		if (i >= j)
		{
			break;
		}

		arr[j] = arr[i];
		j--;
	}

	arr[i] = tmp;

	return i;
}

void NiceQuick(int *arr, int head, int tail)
{
	SqStack st;
	InitStack(&st);
	PushStack(&st, head);
	PushStack(&st, tail);

	while (!EmptyStack(&st))
	{
		int right = 0;
		PopStack(&st, &right);
		int left = 0;
		PopStack(&st, &left);
		int mod = OneQuick(arr, left, right);

		if (mod - left > 1)
		{
			PushStack(&st, left);
			PushStack(&st, mod - 1);
		}
		if (right - mod > 1)
		{
			PushStack(&st, mod + 1);
			PushStack(&st, right);
		}
	}
}

void QuickSort(int *arr, int len)
{	
	NiceQuick(arr, 0, len - 1);
}

三数取中法
之前的快排一般都是取第一个值作为基准,但是如果这个值本身就是最大值或者最小值,那么此次的排序就毫无意义,极大的浪费了时间。
三数取中法即取第一个值、中间一个位置的值和最后一个值,把其中间值与第一个值交换作为基准来进行快排。

优化部分代码:

//快速排序
void SelectPivoMedianOfThree(int *arr, int head, int tail)
{
	int mod = (tail - head) / 2 + head;

	if (arr[mod] > arr[tail])
	{
		Swap(&arr[tail], &arr[mod]);
	}
	if (arr[head] > arr[tail])
	{
		Swap(&arr[head], &arr[tail]);
	}	
	if (arr[head] < arr[mod])
	{
		Swap(&arr[head], &arr[mod]);
	}
}

int OneQuick(int *arr, int head, int tail)
{
	int i = head,
		j = tail;

	SelectPivoMedianOfThree(arr, head, tail);
	int tmp = arr[i];

	while (i < j)
	{
		while (tmp <= arr[j] && i < j)
		{
			j--;
		}

		if (i >= j)
		{
			break;
		}

		arr[i] = arr[j];
		i++;

		while (tmp >= arr[i] && i < j)
		{
			i++;
		}

		if (i >= j)
		{
			break;
		}

		arr[j] = arr[i];
		j--;
	}

	arr[i] = tmp;

	return i;
}

当快排小到一定程度使,使用直接插入排序
这个是因为,当数据量小到一定程度时,快排的效率不如直接插入排序好。

优化部分代码:

void Quick(int *arr, int head, int tail)
{
	if (tail - head < 10)
	{
		InsertSort(arr + head, tail - head + 1);
		return;
	}

	int i = OneQuick(arr, head, tail);

	if (i - head > 1)
	{
		Quick(arr, head, i - 1);
	}

	if (tail - i > 1)
	{
		Quick(arr, i + 1, tail);
	}
}

八、二路归并排序

个人理解:
二路归并排序实质上是把无序的数组用分治法分成数个小数组,把小数组变成有序的来逐步把整个数组变为有序。

代码思想:
把数组分成两两的数据段,两个数据段合并为有序的数据段,两个合并完的有序数据段再合并成一个有序的数据段,直至剩一个有序的数据段,即为所要排列的数组。

复杂度和稳定性分析:
时间复杂度O(nlogn)
空间复杂度O(n)
稳定

图解二路归并排序:
在这里插入图片描述

在这里插入图片描述

代码:

//二路归并排序
void Merge(int *arr, int len, int width)
{
	int *brr = (int *)malloc(sizeof(int)* len);
	assert(brr != NULL);

	int low1 = 0;
	int high1 = low1 + width - 1;
	int low2 = high1 + 1;
	int high2 = low2 + width - 1 < len - 1 ? low2 + width - 1 : len - 1;

	int count = 0;
	
	while (low2 < len)
	{
		//两个归并段
		while (low1 <= high1 && low2 <= high2)
		{
			if (arr[low1] < arr[low2])
			{
				brr[count++] = arr[low1++];
			}
			else
			{
				brr[count++] = arr[low2++];
			}
		}

		//只剩一个归并段
		if (low1 <= high1)
		{
			while (high1 - low1 + 1)
			{
				brr[count++] = arr[low1++];
			}
		}
		else
		{
			while (high2 - low2 + 1)
			{
				brr[count++] = arr[low2++];
			}
		}

		low1 = high2 + 1;
		high1 = low1 + width - 1 < len - 1 ? low1 + width - 1 : len - 1;
		low2 = high1 + 1;
		high2 = low2 + width - 1 < len - 1 ? low2 + width - 1 : len - 1;
	}
	
	while (high1 - low1 + 1)
	{
		brr[count++] = arr[low1++];
	}

	for (int i = 0; i < len; ++i)
	{
		arr[i] = brr[i];
	}

	free(brr);
}

void TwoMergeSort(int *arr, int len)
{
	for (int i = 1; i < len; i *= 2)
	{
		Merge(arr, len, i);
	}
}

九、基数排序

个人理解:
基数排序是多关键字的排序,其核心在于不同关键字的权重不一样,不同权重所带来的的优先级是不一样的,通过同一优先级先进行比较来区分数据。

代码思想:
申请关键字范围个数的队列,再循环根据各关键字将数据依次存储到对应的队列中,最后将队列依次输出。

复杂度与稳定性分析:
时间复杂度O(d(r+n))
空间复杂度O(rd+n)
稳定

图解基数排序:
在这里插入图片描述

代码:

//基数排序
int GetMaxDigits(int *arr, int len)
{
	int maxValue = arr[0];
	for (int i = 0; i < len; ++i)
	{
		if (arr[i] > maxValue)
		{
			maxValue = arr[i];
		}
	}

	int digits = 0;
	while (maxValue)
	{
		digits++;
		maxValue /= 10;
	}

	return digits;
}

int GetDigitsValue(int value, int digits)
{
	while (digits)
	{
		value /= 10;
		digits--;
	}

	return value % 10;
}

void RadixSort(int *arr, int len)
{
	//获取最大关键字的位数
	int maxDigits = GetMaxDigits(arr, len);

	//申请关键字范围个数的队列 0--9
	SqQueue que[10];
	for (int i = 0; i < 10; ++i)
	{
		InitQueue(&que[i]);
	}

	//循环根据各关键字将数据依次存储到对应的队列中,最后将队列依次输出
	for (int i = 0; i < maxDigits; ++i)
	{
		//将所有的数据依次存储在对应队列中
		for (int j = 0; j < len; ++j)
		{
			int digValue = GetDigitsValue(arr[j], i);
			PushQueue(&que[digValue], arr[j]);
		}

		//将队列中的所有数据依次输出到arr中
		int index = 0;
		for (int k = 0; k < 10; ++k)
		{
			while (!EmptyQueue(&que[k]))
			{
				PopQueue(&que[k], &arr[index]);
				index++;
			}
		}
	}

	for (int i = 0; i < 10; ++i)
	{
		ClearQueue(&que[i]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值