算法--排序(希尔,归并)
来源:互联网 发布:kx tda200控制软件 编辑:程序博客网 时间:2024/06/05 15:56
希尔排序
插入排序在对几乎已经排好序的数组进行排序时,效率高,可以达到线性排序的效率。但对于大规模乱序数组(特别是逆序数组),插入排序很慢。因为他只会交换相邻的元素,元素只能一点点从数组一端移动到另一端。
希尔排序的思想:
交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。使数组中任意间隔为h的元素都是有序的。其中h为任意以1结尾的整数序列。
增量序列h:
希尔排序的执行时间依赖于增量序列,好的增量序列的共同特征:
1. 最后一个增量必须为1;
2. 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
排序过程:
程序代码:
public class ShellSort<T extends Comparable<T>> implements Sort<T> { //核心算法,增量序列 1 4 13 ....(3*h+1) public void sort(T[] a) { int N = a.length; int h = 1; while(h < N/3) h = 3*h + 1; while(h > 0 ) { for(int i=h; i<N; i++) { for(int j=i; j>=h && less(a[j], a[j-h]); j -= h) { swap(a, j, j-h); } } h = h/3; } } //比较两个元素大小 public boolean less(T a,T a2) { return a.compareTo(a2)<0; } //交换两个元素 public void swap(T[] a,int i,int j) { T temp = a[i]; a[i] = a[j]; a[j] = temp; } public boolean isSorted(T[] a) { int N = a.length; for(int i=1; i<N; i++) { if(less(a[i],a[i-1])) return false; } return true; } //测试 public static void main(String[] args) { Integer[] a = {3,8,2,5,9,1,4,6,0,7}; ShellSort<Integer> ss = new ShellSort<Integer>(); ss.sort(a); System.out.println(ss.isSorted(a)); }}
分析:
希尔排序的运行时间最坏情况达不到平方级别
希尔排序是不稳定的排序,代码量小,原地排序。
- 希尔排序优于插入排序,但没有快速排序快(快速排序最坏情况下除外)。
测试
代码如下:
public class Test { public static final int NUM = 10000; public static <T> double testSort(T[] data, Sort<T> sort) { StopWatch timer = new StopWatch(); sort.sort(data); return timer.elapsedTime(); } public static void main(String[] args) { Double[] data1 = new Double[NUM]; for(int i=0; i < NUM; i++) { data1[i] = Math.random(); } Double[] data2 = new Double[NUM]; for(int i=0; i < NUM; i++) { data2[i] = data1[i]; } Double[] data3 = new Double[NUM]; for(int i=0; i < NUM; i++) { data3[i] = data1[i]; } InsertionSort<Double> its = new InsertionSort<Double>(); SelectionSort<Double> sts = new SelectionSort<Double>(); ShellSort<Double> sls = new ShellSort<Double>(); double time1 = Test.testSort(data1,sts); double time2 = Test.testSort(data2,its); double time3 = Test.testSort(data3,sls); System.out.println("SelectionSort---->" + time1); System.out.println("InsertionSort---->" + time2); System.out.println("ShellSort---->" + time3); }}class StopWatch { public final long start; public StopWatch() { start = System.currentTimeMillis(); } public double elapsedTime() { long now = System.currentTimeMillis(); return (now-start)/1000.0; }}
结果如下:
希尔排序比插入排序和选择排序要快的多,并且数组越大,优势越大。
归并排序
排序思想:
归并排序是基于归并操作实现的,即将两个有序的数组归并成一个更大的有序数组。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序可分为
- 自顶向下的归并排序
- 自底向上的归并排序
自顶向下的归并排序
自顶向下的归并排序过程:
代码如下:
/** 1. 自顶向下的归并排序 */public class MergeSort<T extends Comparable<T>> implements Sort<T> { private T[] array; //辅助数组 public void sort(T[] a) { array = (T[]) new Comparable[a.length]; mergeSort(a, 0, a.length - 1); } //核心算法 public void mergeSort(T[] a, int down, int up) { if (up <= down) return; //结束条件 int mid = (up - down) / 2 + down; mergeSort(a, down, mid); //左半边排序 mergeSort(a, mid + 1, up); //右半边排序 merge(a, down, mid, up); } //一个数组左右半边分别有序,归并 public void merge(T[] a, int down, int mid, int up) { int i = down, j = mid + 1; //复制数组中元素 for (int k = down; k <= up; k++) { array[k] = a[k]; } for (int k = down; k <= up; k++) { if (i > mid) a[k] = array[j++]; //左半边用尽,取右半边元素 else if (j > up) a[k] = array[i++]; else if (less(array[i], array[j])) //左半边元素比右半边小 a[k] = array[i++]; else a[k] = array[j++]; } } //比较两个元素大小 public boolean less(T a, T a2) { return a.compareTo(a2) < 0; } public boolean isSorted(T[] a) { int N = a.length; for (int i = 1; i < N; i++) { if (less(a[i], a[i - 1])) return false; } return true; } //测试 public static void main(String[] args) { Double[] a = new Double[100]; for (int i = 0; i < 100; i++) { a[i] = Math.random(); } MergeSort<Double> ss = new MergeSort<Double>(); ss.sort(a); System.out.println(ss.isSorted(a)); }}
分析:
比较次数0.5*NlogN到NlogN,访问数组最多6NlogN
时间复杂度O(NlogN),空间复杂度O(N)
优点:所需时间短,速度快。 缺点:所需空间和数组大小成正比
自底向上的归并排序
排序思想:
先两两归并(把每个元素当成一个长度为1的数组),在四四归并,然后八八归并,一直下去。每轮归并中最后一次归并的第二个数组可能比第一个小(注意不要越界)。
代码如下:
public class MergeUpSort<T extends Comparable<T>> implements Sort<T> { private T[] array; //辅助数组 public void sort(T[] a) { array = (T[]) new Comparable[a.length]; mergeSort(a); } //核心算法 public void mergeSort(T[] a) { int N = a.length; for (int i = 1; i < N; i = 2 * i) { for (int j = 0; j < N - i; j += 2 * i) merge(a, j, j + i - 1, Math.min(j + 2 * i - 1, N - 1)); } } //一个数组左右半边分别有序,归并 public void merge(T[] a, int down, int mid, int up) { int i = down, j = mid + 1; //复制数组中元素 for (int k = down; k <= up; k++) { array[k] = a[k]; } for (int k = down; k <= up; k++) { if (i > mid) a[k] = array[j++]; //左半边用尽,取右半边元素 else if (j > up) a[k] = array[i++]; else if (less(array[i], array[j])) //左半边元素比右半边小 a[k] = array[i++]; else a[k] = array[j++]; } } //比较两个元素大小 public boolean less(T a, T a2) { return a.compareTo(a2) < 0; } public boolean isSorted(T[] a) { int N = a.length; for (int i = 1; i < N; i++) { if (less(a[i], a[i - 1])) return false; } return true; } //测试 public static void main(String[] args) { Double[] a = new Double[1050]; for (int i = 0; i < 1050; i++) { a[i] = Math.random(); } MergeUpSort<Double> ss = new MergeUpSort<Double>(); ss.sort(a); System.out.println(ss.isSorted(a)); }}
改进
对于自顶向下的归并排序:
- 归并排序在处理小规模问题时,由于方法的调用过于频繁,会产生过多的额外开销。插入排序在处理小数组上比归并排序更快。在用归并排序处理大规模数据时,,使用插入排序来处理小规模的子数组,一般可使归并排序的运行时间缩短10%-15%
- 添加一个判断,如果a[mid]小于a[mid+1],则数组已经有序,不需要进行归并操作。这样可大大减小有序子数组的运行时间。
- 通过在递归调用的每个层次交换输入数组和辅助数组的角色,节省元素复制到辅助数组中的时间(空间不行)。即在递归中,数据从输入数组排序到辅助数组和从辅助数组排序到输入数组交替使用。
改进后代码如下:
package com.gain.sort;/** * 改进自顶向下的归并排序 * 1. 对小规模数组使用插入排序 * 2. 加入数组是否有序的判断,减少归并次数 * 3. 通过在递归中交换参数,避免数组复制 * Created by gain on 2016/1/18. */public class MergeInsSort<T extends Comparable<T>> implements Sort<T> { public static final int CUTOFF = 10; //插入排序处理数组长度 private T[] array; public void sort(T[] a) { array = a.clone(); mergeSort(array, a, 0, a.length - 1); } //核心算法, 对dst进行排序 public void mergeSort(T[] src, T[] dst, int down, int up) { //if (up <= down) return; //结束条件 //改进,小规模用插入排序 if (up - down <= CUTOFF) { insertionSort(dst, down, up); return; } int mid = (up - down) / 2 + down; mergeSort(dst, src, down, mid); //左半边排序,交换输入数组和辅助数组角色 mergeSort(dst, src, mid + 1, up); //右半边排序,结果:src中有序 /*if(less(src[mid], src[mid + 1])) { for(int i=down; i<=up;i++) { dst[i] = src[i]; } return; }*/ //比上面循环更快 if(less(src[mid], src[mid + 1])) { System.arraycopy(src, down, dst, down, up-down+1); return; } merge(src, dst, down, mid, up); } //一个数组左右半边分别有序,src归并到dst public void merge(T[] src, T[] dst, int down, int mid, int up) { assert isSorted(src, down, mid); //断言,左右半边均有序 assert isSorted(src, mid+1,up); int i = down, j = mid + 1; for (int k = down; k <= up; k++) { if (i > mid) dst[k] = src[j++]; //左半边用尽,取右半边元素 else if (j > up) dst[k] = src[i++]; else if (less(src[i], src[j])) //左半边元素比右半边小 dst[k] = src[i++]; else dst[k] = src[j++]; } assert isSorted(dst, down, up); } public void insertionSort(T[] a, int down, int up) { for (int i = down+1; i <= up; i++) { for (int j = i; j >= down+1 && less(a[j], a[j-1]); j--) { swap(a, j, j-1); } } } /*******************************************************************************/ //交换两个元素 public void swap(T[] a,int i,int j) { T temp = a[i]; a[i] = a[j]; a[j] = temp; } //比较两个元素大小 public boolean less(T a, T a2) { return a.compareTo(a2) < 0; } public boolean isSorted(T[] a) { int N = a.length; for (int i = 1; i < N; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public boolean isSorted(T[] a, int down, int up) { for (int i = down+1; i <= up; i++) { if (less(a[i], a[i - 1])) return false; } return true; } //测试 public static void main(String[] args) { Double[] a = new Double[1500]; for (int i = 0; i < 1500; i++) { a[i] = Math.random(); } MergeInsSort<Double> mis = new MergeInsSort<Double>(); mis.sort(a); System.out.println(mis.isSorted(a)); System.out.println(mis.isSorted(mis.array)); }}
测试
代码如下:
public class Test { //测试数据个数 public static final int NUM = 1000000; public static <T> double testSort(T[] data, Sort<T> sort) { StopWatch timer = new StopWatch(); sort.sort(data); return timer.elapsedTime(); } public static void main(String[] args) { Double[] data1 = new Double[NUM]; for(int i=0; i < NUM; i++) { data1[i] = Math.random(); } Double[] data2 = data1.clone(); Double[] data3 = data1.clone(); Double[] data4 = data1.clone(); //InsertionSort<Double> its = new InsertionSort<Double>(); //SelectionSort<Double> sts = new SelectionSort<Double>(); ShellSort<Double> sls = new ShellSort<Double>(); MergeSort<Double> mgs = new MergeSort<Double>(); MergeInsSort<Double> mis = new MergeInsSort<Double>(); MergeUpSort<Double> mus = new MergeUpSort<Double>(); System.out.println("the number of data: " + Test.NUM); System.out.println("MergeInsSort---->" + Test.testSort(data1,mis)); System.out.println("MergeUpSort---->" + Test.testSort(data2,mus)); System.out.println("MergeSort---->" + Test.testSort(data3,mgs)); System.out.println("ShellSort---->" + Test.testSort(data4,sls)); }}class StopWatch { public final long start; public StopWatch() { start = System.currentTimeMillis(); } public double elapsedTime() { long now = System.currentTimeMillis(); return (now-start)/1000.0; }}
结果:
0 0
- 算法--排序(希尔,归并)
- 排序算法二(归并排序、快速排序、希尔排序)
- JavaScript排序算法(希尔排序、快速排序、归并排序)
- 排序算法:希尔、归并、快速、堆排序
- 排序算法:希尔、归并、快速、堆排序
- 排序算法,选择,插入,冒泡,希尔,归并
- 希尔、归并排序C++算法实现
- 各类排序算法实现(堆排序、希尔排序、快速排序、归并排序等)
- 经典排序算法(希尔排序,归并排序,快速排序,插入排序)
- Javascript数据结构算法之排序二(希尔排序,归并排序,快速排序)
- java排序算法二分法 希尔排序 快速排序 归并排序
- 经典排序算法--插入排序 希尔排序 归并排序
- 【算法分析】排序算法:希尔、归并、快速、堆排序
- 【算法分析】排序算法:希尔、归并、快速、堆排序
- 【算法分析】排序算法:希尔、归并、快速、堆排序
- 常见比较排序算法的实现(归并排序、快速排序、堆排序、选择排序、插入排序、希尔排序)
- 数据结构-排序算法详解(插入排序,希尔排序,堆排序,归并排序,快速排序,桶式排序)
- 数据结构6-排序算法(直接插入排序、希尔排序、快速排序、归并排序和堆排序)
- 算法与数据结构学习资源大搜罗——良心推荐
- PHP八大安全函数解析
- LD-LINUX.SO.2: BAD ELF INTERPRETER的解决办法【阿里云】
- Block块/Swift闭包介绍与使用
- Android开发-前端视图Activity
- 算法--排序(希尔,归并)
- 【第十二章】零配置 之 12.5 综合示例-积分商城 ——跟我学spring3
- 提高VBA程序效率的技巧
- Oracle 日常巡检
- 2016【太原网络营销师】郭文军讲解如何优化网站长尾关键词
- PHP 程序员解决问题能力的八个级别
- Android开发-前后端控制器intent
- 配置Oracle Gateway 12连接到SQL server 2014
- 动态分配内存