常用的排序算法

106 阅读4分钟

一、冒泡排序

  1. 原理:比较两个相邻的元素,如果说前一个元素的值比后一个元素的值大,则交换

  2. 思路: N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数

  3. coding

public static void bubbleSort(int[] arr){
    for (int i=0;i<arr.length-1;i++){
        for (int j = 0;j<arr.length-1-i;j++){
            if (arr[j]>arr[j+1]) {
                swap(arr, j, j+1);
            }
        }
    }
}
//这样做的前提,一定要保证不是同一块内存地址
public static void swap(int[] arr,int i, int j){
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

二、选择排序

  1. 原理:如果一个数组中有N个元素,首先从这N个数中挑出来一个最小的放到 [0] 的位置上,然后再从N-1个数中挑出一个最小的放到[1]位置上......依次类推
  2. coding
public static void selectSort(int[] arr){
    if (arr.length == 0 || arr == null || arr.length == 1){
        return;
    }
    int minIndex;
    for (int i=0;i<arr.length;i++){
        minIndex = i;
        for (int j = i+1;j<arr.length-1;j++) {
            minIndex = arr[minIndex]>arr[j]?j:minIndex;
        }
        swap(arr,i,minIndex);
    }
}

public static void swap(int[] arr ,int i , int j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

三、插入排序

  1. 原理:构建一个有序的序列,首先让他在 0 - 0上有序 然后就是在 0 - 1 上有序 0 - 2 有序 ...... 一直到0-N上有序。类似于我们抓牌,当抓到一张较小的拍的时候,我们通常是把它放在比它大的前一个位置。

  2. coding

public static void insertSort(int[] arr){
    // 0 - 0 上有序
    // 0 - N 上有序
    for (int i=1;i<arr.length;i++){
        for (int j=i-1;j>=0;j--){
            if (arr[j]>arr[j+1]){
                swap(arr,j,j+1);
            }
        }
    }
}
public static void swap(int[] arr,int i, int j){
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

四、归并排序

  1. 原理:核心的一个思想就是分而治之,将一整个较大规模的数据,进行一步一步的进行拆解,最后合并到一块

我是感觉这个图画的就已经很清楚了 image.png

首先看Merger的部分,如何进行Merge

  • 过程:
    1. 准备一个临时的数组help
    2. 准备变量 i 用来记录 help数组的下标,p1 记录 arr数组的左边界, p2用来记录arr数组的右边界
    3. 在左边这部分的数组 和 在右边者部分的数组在不越界的情况下,如果谁的值小,谁就给help 同时 i++ p1++ 或者 p2++
    4. 当有一个不满足这个条件的时候退出循环,剩下的数依次放入到help 数组中
    5. 将help数组中的元素给arr
public static void merger(int[] arr , int L , int M , int R){
    int[] help = new int[R-L+1];
    int i=0;
    int p1=L;
    int p2=M+1;
    while(p1<=M && p2<=R){
      help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1<=M){
        help[i++] = arr[p1++];
    }
    while(p2<=R){
        help[i++] = arr[p2++];
    }
    for (i=0;i<help.length;i++){
        arr[L+i] = help[i];
    }
}
  • 递归过程:
    1. 当L == R 递归终止条件
    2. 完成左边有序
    3. 完成右边有序
public static void process(int[] arr, int L , int R){
    if (L == R){
        return;
    }
    int mid = L + ((R-L)>>1);
    process(arr,L,mid);
    process(arr,mid+1,R);
    merger(arr,L,mid,R);
}

五、快速排序

  1. 原理: 荷兰国旗的问题 + 随机值的选取

    首先:要知道荷兰国旗问题,将一个数组分成左边小于某个number,中间的值是 number , 右边是大于number的值

  • 解决思路:
    1. 将这个数组划分成两个边界分别是,小于边界和大于边界。
    2. 当 i 位置上的数小于 number 的时候 ,左边界的下一个值和当前元素交换 , i 跳下一个 , 小于区域进行右阔。
    3. 当 i 位置上的值等于 number 的时候 , i直接跳下一个。
    4. 当 i 位置上的元素大于 number 的时候 ,i位置上的元素和大于区域的前一个元素经行交换,然后 i 位置不动,大于区域向左边扩展。
  • 荷兰国旗Coding
public static int[] partition(int[] arr , int L , int R){
    int less = L - 1;   //小于区域的右边界
    int more = R;       //大于区域的左边界

    while(L<more){      //L 代表当前数的位置 arr[R] ---> 划分值
        if (arr[L] < arr[R]){
            swap(arr,++less,L++);
        }else if (arr[L] > arr[R]){
            swap(arr,--more,L);
        }else {
            L++;
        }
    }
    swap(arr,more,R);
    return new int[]{less+1,more};          //这里返回的是左边界和右边界的位置
}
  • 划分值的选取 如果说划分值选的很偏,可能会退化成O(N^2), 举例:{1,2,3,4,5,6,7,8,9}
public static void quickSort(int[] arr,int L , int R){
    if (L<R){
        swap(arr,L + (int)(Math.random() * (R-L+1)),R);
        int[] p = partition(arr,L,R);
        quickSort(arr,L,p[0]-1);            //小于区域做递归
        quickSort(arr,p[1]+1,R);            //大于区域做递归
    }
}

六、堆排序

  1. 原理:heapInsert + heapIfy
  • 堆排序Coding
public static void heapSort(int[] arr){
    if (arr == null || arr.length<2)
        return;
    //首先变成大根堆
    for (int i=0;i<arr.length;i++){
        heapInsert(arr,i);
    }
    int heapSize = arr.length;
    swap(arr,0,--heapSize);   //让 索引 为0的位置和最后一个位置的元素进行交换
    while(heapSize>0){
        heapify(arr,0,heapSize);
        swap(arr,0,--heapSize);
    }
}
  • heapInsert
  • 调整成大根堆,然当前元素和它的父元素进行比较,如果当前元素大于它的父,那么让它进行向上窜
//变成大根堆,向上窜的过程
public static void heapInsert(int[] arr , int index){
    //arr[index] > arr[(index-1)/2]  当前位置上的数和,父位置上的数进行比较,如果大与父位置上的数,则进行交换
    while(arr[index] > arr[(index-1)/2]){
        swap(arr,index,(index-1)/2);
        index = (index-1)/2;
    }
}
  • heapIfy
  • 同样是调整堆结构,这个heapIfy是将小的元素向下沉
public static void heapify(int[] arr,int index,int heapSize){
    int left = index*2+1;           //左孩子的下标
    while(left<heapSize){            //左边的下标和heapSize作比较,小于heapSize证明没有越界
       //比较两个孩子的值,谁的值大就将谁的下标给 largest
       int largest = left+1<heapSize && arr[left+1]>arr[left]?left+1:left;
       //父亲和孩子之间,谁的值大,把下标给largest
        largest = arr[largest] > arr[index] ? largest:index;
        //当前节点下面没有更大的节点了 break
        if (largest == index){
            break;
        }
        swap(arr,largest,index);
        index = largest;
        left = index*2+1;
    }
}

总结:

image.png