题目
插入排序及希尔排序的并行化实现
插入排序定义
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。
插入排序串行算法
public class Solution {
public void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int number = array[i];
int j = i - 1;
while (j >= 0 && array[j] > number) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = number;
}
}
}
通过循环将要插入的位置空出来,然后将要插入的数据插进去。从后往前遍历将查找与移动合二为一,减少了复杂度。
希尔排序定义
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量
,即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法
比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。
一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
给定实例的shell排序的排序过程
假设待排序文件有10个记录,其关键字分别是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,3,1
希尔排序分析
稳定性
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
优劣
不需要大量的辅助空间,和归并排序一样容易实现。希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(n^(3/2)),希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O(n2)复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。 原因是,当n值很大时数据项每一趟排序需要移动的个数很少,但数据项的距离很长。当n值减小时每一趟需要移动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。Shell算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。
希尔排序串行算法
public class Solution {
public void shellSort(int[] array) {
int d = array.length;
while (d > 0) {
d = d / 2;
for (int i = d; i < array.length; i++) {
int number = array[i];
int j = i - d;
while (j >= 0 && array[j] > number) {
array[j + d] = array[j];
j -= d;
}
array[j + d] = number;
}
}
}
}
希尔排序并行化设计
很明显,希尔排序在相同间隔d的情况时要对多个子数组进行排序,而这些子数组间没有关联是完全独立的,可以很容易写出并行化程序。
public class Solution {
static ExecutorService es = Executors.newCachedThreadPool();
static int[] array = {1, 3, 4, 5, 2};
public static class shellSort implements Runnable {
static int d;
static int start;
static CountDownLatch latch;
public shellSort(int d, int start, CountDownLatch latch) {
this.d = d;
this.start = start;
this.latch = latch;
}
@Override
public void run() {
for (int i = start; i < array.length; i++) {
int number = array[i];
int j = i - d;
while (j >= 0 && array[j] > number) {
array[j + d] = array[j];
j -= d;
}
array[j + d] = number;
latch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException{
int d = array.length;
while (d > 0) {
d /= 2;
CountDownLatch latch = new CountDownLatch(d);
for (int i = 0; i < d; i++) {
es.submit(new shellSort(d, i, latch));
}
latch.await();
}
}
}
使用CountDownLatch来保证所有线程运行完再进行下一步。疑问见《Java高并发程序设计实战》批注。未完待续。。。。