数据结构-经典排序之插入排序与希尔排序

插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作原理类似于我们手动排序一手扑克牌。算法的基本思想是将一个数据元素从其输入数组中取出,然后将其插入到已排序的数组中适当的位置,以达到排序的目的。
算法步骤
开始假设:
在开始时,我们假设数组的第一个元素是已排序的。这是我们的初始已排序部分。
选择未排序的元素:
从未排序的部分选择一个元素。在第一次迭代中,这个元素是数组的第二个元素。
找到插入位置:
将这个选定的元素与已排序部分的元素逐一比较,从后往前,找到它应该插入的位置。这类似于我们在扑克牌中找到一张牌应该插入的位置。
移动元素:
一旦找到了插入位置,就将该位置及其后面的所有元素向后移动一位,为新元素腾出空间。
插入元素:
将选定的元素插入到腾出的空间中。
重复过程:
重复步骤2到5,直到整个数组排序完成。
示例
假设我们有一个数组 [4, 3, 2, 10, 12, 1, 5, 6],我们按照插入排序的步骤对其进行排序:

开始时,假设第一个元素 [4] 是已排序的。
选择下一个元素 3,与已排序部分的元素 4 进行比较。因为 3 小于 4,所以 3 应该插入到 4 的前面。移动 4 并插入 3,得到 [3, 4]。
选择下一个元素 2,与已排序部分的元素 [3, 4] 进行比较。2 应该插入到最前面,移动其他元素并插入 2,得到 [2, 3, 4]。
以此类推,直到整个数组排序完成。
性能分析
时间复杂度:插入排序的时间复杂度在最坏情况下(即数组是逆序)是 O(n^2),其中 n 是数组的长度。这是因为对于每个元素,我们可能需要将它与已排序部分的所有元素进行比较,并移动它们。然而,在最好情况下(即数组已经排序或接近有序),插入排序的时间复杂度可以降低到 O(n)。
空间复杂度:插入排序的空间复杂度是 O(1),因为它只需要一个额外的空间来存储当前正在插入的元素。
尽管插入排序在处理大规模数据时可能不是最高效的算法,但由于其实现简单且对于小规模或部分有序的数据集表现良好,因此在某些情况下仍然是一个实用的选择。
以下这个插入排序的版本是从后往前进行比较和插入的。对于数组中的每一个元素,它都会找到该元素在已排序部分中的正确位置,并将其插入。

 定义InsertSort函数,参数为一个整数数组a和数组的长度n  
void InsertSort(int* a, int n) {  //n为数组数据个数
    // 外层循环:遍历数组中的每一个元素(除了最后一个)  
    for (int i = 0; i < n - 1; i++) {  
        // 初始化end为当前遍历到的元素的索引  
        int end = i;  
        // tmp保存end+1位置(即这一次要插入排序的元素)的值  
        int tmp = a[end + 1];  
          
        // 内层循环:找到tmp应该插入的位置  
        while (end >= 0) {  
            // 如果tmp小于当前end位置的元素,则将end位置的元素后移  
            if (tmp < a[end]) {  
                a[end + 1] = a[end];  
                end--; // 继续向前查找插入位置  
            }  
            else {  
                break; // 如果tmp不小于当前元素,则跳出循环  
            }  
        }  
        // 将tmp插入到正确的位置  
        a[end + 1] = tmp;  //这一句可以放到while循环外,也可以放到while循环内的最后一句,
        //因为每次比较大小都是用目标元素tmp
        //的值(也就是需要进行插入排序的元素)去与它后面的一个元素(也就是a[end])比较,
        //由于tmp已保存了目标元素的值,所以目标元素在进行插入排序时并不需要移动位置。
        //当要插入的目标元素找到了正确的位置(也就是在end+1这个位置)后,才需要
        //移动该元素的位置,通过a[end + 1] = tmp;移动到end+1这个位置。
    }  
}

使用示例

#include<stdio.h>

