1.冒泡排序(BubbleSort):有多少数据就得排序多少-1次,双循环,外层循环控制排序次数,边界是0 到 array.length-1,内层循环进行从头到尾的比较,边界是0 到 array.length-外层循环次i的数-1,每次比较把最大的沉底。 稳定的排序算法 ,无优化情况下时间复杂度最好最坏情况都是O(n²),空间复杂度是O(1)。
public static void bubbleSort(int[] array){
//有多少数据就得排序 -1次 因为最后一次就不用再排序了已经有序了
for (int i = 0; i <array.length-1 ; i++) {
//用来判定数据是否已经有序 没有就继续排序 已经有序了就不需要后续排序了直接跳出循环
boolean flag = true;
//每次排序都会新出来一个有序的数据 所以每次比较的次数需要减一
for (int j = 0; j <array.length-1-i ; j++) {
if (array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
flag = false;
}
}
if (flag){
break;
}
}
}
2.选择排序(selectSort):有多少数据就得排序多少-1次,从无序的数据中选出最小的放在队首,再从剩下的数据选出最小的放到有序元素最后一位,双循环,外层循环控制排序好的数据应该放在哪里,边界是0 到 array.length,内层循环对剩下的数据进行比较选出最小的放在外循环的循环到的位置上,边界是0+外循环选择的位置 到 array.length。 不稳定的排序算法, 时间复杂度最好最坏情况下都是O(n²), 空间复杂度是O(1)。
public static void selectSort(int[] array) {
//有多少数据 就需要选择 多少-1 次
for (int i = 0; i <array.length-1 ; i++) {
//每次从剩下数据中选出最大的或者最小的 放到没排序的最前面 所以 j从1开始每次加1
for (int j = i+1; j <array.length ; j++) {
if(array[j] < array[i] ){
int tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
}
}
}
3.直接插入排序(insertSort):第一次假设认为第一个数据是有序的, 从第一个无序的数据记录为tmp 往前找,只要碰见比tmp小的就 tmp就插入到那个数据后面 不用再往前找了 因为前面都是有序的 如果碰见大于tmp的 它就放到他自己的下一位 再继续向前找 直到找到比tmp小的 tmp 就放到哪个比他小的数据后面 或者找到头了 tmp 是最小的 就把 tmp 放到队首,双循环 ,外层循环是确定无序元素的位置 ,边界是 第二个数据 到 array.length,内循环是确定此次外循环走到的位置上的无序数据应该插入到有序数据的哪个位置,边界是 外层循环位置-1 到 0。 稳定的排序算法,时间复杂度,最好情况下(数据本来就有序)是O(n),最坏的情况下是O(n²),空间复杂度为O(1)。
public class InsertSort {
public static void insertSort(int[] array) {
//认为第一个数据是有序的 从无序的数据开始 所以i==1 每次多出来一个有序的 所以 每次 i+1;
for (int i = 1; i <array.length ; i++) {
//记录下来这个无序的数据 最后放在合适的位置
int tmp = array[i];
//从有序的数据开始 向前找 有没有比这个tmp大的数据
int j =i-1;
for ( ;j >= 0 ; j--) {
//如果有比 tmp大的数据 那么这个数据就放到它的下一位
if (array[j] > tmp){
array[j+1] = array[j];
//如果找到了比tmp小的就退出循环 把tmp给比它小的数据的下一位
}else {
break;
}
}
//如果找到了比tmp小的就退出循环 把tmp给比它小的数据的下一位 或者循环走完了 把tmp放到第0位
array[j+1] = tmp;
}
}
4.希尔排序(shellSort):直接插入排序的变种 是把数据分组了然后再排序 ,不稳定的排序算法, 时间复杂度 是O(n^1.3到n^1.5),空间复杂度是O(1).
5.快速排序(quickSort):关键点是把基准放到正确的位置,先再整个数组中 认为数组中第一个数据是基准 ,然后从基准后的第一个数据找到数组最后一个数据,把所有比基准小的数据都放在基准后面,第一个找到的放在基准后的第一位 第二个找的的放在基准后的第二位 以此类推,最后找完所有比基准小的数据后 把基准放到 最后一个找到的比基准小的数据的下一位,这样基准左面就都是比基准小的,基准右边都是比基准大的,之后再分别认为基准左面的是一个单独的数组 右面的是一个单独的数组 两个数组中都不包含基准 再重复上面的操作 ,直到出现只有一个数据的数组为止, 是不稳定的排序算法 , 时间复杂度为O(n*logn)最坏情况下时间复杂度为O(n²),空间复杂度 最好情况下为O(logn)最坏情况下O(n);
//找到基准 把比它小的放它前面 比它大的放它后面
public static int surek(int[] array,int low , int heigh ){
//设定基准值
int pivot = low;
//基准的后一位
int index = low+1;
//从基准的后一位开始直到最后一个数据 这里的i<=有=是因为前面传参的原因 也可一写成i< 只需要改一下quicksort里的传参即可
for (int i = index; i <= heigh ; i++) {
//第一次如果找到了比基准小的值 就把那个值和 index位置的数据换位 然后index++是找基准后的第二位 以此类推
//最后就以基准为界限 前面的所有数据 和后面的所有数据 再重复这个方法即可完成排序
if(array[i] < array[pivot]){
swap(array,index,i);
index++;
}
}
//交换基准和最后一个比基准小的值 因为上面index++了 所以要-1才是最后一个比基准小的值 这样后privot左面全是比它小的右面全是比它大的
swap(array,pivot,index-1);
//返回交换后基准的位置
return index-1;
}
public static void swap(int[] array,int low , int heigh ) {
int tmp = array[low];
array[low] = array[heigh];
array[heigh] = tmp;
}
public static int[] quickSort(int[] array,int low, int heigh) {
//如果low < heigh就说明还有一段数据是没排序的 直到low==heigh只有一个数据了才说明排序完成了
if(low<heigh) {
int k = surek(array, low, heigh);
quickSort(array, low, k - 1);
quickSort(array, k + 1, heigh );
}
return array;
}
6.堆排序(heapSort) :先是要建堆(大根堆),然后每次把堆顶数据和最后一个数据调换位置(每次调换位置就表明找到了剩下的数据中的最大的数据, 就是换位之前的堆顶元素),还要有一个数据记录数组的长度,每次调换一次位置,这个数据就减一,然后调整堆(关键点),直到记录数组长度 的数据减到0为止 ,表示已经排好序了。 是一个不稳定的排序算法 ,时间复杂度无论好坏都是O(n*logn),空间复杂度,最好为O(1)。
如何调整堆(关键点):建堆时就需要第一次调整 从下向上调整,第一次传入的数据是 最后一个父节点 我称之为p((array.length-1-1)/2) 然后根据传入的父亲节点确定左子节点我称之为child(2*p+1)之后 判断是否存在右子节点 ,并且右子节点是否大于左子节点,用较大的节点去和父节点比大小,如果字节点小于父节点, 就此次调整结束,如果子节点大就把子节点和父节点换位,再让p==child ,child == 2*p+1 重复上述操作 直到 子节点位置超出了数组长度为止,此时一次调整结束,然后再次传入 p-- 重复调整 直到 p<0为止 说明整个堆就是大根堆了。
public static void adjust(int[] array,int p ,int len){
//记录左左子节点的位置
int child = 2 * p + 1;
//当子节点没有大于数组长度时循环
while (child<len){
//如果 有右子节点 并且 右子节点大于左子节点 就child++用 右子节点和父节点比较大小
if (child+1 < len && array[child+1] > array[child]){
child++;
}
//如果子节点比父节点大 就换位
if (array[child] > array[p]){
int tmp = array[p];
array[p] = array[child];
array[child] = tmp;
//让换位后的 那个数据 当父节点 再来循环一遍 看看它换位后 是否还符合大根堆 不符合就调整
p = child;
child = 2 * p + 1;
//如果子节点小于等于父节点 因为是从下往上建堆的 以那个父亲节点为根节点的堆 是大根堆 所以直接退出循环;
}else {
break;
}
}
}
public static void createHeap(int[] array) {
//建立大根堆 以父亲节点为基准 从下往上建堆
for (int p = (array.length-1-1)/2; p >= 0 ; p--) {
adjust(array,p,array.length);
}
}
public static void heapSort(int[] array) {
//先建立一个堆 这里建的是大根堆 父亲节点 大于等于 子节点
createHeap(array);
//记录最后一个数据的位置
int end = array.length-1;
//每次换位就是把最大的和最后一个一换 然后数组长度减一 调整减一后剩下的 知道end=0了就是全换完了
while (end != 0){
int tmp = array[0];
array[0] = array[end];
array[end] = tmp;
//换完之后重新调整为大根堆
adjust(array,0,end);
//数组长度减一
end--;
}
7.归并排序(mergeSort)
思想就是先分割 第一次从中间(array.length-1)/ 2 的位置分割 分割成两小块 然后两小块继续这样子分割 直到分割成很多长度为1的小块为止(注意中间位置要包含在左小块中),然后再新建一个长度为两个小块长度之和的数组(用位置靠后的小块减去靠前位置的小块去算长度 因为说分成小块是便于理解 实质上还是那个原本数组 有的小块的在的位置不一定是数组的0下标位置),新建的数组是用来排序两个小块中的数据的,两个小块都从它们的第一个数据开始 比较谁小 小的放进新数组的第一个位置 ,然后小的数据的小块数据位置++,新数组位置++ 再去继续比较,直到某一个小块的元素全部放入到新数组中,之后再把另一个小块剩下的数据全放到新数组中(因为是从长度为1的 小块开始归并的所以剩下的数据也是有序的),之后再用新数组(merge)去更新原数组数据(array),array[i+low] == merge[i] (因为新数组对应的是原数组中的某一块不是从0位置开始的所以 array数组需要加上小块的low下标才能直到这次更新的是那一块)。 是稳定的排序算法,时间复杂度无论是么情况都为为O(nlogn),空间复杂度为O(n);
//排序组合
public static void merge(int[] array,int low,int mid,int high) {
//new 一个长度是两个小块长度和 的数组 这里用high-low是因为 右边的分组的low不是从0开始的 low+high的话长度就会错误
int[] merge = new int[high-low+1];
//新数组的下标
int index = 0;
//左小块的头尾下标
int s1 = low;
int e1 = mid;
//右小块的头尾下标
int s2 = mid+1;
int e2 = high;
while (s1 <= e1 && s2 <= e2){
//左 右两个小块 第一位谁小 就把谁放到merge数组第一位 然后 小的那边的下标加1 merge的下标加1 大的下标不变 重复循环比较
if (array[s1] < array[s2]){
merge[index++] = array[s1++];
}else {
merge[index++] = array[s2++];
}
}
//如果上面循环是因为(s2 >e2) 退出的 就把s1剩下的数据 依次放入merge数组剩下的位置
while (s1 <= e1){
merge[index++] = array[s1++];
}
//同上
while (s2 <= e2){
merge[index++] = array[s2++];
}
//修改参数array数组的数据 修改成排完序后的merge 数组的数据
for (int i = 0; i < merge.length; i++) {
//i+low 是为了修改low不是0 的小块 就是分完后右面的小块
array[i+low] = merge[i];
}
}
//分割数组 low high 分别为数组的头和尾的下标
public static void mergeSortRec(int[] array,int low,int high) {
//因为要分割成长度为1的小块才结束 那时候low == high 所以条件是low < high 但是最后meige传的参数low high是分成两小块前的大块的low high不是那个两个小块的
if (low < high){
//每次从中间分割 mid就是 中间位置下标
int mid = (low+high)/2;
//再把左边的继续分知道分成了左右各一块为止
mergeSortRec(array,low,mid);
//再把右边的继续分知道分成了左右各一块为止
mergeSortRec(array,mid+1,high);
//最后再把一小块一小块排序组合
merge(array,low,mid,high);
}else {
return;
}
}
public static void mergeSort(int[] array) {
//先分割数组
mergeSortRec(array,0,array.length-1);
}