数据结构排序算法

数据结构排序算法

选择排序

选择排序是一种简单的基于交换的排序。时间复杂度为O(N^2),算法的原理是每一次循环就确定一个坐标排好序的状态。

public class Main {
    
    private static int aux[];//辅助数组
    
    public static void main(String[] args) {
        int a[] = {9, 8, 7, 6, 3};
        aux = new int[a.length];
        MergeSort(a);
        System.out.println(Arrays.toString(a));
    }

    /**
     * 交换
     *
     * @param a 数组
     * @param preindex
     * @param lastindex
     */
    private static void Swap(int a[], int preindex, int lastindex) {
        int temp = a[preindex];
        a[preindex] = a[lastindex];
        a[lastindex] = temp;
    }
    
 /**
     * 选择排序
     *
     * @param a
     */
    public static void SelectionSort(int[] a) {
        int length = a.length;
        for (int i = 0; i < length; i++) {
            //外层循环找到是这个坐标对应的最小值。
            int minIndex = i;
            for (int j = i + 1; j < length; j++) {
                if (a[j] < a[i]) minIndex = j;
            }
            Swap(a, minIndex, i);
        }
    }

}

算法特点:

  • 运行时间与输入无关。也就是一个数组无论有序或者无序,在长度一定的情况下,他们所花费的时间是相同的。
  • 数据移动是最少的。整个算法一共需要N次交换(尽管那个数据已经在所在的位置了,算法依然进行了一次交换)交换的次数与数组的长度是线性的关系,这是其他所有排序算法所不具备的。

插入排序

插入排序与打牌时整理牌的过程是一样的。我们拿到属于我们的全部牌后,整理时从左边开始,依次同前一张牌进行比较,如果较小就进行交换,如果前一张牌比这张牌小,那么就停止。因为前面的所有牌都比这张牌小了。

/**
     * 插入排序
     *
     * @param a
     */
    public static void InsertSort(int a[]) {
        int length = a.length;
        for (int i = 1; i < length; i++) {
            //依次与上一个坐标的数字比较,如果小就互换,如果大就停止此次循环。
            for (int j = i; j > 0 && a[j] < a[j - 1]; j--) {
                Swap(a, j, j - 1);
            }
        }
    }

算法特点:

  • 从代码实现中就可以看出,“有序”对插入排序影响十分大(因为有停止条件)
  • 当每个元素距离它的最终位置都不远时;
  • 部分有序。

插入算法的改进

这是插入算法的另一种实现,《算法》一书中说可以节省时间,但是我还没有认真思考。这种升级的实现确实没有进行交换,但是频繁的数组移位应该也是一笔不小时间开销。PS:《算法》P168,2.1.25

 /**
     * 插入排序的升级版
     * @param a
     */
    public static void InsertUpdateSort(int a[]) {
        int length = a.length;
        for (int i = 1; i < length; i++) {
            int temp = a[i];
            int j;
            for (j = i; j > 0 && temp < a[j - 1]; j--) {
                a[j] = a[j - 1];
            }
            //说明temp比a[j-1]要大了,那么就是a[j]就是它这次循环的归宿。
            //或者j=0了,触发了另一个终止循环的条件。那么a[j]也是它这次循环的归宿
            a[j] = temp;
        }
    }

希尔排序

希尔排序是多次的插入排序,该算法的思想是每隔h个为一组,对这个组进行排序。排完之后再对下一个组进行排序。

如:

1,5,9;一组。

2,6,10;二组。

3,7,11;三组;

……

这都是坐标。

当每隔h组排完之后,在每隔h/2成一组再排序,一直到h=1变为了插入排序在最后依次排序。

Q:为什么不直接使用插入排序呢?

A:一般情况下,希尔排序的要比插入排序更快。

    /**
     * 希尔排序
     *
     * @param a
     */
    public static void ShellsSort(int a[]) {
        int length = a.length;
        int h = length / 2;
        while (h >= 1) {
            //注意,代码最终的效果一样,并不是把一个组内所有的数据都拿出来然后归并排序。
            for (int i = h; i < length; i++) {
                for (int j = i; j >= h && a[j] < a[j - h]; j -= h) {
                    swap(a, j, j - h);
                }
            }
            h /= 2;
        }
    }

归并排序

归并排序是一种基于分治策略的排序方法。它的主要方法是将数组一直对半分组,分到每一组中只有一个数字为止。需要注意的是这些分组并不是一个新的数组,而是使用坐标记录下这个虚拟分组的起止位置。这是数组的“排序”阶段。

接下来是归并。归并一定是两个相邻的数组才可以归并,而这两个数组之间通过一个坐标进行隔断。例如:low,mid,high。这里的mid就是隔断点。[low,mid]是一个数组,[mid+1,high]是另一个数组。

 /**
     * 归并排序
     * @param a
     */
    public static void MergeSort(int a[]) {
        sort(a, 0, a.length - 1);
    }

    /**
     * 分治
     * @param a
     * @param lo
     * @param hi
     */
    private static void sort(int a[], int lo, int hi) {
        if (hi<=lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(a, lo, mid);
        sort(a, mid+1, hi);
        merge(a, lo, mid, hi);
    }

    /**
     * 归并操作
     * @param a
     * @param lo
     * @param mid
     * @param hi
     */
    private static void merge(int a[], int lo, int mid, int hi) {
        //在这里的操作,
        int i=lo;
        int j = mid + 1;
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }

        for (int k = lo; k <= hi; k++) {
            if (i>mid) a[k] = aux[j++];
            else if (j>hi) a[k] = aux[i++];
            else if(aux[j]<aux[i]) a[k] = aux[j++];
            else a[k] = aux[i++];
        }
    }

快速排序

快速排序是一种基于分治策略的排序,一般认为是最优秀的内部排序方法。

算法的基本思路是挑选一个点,设其为基点,建立两个指针,右指针然后从数组的右边开始向前依次查询,当碰到一个数小于基点时暂停;左指针从数组的左边开始向后一次查询,当碰到一个数大于基点时暂停,随后交换这两个数。依次进行,直到这两个指针相遇或者右指针跑到了左指针的左面。

当一次快速排序结束时,基点便位于它最终的位置,即它的左边都是不大于它的数,它的右边都是不小于它的数。随后以基点作为分界线,它的左边数组继续进行快速排序,右边数组也进行快速排序。这些都是在原来数组上以index来进行划分的。最终整个数组都是有序的 。

一般挑选数组最左边的数作为基点。

  /**
     * 快速排序
     * @param a
     * @param lo
     * @param hi
     */
    private static void QuickSort(int a[], int lo, int hi) {
        if (lo >= hi) return;
        int mid = partionsh(a, lo, hi);
        QuickSort(a, lo, mid);
        QuickSort(a, mid + 1, hi);
    }

    /**
     * 筛选出基点
     * @param a
     * @param lo
     * @param hi
     * @return
     */
    private static int partionsh(int[] a, int lo, int hi) {
        int begin = a[lo];
        int i = lo;
        int j = hi;

        while (true) {

            //貌似必须从数组的右边先开始,如果和下面的代码交换顺序,会导致崩溃。
            while (i < j && a[--j] > begin) {
                if (j <= lo) break;
            }
            while (i < j && a[++i] < begin) {
                if (i >= hi - 1) break;
            }

            if (i >= j) {
                break;
            }
            Swap(a, i, j);
        }
        Swap(a, lo, i);
        return i;
    }
原文地址:https://www.cnblogs.com/HyPhoenix/p/13779696.html