排序算法---选择排序(Selection Sort)
算法基本思想:
从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推,就可以完成整个的排序工作了。可以很清楚的发现,选择排序是固定位置,找元素,然后交换。
代码实现:
选择排序是不稳定的。分析可以看出,对于一个长度为n的数组,需要进行n-1趟操作,才能完全确保排序完成,时间复杂度为O(n^2)。
import java.util.Arrays;
public class SelectionSort {
public static void sort(int[] arr) {
int len = arr.length;
int slcIndex;
int tmp;
System.out.println("原始顺序: " + Arrays.toString(arr));
for (int i = 0; i < len - 1; i++) {
//依次选择前n-1个数,以索引作为依据
slcIndex = i;
for (int j = i + 1; j < len; j++) {
//与被选中的数之后的每个数进行比较
if (arr[j] < arr[slcIndex]) {
//存在更小的数,替换索引
slcIndex = j;
}
}
//交换数据
if (slcIndex != i) {
tmp = arr[i];
arr[i] = arr[slcIndex];
arr[slcIndex] = tmp;
}
System.out.println("第" + (i + 1) + "趟排序:" + Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
SelectionSort.sort(arr);
}
}
运行结果:
排序算法---冒泡排序(Bubble Sort)
算法基本思想:
对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。就好像一串气泡一样,最终从小到大或从大到小依次排下来。
代码实现:
分析可以看出,对于一个长度为n的数组,需要进行n-1趟操作,才能完全确保排序完成,时间复杂度为O(n^2)。
import java.util.Arrays;
public class BubbleSort {
public static void sort(int[] arr) {
int len = arr.length;
int tmp;
System.out.println("原始顺序: "+ Arrays.toString(arr));
//i表示第几趟排序
for (int i = 1; i < len; i++) {
//每次都从最后一个开始,知道第len-1趟排序
for (int j = len - 1; j > i-1; j--) {
//如果后面的比前面的小,就像泡泡一样冒上去
if (arr[j] < arr[j - 1]) {
tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
}
}
System.out.println("第"+i+"趟排序: "+ Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
BubbleSort.sort(arr);
}
}
输出结果:
排序算法---插入排序(Insertion Sort)
算法基本思想:
插入排序(Insertion Sort)算法通过对未排序的数据执行逐个插入至合适的位置而完成排序工作。
排序流程:
- 首先对数组的前两个数据进行从小到大排序
- 然后将第三个数据与前面排好的数据进行比较,把第三个数插入合适的位置
- 然后将第四个数据插入到前三个数据中
- 重复此步骤,直到最后一个数插入合适的位置为止,到此排序完成
代码实现
import java.util.Arrays;
public class InsertionSort {
public static void sort(int[] arr) {
int len = arr.length;
int tmp;//要插入的数据
int istIndex;//插入位置索引
System.out.println("原始顺序: " + Arrays.toString(arr));
for (int i = 1; i < len; i++) {
if (arr[i] < arr[i - 1]) {
tmp = arr[i];
istIndex = i;
while (istIndex > 0 && arr[istIndex-1] > tmp) {
//插入位置往前移,寻找合适位置
arr[istIndex] = arr[istIndex - 1];
istIndex--;
}
arr[istIndex] = tmp;
}
System.out.println("第" + i + "趟排序:" + Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
InsertionSort.sort(arr);
}
}
排序算法---希尔排序(Shell Sort)
算法基本思想:
- 将有n个元素的数组分成n/2份,第1个数据与第n/2+1个数据属于同一份。。。
- 使用类似插入排序的方法,将同一份的数据排序
- 然后,再变为n/4份,同样的操作再次排序
- 不断重复上述3个步骤之后,最后分成n份数据,再通过一次插入排序就完成了全部的排序。
代码实现:
import java.util.Arrays;
public class ShellSort {
public static void sort(int[] arr) {
int len = arr.length;
int gap;//步长
int istIndex;//插入位置索引
int tmp;
System.out.println("原始顺序: "+ Arrays.toString(arr));
//按照步长来分组
for(gap = len / 2; gap >= 1; gap /= 2) {
//类似插入排序的方法
for (int i = gap; i < len; i++) {
tmp = arr[i];//取出暂存
istIndex = i;//插入的位置
while ((istIndex > (gap-1) && tmp < arr[istIndex - gap])) {
//插入位置往前移,寻找合适位置
arr[istIndex] = arr[istIndex - gap];
istIndex -= gap;
}
arr[istIndex] = tmp;
}
System.out.println("步长为"+gap+"的排序: "+ Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
ShellSort.sort(arr);
}
}
分析小结:
其实希尔排序就是分了组的插入排序,通过这样做可以减少大部分的情况下,数据的移动次数,从而减小时间复杂度,同时希尔排序也是当时冲破O(n^2)的第一批算法之一。
在代码中可以看到,当我们将第二层for循环里的gap视为1的时候,其实接下来的代码就和之前的插入排序一模一样了!!!
其实对于该算法,还可以继续优化,就是改善步长的计算部分,现在是每次变为原来的1/2,其实有个公式h=3*h+1来计算步长,有兴趣的话可以自己研究一下
排序算法---合并排序(Merge Sort)
合并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。合并排序也叫归并排序。
算法基本思想:
- 首先将未排序的数组,进行拆分成n个单一的数据。
- 然后将这n个数据按照索引顺序,两两组合、排序。(例如:有8个数据,那么第一个和第二个组合,第三个和第四个组合。。。如有未组合的数据这放任其暂时不管)
- 将上面已经两两组合、排序好的数据看成是一个“元素”,再进行两两组合、排序。。。
- 重复上述的步骤,最后就得到已经排序好的数组。
代码实现:
import java.util.Arrays;
import java.util.Date;
/**
* Created by noonbiteun
* Date: 2017/8/1
*/
public class MergeSort {
private static void merge(int[] unsortArr, int frontIndex, int backIndex, int lastIndex, int[] sortArr) {
int i = frontIndex;//前半段的起始索引
int j = backIndex;//后半段的起始索引
int k = 0;
//合并两个小分组
while (i < backIndex && j < lastIndex) {
if (unsortArr[i] < unsortArr[j]) {
sortArr[k++] = unsortArr[i++];
} else {
sortArr[k++] = unsortArr[j++];
}
}
while (i < backIndex) {
//前半段还有数据
sortArr[k++] = unsortArr[i++];
}
while (j < lastIndex) {
//后半段还有数据
sortArr[k++] = unsortArr[j++];
}
for (int l = 0; l < k; l++) {
//将排序好的数放回
unsortArr[frontIndex + l] = sortArr[l];
}
}
public static void sort(int[] arr, int first, int last, int[] sorted) {
if (first < last - 1) {
int back = (first + last) / 2;
sort(arr, first, back, sorted);
sort(arr, back, last, sorted);
merge(arr, first, back, last, sorted);
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
long t1 = new Date().getTime();
System.out.println("原始的顺序: "+ Arrays.toString(arr));
MergeSort.sort(arr, 0, arr.length, new int[arr.length]);
long t2 = new Date().getTime();
System.out.println("排序后顺序: "+ Arrays.toString(arr));
System.out.println("耗时:"+(t2-t1)+" ms");
}
}
输出结果:
分析小结:
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
排序算法---快速排序(Quick Sort)
快速排序和冒泡排序类似,都是基于交换的思想,快速排序对冒泡排序进行了优化,从而更加快速高效
算法基本思想:
- 首先选取一个基准值,然后根据这个基准将数组分为了左右两部分
- 将这左右两个部分中的数据,和基准值做比较,大于基准值的放在右边,小于基准值的放在左边
- 此时对于基准值而言,已经 “排序 ” 完成,即“比我小的全在我左边,比我大的全在我右边”
- 然后再对左右两部循环进行同样的操作(递归),最后整个数组的排序就完成了。
代码实现:
import java.util.Arrays;
public class Code_04_QuickSort {
public static int partition(int [] arr,int left,int right) {
int mid = (left + right)/2;
if(arr[mid] > arr[left]) {
swap(arr,mid,left);
}
if(arr[mid] > arr[right]) {
swap(arr,mid,right);
}
if(arr[left] > arr[right]) {
swap(arr,left,right);
}
int key =arr[left];
while(left < right) {
while(left < right && arr[right] >= key) {
right--;
}
arr[left] = arr[right];
while(left < right && arr[left] <= key) {
left++;
}
arr[right] = arr[left];
}
arr[right] = key;
return right;
}
public static void qSort(int [] arr,int left,int right) {
if(left >= right) {
return;
}
int key = partition(arr,left,right);
qSort(arr,left,key - 1);
qSort(arr,key + 1, right);
}
public static void swap(int [] arr,int i,int j) {
int temp = arr[i];
arr[i] =arr[j];
arr[j] = temp;
}
public static void main(String [] args) {
int [] arr = new int [10];
for(int i = 0;i<10;i++) {
arr[i] = (int)(Math.random()*100);
}
System.out.println("原始序列:"+Arrays.toString(arr));
Code_04_QuickSort.qSort(arr, 0, arr.length-1);
System.out.println("排序后数据:"+Arrays.toString(arr));
}
}
输出结果:
排序算法---堆排序(Heap Sort)
堆排序算法,基于选择排序的思想,利用堆结构的性质来完成对数据的排序。
前提准备:
-
什么是堆结构:
堆数据结构是一种数组对象,它可以被视为一科完全二叉树结构。它的特点是父结点的值大于(小于)两个子结点的值(分别称为大顶堆和小顶堆)
2.堆结构的性质:
对于第n个结点而言 --- 它的父结点下标 i = (n-1)/ 2,左子结点 left = 2n+1,右子结点 right =2n+2
算法基本思想:
1. 构建堆(大顶堆或小顶堆,现以大顶堆为例):
- 首先从最后一个结点开始,找到它的父结点,在父结点和父结点的左右结点中,选出最大的值,与父节点交换。
- 然后对倒数第二个结点执行同样的操作
- 这样直到根结点的时候,根结点变成了所以数据中最大的值。
2. 替换根结点,重建堆:
- 将根结点与最后一个结点的数据交换,在把倒数第二个结点视为最后一个结点,重新建堆。
- 重复上述的操作。最终,数据排序完成。
代码实现:
import java.util.Arrays;
public class HeapSort {
//建堆
private static void heapConstruct(int[] arr, int last) {
int tmp;
for (int parent = (last - 1) / 2; parent >= 0; parent = (last-1) / 2) {
if (2*parent+1 < last) {
//第last个节点为右子树
if (arr[last - 1] < arr[last]) {
//大的数据往前移
tmp = arr[last];
arr[last] = arr[last - 1];
arr[last - 1] = tmp;
} else {
last--;
}
}
if (arr[parent] < arr[last]) {
//大的数据移到父节点
tmp = arr[last];
arr[last] = arr[parent];
arr[parent] = tmp;
}
last--;
}
}
private static void sort(int[] arr) {
int last = arr.length - 1;
int tmp;
System.out.println("原始顺序: " + Arrays.toString(arr));
for (int i = 0; i < arr.length; i++) {
heapConstruct(arr, last);
//第一个和最后一个交换
tmp = arr[0];
arr[0] = arr[last];
arr[last] = tmp;
last--;
System.out.println("第" + i + "趟排序:" + Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
HeapSort.sort(arr);
}
}
输出结果:
小结
1. 排序算法的比较
算法 | 稳定 | 时间复杂度 | 空间复杂度 | 备注 |
---|---|---|---|---|
选择排序 | × | N2 | 1 | |
冒泡排序 | √ | N2 | 1 | |
插入排序 | √ | N ~ N2 | 1 | 时间复杂度和初始顺序有关 |
希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | |
快速排序 | × | NlogN | logN | |
三向切分快速排序 | × | N ~ NlogN | logN | 适用于有大量重复主键 |
归并排序 | √ | NlogN | N | |
堆排序 | × | NlogN | 1 |
快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 ~cNlogN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
(资源搬运工,下面附源链接)
作者:noonbiteun
链接:https://www.jianshu.com/nb/14859443
來源:简书