数据结构——排序与查找(2)——希尔排序(C++实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/redRnt/article/details/82958072

希尔排序原理

希尔排序(Shell’s Sort),也称为“缩小增量排序”,是一种插入排序类的算法。最简单的插入排序,我在上一个专栏的一篇文章C++抽象编程——算法分析(8)——插入排序算法与分析有提到过,这里就不再赘述,这里就只介绍一些我以前没写过的算法。
希尔排序是一种改进的插入排序算法。其基本思想如下:将整个待排序列分割成若干个自序列,然后对每个子序列分别进行直接插入排序算法。待整个序列中的每条记录都呈现出有序状态的时候,再对整体进行排序。(是不是有点devid and conquer算法的味道?)。其实直接插入排序算法在元素较小的序列中效率还是很高的,所以希尔排序就是基于这个原理来进行分割重组的。

希尔排序过程分析

假设我们需要对下面的一组数据进行希尔排序:
49 38 65 97 76 13 27 48 55 04

  1. 根据给定的增量序列,对数据进行分组(即子序列),假设一开始增量 gap = 5(每隔5个单位取一个数).那么就分成下面的5组序列
    (49, 13),(38, 27),(65, 48),(97,55),(76, 04)
    在这一步尤其注意,这里说的是每隔5个单位取一个数,不是每5个连续单位取一个数!!我一开始理解的时候理解错就很棘手。比如有人会理解成第一个序列是(49 38 65 97 76),这是不对的。而且同样注意,增量是从第二个元素开始数起的。
  2. 然后对组的元素进行组内插入排序,也就是形成这样的一个情形:
    (13, 49),(27, 38),(48, 65),(55, 97),(04, 76)
  3. 将较小的数字往前移动,构成第一趟的排序(注意:我们刚刚的排序是在同一个子序列中进行的比较,所以这个时候组间的较小的元素不是一步步往前挪动,而是跳跃式的往前移动(即插入)),就像这样:
    在这里插入图片描述
    如果这种方式不好看,我们可以换种方式来看:
    在这里插入图片描述
    这样,第一趟排序的顺序就出来了。
  4. 在第一趟排序的基础上,我们取增量gaps = 3,同上操作可以得到第二趟的排序:
    在这里插入图片描述
  5. 因此,这个时候我们取增量gaps = 1,也就是这个时候退化为简单的插入排序。这个时候序列基本有序,适合用简单的插入算法。所以,第三趟的排序就是:
    **04 13 27 38 48 49 55 65 76 97 **
    排序完成。
    下面再给出一个实例:
    在这里插入图片描述

希尔排序的不确定性

我们从上面的步骤可以看出,这个增量gap的值是我们事先给定的,不同的gap值会产生不同的排序序列,也就是说,排序的时间与gap的取值有关,当gap = 1的时候,退化为简单的排序算法。对于如何取这样的gap值,是个难解的问题。常见的gap取值可以参见:Shellsort

希尔排序的实现

先贴一段伪代码:


# Sort an array a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1]

# Start with the largest gap and work down to a gap of 1
foreach (gap in gaps)
{
    # Do a gapped insertion sort for this gap size.
    # The first gap elements a[0..gap-1] are already in gapped order
    # keep adding one more element until the entire array is gap sorted
    for (i = gap; i < n; i += 1)
    {
        # add a[i] to the elements that have been gap sorted
        # save a[i] in temp and make a hole at position i
        temp = a[i]
        # shift earlier gap-sorted elements up until the correct location for a[i] is found
        for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
        {
            a[j] = a[j - gap]
        }
        # put temp (the original a[i]) in its correct location
        a[j] = temp
    }
}

如果理解有困难,我大致讲一下,思路是这样的,先把给定的增量放在一个数组中存放,这个时候,第一层循环是遍历我们的增量,第二层循环是从我们增量的元素下标后面开始遍历后面的元素,最后一层是遍历我们的分组,并且根据情况判断是否进行交换。我在写的时候,特意写了一个C++代码,也能成功运行,代码贴上:

#include <iostream>
#include <vector>
using namespace std;
/*函数原型*/
void shellsort(vector<int> & vec,vector<int> gaps);
/*主函数*/
int main() {
	vector<int> vec,gaps;
	cout << "请输入待排序的序列" << endl;
	for (int i = 0; i < 12; i++) {
		int n;
		cin >> n;
		vec.push_back(n);
	}
	cout << "请输入增量:" << endl;
	for (int j = 0; j < 3; j++)
	{
		int m;
		cin >> m;
		gaps.push_back(m);
	}
	shellsort(vec, gaps);//shellsort(vec);//使用希尔排序对vector进行排序
	cout << "希尔排序后:" << endl;
	for (int k = 0; k < vec.size(); k++) {
		cout << vec[k] << " ";
	}
	return 0;
}

void shellsort(vector<int> &vec, vector<int> gaps) {
	int i,j,k,temp;
	for (i = 0; i < gaps.size();i++) {//遍历所给的增量gaps
	//对于vec增量后的每一个元素,都执行下面的操作
		for (k = gaps[i]; k < vec.size();k++){ 
			    temp = vec[k];//暂存第增量个元素
				//cout << "这个时候的temp值为:" << temp;
				//将此时增量后面的第k个元素作为分组的末尾元素,用插入算法比较
			for (j = k; j >= gaps[i] && vec[j - gaps[i] ]> temp; j -= gaps[i]) {
				vec[j] = vec[j - gaps[i]];//将vec下标较小值部分赋值给较大的那边
				//cout << " 最里面的vec[j]的值" << vec[j];
			}
			vec[j] = temp;
			//cout << "外部的vec[j]= " << vec[j] << endl;
		}
	}

}

测试数据跟结果如下:
在这里插入图片描述

写代码的过程

瞎扯一下,写这段代码着实不是很容易。在初期,我犯了一个错误,就是到后面把gaps[i]当k使用了,因为有一段语句是将gaps[i]的值赋值给k。而我忽略了k是有在递增变化的,而gaps[i]在跳出循环时始终是不变的。在寻找问题的过程中,我添加了代码(就是我注释掉的那三句),用来追踪循环中的值变化,从而找到相应的问题。这是一种常用的找循环错误的方法。有兴趣的可以试试将gaps[i]改为k,并取消注释。观察出现的问题。同样可以在里面加一句输出语句,把每一趟的情况输出出来。

猜你喜欢

转载自blog.csdn.net/redRnt/article/details/82958072
今日推荐