排序算法

2021-11-10  本文已影响0人  墨_0b54
菜鸟十大经典排序算法.png
完整代码加测试

冒泡排序(Bubble Sort)

从左往右对比相邻的两个元素,如果左边的元素更大就交换,越大的元素会经由交换慢慢"浮"到数列的顶端。

算法步骤:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    for (int i = 1; i < arr.length; i++) {
        for (int j = 0; j < arr.length - i; j++) {
            if (arr[j].compareTo(arr[j + 1]) > 0) {
                swap(arr, j, j + 1);
            }
        }
    }
    return arr;
}

选择排序(Selection Sort)

相对于冒泡排序,直接遍历并对比整个数组找到最小(大)的元素,然后直接与序列末尾交换,减少交换,但需要一个记录最小(大)元素的指针。

算法步骤:
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    int index;
    for (int i = 0; i < arr.length - 1; i++) {
        index = i;
        for (int j = i + 1; j < arr.length; j++) {
            index = arr[index].compareTo(arr[j]) > 0 ? j : index;
        }
        if (index != i) {
            swap(arr, i, index);
        }
    }
    return arr;
}

插入排序(Insertion Sort)

插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。对于基本有序的序列,速度很快。

算法步骤:
1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    for (int i = 1; i < arr.length; i++) {
        Comparable temp = arr[i];
        int j = i;
        while (j > 0 && temp.compareTo(arr[j - 1]) < 0) { //往前插
            arr[j] = arr[--j];
        }
        arr[j] = temp;
    }
    return arr;
}

希尔排序(Shell Sort)

希尔排序是插入排序的一种更高效的改进版本,但是不稳定。希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

算法步骤:
1.选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
2.按增量序列个数 k,对序列进行 k 趟排序;
3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    for (int step = arr0.length / 2; step >= 1; step /= 2) {//在插入排序最外层加入步长,当步长是1时就是插入排序
        for (int i = step; i < arr0.length; i++) {
            Comparable temp = arr[i];
            int j = i - step;
            while (j >= 0 && temp.compareTo(arr[j]) < 0) {
                arr[j + step] = arr[j];
                j -= step;
            }
            arr[j + step] = temp;
        }
    }
    return arr;
}

归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

算法步骤:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;
5.将另一序列剩下的所有元素直接复制到合并序列尾。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    Comparable[] temp = new Comparable[arr.length];
    sort(arr, 0, arr.length - 1, temp);
    return arr;
}
/**
 * @param arr    排序的数组
 * @param left   左边界索引
 * @param right  右边界索引
 * @param temp   临时数组
 */
private void sort(Comparable[] arr, int left, int right, Comparable[] temp) {
    if (left < right) {
        int mid = (left + right) / 2;
        sort(arr, left, mid, temp);
        sort(arr, mid + 1, right, temp);
        merge(arr, left, right, temp);
    }
}
/**
 * @param arr    排序的数组
 * @param left   左边界索引
 * @param right  右边界索引
 * @param temp   临时数组
 */
private void merge(Comparable[] arr, int left, int right, Comparable[] temp) {
    int mid = (left + right) / 2;
    int i = left; //左序列指针
    int j = mid + 1; // 右序列指针
    int t = 0;
    while (i<= mid && j <= right) {
        if (arr[i].compareTo(arr[j]) <= 0) {
            temp[t++] = arr[i++];
        } else {
            temp[t++] = arr[j++];
        }
    }
    while (i <= mid) {
        temp[t++] = arr[i++];
    }
    while (j <= right) {
        temp[t++] = arr[j++];
    }
    t = 0;
    while (left <= right) {
        arr[left++] = temp[t++];
    }
}

快速排序(Quick Sort)

快速排序又是一种分而治之思想在排序算法上的典型应用,快速排序使用分治法策略来把一个序列分为两个子序列。
在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,它是处理大数据最快的排序算法之一了,可是这是为什么呢。
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

算法步骤:
1.从数列中挑出一个元素,称为 "基准"(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    sort(arr, 0, arr.length - 1);
    return arr;
}
private void sort(Comparable[] arr, int left, int right) {
    if (left >= right) {
        return;
    }
    int start = left;
    int end = right;
    Comparable pivot = arr[start]; //初始化start为基准位
    while (start != end) {
        while (start != end && pivot.compareTo(arr[end]) <= 0) { //从右往左找到比基准小的对象end
            end--;
        }
        if (start != end) {
            arr[start++] = arr[end]; //将end放到基准位,因为end已经与基准比较过,所以start + 1
            //这里有一个隐含的变化,end被放到了start位置,end此时变为基准位
        }
        while (start != end && pivot.compareTo(arr[start]) >= 0) { //然后从左往右找到比基准大的对象end
            start++;
        }
        if (start != end) {
            arr[end--] = arr[start]; // 将start放到基准位end,因为start已经与基准比较过,所以end - 1
            //同上,start被放到了end位置,start此时变为基准位
        }
    }
    arr[start] = pivot; //当start与end相等,start或者end就是基准位,即基准左边都小于基准,基准右边都大于基准
    sort(arr, left, start - 1); //递归排序基准左边数组
    sort(arr, start + 1, right);//递归排序基准右边数组
}

堆排序(Heap Sort)

堆排序是指利用堆这种数据结构所设计的一种排序算法。
堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列。

算法步骤:
1.数组调整为一个堆;
2.把堆首(最大值)和堆尾互换;
3.把堆的尺寸缩小 1,像第一步一样重新调整堆;
4.重复步骤 2,直到堆的尺寸为 1。

实现:

public Comparable[] sort(Comparable[] arr0) {
    if (arr0.length < 2) {
        return arr0;
    }
    Comparable[] arr = Arrays.copyOf(arr0, arr0.length);
    buildMaxHeap(arr);
    for (int i = arr.length - 1; i >= 0; i--) {
        swap(arr, 0, i); //将最大根换到最后
        adjustMaxHeap(arr, i, 0); //调整大根堆,除了最后已经排好序的元素
    }
    return arr;
}
private static void buildMaxHeap(Comparable[] arr) {//构造一个大根堆
    for (int i = arr.length/2 - 1; i >= 0; i--) {//从最后一层
        adjustMaxHeap(arr, arr.length, i);
    }
}
private static void adjustMaxHeap(Comparable[] arr, int size, int i) {//调整大顶堆,使每个子树的父节点都是当前子树的最大值。
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int max = i; //最大值的索引,默认为父节点
    if (left < size && arr[left].compareTo(arr[max]) > 0) { //左子节点与父节点比大小
        max = left;
    }
    if (right < size && arr[right].compareTo(arr[max]) > 0) { //右子节点与父节点比大小
        max = right;
    }
    if (max != i) { //最大点是子节点
        swap(arr, max, i); //与子节点交换位置
        adjustMaxHeap(arr, size, max); //因为子节点发生了改变,要调整子节点
    }
}
上一篇下一篇

猜你喜欢

热点阅读