【算法】七大排序算法

在这里插入图片描述
以下以无序的整形数组调整至非降序为例

直接插入排序

在这里插入图片描述

只存在一个元素的时候是有序的,所以直接调整数组的第二个元素,此时符合上面图示
让需要插入元素从后往前依次与前面的已经有序部分进行比较,如果插入元素大于比较的元素,表明已经找到需要插入的位置,否则让前面直接覆盖后面一个元素,从而为插入元素腾出空间,覆盖前已经用临时变量保存了插入元素
最后找到合适位置,将临时空间保存的元素赋给需要插入的位置

在这里插入图片描述
上面是一次调整的过程,依次将剩下无序的部分都重复上述过程,最终整个序列将有序

void InsertSort(int array[], int size)
{
	int key, i, j;
	for (i=1; i<size; i++)
	{
		key = array[i];
		for (j=i-1; j>=0; j--)
		{
			if (key > array[j])
			{
				break;
			}
			array[j+1] = array[j];
		}
		array[j+1] = key;
	}
}

希尔排序

希尔排序是对直接插入排序的优化,在插入排序中如果遇到极端情况:最后一个元素比第一个元素小,则需要从后往前将前面所有的元素往后覆盖,以空出第一个位置,以便最后一个元素的插入

希尔排序则是在整体排序之前,做多次预排序,预排序的目的是保证序列尽可能有序,尽量避免不再出现上面的极端情况

如何实现预排序,则是通过分组的方式,在组内先进性小范围排序。分组的方法是通过保留固定间隙来完成,当间隙为0,则表示进行的是最后一次的整体排序,此时上面的极端情况已经通过前面的预排序避免掉了
在这里插入图片描述
在这里插入图片描述

void _InsertSort(int array[], int size, int gap)
{
	int key, i, j, g;
	for (g=0; g<gap; g++)
	{
		for (i=gap+g; i<size; i+=gap)
		{
			key = array[i];
			for (j=i-gap; j>=0; j-=gap)
			{
				if (key >= array[j])//如果只有大于则算法将不是稳定的
				{
					break;
				}
				array[j+gap] = array[j];
			}
			array[j+gap] = key;
		}
	}
}

void ShellSort(int array[], int size)
{
	int gap = size;
	while (1)
	{
		gap = gap / 3 + 1;
		_InsertSort(array, size, gap);
		if (gap == 1)
		{
			break;
		}
	}
}

当然上面的排序方式是先将一个组通过插入排序排好后,再去排第二组,也可以先统一调整每组的第一个需要调整的元素,调整完后再调整后面的元素,最终的目的是相同的

void _InsertSort(int array[], int size, int gap)
{
	int key, i, j;
	for (i=gap; i<size; i++)
	{
		key = array[i];
		for (j=i-gap; j>=0; j-=gap)
		{
			if (key >= array[j])//如果只有大于则算法将不是稳定的
			{
				break;
			}
			array[j+gap] = array[j];
		}
		array[j+gap] = key;
	}

}

选择排序

每次找无序序列中最大/最小的,将该元素放到合适的位置,接下来需要调整的序列个数就比原来的序列个数少一个,直到需要调整的序列元素个数为1,此时有序不需要调整,这个过程就是减治的思想

void SelectSort(int array[], int size)
{
	int i, j, max;
	for (i=0; i<size-1; i++)
	{
		max = 0;//假设第一个元素最大
		for (j=1; j<size-i; j++)
		{
			if (array[j] > array[max])
			{
				max = j;//不断更新最大元素
			}
		}
		Swap(array+size-1-i, array+max);
	}
}

当然也可以对选择排序做出优化,就是在一次查找过程中找出最大和最小两个元素,分别将两个元素放到序列的尾和首,这样接下来最调整的序列个数就比原序列元素个数少2个,假设需要调整的序列起始位置为left,结束位置为right,当序列元素为奇数个时,left=right表示需要调整序列元素个数为1,不再需要调整;当序列元素个数为偶数个时left>right表示调整结束,统一起来便是当left<right时持续做调整