void PrintArray(int* a, int n)//打印
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//插入排序
void InsertSort(int* a, int size) {
	for (int i = 0; i < size - 1; i++) {
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0) {
			if (tmp < a[end]) {
				a[end + 1] = a[end];
				end--;
			}
			else {
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

void TestInsertSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印

	InsertSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}
int main() {
	TestInsertSort();
	
	return 0;
}


 

如果上述代码实在不理解可以看这个视频插入排序,这个视频有些地方和我不一样但是讲的非常清楚

希尔排序

希尔排序(Shell Sort)是插入排序的一种高效改进版本,也称为“缩小增量排序”。它的基本思想是:先将整个待排序的记录序列分割成为若干子序列(由相隔某个“增量”的记录组成)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

希尔排序的步骤
选择一个增量序列:希尔排序使用一个增量序列来控制排序的子序列。这个增量序列通常是逐渐减小的,常用的增量序列有如Hibbard增量(1, 3, 7, …, 2^k - 1)或Sedgewick增量(如94^i - 92^i + 1 或 4^i - 3*2^(i-1) + 1)。
**对子序列进行插入排序:**根据当前的增量值,将数据分成多个子序列,并对每个子序列进行插入排序。例如,如果增量是3,则第1、4、7、…个元素会组成一个子序列,第2、5、8、…个元素组成另一个子序列,以此类推。然后对每个这样的子序列进行插入排序。
减小增量并重复:减小增量值,并重复步骤2,直到增量减小到1。当增量为1时,进行最后一次全面的插入排序,以确保列表完全有序。
希尔排序的特点
时间复杂度:希尔排序的时间复杂度依赖于所选的增量序列。最坏情况下的时间复杂度仍然是O(n^2),但在实际应用中,由于数据在排序过程中逐渐变得有序,希尔排序的性能通常远优于普通的插入排序。
空间复杂度:希尔排序是一种原地排序算法,因此其空间复杂度为O(1)。
稳定性:希尔排序是一种不稳定的排序算法,因为在分割成子序列进行插入排序的过程中,相同值的元素可能会因为增量的改变而改变其相对顺序。
适用场景:希尔排序适用于中等大小的数据集,特别是当数据部分有序或完全无序时。对于大数据集,通常建议使用更高效的排序算法,如归并排序或快速排序。

// 定义ShellSort函数,参数为一个整数数组a和数组的长度n  
void ShellSort(int* a, int n)  
{  
    // 初始化间隔gap为数组的长度  
    int gap = n;  
      
    // 当间隔大于1时,继续缩小间隔并执行排序  
    while (gap > 1)  
    {  
        // 每次循环都将间隔减半,这是希尔排序中常用的一种间隔选择策略  
        gap /= 2;  
          
        // 对每个子序列进行插入排序  
        for (int i = 0; i < n - gap; i++)  
        {  
            // 记录当前要插入的元素的位置  
            int end = i;  
            // 要插入的元素值  
            int tmp = a[end + gap];  
              
            // 使用插入排序的思想,将tmp插入到前面的已排序序列中  
            while (end >= 0)  
            {  
                // 如果tmp小于前面的元素,则将前面的元素后移gap位  
                if (tmp < a[end])  
                {  
                    a[end + gap] = a[end];  
                    end -= gap; // 继续向前检查  
                }  
                else  
                {  
                    break; // 如果tmp不小于前面的元素,则退出循环  
                }  
            }  
              
            // 找到tmp的合适位置后,将其插入  
            a[end + gap] = tmp;  
        }  
    }  
}

使用示例

#include<stdio.h>

void PrintArray(int* a, int n)//打印
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap /= 2;
		gap = gap / 3 + 1;

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}

			a[end + gap] = tmp;
		}

	}
}

void TestShellSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印

	ShellSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}
int main() {
	TestShellSort();
	
	return 0;
}

若有错误或问题,欢迎在评论区提出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值