冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。
如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。大的数往下沉,小的数往下冒。 稳定、原地排序,最好时间复杂度为O(n),最坏与平均时间复杂度为O(n2)。
原地排序指的是空间复杂度为O(1)的排序算法。
稳定性指的是:如果待排序列中存在值相等的元素,经过排序之后,相等元素的先后顺序没有改变 比如我们有一组数据 2,9,3,4,8,3,按照大小排序之后就是 2,3,3,4,8,9。 这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那我们就把这种排序算法叫作稳定的排序算法;如果前后顺序发生变化,那对应的排序算法就叫作不稳定的排序算法。
public class BubbleSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};
bubbleSort(data);
for(int i=0;i<data.length;i++){
System.out.print(data[i]+" ");
}
}
public static void bubbleSort(int[] data){
if(data.length<=1) return;
for(int i=0;i<data.length;i++){
//提前退出冒泡循环的标志位
boolean flag = false;
for(int j=0;j<data.length-1-i;++j){
if(data[j]>data[j+1]){
int temp = data[j];
data[j] = data[j+1];
data[j+1] = temp;
flag = true;//有数据交换
}
}
if(!flag) break;//没有数据交换,退出
}
}
}
我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。 稳定、原地排序,最好时间复杂度为O(n),最坏与平均时间复杂度为O(n2)。
public class InsertSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] data=new int[]{4,3,2,1,5};
insertSort(data);
for(int i=0;i<data.length;i++){
System.out.print(data[i]+" ");
}
}
public static void insertSort(int[] data){
if(data.length<=1) return;
for(int i=1;i<data.length;++i){
int value = data[i];
int j=i-1;
for(;j>=0;--j){
if(data[j]>value){
data[j+1] = data[j];
}else {
break;
}
}
data[j+1]=value;
}
}
}
选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。 不稳定、原地排序,最好时间复杂度为O(n2),最坏与平均时间复杂度为O(n2)。 选择排序是一种不稳定的排序算法。选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。
public class SelectSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};
selectSort(data);
for(int i=0;i<data.length;i++){
System.out.print(data[i]+" ");
}
}
public static void selectSort(int[] data){
if(data.length<=1) return;
for(int i=0;i<data.length-1;++i){
int minIndex=i;
for(int j=i+1;j<data.length;++j){
if(data[j]<data[minIndex]){
minIndex = j;
}
}
int temp = data[i];
data[i] = data[minIndex];
data[minIndex] = temp;
}
}
}
如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。 归并排序用的是一种分治思想,它不是原地排序,即空间复杂度不是O(1),空间复杂度为O(n),不管最好还是最坏或平均,它的时间复杂度都为O(nlogn),是一个稳定的算法。
/**
* 归并排序
* 将需要排序的数组从中间分成两部分,将这两部分分别排序,再将排序好的两部分合并在一起
* 时间复杂度O(nlogn)
* 空间复杂度(n)
*
*/
public class MergeSort {
public static void main(String[] args) {
int[] a= new int[]{1,2,4,3,45,34,23,6,7};
mergeSort(a, 9);
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
private static void mergeSort(int[] a,int n){
mergeIntaily(a, 0, n-1);
}
private static void mergeIntaily(int[] a,int p,int r){
if(p>=r) return;
int q = p+(r-p)/2;
mergeIntaily(a, p, q);
mergeIntaily(a, q+1, r);
merge(a, p, q, r);
}
private static void merge(int[] a,int p,int q,int r){
int i=p;
int j = q+1;
int k=0;
//申请一个数组,大小与a[p..r]相同
int[] tmp = new int[r-p+1];
//用两个游标,一个i指向a[p..q]中的第一个元素,一个j指向a[q+1..r]的第一个元素,
//如果a[i]<=a[j],就将a[i]的值放进tmp数组中,i后移,否则将a[j]元素放进tmp数组中,j后移
while(i<=q&&j<=r){
if(a[i]<=a[j]){
tmp[k++] = a[i++];
}else {
tmp[k++] = a[j++];
}
}
//判断那部分数组剩余
int start = i;
int end = q;
if(j<=r){
start = j;
end = r;
}
//将剩余数组放进tmp中
while(start<=end){
tmp[k++] = a[start++];
}
//在将tmp数组中的元素放回原数组中
for(i=0;i<=r-p;i++){
a[p+i] = tmp[i];
}
}
}
merge(A[p…r], A[p…q], A[q+1…r])
这个函数的作用就是,将已经有序的 A[p…q]
和 A[q+1…r]
合并成一个有序的数组,并且放入 A[p…r]
。
申请一个临时数组 tmp,大小与 A[p…r]
相同。我们用两个游标 i 和 j,分别指向 A[p…q] 和 A[q+1…r]
的第一个元素。比较这两个元素 A[i] 和 A[j],如果 A[i]<=A[j]
,我们就把 A[i] 放入到临时数组 tmp,并且 i 后移一位,否则将 A[j] 放入到数组 tmp,j 后移一位。
继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 A[p…r]
中。
要排序的数组下标从p到r的一组数据,选取从p到r之间的任意一个数据作为pivot(分区点),也是分治思想。 快速排序是原地排序,不稳定,最坏时间复杂度为O(n2),最好和平均时间复杂度都为O(nlogn),空间复杂度为O(1)。
public class QuickSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a= new int[]{1,2,4,3,45,34,23,6,7};
quickSort(a, 9);
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
private static void quickSort(int[] a,int n){
quickSortIntaily(a, 0, n-1);
}
private static void quickSortIntaily(int[] a,int p,int r){
if(p>=r) return; //递归终止条件
int q= partition(a, p, r);
quickSortIntaily(a, p, q-1);
quickSortIntaily(a, q+1, r);
}
//分区函数,随机选择一个元素作为pivot,一般选取从p到r的最后一个元素,然后对a[p..r]分区,返回pivot下标
private static int partition(int[] a,int p,int r){
int pivot = a[r];
int i=p;
for(int j=p;j<r;++j){
if(a[j]<pivot){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
++i;
}
}
int tmp = a[i];
a[i] = a[r];
a[r] = tmp;
return i;
}
}
遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。