排序算法之希尔排序和堆排序

希尔排序简述:

希尔排序可以看作是直接插入排序的一种优化,即把一个数插入一个有序表,不过希尔排序多了一个默认的增量,把一个序列分成若干个增量大小的增量序列,然后在子序列直接进行直接插入排序,每次都使较小的数排到靠前的子序列里面,增量不断减小,子序列也不断缩小,最终使整个表有序。

排序前的准备:

准备了一个默认的顺序表

#define MAXSIZE 10
//顺序表结构
template <class T>
struct SqList
{
	T s[MAXSIZE + 1] = { NULL, 98, 24, 55, 81, 32, 77, 48, 60, 14, 8 };          //数组s,s[0]用作哨兵
	int length = MAXSIZE;              //s长度
	void PrintArray();
};
typedef SqList<int> SList;

//交换l中的数组中下标为i和j的值
void Swap(SList *L, int i, int j)
{
	int temp = L->s[i];
	L->s[i] = L->s[j];
	L->s[j] = temp;
}

希尔排序实现:

这里的length为10,增量选择为length/3 +1=4。

增量的选择可以是任意小于length的一个数,但最后要等于1,即能进行最后一次直接插入排序即可。

//希尔排序
//设置一个增量,来进行直接插入排序。
void ShellSort(SList *L)
{
	int i, j;
	int increment = L->length;       //增量	
	do
	{
		increment = increment / 3 + 1;      //增量
		for (i = increment+1; i <= L->length; i++)      //遍历从第一个增量序列的后一个元素开始
		{
			if (L->s[i] < L->s[i-increment])       //增量序列后一个元素小于增量序列的第一个元素
			{
				L->s[0] = L->s[i];         //把s[0]哨兵赋值为s[i]
				for (j = i-increment; j>0 && L->s[0]<L->s[j] ;j-= increment)
				{//只循环一次
					L->s[j + increment] = L->s[j];         //将增量序列后一个元素赋值为增量序列的第一个元素
				}
				L->s[j+increment] = L->s[0];         //最后改变增量序列的第一个元素为哨兵值
			}
		}
	} while (increment > 1); //当增量小于等于1时终止循环
}

希尔排序时间复杂度:

希尔排序的时间复杂度和增量的选取有关系,并不是完全相同的。

最优情况下的时间复杂度为O(n^1.3);

最坏情况下的时间复杂度为O(n^2);

堆排序简述:

堆排序利用了完全二叉树的性质来进行,堆排序把一个顺序表先构造成一个大顶堆,大顶堆即所有父节点比子节点大的一个完全二叉树,构造成大顶堆后,把根节点(即当前最大值)和最后的元素交换,并继续把长度减一的剩余顺序表构造成大顶堆,如此循环。

堆排序实现:

这里碰到了一个问题是如何将顺序表构造成大顶堆?

代码如下:

//构造成大顶堆
void HeapAdjust(SList *L,int i,int length)
{
	int temp, j;
	temp = L->s[i];                         //临时变量赋值为当前父节点,用于后续的比较
	for (j = 2 * i; j <= length; j *= 2)    //j=2*i指向左孩子节点
	{
		if (L->s[j]<L->s[j+1] && j<length)            //如果左孩子比右孩子小,则j指向右孩子.j<length说明j不是最后节点
		{
			++j;
		}
		if (temp>L->s[j])           //如果一开始的父节点比两个子节点都大,break
		{
			break;
		}
		L->s[i] = L->s[j];          //父节点比子节点小,把父节点赋值为两个孩子中较大的一个
		i = j;                      //父节点指向子节点中较大的一个继续循环
	}
	L->s[i] = temp;                 //把子节点较大的一个赋值为父节点
}
堆排序如下:

//堆排序
//将无序数列构造成大顶堆,把根节点和层序遍历最后节点互换,然后把剩下元素继续构造大顶堆
void HeapSort(SList *L)
{
	int i;
	for (i = L->length / 2; i > 0; i--)       //i=Length/2 因为完全二叉树的父节点数量为叶子节点的一半
	{
		HeapAdjust(L, i, L->length);       //将整个序列构造成大顶堆
	}
	for (i = L->length; i > 1; i--)
	{
		Swap(L, 1, i);                  //把根节点和最后一位互换
		HeapAdjust(L, 1, i - 1);        //继续构造大顶堆
	}
}
程序首先从完全二叉树的父节点开始遍历到根节点(即(length/2)个父节点),将这些父节点下面的小子树分别构造成大顶堆,最终得到一个完全的大顶堆;

然后从尾到头开始遍历,把最大的数和最后节点交换,并继续遍历除最大节点意外剩余节点,最终即得到一个从小到大的有序表。

堆排序时间复杂度:

构建一个大顶堆从最下层最右边的父节点开始遍历,进行交换,构造堆需要的时间复杂度为O(n);

而重建堆需要的时间复杂度为O(nlogn),所以整体的堆排序时间复杂度为O(nlogn)。

堆排序不适合元素数量较少的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值