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

news/2024/5/19 5:47:31 标签: c++, 堆排序, 希尔排序

希尔排序简述:

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

排序前的准备:

准备了一个默认的顺序表

#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)。

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


http://www.niftyadmin.cn/n/627120.html

相关文章

数据结构List实例(二): 调整奇偶次序

输入一个整数数组&#xff0c;调整数组中数字的顺序&#xff0c;使得所有奇数位于数组的前半部分&#xff0c; 所有偶数位于数组的后半部分。要求时间复杂度为O(n)。 分析&#xff1a;如果不考虑时间复杂度&#xff0c;最简单的思路应该是从头扫描这个数组&#xff0c;每碰到一…

排序算法之归并排序的递归与迭代实现方法

归并排序简述: 归并排序是排序算法的一种新的思路&#xff0c;旨在把两个或以上有序的数列归并一个有序的数列&#xff0c;是为归并。 假如有一个含有n个元素的数组&#xff0c;将其看作n个有序列&#xff0c;然后两两归并&#xff0c;最终得到一个有序列。 排序前的准备&am…

数据结构List实例(三):寻找倒数第k个结点值

这个也是非常常见的问题。 分析&#xff1a;为了得到倒数第k个结点&#xff0c;很自然的想法是先走到链表的尾端&#xff0c;再从尾端回溯k步。可是输入的是单向链表&#xff0c;只有从前往后的指针而没有从后往前的指针。因此我们需要打开我们的思路。既然不能从尾结点开始遍历…

数据结构List实例(四):使用归并排序对单链表进行排序

对链表进行排序&#xff0c;要求的时间复杂度为O(n log n)。nlogn的排序有快速排序、归并排序、堆排序。双向链表用快排比较适合&#xff0c;堆排序也可以用于链表&#xff0c;单向链表适合用归并排序。题目要求的是常数的空间复杂度&#xff0c;因为这里用了递归&#xff0c;如…

《Python进行自然语言处理》代码笔记(一):第一章示例

利用空余时间&#xff0c;将《用Python进行自然语言处理》的前面几章内容都敲了一遍&#xff0c;其中遇到与书中示例不太一致的地方也进行了修改。第一章示例如下&#xff1a; #!/usr/bin/env python # -*- coding: utf-8 -*- # Author : Peidong # Site : # File : eg…

无向图的深度优先搜索与广度优先搜索

图的简述&#xff1a; 图是一种复杂的数据结构&#xff0c;图(Graph)是由顶点(Vertex)组成的非空有穷集合和顶点之间的边(Edge)组成。 图的边可以由权值(weight)&#xff0c;也可以没有&#xff0c;有权值的图称为网图。 图的边也可以有方向&#xff0c;没有方向的为无向图&…

《用Python进行自然语言处理》代码笔记(二):第二章 获得文本语料和词汇资源

#!/usr/bin/env python # -*- coding: utf-8 -*- # Author : Peidong # Site : # File : eg2.py # Software: PyCharm """ 获得文本语料和词汇资源 """ # # 获取古腾堡语料库 import nltk print(nltk.corpus.gutenberg.fileids()) # # 获…

最小生成树简单实现(Prim算法与Kruskal算法)

最小生成树简述&#xff1a; 最小生成树(Minimum Spanning Tree)指&#xff0c;在一个有权图中寻找最小权值和的树来连通所有顶点。 Prim算法简述及准备&#xff1a; Prim的思路很简单&#xff0c;设定一个初始点(列如V0)&#xff0c;寻找从此连接的最小的权值的边&#xff…