每次冒泡过程都是从数列的第一个元素开始,然后依次和剩余的元素进行比较,若小于相邻元素,则交换两者位置,同时将较大元素作为下一个比较的基准元素,继续将该元素与其相邻的元素进行比较,直到数列的最后一个元素 . 示意图如下:
/**
* 冒泡排序:
* 依次比较相邻的元素,若发现逆顺序,则交换。小的向前换,大的向后换,
* 本次循环完毕之后再次从头开始扫描,直到某次扫描中没有元素交换,
* 说明每个元素都不比它后面的元素大,至此排序完成。
*/
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] arr = new int[]{9, 2, 1, 0, 5, 3, 6, 4, 8, 7};
System.out.println("排序前:" + Arrays.toString(arr));
sort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
public static void sort(int[] arr) {
for (int i = 1; i < arr.length; i++) { //第一层for循环,用来控制冒泡的次数
for (int j = 0; j < arr.length - 1; j++) { //第二层for循环,用来控制冒泡一层层到最后
//如果前一个数比后一个数大,两者调换 ,意味着泡泡向上走了一层
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
运行结果:
排序前:[9, 2, 1, 0, 5, 3, 6, 4, 8, 7]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
在这个版本中,改动了两点 . 第一点是加入了一个布尔值,判断第二层循环中的调换有没有执行,如果没有进行两两调换,说明后面都已经排好序了,已经不需要再循环了,直接跳出循环,排序结束 ; 第二点是第二层循环不再循环到arr.length - 1,因为外面的i循环递增一次,说明数组最后就多了一个排好序的大泡泡.第二层循环也就不需要到最末尾一位了,可以提前结束循环
/**
* 升级版冒泡排序:
* 加入一个布尔变量,如果内循环没有交换值,说明已经排序完成,提前终止
*/
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] arr = new int[]{9, 2, 1, 0, 5, 3, 6, 4, 8, 7};
System.out.println("排序前:" + Arrays.toString(arr));
plusSort(arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
public static void plusSort(int[] arr){
if(arr != null && arr.length > 1){
for(int i = 0; i < arr.length - 1; i++){
// 初始化一个布尔值
boolean flag = true;
for(int j = 0; j < arr.length - i - 1 ; j++){
if(arr[j] > arr[j+1]){
// 调换
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
// 改变flag
flag = false;
}
}
if(flag){
break;
}
}
}
}
}
运行结果:
排序前:[9, 2, 1, 0, 5, 3, 6, 4, 8, 7]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
选择排序也是一种简单直观的排序算法,实现原理比较直观易懂:首先在未排序数列中找到最小元素,然后将其与数列的首部元素进行交换,然后,在剩余未排序元素中继续找出最小元素,将其与已排序数列的末尾位置元素交换。以此类推,直至所有元素圴排序完毕
/**
* 选择排序:
* 每一次从待排序的数据元素中选出最小(或最大)的一个元素,
* 存放在序列的起始位置,直到全部待排序的数据元素排完。
*/
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr = new int[] {3,4,5,7,1,2,0,9,3,6,8};
System.out.println("排序前:"+Arrays.toString(arr));
selectSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void selectSort(int[] arr) {
for(int i=0;i<arr.length;i++) {
int minIndex=i;
for(int j=i+1;j<arr.length;j++) {
if(arr[minIndex]>arr[j]) {
minIndex=j;
}
}
if(i!=minIndex) {
int temp=arr[i];
arr[i]=arr[minIndex];
arr[minIndex]=temp;
}
}
}
}
运行结果:
排序前:[3, 4, 5, 7, 1, 2, 0, 9, 3, 6, 8]
排序后:[0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9]
一次插入排序的操作过程:将待插元素,依次与已排序好的子数列元素从后到前进行比较,如果当前元素值比待插元素值大,则将移位到与其相邻的后一个位置,否则直接将待插元素插入当前元素相邻的后一位置,因为说明已经找到插入点的最终位置
/**
* 插入排序:
* 从第一个元素开始,该元素可以认为已经被排序
* 取出下一个元素,在已经排序的元素序列中从后向前扫描
* 如果该元素(已排序)大于新元素,将该元素移到下一位置
* 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
* 将新元素插入到该位置后
* 重复上面步骤
*/
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+Arrays.toString(arr));
insertSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void insertSort(int[] arr) {
for(int i=1;i<arr.length;i++) {
if(arr[i]<arr[i-1]) {
int temp=arr[i];
int j;
for(j=i-1;j>=0&&temp<arr[j];j--)
arr[j+1]=arr[j];
arr[j+1]=temp;
}
}
}
}
运行结果:
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
快速排序算法利用的是一趟快速排序,基本内容是选择一个数作为准基数,然后利用这个准基数将遗传数据分为两个部分,第一部分比这个准基数小,都放在准基数的左边,第二部分都比这个准基数大,放在准基数的右边.
import java.util.Arrays;
/**
* 快速排序:
* 快速排序算法利用的是一趟快速排序,基本内容是选择一个数作为准基数,
* 然后利用这个准基数将遗传数据分为两个部分,第一部分比这个准基数小,
* 都放在准基数的左边,第二部分都比这个准基数大,放在准基数的右边.
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+ Arrays.toString(arr));
quickSort(arr,0,arr.length-1);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void quickSort(int[] arr,int begin,int end) {
//先定义两个参数接收排序起始值和结束值
int a = begin;
int b = end;
//先判断a是否大于b
if (a >= b) {
//没必要排序
return;
}
//基准数,默认设置为第一个值
int x = arr[a];
//循环
while (a < b) {
//从后往前找,找到一个比基准数x小的值,赋给arr[a]
//如果a和b的逻辑正确--ax,就一直往下找,直到找到后面的值大于x
while (a < b && arr[b] >= x) {
b--;
}
//跳出循环,两种情况,一是a和b的逻辑不对了,a>=b,这时候排序结束.二是在后面找到了比x小的值
if (a < b) {
//将这时候找到的arr[b]放到最前面arr[a]
arr[a] = arr[b];
//排序的起始位置后移一位
a++;
}
//从前往后找,找到一个比基准数x大的值,放在最后面arr[b]
while (a < b && arr[a] <= x) {
a++;
}
if (a < b) {
arr[b] = arr[a];
//排序的终止位置前移一位
b--;
}
}
//跳出循环 a < b的逻辑不成立了,a==b重合了,此时将x赋值回去arr[a]
arr[a] = x;
//调用递归函数,再细分再排序
quickSort(arr,begin,a-1);
quickSort(arr,a+1,end);
}
}
运行结果:
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
归并排序,简单的说把一串数,从中平等分为两份,再把两份再细分,直到不能细分为止,这就是分而治之的分的步骤. 再从最小的单元,两两合并,合并的规则是将其按从小到大的顺序放到一个临时数组中,再把这个临时数组替换原数组相应位置
import java.util.Arrays;
/**
* 归并排序:
* 归并操作的工作原理如下:
* 第一步:申请空间,使其大小为两个已经 排序序列之和,该空间用来存放合并后的序列
* 第二步:设定两个 指针,最初位置分别为两个已经排序序列的起始位置
* 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置重复步骤3直到某一指针超出序列尾
* 将另一序列剩下的所有元素直接复制到合并序列尾
*
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+ Arrays.toString(arr));
mergeSort(arr, 0, arr.length-1);
System.out.println("排序后:"+ Arrays.toString(arr));
}
public static void mergeSort(int[] a,int s,int e){
int m = (s + e) / 2;
if (s < e){
mergeSort(a,s,m);
mergeSort(a,m+1,e);
//归并
merge(a,s,m,e);
}
}
private static void merge(int[] a, int s, int m, int e) {
//初始化一个从起始s到终止e的一个数组
int[] temp = new int[(e - s) + 1];
//左起始指针
int l = s;
//右起始指针
int r = m+1;
int i = 0;
//将s-e这段数据在逻辑上一分为二,l-m为一个左边的数组,r-e为一个右边的数组,两边都是有序的
//从两边的第一个指针开始遍历,将其中小的那个值放在temp数组中
while (l <= m && r <= e){
if (a[l] < a[r]){
temp[i++] = a[l++];
}else{
temp[i++] = a[r++];
}
}
//将两个数组剩余的数放到temp中
while (l <= m){
temp[i++] = a[l++];
}
while (r <= e){
temp[i++] = a[r++];
}
//将temp数组覆盖原数组
for (int n = 0; n < temp.length; n++) {
a[s+n] = temp[n];
}
}
}
运行结果:
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
**希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止 . 希尔排序实质上是一种分组插入的方法 . **
/**
* 希尔排序:
* 希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”。
* 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
* 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
*/
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
public static void shellSort(int[] arr) {
int k = 1;
for (int d = arr.length / 2; d > 0; d /= 2) {
for (int i = d; i < arr.length; i++) {
for (int j = i - d; j >= 0; j -= d) {
if (arr[j] > arr[j + d]) {
int temp = arr[j];
arr[j] = arr[j + d];
arr[j + d] = temp;
}
}
}
System.out.println( Arrays.toString(arr));
k++;
}
}
}
运行结果:
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
[5, 1, 0, 7, 4, 9, 3, 2, 8, 6]
[0, 1, 3, 2, 4, 6, 5, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
/**
* 基数排序:
* 取得数组中的最大数,并取得位数;
* arr为原始数组,从最低位开始取每个位组成radix数组;
* 对radix进行计数排序(利用计数排序适用于小范围数的特点);
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RadixSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+ Arrays.toString(arr));
radixSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
/**
* 基数排序
*/
public static void radixSort(int[] a) {
int max = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max) {
max = a[i];
}
}
int time = 0;
while (max > 0) {
max /= 10;
time++;
}
List<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> queue1 = new ArrayList<Integer>();
queue.add(queue1);
}
for (int i = 0; i < time; i++) {
for (int j = 0; j < a.length; j++) {
int x = a[j] % (int) Math.pow(10, i+1)/(int)Math.pow(10, i);
ArrayList<Integer> queue2 = queue.get(x);
queue2.add(a[j]);
queue.set(x, queue2);
}
int count = 0;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size()>0) {
ArrayList<Integer> queue3 = queue.get(k);
a[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
}
运行结果:
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
/**
* 堆排序
* 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
* 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
* 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,
* 然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。
* 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
*/
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
System.out.println("排序前:"+ Arrays.toString(arr));
heapSort(arr);
System.out.println("排序后:"+Arrays.toString(arr));
}
/**
* 堆排序
*/
public static void heapSort(int[] a) {
int len = a.length;
for (int i = 0; i < len - 1; i++) {
buildMaxHeap(a,len - 1 - i);
swap(a,0,len - 1 - i);
}
}
private static void buildMaxHeap(int[] data, int lastIndex) {
//从lastIndex处节点(最后一个节点)的父节点开始
for (int i = (lastIndex - 1)/2; i >= 0; i--) {
//k保存正在判断的节点
int k = i ;
//如果当前K节点的子节点存在
while(k * 2 + 1 <= lastIndex) {
//k节点的左子节点的索引
int biggerIndex = 2 * k +1;
//如果biggerIndex小于lastIndex,即biggerIndex +1代表的K节点的右子节点存在
if(biggerIndex < lastIndex) {
//如果右子节点的值较大
if(data[biggerIndex] < data[biggerIndex + 1]) {
biggerIndex++;
}
}
//如果K节点的值小于其较大的子节点的值
if(data[k] < data[biggerIndex]) {
//交换他们
swap(data, k, biggerIndex);
k = biggerIndex;
} else {
break;
}
}
}
}
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
运行结果
排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
排序法 | 平均时间 | 最小时间 | 最大时间 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n) | O(n2) | 稳定 | O(1) | n小时较好 |
选择排序 | O(n2) | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入排序 | O(n2) | O(n) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数排序 | O(logRB) | O(n) | O(logRB) | 稳定 | O(n) | B是真数(0-9),R是基数(个十百) |
Shell排序 | O(nlogn) | - | O(ns) 1不稳定 |
O(1) |
s是所选分组 |
|
快速排序 | O(nlogn) | O(n2) | O(n2) | 不稳定 | O(logn) | n大时较好 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | 稳定 | O(n) | 要求稳定性时较好 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
速度: 快速排序>>归并排序>>>>>插入排序>>选择排序>>冒泡排序
并且可以看到,选择排序,冒泡排序在数据量越来越大的情况下,耗时已经呈指数型上涨,而不是倍数上涨(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。