java7种常见的排序算法:选择排序、冒泡排序、直接插入排序、快速排序、希尔排序、归并排序、堆排序

发布于:2021-11-29 23:31:07

链接:java 7 种算法的完整实例及测试


一、选择排序


/**
* 选择排序原理:挨个比较
* 外层 循环长度-1次,内层循环每次从第二个开始
* 将外层循环中的值挨个与内层循环中的元素作比较
* 时间复杂度为:O(N^2)
*
* @param array
* @return
*/
public static int[] selectSort(int[] array) {
//根据原理分析,使用两层循环即可实现
for (int i = 0; i < array.length - 1; i++) {//第一层
for (int j = i + 1; j < array.length; j++) { //第二层
if (array[i] > array[j]) { //数据互换。将小的放前面
int t = array[i];
array[i] = array[j];
array[j] = t;
}
}
}
return array;
}

二、冒泡排序


/**
* 冒泡排序原理:相邻的两个元素之间的比较
* 时间复杂度为:O(N^2)
*
* @param array
* @return
*/
public static int[] bubbleSort(int[] array) {
//根据原理分析,使用两层循环即可实现
for (int i = 0; i < array.length - 1; i++) {//外层循环长度-1
for (int j = 0; j < array.length - 1 - i; j++) { //内层循环环长度-1-i
if (array[j] > array[j + 1]) { //数据互换。将小的放前面
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
}

三、直接插入排序


/**
* 直接插入排序原理:
* 给定的一组记录,将其分为两个序列组,一个为有序序列(按照顺序从小到大或者从大到小),
* 一个为无序序列,初始时,将记录中的第一个数当成有序序列组中的一个数据,
* 剩下其他所有数都当做是无序序列组中的数据。然后从无序序列组中的数据中(也就是从记录中的第二个数据开始)
* 依次与有序序列中的记录进行比较,然后插入到有序序列组中合适的位置,
* 直到无序序列组中的最后一个数据插入到有序序列组中为止。
* [21], 2, 33, 4, 55, 6, 77
* 无序数组中的第一个元素同有序数组中的最后一个元素(即最大值,也即无序数组第一个元素的前一个元素)比较,
* 比最大值还大,不执行操作,比最大值小则继续比较
* 时间复杂度为:O(N^2)
*
* @param array
* @return
*/
public static int[] insertSort(int[] array) {
//temp:记录较小值,将j提取到for循环外
int j, temp;
//循环无序数组
for (int i = 1; i < array.length - 1; i++) {//[2, 33, 4, 55, 6, 77]
if (array[i - 1] > array[i]) {//比最大值还大,不执行操作,比最大值小则继续比较
temp = array[i];//temp:记录较小值
//倒序循环有序数组
for (j = i - 1; j >= 0 && array[j] > temp; j--) {//[21]
array[j + 1] = array[j];//将大的值往后移动
}
//内层循环结束,因为j--的原因,j 有可能为-1
array[j + 1] = temp;
}
System.out.println("循环第" + i + "次:" + Arrays.toString(array));
}
return array;
}

四、快速排序

/**
* 快速排序是算法中最快的算法,时间复杂度为:O(N*logN)
* 但最差的情况,运行时间也会很慢
* 快速排序的原理:
* 一:划分方法
* 使用左右扫描指针同时从左右两端进行扫描,若满足条件,一个++,一个--
* 当连个子循环都停下来时交换数据,当两个指针相遇时,程序结束
* 当外层循环结束时,把最后一个元素(枢纽)放到指定位置(leftPar处),
* 因为此时两个指针相遇leftPar=rightPar,也即交换两元素位置swap(array,leftPar,right);
* 二:划分方法结束后返回枢纽下标,也就是分界线的下标 ,然后递归调用枢纽的左边和右边
*
* @param array 数组
* @param left 起始点
* @param right 结束点
* @return
*/
public static int[] quickSort(int[] array, int left, int right) {
// int left = 0;
// int right = array.length - 1;
if (left < right) {
int pivot = array[right];//使用数组最后一个元素作为枢纽
//这一步是分成左右两组:一组大,一组小,但是两组都无序
int partition = partitionIn(array, left, right, pivot);
//递归调用,使小的一组排好序
quickSort(array, left, partition - 1);//递归 枢纽的左边
//递归调用,使大的一组排好序
quickSort(array, partition + 1, right);//递归枢纽的右边
}
return array;
}

/**
* 划分方法
*
* @param array 数组
* @param left 起始点
* @param right 结束点
* @param pivot 枢纽(默认为数组最后一个元素)
* @return
*/
private static int partitionIn(int[] array, int left, int right, int pivot) {
int leftPar = left - 1;//左扫描指针
int rightPar = right;//右扫描指针,排除数组最后一个元素
while (true) {
while (array[++leftPar] < pivot) ;//因为使用数组最后一个元素作为枢纽,不用担心下标越界
while (rightPar > 0 && array[--rightPar] > pivot) ;//使用rightPar>0,防止下标越界
if (leftPar >= rightPar)//当两个扫描指针相遇,程序结束
break;
else {
swap(array, leftPar, rightPar);//否则交换
}
}
swap(array, leftPar, right);
return leftPar;
}

/**
* 交换两个元素
*
* @param one
* @param another
*/
private static void swap(int[] array, int one, int another) {
int temp = array[one];
array[one] = array[another];
array[another] = temp;
}

五、希尔排序


/**
* 希尔排序顾名思义就是因为计算机科学家Donald L.Shell而得名
* 他在1959年创造了该算法,希尔排序是基于插入排序的,
* 但是增加了一个新特性 (n减增量),不断减少增量的间隔,最后使得增量为1
* 大大提高了插入排序的执行效率,希尔排序比其它简单排序算法要快,它在最坏的情况下,执行
* 效率也不会和*均效率相差很多,但是像快速排序虽然执行效率高,但最坏的时候会效率会很低
* 理解:所谓创造就是头脑中原有的知识的重新排列组合,所以学得越多,你创造的可能性就越大!
* 时间复杂度为O(N*(logN)^2)
*


* 希尔排序的原理:
* 在插入排序的基础上通过增量不断进行分组、插入排序的过程
*
* @param array
*/
public static int[] shellSort(int[] array) {
// Donald Ervin Knuth 唐纳德?欧文?克努斯(Knuth发音为/knu?θ/)
int inner/*内部循环变量*/, outer/*外部循环变量*/, temp, h = 1;/*Knuth增量*/
//增量的选择:使用Knuth增量间隔序列 h = h * 3 + 1;
while (h <= array.length / 3) {
h = h * 3 + 1;
}
while (h > 0) {
for (outer = h; outer < array.length; outer++) {//outer会变化
inner = outer;
temp = array[outer];
for (; inner - h >= 0 && array[inner - h] >= temp; inner -= h) {//inner会变化
array[inner] = array[inner - h];//要交换的两个元素相差一个增量
}
//inner 已经变化
array[inner] = temp;
}
//增量倒推公式: h = (h - 1) / 3;
h = (h - 1) / 3;
}
return array;
}

六、归并排序


/**
* 归并排序:将两个有序子数组归并为一个大数组
* 归并排序的原理:
* 将一个无序的数组不断的二分成若干个子数组,通过递归的方式
* 使得两两子数组有序且归并,最终使得整个数组有序
*
* @param array
* @return
*/
public static int[] mergeSort(int[] array) {
int[] workSpace = new int[array.length];//创建一个与原数组相同大小的新数组
recMergeSort(array, workSpace, 0, array.length - 1);
return array;
}

/**
* rec 表示recursion 递归
*
* @param array 原数组
* @param workSpace 工作数组
* @param lowerBound 下限
* @param upperBound 上限
*/
private static void recMergeSort(int[] array, int[] workSpace, int lowerBound, int upperBound) {
if (lowerBound == upperBound)
return;
else {
int mid = (lowerBound + upperBound) >> 1;// 左移1相当于除以2
recMergeSort(array, workSpace, lowerBound, mid);//归并数组左边部分,使之变为有序
recMergeSort(array, workSpace, mid + 1, upperBound);//归并数组右边部分,使之变为有序
//归并两个有序数组
merge(array, workSpace, lowerBound, mid + 1, upperBound);
}
}

/**
* 归并数组的方法
* 将一个数组分为两个子数组
* 并构造出四限(左上限,右上限,右下限,右上限)
*
* @param array 原数组
* @param workSpace 工作数组
* @param lowPtr 左边子数组下限
* @param highPtr 右边子数组下限(highPtr-1左边子数组上限)
* @param upperBound 右边子数组上限
*/
private static void merge(int[] array, int[] workSpace, int lowPtr, int highPtr, int upperBound) {
int index = 0;//workSpace 的下标
int lowerBound = lowPtr;
int mid = highPtr - 1;
int n = upperBound - lowerBound + 1;
//将数组划分为两个子数组,以mid 为分界线 workSpace[index]记录比较中较小的一个
while (lowPtr <= mid && highPtr <= upperBound) {
workSpace[index++] = (array[lowPtr] < array[highPtr]) ?
array[lowPtr++] : array[highPtr++];
}
//第一个循环结束,如果lowPtr还是 <=mid,复制剩下的元素到workSpace中
while (lowPtr <= mid)
workSpace[index++] = array[lowPtr++];
//第二个循环结束,如果highPtr还是 <=upperBound,复制剩下的元素到workSpace中
while (highPtr <= upperBound)
workSpace[index++] = array[highPtr++];
//将workSpace中的元素复制回原数组
for (index = 0; index < n; index++) {
array[lowerBound + index] = workSpace[index];
}
System.out.println("第" + (i++) + "趟递归结果:" + Arrays.toString(array));
}

七、堆排序





图解


/**
*堆排序:在完全二叉树的基础上,利用大顶堆或小顶堆来完成的排序
* 要知道什么是堆这种数据结构:区别于java和C++中的堆
* 堆是一颗完全二叉树。分为大顶堆和小顶堆,
* 大顶堆,每个父结点都比自己的子节点大,也就是根结点为最大
* 小顶堆,每个父结点都比自己的子节点小,也就是根结点最小。
* 按照大顶堆和小顶堆这种特点,将一个无序的n个记录的序列构建成大顶堆,将根节点上的数与最后一个
* 结点n进行交换,然后在对n-1个记录进行构建大顶堆,继续把根节点与最后一个结点(n-1)互换,继续上面的操作。
* 从小到大排序,则使用大顶堆
* 从大到小排序,则使用小顶堆
*
* 从小到大
* 通过讲解原理:堆排序分为三步
*   1、构建大顶堆或小顶堆
*   2、循环
  *       根节点和末尾结点进行互换,
   *       构建大顶堆或小顶堆 
*   3、排序完成
*/
public static int[] heapSort(int[] array) {
//第一步:将数组构建成一个大顶堆
int length = array.length;
//找到完全二叉树中的最后一个拥有子结点父结点的位置length/2,
// 也就是最后一个父节点是在完全二叉树的第length/2的位置上,
// 但是在数组中的位置是 (length/2)-1,它代表父节点在数组中的位置
//依次遍历每一个父节点,比如最后一个父节点的值是33,那么它前面所有结点都是父节点,都需要进行构建
for (int i = length / 2 - 1; i >= 0; i--) {
//无序序列,所以需要从下往上全部进行构建。该方法做的事情就是,比较找到父节点,
// 和两个子节点中最大的数放置到父节点的位置上。
adjustMaxHeap(array, i, length);
System.out.println("array = " + Arrays.toString(array));
}

//第二步:构建好了大顶堆后,将第一个数与最后一个进行互换,互换后继续调整大顶堆,
for (int maxIndex = length - 1; maxIndex > 0; maxIndex--) {
//互换数据,提取出来了。互换数据后,就已经不在是大顶堆了,需要重新进行构建
swap(array, 0, maxIndex);
System.out.println(" after swap array = " + Arrays.toString(array));
//从上往下,因为基本上都已经有序了,没必要在从下往上重新进行构建堆了,
// 这就利用了前面比较的结果,减少了很多次比较。
adjustMaxHeap(array, 0, maxIndex);
System.out.println(" after adjust array = " + Arrays.toString(array));
}
return array;
}


/**
* 构建大顶堆的操作,
* 父节点和其左右子节点的大小比较和互换,每次将父结点的位置和数组传进来,
* 就能构建出大顶堆了。
*
* @param array 排序数组
* @param pIndex 当前所指父节点在数组中位置(下标)
* @param length 数组的长度。用来判断父节点的两个子节点是否存在。
* 父节点和左子结点的关系:2pIndex+1
* 父结点和右子结点的关系: 2pIndex+2
*/
private static void adjustMaxHeap(int[] array, int pIndex, int length) {
int temp = array[pIndex];//临时记录父节点的值
int child; //记录较大的子节点的下标
//根据逻辑关系,父节点的下标为pIndex,则左子节点的下标为2pIndex+1
//child <= length-1 说明肯定有子节点,如果child=length-1,说明只有左结点
for (child = pIndex * 2 + 1; child <= length - 1; child = child * 2 + 1) {
//child if (child < length - 1 && array[child] < array[child + 1]) {
//变成右子节点所在数组中的下标,找到那个较大的子节点
child++;
}
//将较大的子节点与父节点进行比较,
// 若子节点大,互换父子节点的值
if (array[child] > temp) {
array[pIndex] = array[child];
array[child] = temp;
//因为子节点的值变了,那么就不知道这个子节点在他自己的两个子节点中是否还是最大,
// 所以需要将该子节点的下标给pIndex,去重新检测一遍。只有当父节点为最大时,才会执行break退出循环。
pIndex = child;
} else { //否则结束循环。
break;
}

}
}

?

相关推荐

最新更新

猜你喜欢