插入排序和希尔排序:用C语言打造高效的排序算法

插入排序和希尔排序:用C语言打造高效的排序算法_第1张图片

插入排序

插入排序的思路就像是你在整理一堆扑克牌。你先拿起第一张牌,然后拿起第二张牌,把它插入到合适的位置,使得你手上的两张牌是有序的。接着,你再拿起第三张牌,也把它插入到合适的位置,使得你手上的三张牌是有序的。依此类推,直到你把所有的牌都拿到手上,这时你手上的牌就是有序的了。

如果我们要对一个数组a进行插入排序(假设按照升序排列),我们可以这样做:

首先,我们认为数组的第一个元素a[0]是有序的。然后,我们把第二个元素a[1]插入到合适的位置,使得a[0]和a[1]是有序的。接着,我们把第三个元素a[2]也插入到合适的位置,使得a[0]、a[1]和a[2]是有序的。以此类推,直到我们把最后一个元素a[n-1]也插入到合适的位置,这时整个数组就是有序的了。

具体来说,如果我们已经知道下标为[0, end]的元素是有序的,那么如何把下标为end+1的元素插入进去呢?我们可以先把a[end+1]保存在一个临时变量tmp中,因为它很快就会被覆盖掉。然后,我们从右向左依次比较tmp和a[end]、a[end-1]、a[end-2]……等等。如果tmp比某个元素小,就把那个元素向右移动一位,为tmp腾出空间。如果tmp不比某个元素小,或者已经到达数组的左边界了,就停止移动,并把tmp放在当前空出来的位置上。

最后我们只需要控制end的变化即可。一开始end为0,表示只有一个元素是有序的。然后end逐渐增加,每次增加1,直到end为n-2时停止。这时候[0, n-2]是有序的,只需要把最后一个元素a[n-1]插入进去就完成了排序。

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// 单趟插入排序
		// [0, end]有序
		int end = i;
		// 插入end+1
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		// 把tmp插入到此时的end后面
		a[end + 1] = tmp;
	}
}

希尔排序

希尔排序的思路就像是你在打扫一间房间。你不是一次性把所有的东西都放到正确的位置,而是先按照一定的间隔分成几个小组,每个小组内部进行一次插入排序,使得每个小组内部的东西都相对有序。然后你再缩小间隔,重新分组,再进行一次插入排序,使得每个小组内部的东西更加有序。你不断重复这个过程,直到间隔为1,也就是所有的东西都在同一个小组里,这时你再进行一次插入排序,就可以把所有的东西都放到正确的位置了。

如果我们要对一个数组a进行希尔排序(假设按照升序排列),我们可以这样做:

首先,我们选择一个合适的间隔gap,比如数组长度的一半。然后我们把数组分成gap个小组,每个小组由相隔gap个元素组成。例如,如果gap为3,那么第一个小组由a[0]、a[3]、a[6]……等等组成,第二个小组由a[1]、a[4]、a[7]……等等组成,第三个小组由a[2]、a[5]、a[8]……等等组成。接着我们对每个小组进行一次插入排序,使得每个小组内部的元素都相对有序。

然后我们缩小间隔gap,比如取原来的三分之一加一。这时我们又可以把数组分成新的gap个小组,每个小组由相隔gap个元素组成。例如,如果新的gap为1,那么第一个小组就是整个数组。然后我们再对每个小组进行一次插入排序,使得每个小组内部的元素更加有序。

最后我们重复这个过程,直到间隔gap为1时停止。这时我们只需要对整个数组进行一次插入排序,就可以把所有的元素都排好序了。

具体来说,我们可以用一个外层循环控制间隔gap的变化,每次取原来的三分之一加一,直到为1时停止。然后我们用一个中层循环控制每个小组内部的插入排序过程。在中层循环中,我们需要从0开始向右遍历数组,每次只移动一个元素,直到到达n-gap为止。这样就可以把数组分成gap个小组,每个小组由相隔gap个元素组成。最后我们用一个内层循环控制每个小组内部的元素插入过程。在内层循环中,我们需要先把当前元素a[end+gap]保存在一个临时变量tmp中,并把它和左边相隔gap个元素的元素a[end]比较。如果tmp比a[end]小,就把a[end]向右移动gap位,为tmp腾出空间。如果tmp不比a[end]小,或者已经到达数组的左边界了,就停止移动,并把tmp放在当前空出来的位置上。这样就完成了一次插入排序。

注意:gap在不断缩小的过程中,最后一定会变成1,这时候就相当于执行了一次普通的插入排序,这是为了确保数组在最后能够完全有序。

void ShellSort(int* a, int n)
{
	int gap = n; // 组距,即同组中两两间隔的距离
	while (gap > 1) // 保证gap已经是1时不会重复循环
	{
		gap = gap / 3 + 1; // 保证最后一次是1
		for (int i = 0; i < n - gap; ++i) // 防止end+gap越界,进行分组预排
		{
			// [0, end]同组的已经有序,插入end+gap
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			// 插入到end后面,保持组距
			a[end + gap] = tmp;
		}
	}
}

总结

  1. 插入排序的思想:每次从未排序的数中选取一个,将其插入到已排序的数中合适的位置,保证插入后仍然有序。
  2. 希尔排序的思想:把数组按照一定的间隔gap分成若干组,每组的元素下标相差gap,然后对每组内的元素进行插入排序。随着gap的不断减小,每组内的元素越来越多,最终当gap减小到1时,整个数组就变成了一组,此时进行最后一次插入排序,就可以得到完全有序的数组。

感谢大家的阅读!

你可能感兴趣的:(数据结构和算法,排序算法,c语言,算法,数据结构,开发语言)