1.插入排序
一个很形象的例子:打扑克。
手里的扑克牌(数组)分成两部分,一部分是你手里的牌(已经排好序),一部分是要拿的牌(无序)。然后将拿过来的牌插入到自己的手里面去(把一个无序的数列一个个插入到有序数列中去)。
具体步骤如下:
1.将数组分成已排序和未排序段。初始化已排序的只有一个元素。
2.到未排序取元素插入到已排序段,并保证插入后仍然有序。
3.重复执行上述操作,直到未排序段元素全部加完。
代码如下:
public static void insertionSort(int[] data) {
//i从1开始,第一个默认已经进入排好序段
for (int i = 1; i < data.length; i++) {
int j;
int locData = data[i];
//从已排好序段最后一个开始和未排序段第一个比较
for (j = i - 1; j >= 0; j--) {
if (data[j] > locData) {
//往后挪一个
data[j + 1] = data[j];
} else {
//前面已经排好序了,那么找到一个比他小的就不用找了,因为前面的肯定更小
break;
}
}
data[j + 1] = locData;
}
}
public static void main(String[] args) {
int[] data = {7, 8, 9, 0, 4, 3};
insertionSort(data);
System.out.println(Arrays.toString(data));
}
测试结果为:
[0, 3, 4, 7, 8, 9]
分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
2.希尔排序
希尔排序其实就是插入排序的一个改进版。
希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
代码如下:
public static void shellSort(int[] data){
//和插入排序就多了一个分组
for (int i = data.length/2; i>0 ; i/=2) {
for (int j = i; j <data.length ; j++) {
int k;
int locData=data[j];
for ( k = j-i; k >=0 ; k-=i) {
if(data[k]>locData){
data[k+i]=data[k];
}else {
break;
}
}
data[k+i]=locData;
}
}
}
public static void main(String[] args) {
int[] data={7,8,9,0,4,3,1,2,5,10};
shellSort(data);
System.out.println(Arrays.toString(data));
}
测试结果:
[0, 1, 2, 3, 4, 5, 7, 8, 9, 10]
分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
3.归并排序
归并排序是一种非常高效的排序算法,其核心思想就用到了我们上节课讲的递归和分治的思想。
举个例子:归并排序数组[9,5,6,8,0,3,7,1]
代码如下:
public static void mergeSort(int[] data, int left, int right) {
// 相等了就表示只有一个数了 不用再拆了
if (left < right) {
int mid = (left + right) / 2;
mergeSort(data, left, mid);
mergeSort(data, mid + 1, right);
// 分完了 接下来就要进行合并,也就是我们递归里面归的过程
merge(data, left, mid, right);
}
}
private static void merge(int[] data, int left, int mid, int right) {
//借助一个临时数组用来保存合并的数据
int[] temp = new int[data.length];
//表示的是左边的第一个数的位置
int point1 = left;
//表示的是右边的第一个数的位置
int point2 = mid + 1;
//表示的是我们当前已经到了哪个位置了
int loc = left;
while (point1 <= mid && point2 <= right) {
//如果没有这个=,排序就不稳定
if (data[point1] <= data[point2]) {
temp[loc] = data[point1];
loc++;
point1++;
} else {
temp[loc] = data[point2];
loc++;
point2++;
}
}
//将剩下还没加入临时数组的元素放进去
while (point1 <= mid) {
temp[loc++] = data[point1++];
}
while (point2 <= right) {
temp[loc++] = data[point2++];
}
for (int i = left; i <= right; i++) {
data[i] = temp[i];
}
}
public static void main(String[] args) {
int[] data = {9, 5, 6, 8, 0, 3, 7, 1};
mergeSort(data, 0, data.length - 1);
System.out.println(Arrays.toString(data));
}
测试结果:
[0, 1, 3, 5, 6, 7, 8, 9]
分析:
时间复杂度:O(n*logn)
空间复杂度 O(n)
稳定性:稳定
4.选择排序
选择排序的思路和插入排序非常相似,也分已排序和未排序区间。但选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。但是不像插入排序会移动数组 选择排序会每次进行交换。每一轮都前往后确定了一个数字。
如以下例子:
4 5 6 3 2 1
第一次: 1 5 6 3 2 4
第二次: 1 2 6 3 5 4
第三次:1 2 3 6 5 4
第四次: 1 2 3 4 5 6
代码如下:
public static void selectSort(int[] data) {
for (int i = 0; i < data.length - 1; i++) {
int loc = i;
for (int j = i + 1; j < data.length; j++) {
if (data[j] < data[loc]) {
loc = j;
}
}
//a^a=0,所以一定要loc!=i
if (loc != i) {
data[i] = data[i] ^ data[loc];
data[loc] = data[i] ^ data[loc];
data[i] = data[i] ^ data[loc];
}
}
}
public static void main(String[] args) {
int[] data = {4, 5, 6, 3, 2, 1};
selectSort(data);
System.out.println(Arrays.toString(data));
}
测试结果:
[1, 2, 3, 4, 5, 6]
分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
5.冒泡排序
核心思路:冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。每一轮都从后往前确定了一个数字。
举例说明:
数组[4 5 6 3 2 1],从小到大排序。
第一次冒泡的结果:4 5 6 3 2 1->4 5 3 6 2 1 - > 4 5 3 2 6 1 -> 4 5 3 2 1 6
第二次冒泡的结果:4 5 3 2 1 6->4 3 5 2 1 6 -> 4 3 2 5 1 6 -> 4 3 2 1 5 6
第三次冒泡的结果:4 3 2 1 5 6->3 4 2 1 5 6 -> 3 2 4 1 5 6 -> 3 2 1 4 5 6
第四次冒泡的结果:3 2 1 4 5 6->2 3 1 4 5 6 -> 2 1 3 4 5 6
第五次冒泡的结果:2 1 3 4 5 6->1 2 3 4 5 6
代码如下:
public static void bubbleSort(int[] data) {
for (int i = 0; i < data.length - 1; i++) {
boolean flag=false;
for (int j = 0; j <data.length-1-i ; j++) {
//如果加上了=,就不稳定了
if(data[j]>data[j+1]){
data[j]=data[j]^data[j+1];
data[j+1]=data[j]^data[j+1];
data[j]=data[j]^data[j+1];
flag=true;
}
}
//用标记,当后面已经是排好序的时候,直接跳出循环
if(!flag){
break;
}
}
}
public static void main(String[] args) {
int[] data={4,5,6,3,2,1};
bubbleSort(data);
System.out.println(Arrays.toString(data));
}
测试结果:
[1, 2, 3, 4, 5, 6]
分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
6.快速排序
直接上例子理解:
给数组[45,28,80,90,50,16,100,10]排序。
定基准数:一般就是取要排序序列的第一个。(也可以取三个数的中间值,优化的关键是如何定基准数)
第一次排序基准数:45
从后面往前找到比基准数小的数进行对换:
10 28 80 90 50 16 100 45
从前面往后面找比基准数大的进行对换:
10 28 45 90 50 16 100 80
10 28 16 90 50 45 100 80
10 28 16 45 50 90 100 80
以基准数分为3部分,左边的比之小,右边比之大:
{10 28 16} 45 {50 90 100 80}
到此第一次以45位基准数的排序完成,每一轮后都能使基准到它的最终位置。
代码如下:
public static void quickSort(int[] data, int left, int right) {
// 表示的是从左边找的位置
int ll = left;
// 表示从右边开始找的位置
int rr = right;
//就是我们的基准数,取序列的第一个,不能用data[0]
int base = data[left];
while (ll < rr) {
// 从后面往前找比基准数小的数
while (data[rr] > base) {
rr--;
}
// 表示是找到有比之大的
if (ll < rr) {
data[ll] = data[ll] ^ data[rr];
data[rr] = data[ll] ^ data[rr];
data[ll] = data[ll] ^ data[rr];
ll++;
}
while (data[ll] < base) {
ll++;
}
if (ll < rr) {
data[ll] = data[ll] ^ data[rr];
data[rr] = data[ll] ^ data[rr];
data[ll] = data[ll] ^ data[rr];
rr--;
}
}
// 肯定是递归 分成了三部分,左右继续快排,注意要加条件不然递归就栈溢出了
if (ll > left) {
quickSort(data, left, ll - 1);
}
if (rr < right) {
quickSort(data, ll + 1, right);
}
}
public static void main(String[] args) {
int[] data = {45, 28, 80, 90, 50, 16, 100, 10};
quickSort(data, 0, data.length - 1);
System.out.println(Arrays.toString(data));
}
测试结果:
[10, 16, 28, 45, 50, 80, 90, 100]
分析:
时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定
六种排序的分析总结
这么多种排序算法我们究竟应该怎么选择呢?:
1.分析场景:稳定还是不稳定
2.数据量:数据量小的时候选什么?比如就50个数,优先选插入;如果5000个数据 5000*5000=25000000,就不行了
3.分析空间:
综上所述,没有一个固定的排序算法,都是要根据情况分析的。但是如果你不会分析的情况下 选择归并或者快排。