归并排序被列为基础排序算法的第二位,仅次于快排。主要思想是将数组分成两两一组,排序各个分组,然后再将已经排序好的分组看成一个元素,再两两分组并排序。
java代码:
/** * @Title: mergeSort * @Description: TODO(两路归并算法) * @param @param is * @param @param left * @param @param right 设定文件 * @return void 返回类型 */ private void mergeSort(int[] is, int left, int right) { if (left < right) { int middle = (left + right) / 2; // 对左边进行递归 mergeSort(is, left, middle); // 对you边进行递归 mergeSort(is, middle + 1, right); // 合并 merge(is, left, middle, right); } }
合并算法:
private void merge(int[] is, int left, int middle, int right) { // 右边的起始位置 int mid = middle + 1; int tem = left; int temp = left; // 循环遍历,从两个数组中找出较小的放入中间数组 while (left <= middle && mid <= right) { if (is[left] <= is[mid]) { tempArr[temp++] = is[left++]; } else { tempArr[temp++] = is[mid++]; } } // 将剩余数组里面的数放入临时数组 while (left <= middle) { tempArr[temp++] = is[left++]; } while (mid <= right) { tempArr[temp++] = is[mid++]; } // 将临时数组拷贝回原数组 while (tem <= right) { is[tem] = tempArr[tem++]; } // System.arraycopy(tempArr, tem, is, tem, (right - tem + 1)); }
排序1000000个乱序数组,归并排序耗时176毫秒,比快排序慢,优于希尔排序。
做完两路归并排序,做一下三路归并排序,代码如下:
/** * @Title: mergeSort3 * @Description: TODO(三路归并) * @param @param is * @param @param left * @param @param right 设定文件 * @return void 返回类型 */ private void mergeSort3(int[] is, int left, int right) { if (left < right) { if(left < right - 2){ int len = (right + 1 - left) / 3; int leftMid = left + len; int rightMid = left + len + len; // System.out.println(left+"\t"+right+"\t"+leftMid+"\t"+rightMid); // 对第一段进行递归 mergeSort3(is, left, leftMid); // 对第二段进行递归 mergeSort3(is, leftMid + 1, rightMid); // 对第三段进行递归 mergeSort3(is, rightMid + 1, right); // 合并 merge(is, left, leftMid, rightMid, right); }else if(left < right){ int middle = (left + right) / 2; // 对左边进行递归 mergeSort(is, left, middle); // 对you边进行递归 mergeSort(is, middle + 1, right); // 合并 merge(is, left, middle, right); } } }
private void merge(int[] is, int left, int leftMid, int rightMid, int right) { int[] tempArr = new int[MAX]; // 第二个数组的起始位置 int mid = leftMid + 1; int mid2 = rightMid + 1; int tem = left; int temp = left; // 循环遍历,从三个数组中找出较小的放入中间数组 while (left <= leftMid && mid <= rightMid && mid2 <= right) { if (is[left] <= is[mid] && is[left] <= is[mid2]) { tempArr[temp++] = is[left++]; } else if (is[mid] <= is[mid2]) { tempArr[temp++] = is[mid++]; } else { tempArr[temp++] = is[mid2++]; } } /** * 本别考虑剩下的几种情况(剩下两个分数组,剩下哪段都可能) */ // 这是剩下第一段和第二段的情况 while (left <= leftMid && mid <= rightMid) { if (is[left] <= is[mid]) { tempArr[temp++] = is[left++]; } else { tempArr[temp++] = is[mid++]; } } // 这是剩下第三段和第二段的情况 while (mid <= rightMid && mid2 <= right) { if (is[mid] <= is[mid2]) { tempArr[temp++] = is[mid++]; } else { tempArr[temp++] = is[mid2++]; } } // 这是剩下第一段和第三段的情况 while (left <= leftMid && mid2 <= right) { if (is[left] <= is[mid2]) { tempArr[temp++] = is[left++]; } else { tempArr[temp++] = is[mid2++]; } } // 将剩余数组里面的数放入临时数组 // 最后剩下第一段 while (left <= leftMid) { tempArr[temp++] = is[left++]; } // 最后剩下第二段 while (mid <= rightMid) { tempArr[temp++] = is[mid++]; } // 最后剩下第三段 while (mid2 <= right) { tempArr[temp++] = is[mid2++]; } // 将临时数组拷贝回原数组 while (tem <= right) { is[tem] = tempArr[tem++]; } // System.out.println(tem+""+right); // System.arraycopy(tempArr, tem, is, tem, (right - tem + 1)); }
3路归并的情况下,我知道是不比二路归并快的,因为虽然合并的次数减少了,但是明显比较和移动的次数增加很多。代码检查是没发现问题的,但是3路归并排序1000000个乱序数组,耗时高达100秒,是两路归并的500倍。。。 肯定是我代码有问题,希望有大神可以赐教。
另外实现一下两路归并的两个线程,发现在100万数据的情况下,两线程和单线程不相上下,说明耗时短的情况下,线程开销与多线程的时间减少不相上下,但是在10000000万数据的排序中,两个线程明显比单线程要占上风。 告诉我们在数据量多的情况下,能用多线程还是用多线程吧o(∩_∩)o
代码:
/** * @Title: mergeSort * @Description: TODO(两路归并算法(多线程实现)) * @param @param is * @param @param left * @param @param right 设定文件 * @return void 返回类型 */ private void mergeSortThread(int[] is, int left, int right) { if (left < right) { int middle = (left + right) / 2; // 对左边进行递归 merageThread t1 = new merageThread(left, middle); // mergeSort(is, left, middle); // 对右边进行递归 merageThread t2 = new merageThread(middle + 1, right); t1.start(); t2.start(); /** * 因为合并要用到上面归并的结果,所以合并要等到归并结束之后才能进行 */ try { t1.join(); t2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // mergeSort(is, middle + 1, right); // 合并 merge(is, left, middle, right); } }
/** * @ClassName: merageThread 归并排序进程 * @Description: TODO(这里用一句话描述这个类的作用) * @author xuejupo [email protected] * @date 2015-11-9 下午6:37:17 * */ static class merageThread extends Thread { private int left; private int right; public merageThread(int left, int right) { this.left = left; this.right = right; } public void run() { mergeSort(is, left, right); } /** * @Title: mergeSort * @Description: TODO(两路归并算法) * @param @param is * @param @param left * @param @param right 设定文件 * @return void 返回类型 */ private void mergeSort(int[] is, int left, int right) { if (left < right) { int middle = (left + right) / 2; // 对左边进行递归 mergeSort(is, left, middle); // 对右边进行递归 mergeSort(is, middle + 1, right); // 合并 merge(is, left, middle, right); } } private void merge(int[] is, int left, int middle, int right) { // 右边的起始位置 int mid = middle + 1; int tem = left; int temp = left; // 循环遍历,从两个数组中找出较小的放入中间数组 while (left <= middle && mid <= right) { if (is[left] <= is[mid]) { tempArr[temp++] = is[left++]; } else { tempArr[temp++] = is[mid++]; } } // 将剩余数组里面的数放入临时数组 while (left <= middle) { tempArr[temp++] = is[left++]; } while (mid <= right) { tempArr[temp++] = is[mid++]; } // 将临时数组拷贝回原数组 while (tem <= right) { is[tem] = tempArr[tem++]; } // System.arraycopy(tempArr, tem, is, tem, (right - tem + 1)); } } /** * @ClassName: merageThread 归并排序多线程算法 * @Description: TODO(这里用一句话描述这个类的作用) * @author xuejupo [email protected] * @date 2015-11-9 下午6:37:17 * */ static class merageThread2 extends Thread { private int left; private int right; public merageThread2(int left, int right) { this.left = left; this.right = right; } public void run() { mergeSort(is, left, right); } /** * @Title: mergeSort * @Description: TODO(两路归并算法) * @param @param is * @param @param left * @param @param right 设定文件 * @return void 返回类型 */ private void mergeSort(int[] is, int left, int right) { if (left < right) { int middle = (left + right) / 2; // 对左边进行递归 merageThread2 t1 = new merageThread2(left, middle); merageThread2 t2 = new merageThread2(middle + 1, right); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 合并 merge(is, left, middle, right); } } private void merge(int[] is, int left, int middle, int right) { // 右边的起始位置 int mid = middle + 1; int tem = left; int temp = left; // 循环遍历,从两个数组中找出较小的放入中间数组 while (left <= middle && mid <= right) { if (is[left] <= is[mid]) { tempArr[temp++] = is[left++]; } else { tempArr[temp++] = is[mid++]; } } // 将剩余数组里面的数放入临时数组 while (left <= middle) { tempArr[temp++] = is[left++]; } while (mid <= right) { tempArr[temp++] = is[mid++]; } // 将临时数组拷贝回原数组 while (tem <= right) { is[tem] = tempArr[tem++]; } // System.arraycopy(tempArr, tem, is, tem, (right - tem + 1)); } }
100万数据的排序结果:
线程执行了129毫秒 普通程序执行了153毫秒
这个结果不是一定的,有时候多线程方法耗时反而多余普通的排序方法。
但是当1000万数据时,两个线程耗时永远低于单线程。
线程执行了1175毫秒 普通程序执行了1510毫秒