void SelectSortOP(int array[], int size)
{
	int max, min, i;
	int left = 0;
	int right = size - 1;
	while (left < right)
	{
		min = max = left;
		for (i=left+1; i<=right; i++)
		{
			if (array[i] < array[min])
			{
				min = i;
			}
			if (array[i] > array[max])
			{
				max = i;
			}
		}
		Swap(array+left, array+min);
		if (left == max)
		{
			max = min;//跟踪最大元素
		}
		Swap(array+right, array+max);
		left++;
		right--;
	}
}

代码中由于归置最小元素,再归置最大元素
如果做元素交换的时候,如果确立的大元素在最小元素应有的位置处,第一次交换会把最大元素交换走,所以需要对最大元素做出“跟踪”
在这里插入图片描述

堆排序

调整成升序,需要建大堆;调整成降序,需要建小堆
在这里插入图片描述
当在调整的过程中,将大堆堆顶的元素放到数组的最后一个位置上时,此时作为非降序序列的最后一个位置的元素已经确定,只需要在逻辑上“删除”掉堆尾的元素,做一次向下调整,堆中剩余元素的最大元素,也就是序列的次大元素将会浮到堆顶,重复上述操作,当堆中仅有一个元素时调整结束

void AdjustDown(int array[], int size, int root)
{
	int cLeft, cRight, max;
	while (1)
	{
		cLeft = root * 2 + 1;
		cRight = root * 2 + 2;
		if (cLeft >= size)
		{
			break;
		}
		max = cLeft;
		if (cRight < size && array[cRight] > array[cLeft])
		{
			max = cRight;
		}
		if (array[root] < array[max])
		{
			Swap(array+root, array+max);
			root = max;
		}
		else
		{
			break;
		}
	}
}

void CreateHeap(int array[], int size)
{
	int i = (size - 2) / 2;//最后一个非叶子结点
	for (; i>=0; i--)
	{
		AdjustDown(array, size, i);
	}
}

void HeapSort(int array[], int size)
{
	int i = 0;
	CreateHeap(array, size);
	for (; i<size-1; i++)
	{
		Swap(array, array+size-1-i);
		AdjustDown(array, size-1-i, 0);
	}
}

冒泡排序

冒泡排序和选择排序十分相似,结果都是比较一遍后将最值确定到正确位置,接着对剩余的元素再做同样的调整。不同的是,选择排序在查找过程中不做交换,只在最后确定最值位置后做一次交换,而冒泡排序每一趟确定最值位置,都需要从前往后作比较,只要前后位置需要调整就做交换

