您的位置:首页 > 编程语言 > C语言/C++

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

2018-01-15 18:36 148 查看
希尔排序简述:

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

排序前的准备:

准备了一个默认的顺序表

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

堆排序不适合元素数量较少的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 堆排序 希尔排序
相关文章推荐