归并排序
归并排序比 冒泡排序、插入排序和选择排序要用O(N2)时间快,归并排序只要O(N*logN)。
归并排序的一个缺点是它需要在存储器中有另一个大小等于被排序的数据项数目的数组。如果初始数组几乎占满整个存储器,那么归并排序将不能工作。但是,如果有足够的空间,归并排序会是一个很好的选择。
归并两个有序数组
归并算法的中心是归并两个已经有序的数组。归并两个有序数组A和B,就生成了第三个数组C,数组C包含数组A和B的所有数据项,并且使它们有序的排列在数组C中。首先考察归并的过程;然后看它是如何在排序中使用的。
假设有两个有序数组,不要求有相同的大小。设数组A有4个数据项,数组B有6个数据项。它们要被归并到数组C中,开始时数组C有10 个空的存储空间。如图显示了这些数组。
归并操作
步骤 | 比较 | 复制 |
1 | 比较23和7 | 复制7从B到C |
2 | 比较23和14 | 复制14从B到C |
3 | 比较23和39 | 复制23从A到C |
4 | 比较39和47 | 复制39从B到C |
5 | 比较55和47 | 复制47从A到C |
6 | 比较55和81 | 复制55从B到C |
7 | 比较62和81 | 复制62从B到C |
8 | 比较74和81 | 复制74从B到C |
9 | 复制81从A到C | |
10 | 复制95从A到C |
注意:数组B从第8步开始就没有数据,我们就不需要比较他了,只需要把A数组中剩下的数据复制进C数组就可以了
java实现代码(归并的原理)
package com.cym.sort.merge_sort;
/**
* 归并排序
* 归并排序的原理,合并两个有序的数组
*/
public class MergeSort {
/**
* @param a 待合并数组1
* @param b 待合并数组2
* @return 返回a 和b合并后的数组 c
*/
public static int[] mergeSort(int[] a, int[] b) {
int[] c = new int[a.length + b.length];//定义a数组和b数组合并后的数组c,数组的长度是两个待合并的数组的长度总和
int ai = 0;//定义a数组的下标,从0开始
int bi = 0;//定义b数组的下标,从0开始
int ci = 0;//定义c数组的下标,从0开始
while (ai < a.length && bi < b.length) {//遍历a数组和b数组,直到其中一个数组结束为止
if (a[ai] < b[bi]) {
//比较a数组和b数组的ai和bi位置的值,小的就插入c数组ci的位置,插入后同时要把a数组和c数组的索引下标往后移动一位
c[ci++] = a[ai++];
} else {
//比较a数组和b数组的ai和bi位置的值,小的就插入c数组ci的位置,插入后同时要把b数组和c数组的索引下标往后移动一位
c[ci++] = b[bi++];
}
}
//从循环中出来的时候,至少有一个数组里面的全部数据已经添加到合并的数组里面了,
// 剩下的我们只要把另一个还没有遍历完的数组剩下的数据依次添加合并的数组就可以了
while (ai < a.length) {
c[ci++] = a[ai++];
}
while (bi < b.length) {
c[ci++] = b[bi++];
}
return c;
}
public static void printArray(int[] array) {
for (int arr : array) {
System.out.print(arr + "\t");
}
System.out.println();
}
public static void main(String[] args) {
int[] array = {1, 4, 6, 8, 9, 12, 34};
int[] array1 = {2, 5, 7, 8, 15, 78};
System.out.println("归并前两个数组:");
printArray(array);
printArray(array1);
int[] result = mergeSort(array, array1);
System.out.println("归并后:");
printArray(result);
}
}
输出:
归并前两个数组:
1 4 6 8 9 12 34
2 5 7 8 15 78
归并后:
1 2 4 5 6 7 8 8 9 12 15 34 78
Java实现一个数组的归并排序
package com.cym.sort.merge_sort;
public class MergeSortApp {
/**
* 利用递归的方式,对数组进行归并
* 划分数组如{1,2,3,4,5}-->...-->{1},{2},{3},{4},{5}
* 把数组划分为一个值,这样子肯定都是有序的,剩下我们再把这些划分好的数组有序合并到新的数组就可以了
*
* @param a
* @param leftIndex
* @param rightIndex
*/
public static void mergeSort(int[] a, int leftIndex, int rightIndex) {
if (leftIndex >= rightIndex) {//当划分的数组只有只有一个长度的跳出递归
return;
}
int midIndex = (leftIndex + rightIndex) / 2;//获得归并数组的中间索引
mergeSort(a, leftIndex, midIndex);//对a数组的左半部的子数组进行递归划分,如a{1,2,3,4,5}-->{1,2,3}-->{1,2}-->{1}
mergeSort(a, midIndex + 1, rightIndex);//同样的道理对a数组的右半部数组进行递归划分
merger(a, leftIndex, midIndex, rightIndex);//对每一个划分好数组进行合并排序
}
/**
* 相当于合并两个数组,一个数组是[leftIndex,midIndex],另一个是[midIndex+1,rightIndex]
*
* @param a 待合并的数组(子序列数组)
* @param leftIndex 数组的开始索引
* @param midIndex 数组的中间索引
* @param rightIndex 数组的结束索引
*/
private static void merger(int[] a, int leftIndex, int midIndex, int rightIndex) {
int[] temp = new int[a.length];//定义临时数组,用于存储合并后有序的数组
int ai = leftIndex; //定义左边的数组的开始索引
int bi = midIndex + 1;//定义右边的数组的开始索引
int ci = leftIndex;//定义临时数组的开始索引
while (ai <= midIndex && bi <= rightIndex) {//遍历左边数组和右边数组,直到其中一个数组结束为止,注意传进来的是索引,所以条件是<=
if (a[ai] < a[bi]) {
//比较左边数组和右边数组的ai和bi位置的值,小的就插入temp数组ci的位置,插入后同时要把左边数组和临时数组的索引下标往后移动一位
temp[ci++] = a[ai++];
} else {
//比较左边数组和右边数组的ai和bi位置的值,小的就插入temp数组ci的位置,插入后同时要把临时数组和右边数组的索引下标往后移动一位
temp[ci++] = a[bi++];
}
}
//从循环中出来的时候,至少有一个数组里面的全部数据已经添加到合并的数组里面了,
// 剩下的我们只要把另一个还没有遍历完的数组剩下的数据依次添加合并的数组就可以了
while (ai <= midIndex) {//注意传进来的是索引,所以条件是<=
temp[ci++] = a[ai++];
}
while (bi <= rightIndex) {//注意传进来的是索引,所以条件是<=
temp[ci++] = a[bi++];
}
//把临时数组拷贝会原数组a(注意,拷贝数组的时候一定不能是for (int i = 0; i < arr.length; i++) 因为每次拷贝一定从0开始)
for (int i = leftIndex; i <= rightIndex; i++) {
a[i] = temp[i];
}
}
/**
* 遍历数组的方法
*
* @param arr
*/
private static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
public static void main(String[] args) {
int[] arr = {1, 4, 6, 95, 43, 2, 34, 43, 23, 65};
//int[] arr = {95, 43, 2};
System.out.println("归并排序前:");
printArray(arr);
mergeSort(arr, 0, arr.length - 1);
System.out.println();
System.out.println("归并排序后:");
printArray(arr);
}
}
输出:
归并排序前:
1 4 6 95 43 2 34 43 23 65
归并排序后:
1 2 4 6 23 34 43 43 65 95
希尔排序
思路分析:
希尔排序通过加人插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能大跨度地移动。当这些数据项排过一趟序后,希尔排序算法减小数据项的间隔再进行排序,依此进行下去。进行这些排序时数据项之间的间隔被称为增量,并且习惯上用字母h来表示。如图显示了增量为4时对包含10个数据项的数组进行排序的第一个步骤的情况。在0、4和8号位置.上的数据项已经有序了。
当对0、4和8号数据项完成排序之后,算法向右移一步,对1、5和9号数据项进行排序。这个排序过程持续进行,直到所有的数据项都已经完成了4-增量排序,也就是说所有间隔为4的数据项之间都已经排列有序。这个过程如下图所示(使用更为简洁形象的图例表示)。
在完成以4为增量的希尔排序之后,数组可以看成是由4个子数组组成: (0, 4, 8), (1,5,9), (2,6)和(3,7),这四个子数组内分别是完全有序的。这些子数组相互交错着排列,然而彼此独立。
knuth间隔序列的计算
java实现代码
package com.cym.sort.shell_sort;
/**
* 希尔排序
*/
public class ShellSort {
public static void shellSort(int[] array) {
int inner, outer;//定义内外层循环的的变量,便于循环的操作
int temp; //定义临时变量,用于数据的交换
int h = 1; //定义希尔排序的knuth的间隔步长
while (h < array.length / 3) {
h = h * 3 + 1;//通过knuth间隔的计算公式,获得该数组的使用的间隔步长
}
while (h > 0) {
//外层循环用于遍历步长以后的数据如{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}就遍历{6,5,4,3,2,1}索引出处的数据
for (outer = h; outer < array.length; outer++) {
temp = array[outer]; //存放outer索引处的值,用于交换数据
inner = outer;//把外层循环的变量赋给内层循环
//inner > h - 1是为了防止array[inner-h]数组越界,当符合间隔的数值前面的比后面的大就交换数据
while (inner > h - 1 && array[inner - h] >= temp) {
array[inner] = array[inner - h];//交换数据
inner -= h;//把inner改为inner-h的索引,如上面的数组 4-4 =0;
}
array[inner] = temp;//交换array[i]和array[i+h],两个符合步长的数据
}
h = (h - 1) / 3;//改变步长,遍历新生成的数组
}
}
public static void printArray(int[] array) {
for (int arr : array) {
System.out.print(arr + "\t");
}
System.out.println();
}
public static void main(String[] args) {
//int [] array = {11,10,3,43,12,64,21,3,6,9};
int[] array = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
System.out.println("排序前:");
printArray(array);
shellSort(array);
System.out.println("排序后:");
printArray(array);
}
}
输出:
排序前:
10 9 8 7 6 5 4 3 2 1
排序后:
1 2 3 4 5 6 7 8 9 10
快速排序
快速排序的原理:选择一个关键值作为基准值。比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。一般选择序列的第一个元素或者最后一个。
快速排序的基本思想:挖坑填数+分治法
- 从序列当中选择一个基准数(pivot)
在这里我们选择序列当中第一个数最为基准数 - 将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧(通过左边比基准数大和右边比基准数小的两个数交换实现)
- 重复步骤1.2,直到所有子集当中只有一个元素为止(通过递归调用)。
Java代码实现:
package com.cym.sort.quicksort;
public class QuickSort {
public static void quickSort(int[] array, int leftIndex, int rightIndx) {
if (rightIndx - leftIndex <= 0) {//当划分的数组长度只有1的时候跳出递归
return;
}
//选择基准元素的方法
int partition = getPartition(array, leftIndex, rightIndx);
quickSort(array, leftIndex, partition - 1);
quickSort(array, partition + 1, rightIndx);
}
private static int getPartition(int[] array, int leftIndex, int rightIndx) {
//选择数组的最后一位作为基准数;
int pivor = array[rightIndx];
//定义开始的索引,-1是为了使array[++startIndex]从数组的第一个数值开始,即所以0开始
int startIndex = leftIndex - 1;
//定义结束索引;
int endIndex = rightIndx;
while (true) {//当endIndex <= startIndex;跳出循环
//下面的语句其实就是为了把小于选择的基准数的值全部放在左边,大于的放在右边,最后把基准数插入他们之间,就
//实现了基准数左边的都是小于基准数的,右边的都是大于基准数的
while (array[++startIndex] < pivor) ;//让数组的索引往下移动,如果array[++startIndex] < pivor,就继续执行
while (endIndex > 0 && array[--endIndex] > pivor) ;//让数组的索引往下移动,如果array[--endIndex] > pivor,就继续执行
if (startIndex >= endIndex){
break;//结束条件
}else {
swap(array,startIndex,endIndex);//把左边大于选择基准数的值和右边小于基准数的值交换
}
}
//当跳出上面循环了,就是开始索引和结束索引相等了,那么开始索引和结束索引对应的值肯定会比会比选择基准数要大
//索引把改索引处的值和选择的基准数交换就是可以实现基准数左边的都是小于基准数的,右边的都是大于基准数了
swap(array,startIndex,rightIndx);
return startIndex;//把交换了基准数的索引值返回去
}
/**
* 交换数据的方法
*
* @param array
* @param leftIndex
* @param rightIndx
*/
private static void swap(int[] array, int leftIndex, int rightIndx) {
int temp = array[leftIndex];
array[leftIndex] = array[rightIndx];
array[rightIndx] = temp;
}
public static void printArray(int[] array) {
for (int arr : array) {
System.out.print(arr + "\t");
}
System.out.println();
}
public static void main(String[] args) {
int[] array = {11, 10, 3, 43, 12, 64, 21, 3, 6, 9};
// int[] array = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
System.out.println("排序前:");
printArray(array);
quickSort(array, 0, array.length - 1);
System.out.println("排序后:");
printArray(array);
}
}
输出:
排序前:
11 10 3 43 12 64 21 3 6 9
排序后:
3 3 6 9 10 11 12 21 43 64
二分查找:适用于已经排好序的数组
思路:
把数组从中间分成两部分,然后看要查找的数据项在数组的哪一部分,再次折半,如此下去直到找到符合的数据项,如果没有找到就返回-1(这个返回随意)
package com.cym.sort.binary_sort;
import com.cym.sort.merge_sort.MergeSortApp;
/**
* 二分查找,适用于排好序的数组
*/
public class BinarySearch {
/**
* 二分查找的方法
* @param array 查找的数组
* @param target 要找的数据
* @return 返回要查找数据的索引
*/
public static int binarySort(int [] array , int target){
int leftIndex = 0 ; //定义开始索引
int rightIndex = array.length -1 ; //定义结束索引
while (leftIndex <= rightIndex){//当结束索引< 开始索引跳出循环
int midIndex = leftIndex +(rightIndex - leftIndex)/2;//获得中间索引,midIndex = (leftIndex + rightIndex)/2也可以
if (array[midIndex] < target){//表示目标数据位于数组的右半部分
leftIndex = midIndex + 1;//去掉数组的中间处和左半部分
}else if (array[midIndex] > target){//表示目标数据位于数组的左半部分
rightIndex = midIndex - 1;//去掉数组的中间处和右半部分
}else {//找到中间值了
return midIndex;
}
}
return -1;
}
public static void main(String[] args) {
int [] array = {11,10,3,43,12,64,21,13,6,9};
MergeSortApp.mergeSort(array,0,array.length-1);//调用写好的归并排序
int index = binarySort(array, 32);
if (index != -1)
System.out.println("要查询的"+array[index] + " 位于数组的第"+(index+1)+"位");
else
System.out.println("要查询的数不存在");
}
}