void BubbleSort(int array[], int size)
{
	int i, j, flag;
	for (i=0; i<size-1; i++)
	{
		flag = 0;
		for (j=0; j<size-1-i; j++)
		{
			if (array[j] > array[j+1])
			{
				Swap(array+j, array+j+1);
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
	}
}

快速排序

快速排序是利用分治的思想来完成的,先确定一个基准值,将比基准值小的放到基准值的左边;比基准值大的放到基准值的右边,如次一来,基准值的位置就归置到了正确的位置,接下来递归的对左右两部分进行调整,递归终止条件是只有一个元素或者没有元素
在这里插入图片描述

调整基准值的函数返回调整结束后基准值的位置,后面的递归调整左右两部分,该位置将不再移动

void _QuickSort(int array[], int left, int right)
{
	int div;
	if (left >= right)
	{
		return;
	}
	div = AdjuestDiv_1(array, left, right);
	_QuickSort(array, left, div-1);
	_QuickSort(array, div+1, right);
}

void QuickSort(int array[], int size)
{
	//统一接口,方便递归调用
	_QuickSort(array, 0, size-1);
}

基准值的选取如果能尽量居中,基准值两边的元素就会尽可能均匀,如果以取两边元素的方式选取基准值不太理想,可以随机选取或者取前中后三元素,取这三元素中间元素

归置基准值的方法有三种,分别是:

hoare版本

在这里插入图片描述
确定基准值后从需要调整的前后未知依次向中间找,如果前面找到一个比基准值大的元素停下来,后面找到一个比基准值小的元素停下来,交换两个元素。直到前后指针调整完中间未知的元素,由于基准值是在最右边,一开始先移动的左指针,所以当最终调整结束停下来的时候,左指针指向的元素是比基准值大的元素,此时将该元素与最右的基准值再做一次交换,基准值便归位,后面递归的对左右两部分进行调整

int AdjuestDiv_1(int array[], int left, int right)
{
	int begin = left;
	int end = right;
	while (begin < end)
	{
		//移动“指针”之前要先判断左右“指针”的位置关系
		while (begin < end && array[begin] <= array[right])
		{
			begin++;
		}
		while (begin < end && array[end] >= array[right])
		{
			end--;
		}
		Swap(array+begin, array+end);
	}
	Swap(array+begin, array+right);

	return begin;
}
挖坑法

将基准值存入临时变量,左“指针”先走,找到一个比基准值大的元素,覆盖基准值,然后右“指针”找到一个比基准值小的元素填进左指针刚挖的坑里面
在这里插入图片描述

int AdjuestDiv_2(int array[], int left, int right)
{
	int begin = left;
	int end = right;
	int tmp = array[right];
	while (begin < end)
	{
		//移动“指针”之前要先判断左右“指针”的位置关系
		while (begin < end && array[begin] <= tmp)
		{
			begin++;
		}
		array[end] = array[begin];
		while (begin < end && array[end] >= tmp)
		{
			end--;
		}
		array[begin] = array[end];
	}
	array[begin] = tmp;

	return begin;
}
前后指针法

div指针的目的是确定基准值的位置,cur指针一直向后遍历,找到了比基准值小的元素,跟div指向的元素交换,div指向下一个元素(指向的下一个元素是cur已经判断过的大于基准值的元素),直到cur遍历完了所有未知元素,此时交换div指向的元素和基准值
在这里插入图片描述

int AdjustDiv_3(int array[], int left, int right)
{
	int cur = left;
	int div = left;

	while (cur < right)
	{
		if (array[cur] < array[right])
		{
			Swap(array+cur, array+div);
			div++;
		}
		cur++;
	}
	Swap(array+div, array+right);
	return div;
}

归并排序

将排序元素“均等”划分,直到仅有一个元素时即有序,然后向上合并,其实排序是在合并时完成的,合并的方法是借助另一块空间,分别从头开始遍历需要合并的两个序列,为了保证算法的稳定性,左序列元素不大于右序列元素时,在合并的空间存放左序列元素,否则存放右序列元素

划分的过程:

在这里插入图片描述

合并的时候两个序列的大小可能不完全相等,最后要再把剩余元素添加到合并数组的后面去
合并的过程:

在这里插入图片描述

合并过程中是将有序序列存放到临时空间上去,所以在合并完成之后再把有序序列copy回array数组中去
临时空间是在堆上创建的,所以整个递归调整的过程都借助这一块空间,使用完后释放

void Merge(int a[], int b[], int left, int mid, int right)
{
	int left_i = left;
	int right_i = mid + 1;
	int b_i = left;

	while (left_i <= mid && right_i <= right)
	{
		if (a[left_i] <= a[right_i])
		{
			b[b_i++] = a[left_i++];
		}
		else
		{
			b[b_i++] = a[right_i++];
		}
	}

	while (left_i <= mid)
	{
		b[b_i++] = a[left_i++];
	}
	while (right_i <= right)
	{
		b[b_i++] = a[right_i++];
	}
}

void Copy(int a[], int b[], int left, int right)
{
	int i = left;
	for (; i<=right; i++)
	{
		a[i] = b[i];
	}
}
void _MergeSort(int array[], int left, int right, int merge[])
{
	int mid = left + (right - left) / 2;
	if (left >= right)
	{
		return;
	}
	_MergeSort(array, left, mid, merge);
	_MergeSort(array, mid+1, right, merge);
	Merge(array, merge, left, mid, right);
	Copy(array, merge, left, right);
}
void MergeSort(int array[], int size)
{
	//在堆上创建空间
	int *merge = (int *)malloc(sizeof(int) * size);
	assert(merge);	
	//统一接口,方便递归调用
	_MergeSort(array, 0, size-1, merge);
	free(merge);
}

各种排序算法比较

稳定的排序算法的是指,在排序之前两个相等元素的位置在排序之后不发生变化
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值