冒泡排序
- 从头到尾依次比较相邻两个元素的大小。
- 如果a[i]>a[i+1],则交换元素,让最大元素排在最后。
- 两两都比较一遍为一轮冒泡。
最初冒泡排序
private static void bubbleSort(int[] arr) {
for (int i=0;i< arr.length-1;i++){
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
System.out.println(Arrays.toString(arr));
}
}
private static void swap(int[] arr, int j, int i) {
int t=arr[j];
arr[j]=arr[i];
arr[i]=t;
}
优化比较次数
-
每轮比较 最后一位肯定是已经排好了,每轮比较次数依次-1
private static void bubbleSort(int[] arr) { for (int i=0;i< arr.length-1;i++){ //每轮比较次数-1 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j]>arr[j+1]){ swap(arr,j,j+1); } } System.out.println(Arrays.toString(arr)); } }
-
判断数组有序后提前退出循环,通过标记方法判断是否发生交换
private static void bubbleSort(int[] arr) { for (int i=0;i< arr.length-1;i++){ boolean swapped=false;//判断是否发生交换 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j]>arr[j+1]){ swap(arr,j,j+1); swapped=true; } } System.out.println(Arrays.toString(arr)); if(!swapped){ break; } } }
-
保留每轮最后发生交换元素的索引,索引后面的元素都已经有序。如果索引为0,代表整个数组都有序,退出循环。
private static void bubbleSortV2(int[] arr) { int n= arr.length-1;//每轮比较次数 int i=0;//记录比较轮数 do { int last = 0;//最后一次交换的索引位置 for (int j = 0; j < n; j++) { System.out.println("比较第" + j + "次"); if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); last = j; } } n = last; System.out.println("第" + (i++) + "轮" + Arrays.toString(arr)); } while (n != 0); }
选择排序
将数组分为两个部分,前半部分已排序,后半部分未排序。每轮从未排序部分找出最小值放入已排序部分。
优化:每轮比较只记录索引,最后再交换,减少交换次数。
private static void selectionSort(int[] arr) {
for (int i = 0; i <arr.length-1; i++) {
//i 表示每轮最小值需要交换的索引
int min=i;//默认第一个索引数 最小值
for (int j = min+1; j < arr.length - 1; j++) {
if (arr[min]>arr[j]){
min=j;//记录最小值索引
}
}
//最小值索引不等于 初始索引时
if (min!=i){
// 交换
swap(arr,min,i);
}
System.out.println(Arrays.toString(arr));
}
}
插入排序
数组分为两个区域:已排序区+未排序区
- 每轮从未排序区取出第一个元素插入排序区。
- 优化:待插入的元素遇到比自己小的元素,代表插入位置。无需继续比较。
void insertSort(int[] a){
for(int i=0;i<a.length;i++){
int t=a[i];//待插入的元素值
int j=i-1;//已排序区的元素索引
while(j>=0){
if(t<a[j]){//待插入元素与前面的元素比较
a[j+1]=a[j];
}else{
break; //前面的元素已经有序
}
j--;
}
a[j+1]=t;//最后元素插入的位置
System.out.println(Array.toString(a));
}
}
希尔排序
对插入排序进行优化:解决大数全在数组前半部分问题。导致大数移动次数很多。
选取间隙分组比较的方法:
- 间隙4:分3组【1,5,9】【2,6,10】【3,7】
- 间隙2:分2组【1,3,5,7,9】【2,4,6,8,10】
快速排序
- 算法时间复杂度:O(nlog2n),最坏时间复杂度O(n2)
- 数据量大时,优势明显;因为分区查找。
- 不稳定排序。
每轮选一个基准点进行分区 - 小于基准点的元素进入一个分区,大于基准点的元素进入另一个分区。
- 重复直到子分区元素个数小于等于1。
单边循环快排(lomuto洛穆托分区方案)
- 选择最右侧元素为基准点元素
- J 指针负责找比基准点小的元素,一旦找到与i交换。
- I 指针维护小于基准点元素的边界,也是每次交换的目标索引。
- 最后基准点与i交换。
//递归方式排序,左右分区
quick(int[] a,int l,int h){
if(l>=h){return;}
int p=partition(a,l,h);//已确定元素位置
quick(a,l,p-1);
quick(a,p+1,h);
}
//独立分区排序规则,返回基准点元素边界的最后位置
int partition(int[] a,int l;int h){
int pv=a[h];//最右侧基准点
int i=l;//i:小于基准点边界,初始0
for(int j=l;j<h;j++){
//j:遍历数组找小于基准点的元素,与i交换
if(a[j]<pv){
if(i!=j){
swap(a,i,j);
}
i++;//小于基准点的边界后移
}
}
swap(a,h,i);//基准点与边界元素交换。
return i;//返回边界索引
}
双边循环快排(与霍尔相似)
- 选择最左侧元素作为基准点元素
- j 指针负责从右向左找比基准点小的元素
- i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至i,j相交。
- 最后基准点与i(此时与j相等)交换,i即为分区位置。
- 基准点左边,先找小于基准点的元素,最后 j 停留位置也就是小于基准点的元素,再找大的。
//独立分区排序规则,返回基准点元素边界的最后位置
int partition(int[] a,int l;int h){
int pv=a[l];//最左侧基准点
int i=l; //从左向右找大于基准点的元素
int j=h;//从右向左找小于基准点的元素
while(i<j){
//第二层循环:i<j;避免发生i越过j,反而把大数交换到前面;
while(i<j&&a[j]>pv){
//j:找小于pv的元素
j--;
}
//必须小于等于,i初始指向基准点元素。
while(i<j&&a[i]<=pv){
//i:找大于pv的元素
i++;
}
//i和j交换位置
swap(a,i,j);
}
//基准点与i交换,形成分区边界
swap(a,l,j);
return j;//返回边界索引
}