文章目录
1. 冒泡排序(Bubble Sorting)
基本介绍:
通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。 —每次循环确定一个最大值。
- 一共进行 arr.length-1 次大的循环
- 每一趟排序的次数在逐渐的减少
- 如果我们发现在某趟排序中,没有发生交换, 可以提前结束冒泡排序。–优化。
import java.util.Arrays;
/**
* @author ming
* @create 2020-02-19 13:10
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, 20};
//排序
bubbleSort(arr);
}
private static void bubbleSort(int[] arr) {
//第一趟排序,将最大的数放在最后
//第一趟排序,将最大的数放在倒数第二位
//...
int temp = 0;
//表示是否发生过交换
boolean flag = false;
int j = 0;
for (j = 0; j < arr.length - 1; j++) {
for (int i = 0; i < arr.length - 1 - j; i++) {
//前面的数 > 后面的数,交换
if (arr[i] > arr[i + 1]) { // 值相等时不交换,稳定
temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
flag = true;
}
}
//某次排序中,未发生数据交换,直接退出循环
if (!flag) {
break;
} else {
//重置flag
flag = false;
}
}
}
}
2.选择排序(Selection sorting)
简单的排序方法。
基本介绍:
第一次arr[0] ~ arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1] ~ arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2] ~ arr[n-1]中选取最小值,与arr[2]交换,…, 第n-1次从arr[n-2] ~ arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序从小到大排列的有序序列。—每次循环确定一个最小的数据。
- 选择排序一共有 数组大小n - 1 轮排序
- 第一轮排序假定第一个数是最小数,第二轮排序假定第二个数是最小数,依次类推。
- 将假定的最小数和后面的每个数进行比较,如果发现有比当前数更小的数,就将假定的最小数替换,并记录最小数的下标索引
- 当遍历到数组的最后时,就得到本轮最小数和下标索引
- 若此轮排序中最小数发生变化,则交换
import java.util.Arrays;
public class SelectionSort {
public static void main(String[] args) {
sort(new int[]{3,5,3,2,7,1,9,0});
}
public static void sort(int[] a) {
int n = a.length;
for (int i = 0; i < n; i++) { // 将a[i]与a[j]~a[n-1]中的最小元素交换
int min = i; // 最小元素索引
for (int j = i+1; j < n; j++) {
// 寻找最小元素索引
// 遍历过程中将遍历到的最小的元素的索引存在min中,
// 每次遍历比较当前值a[j]与存储的最小值a[min]
// 遍历到的当前值较小,用当前值索引j赋值给,nim
if (a[j] < a[min]) min = j;
}
int swap = a[i];
a[i] = a[min];
a[min] = swap;
}
System.out.println(Arrays.toString(a));
}
}
3.插入排序(Insertion Sorting)
基本介绍:
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表,一共进行 n-1 轮。
import java.util.Arrays;
/**
* @author ming
* @create 2020-02-20 13:01
*/
public class InsertSort {
public static void main(String[] args) {
int[] arr = {30, 21, 50, 43, 10, 5};
//排序
insertSort(arr);
}
//排序方法
public static void insertSort(int[] arr) {
//方式一:
// for (int j = 1; j < arr.length; j++) {
// //定义待插入的数
// int insertVal = arr[j];
// //待插入的数前面一个索引
// int insertIndex = j - 1;
//
// //insertIndex >= 0 防止索引越界
// //将insertVal依次与前面的比较,若insertVal较小则insertIndex-1,继续比较
// while(insertIndex >= 0 && insertVal <arr[insertIndex]){
// arr[insertIndex + 1] = arr[insertIndex];
// insertIndex--;
// }
//
//
// arr[insertIndex + 1] = insertVal;
// }
//方式二:
//int[] arr = {30, 21, 50, 43, 10, 5};
int j;
int temp;
for (int i = 1; i < arr.length; i++) {
if (arr[i] < arr[i - 1]) {
temp = arr[i];
for (j = i; j >= 1 && arr[j - 1] > temp; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}
// System.out.println("排序结果:" + Arrays.toString(arr));
}
}
import java.util.Arrays;
/**
* @author ming
* @create 2020-02-20 11:23
*
* 此例更贴近与实际
*/
public class SelectSort {
public static void main(String[] args) {
int[] arr = {4, 6, 2, 1, 9, 2};
selectSort(arr);
}int
//排序方法
public static void selectSort(Comparable[] arr) {
int N = arr.length;
for (int i = 0; i < N; i++) {
int min = i;
for (int j = i + 1; j < N; j++) {
if (less(arr[j], arr[min])) min = j;
}
exch(arr, i, min);
}
// System.out.println("排序后:" + Arrays.toString(arr));
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] arr, int i, int j) {
Comparable t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
4.希尔排序(Donald Shell)
于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
基本介绍:
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组。
- 希尔排序时, 交换法: 单纯的交换位置。
- 希尔排序时, 移位法: 插入排序优化。
import java.time.Instant;
import java.util.Arrays;
/**
* @author ming
* @create 2020-02-20 17:44
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr = {10, 12, 0, 5, 1, 2, 4, 8, -1, 9, 11, 3};
shellSort(arr);//移位:
}
//排序方法
public static void shellSort(int[] arr) {
//方式一(移位法):
//将数据分为arr.length / 2,arr.length / 2 / 2,...直至分组为1是,进行最后一次排序
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//
arr[j] = arr[j - gap];
j -= gap;
}
arr[i] = temp;
}
}
// System.out.println("排序结果:" + Arrays.toString(arr));
}
}
public static void shellSort2(int[] arr) {
//方式二(交换法【更慢】):
int temp;
//将数据分为arr.length / 2,arr.length / 2 / 2,...直至分组为1是,进行最后一次排序
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
//每一小组中后面的数大于前面的数时,互换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
// System.out.println("排序结果:" + Arrays.toString(arr));
}
}
5.快速排序(Quicksort)
快速排序是对冒泡排序的一种改进。
基本介绍:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
import java.util.Arrays;
public class Quick_book {
public static void main(String[] args) {
int[] arr = {5, 4, 1, 6, 1, 9};
sort(arr,0,arr.length - 1);
System.out.println(Arrays.toString(arr));
}
private static void sort(int[] a, int lo, int hi) {
if (hi <= lo) return;
int i = lo, j = hi + 1; // i,j左右指针
int v = a[lo]; // 默认以第一个数的大小为基准
while (true) {
while (less(a[++i], v)) { // 从左开始找,找到>=基准数的元素(i为该元素指针),结束此循环
if (i == hi) break; // 数组遍历完了,结束此循环
}
while (less(v, a[--j])) {} // 从右开始找,找到<=基准数的元素(j为该元素指针),结束此循环
if (i >= j) break; // 检查指针是否交叉或重合【遇到了与v相等的元素回重合,退出循环后v与a[j]还会交换位置,故是不稳定的】,只有指针交叉或重合时才退出这个大while(true)循环
exch(a, i, j); // a[i]与a[j]元素互换
}
// a[j]右边的元素必然比v大;又因为退出了前面的while循环,说明a[j]左边没有比v大的元素
exch(a, lo, j); // 将第一个元素(基准元素v)与a[j]换位,换位后v左边的元素比他小,右边的比他大,即v到了正确的位置
sort(a, lo, j-1);
sort(a, j+1, hi);
}
private static boolean less(int v, int w) {
if(v < w) {
return true;
}
return false; // v >= w
}
private static void exch(int[] a, int i, int j) {
int swap = a[i];
a[i] = a[j];
a[j] = swap;
}
}
三向切分的快速排序
这段排序代码的切分能够将和切分相等的元素归位,这样他们就不会被包含在递归调用处理的子数组织中。对于存在大量重复元素的数组,这种方法比标准的快速排序的效率高得多。
private static void sort(Comparable[] arr, int lo, int hi) {
if (hi <= lo) return;
//维护指针lt使得arr[lo..lt] 中元素小于v
int lt = lo;
//指针使得arr[i..gt] 中元素未确定
int i = lo + 1;
//指针gt使得arr[gt+1..hi] 中元素大于v
int gt = hi;
Comparable v = arr[lo];
while (i <= gt) {
int cmp = arr[i].compareTo(v);
//arr[i] 比 v 小,将v,arr[i] 互换,将i,lt后移
if (cmp < 0) exch(arr, lt++, i++);
//arr[i] 比 v 大,将arr[i],arr[gt] 互换,将gt前移
else if (cmp > 0) exch(arr, i, gt--);
//arr[i] = v ,将i后移
else i++;
}
System.out.println(Arrays.toString(arr));
//左递归
sort(arr, lo, lt - 1);
//右递归
sort(arr, gt + 1, hi);
}
public static void exch(Comparable[] arr, int i, int j) {
Comparable t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
6.归并排序(merge-sort)
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略。分治法:将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之。
原地归并的抽象方法
将2个有序数组归并为1个有序数组
4个判断条件:
- 当左半边用尽,取右半边元素;
- 当右半边用尽,取左半边元素;
- 右半边当前元素 < 左半边当前元素,取右半边当前元素;
- 右半边当前元素 > 左半边当前元素,取左半边当前元素;
public class MergeSort {
//归并所需的辅助数组
private static int[] aux;
private static void merge(int[] arr, int lo, int mid, int hi) {
int[] aux = new int[arr.length];
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) aux[k] = arr[k];
for (int k = lo; k <= hi; k++)
if (i > mid) arr[k] = aux[j++];
else if (j > hi) arr[k] = aux[i++];
else if (aux[i] > aux[j]) arr[k] = aux[j++];
else arr[k] = aux[i++];
}
}
自顶向下的归并排序
基于原地归并的的抽象实现另一种递归归并,这也是分治思想的典型例子,这段递归代码是归纳证明算法能够正确的将数组排序的基础:如果他能将两个子数组排序,它就能够通过归并两个子数组来将整个数组排序。
public class MergeSort {
//归并所需的辅助数组,避免每次归并都要创建数组,这样创建数组将成为归并的主要步骤
private static int[] aux;
private static void mergeSort(int[] arr, int start, int end) {
//一次性分配空间
aux = new int[arr.length];
sort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
//递归方法
private static void sort(int[] arr, int lo, int hi) {
if (hi <= lo) return;
//取左,右两边的中间索引
int mid = lo + (hi - lo) / 2;
sort(arr, lo, mid);//左半边排序
sort(arr, mid + 1, hi);//右半边排序
merge(arr, lo, mid, hi);//原地归并的抽象方法
}
}
自底向上的归并排序
递归实现归并排序是算法设计中分治思想的经典应用。实现归并排序的另一种方法是先归并那些微型数组,然后再成对归并得到的子数组,如此这般。直到我们将整个数组归并在一起,这样的实现方法比标准递归方法所需的代码量更少。
(首先,我们将每个元素想象成大小为1的数组,然后两两归并,然后四四归并,一直下去)。
自底向上归并,排序会多次遍历整个数组,根据子数组的大小进行两两归并,指数组的大小sz的初始值为1,每次加倍。
当数组长度为2的幂,自顶向下和自底向上的归并排序所比较的次数和数组访问的次数正好相同,只是顺序不同。
自底向上的归并排序比较适合用 链表 组织的数据。这种方法只需重新组织链表链表,就能将链表原地排序(不需要创建任何新的链表节点)。
public class MergeSort {
//归并所需的辅助数组
private static int[] aux;
private static void mergeSort(int[] arr, int start, int end) {
//自底向上
int N = arr.length;
aux = new int[N];
for (int sz = 1; sz < N; sz = sz + sz) {
for (int lo = 0; lo < N - sz; lo += sz + sz) {
//Math.min(lo + sz + sz - 1, N - 1):若数组长度为奇数,则最后一次归并,将前面的偶数个数据与最后一个数归并;数组长度为偶数,并不会用到 N- 1
merge(arr, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));//原地归并的抽象方法
}
}
System.out.println(Arrays.toString(arr));
}
}
7.基于堆优先队列
优先队列是一种抽象数据类型,他表示一组直和对这些值的操作。优先队列中最重要的操作就是删除最大元素和插入元素。
当一棵二叉树的每个结点都大于等于它的两个子节点,他被称为堆有序
import java.util.Arrays;
/**
* @author ming
* @create 2020-02-23 13:30
*/
public class TopM {
public static void main(String[] args) {
String[] a = {"3", "6", "2", "8", "5", "9", "7"};
MaxPQ pq = new MaxPQ(a.length);
for (int i = 0; i < a.length; i++) {
pq.insert(a[i]);
}
// pq.delMax();
// pq.delMax();
System.out.println(pq.toString());
}
}
class MaxPQ {
//基于堆的完全二叉树
private String[] pq;
//储存于pq[1..N],pq[0]没有使用
private int N = 0;
public MaxPQ(int maxN) {
pq = new String[maxN + 1];
}
//判空
public boolean isEmpty() {
return N == 0;
}
//队列长度
public int size() {
return N;
}
//插入
public void insert(String v) {
//此时直接将插入的数据加到了数组尾
pq[++N] = v;
//上浮操作,插入的数据比它的父节点的,互换,互换后再与其新的父节点比较,重复操作
swim(N);
}
//删除最大元素
public String delMax() {
//保存最大元素
String max = pq[1];
//将根节点(最大元素)与数组最后元素互换
exch(1, N--);
//将最后元素置为null
pq[N + 1] = null;
//下沉操作,
sink(1);
return max;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
String t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
private void swim(int k) {
//位置k的父节点的位置为k/2
while (k > 1 && less(k / 2, k)) {
exch(k / 2, k);
k = k / 2;
}
}
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
//k的子节点位子为2k,2k + 1
//通过子节点中较大的值恢复有序
if (j < N && less(j, j + 1)) j++;
//父节点大于子节点
if (!less(k, j)) break;
exch(k, j);
k = j;
}
}
@Override
public String toString() {
return "MaxPQ{" +
"pq=" + Arrays.toString(pq) +
'}';
}
}
堆排序(Heapsort)
将所有元素插入一个查找最小元素的优先队列,然后再重复调用删除最小元素的操作来将他们让顺序删除,用无序数组实现优先队列,这么做相当于进行一次选择排序,用基于堆的优先队列,这样等同于哪种排序?一种全新的排序方法,下面让我们用堆来实现一种经典而优雅的排序算法–堆排序。
堆排序可以分为两个阶段:
- 在堆中构造阶段中,我们将原始数组重新安排进一个堆中。
- 只需从左自右遍历数组使用swim(),保证扫描指针左侧的所有元素已经是一颗堆有序的完全树即可,就像连续线优先队列中插入元素一样。
- 一个更聪明高效的方法是,
- 从右至左用sink()函数构造子堆。
- 数组的每个位置都已经是一个子堆的根节点了。(sink()对于这些子堆也适用,如果一个节点的两个子节点都已经是堆了,那么在该节点上调用sink()可以将它们变成一个堆,这个过程会递归的建立起堆的秩序,开始时我们只需扫描数组中的一半元素,因为我们可以跳过大小为1的子堆。)
- 最后我们在位置1上调用sink()方法。扫描结束。再排序的第一阶段堆的构造方法和我们想象的有所不同,因为我们的目标: 是构造一个堆有序的数组,并使最大的元素位于开头(次大的元素在附近),而非构造函数结尾的末尾。
- 然后在下沉排序阶段,我们从堆中按递减顺序取出所有元素并得到排序结果。堆排序的主要工作都是在第二阶段完成的:
- 这里我们将堆中最大的元素删除,然后放入堆缩小后的数组中空出的位置,这个过程和选择排序有些类似,按照降序而非升序,取出所有元素,但需要的比较少的多 ,因为堆提供了一种从未排序部分找到最大元素的有效方法。
如图:
构造阶段(左) 下沉排序(右)
/**
* @author ming
* @create 2020-02-23 18:17
*/
public class Heap {
private Heap() {
}
public static void sort(Comparable[] pq) {
int n = pq.length;
//构造阶段
//取后半段,从右至左
for (int k = n / 2; k >= 1; k--)
sink(pq, k, n);
// for (int i = 0; i < pq.length; i++)
// System.out.println(pq[i]);
//下沉排序
while (n > 1) {
//将根节点最大数据与数组尾数据交换
exch(pq, 1, n--);
//将根节点数据下沉操作
sink(pq, 1, n);
}
}
private static void sink(Comparable[] pq, int k, int n) {
while (2 * k <= n) {
int j = 2 * k;
if (j < n && less(pq, j, j + 1)) j++;
//头结点与子节点比较
if (!less(pq, k, j)) break;
//头结点于子节点互换
exch(pq, k, j);
k = j;
}
}
private static boolean less(Comparable[] pq, int i, int j) {
return pq[i - 1].compareTo(pq[j - 1]) < 0;
}
private static void exch(Object[] pq, int i, int j) {
Object swap = pq[i - 1];
pq[i - 1] = pq[j - 1];
pq[j - 1] = swap;
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.printf("a[%s]=%s\t",i,a[i]);
}
}
public static void main(String[] args) {
String[] a = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
Heap.sort(a);
show(a);
}
}