1 . 冒泡排序( Bubble Sort )
在遍历数组时判断 当前位置的 数组大小(a[ j ])是否比下一位(a[ j + 1 ])大,如果是,则将
a[ j ] 与 a[ j + 1 ]数值交换,即把大的数往后放,就像是冒泡一样,大的数一直后移,直到数组有序。
在此进行了优化,添加了flag判断本次循环是否进行了交换,如未进行交换则说明排序完毕,跳出循环,减少了可能出现的已经排好序但循环没有结束,继续运行的情况;
当前数组 :6 1 5 3 4
内循环第一遍 1 6 5 3 4
内循环第一遍 1 5 6 3 4
内循环第一遍 1 5 3 4 6
下一次内循环
内循环第一遍 1 3 5 4 6
内循环第二遍 1 3 4 5 6
内循环第三遍 1 3 4 5 6
此时未进行交换,flag = 0,跳出循环,无需再进行下一次内循环
冒泡排序的时间复杂度是O(n^2)的
import java.util.Scanner;
public class BubbleSort {
public static void main(String[] args) {
int n;
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
bubble(a, n);
}
public static void bubble(int[] a, int n) {
int x;
int flag = 0;//这句可以不写
x = a[0];
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (a[j] >a[j + 1]) {
flag++;
x = a[j];
a[j] = a[j + 1];
a[j + 1] = x;
}
}
if (flag == 0) {//可以不写
break;
} else
flag = 0;
}
for (int i = 0; i < n; i++) {
System.out.print(a[i] + " ");
}
}
}
2 . 插入排序 ( Insert Sort )
插入排序运用到了链表,将数据存储在链表中,我在写的时候先在头节点后插入了两个节点,这算是一个bug,不能输入两个以下的数。
在输入时,遍历链表,将新建的链表节点按顺序插入链表中:
此时链表中数值:1 2 5 6 9
新输入的值:3
遍历链表,找到第一个大于三的数,在这个数之前插入新的节点:
1 2 3 5 6 9
插入完毕
如果遍历结束没有找到比输入数值大的节点,则将新建的节点插入链表末尾
import javax.xml.soap.Node;
import java.util.*;
public class InsertSort {
public static void main(String[] args) {
ListNode p1;
ListNode head = new ListNode(0);
int n, m;
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
p1 = new ListNode(m);
head.next = p1;
m = scanner.nextInt();
ListNode p2 = new ListNode(m);
if (p2.val > p1.val) {
insert(p1, p2);
} else {
insert(head, p2);
}
p1 = head;
for (int i = 0; i < n - 2; i++) {
m = scanner.nextInt();
p2 = new ListNode(m);
while (p1.next != null) {
if (p2.val > p1.val && p1.next.val > p2.val) {
insert(p1, p2);
p1 = p1.next;
break;
}
if (p2.val == p1.val) {
// ListNode p;
// p = new ListNode(m);
insert(p1, p2);
p1 = p1.next;
break;
}
p1 = p1.next;
}
if (p2.val > p1.val)
insert(p1, p2);
p1 = head;
}
p1 = head.next;
for (int i = 0; i < n; i++) {
System.out.print(p1.val + " ");
p1 = p1.next;
}
}
public static void insert(ListNode p, ListNode p1) {
ListNode p2;
p2 = p1;
p2.next = p.next;
p.next = p2;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
3 . 快速排序 ( Quck Sort )
快速排序是较为不稳定的排序,基准选择的优秀程度直接决定了算法的时间复杂度,最优可以达到O(nlogn),最劣达到了O(n^2)。
算法思想:
选择数组中的第一个数值作为基准(target),从左往右找到大于基准(target)的数值L,再从左往右找到小于基准(target)的数值R,判断 L 是否在 R 的左侧,如果是则将L和R的数值交换,继续往后找,直到 L 的位置在 R 的右边,此时,将target的数值与找到的 比 target 小的数值 R 与target交换。
如果在 target 右侧找不到比 target 小的数,则将 target 右移;可以通过循环实现以上操作。
下图打印了快排的交换过程与每次交换的 target 和交换数组的下标。
快排的实际上是给每一个target对应的数值找一个“家”,即找出target对应的值在数组中对应的位置,他是数组中第一小的值,就给他找第一个位置,即a[0],它是第五小的值,就把它放在a[4],快排就是这样一个过程。
利用递归可以实现这一点,我们按照part方法为target找到位置的下标进行分割,将它左边和右边分为两次递归,代码如下:
import java.util.Scanner;
public class QuckSort {
public static void main(String[] args) {
int n;
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
qucksort(a, 0, n - 1);
for (int i = 0; i < n; i++) {
System.out.print(a[i] + " ");
}
}
public static int part(int a[], int p, int r) {
int i = p, j = r + 1;
int target = a[p];
//System.out.println("target="+x);
while (true) {
while (a[++i] < target && i < r) ;
while (a[--j] > target) ;
if (i >= j)
break;
//System.out.println("swap:a["+i+"],a["+j+"]");
int y = a[i];
a[i] = a[j];
a[j] = y;
}
//System.out.println("swap:a["+p+"],a["+j+"]");
a[p] = a[j];
a[j] = target;
/* for(int value : a){
System.out.print(value+" ");
}
System.out.println();*/
return j;
}
public static void qucksort(int a[], int p, int r) {
if (p < r) {
int q = part(a, p, r);
qucksort(a, p, q - 1);
qucksort(a, q + 1, r);
}
}
}
/*
10
6 1 5 3 4 7 10 2 15 9
*/
4 . 插入排序( Insert Sort)
插入排序就是每次遍历数组,取出其中最小值,放到新数组中的一种排序方法
每次找到数组当前的最小值,将其放到新数组中,并在原来数组中将其更新为极大值或者非法数值,例如 6 1 5 3 4 ----> 取出1 后:6 9999 5 3 4,或者 6 -1 5 3 4;
代码如下:
import java.util.Scanner;
public class SelectSort {
public static void main(String[] args) {
int n;
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
int[] array = new int[n];
for (int i = 0; i < n; i++) {
array[i] = scanner.nextInt();
}
array = sort(array, n);
for (int i = 0; i < n; i++) {
System.out.print(array[i] + " ");
}
}
public static int select(int[] a, int n) {
int m, t = 0;
m = 999999;
for (int i = 0; i < n; i++) {
if (m > a[i]) {
m = a[i];
t = i;
}
}
return t;
}
public static int[] sort(int a[], int n) {
int[] array = new int[n];
for (int i = 0; i < n; i++) {
array[i] = a[select(a, n)];
a[select(a, n)] = 999999;
}
return array;
}
}
5 . 希尔排序 ( Shell Sort )
希尔排序是从大佬的博客里学会的,链接:
https://www.cnblogs.com/chengxiao/p/6104371.html
算法思想是将数组按照增量(一般为数组长度的一半作为初始增量)分组,将每组进行插入排序,然后将增量缩短为原来的一半,将分组减少,使数组逐渐有序化;
直到增量为1,数组化为一组数据时,继续运行,操作类似于冒泡排序遍历一遍,将数组数值微调即可获得排好序的数组;
希尔排序的时间复杂度为O(n^(1.3—2))
偷来的图:
import java.util.Scanner;
public class ShellSort {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n;
n = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
sort(nums);
for (int i = 0; i < n; i++) {
System.out.print(nums[i] + " ");
}
/*sort1(nums);
for (int i = 0; i < n; i++) {
System.out.print(nums[i]+" ");
}*/
}
public static void sort(int[] nums) {
//增量growth,并逐步缩小增量
for (int growth = nums.length / 2; growth > 0; growth /= 2) {
//从第growth个元素,逐个对其所在组进行直接插入排序操作
for (int i = growth; i < nums.length; i++) {
int j = i;
while (j - growth >= 0 && nums[j] < nums[j - growth]) {
//插入排序采用交换法
swap(nums, j, j - growth);
j -= growth;
}
}
}
}
/*
public static void sort1(int[] nums) {//shell分组
int growth = nums.length / 2;
while (growth > 0) {
insert(nums, growth);
growth /= 2;
}
}
public static void insert(int[] nums, int growth) {//插入排序
for (int i = growth; i < nums.length; i++) {
int j = i;
while (j - growth >= 0 && nums[j] < nums[j - growth]) {
//插入排序采用交换法
swap(nums, j, j - growth);
j -= growth;
}
}
}*/
public static void swap(int[] nums, int l, int r) {//交换数组
int x = nums[l];
nums[l] = nums[r];
nums[r] = x;
}
}
6 . 归并排序 ( Merge Sort )
归并排序和堆排序是可以稳定达到O(nlogn)时间复杂度的排序方法,但代码复杂程度较高
算法思想:
归并排序是将数组一分为二,再分为四,直到每个元素都各自为一组,不能再分时,将所有元素有序合并,将完全拆分的数组再重新组合成有序的数组的一个过程。
通常我们用递归的方法实现归并排序。
图片来自百度百科
import java.util.Scanner;
public class MergeSort {
public static void main(String[] args) {
int n;
Scanner scanner = new Scanner(System.in);
int[][] a = new int[1000][2];
int[][] b = new int[1000][2];
n = scanner.nextInt();
for (int i = 0; i < n; i++) {
a[i][0] = scanner.nextInt();
a[i][1] = i;
}
mergeSort(a, 0, n - 1, b);
for (int i = 0; i < n; i++) {
System.out.print(b[i][0] + " ");
}
System.out.println();
}
public static void Merge(int a[][], int b[][], int l, int m, int r) {
int k = 0;
int p1 = l, p2 = m + 1;
while (p1 <= m && p2 <= r) {
if (a[p1][0] < a[p2][0]) {
b[k++][0] = a[p1++][0];
b[k][1] = a[p1][1];
} else {
b[k++][0] = a[p2++][0];
b[k][1] = a[p2][1];
}
}
while (p1 <= m) {
b[k++][0] = a[p1++][0];
b[k][1] = a[p1][1];
}
while (p2 <= r) {
b[k++][0] = a[p2++][0];
b[k][1] = a[p2][1];
}
for (int i = 0; i < r - l + 1; i++) {
a[l + i][0] = b[i][0];
}
}
public static void mergeSort(int a[][], int left, int right, int b[][]) {
if (left < right) {
int i = left + (right - left) / 2;
mergeSort(a, left, i, b);
mergeSort(a, i + 1, right, b);
Merge(a, b, left, i, right);
}
}
}
上述六种排序各有优劣,
其中 冒泡排序,插入排序,选择排序 实现较为简单,但时间复杂度较高
快速排序,希尔排序 实现复杂且 耗费时间不稳定
归并排序,堆排序的时间复杂度较低,但是代码非常复杂,不容易实现
堆排序是本人学习的漏洞,会单独写一个文章