八大排序算法java实现

来源:互联网 发布:南京雨花台区网络问政 编辑:程序博客网 时间:2024/05/01 18:27

排序

插入排序

直接插入排序

package ccnu.allSort;public class InsertSort {    /**     * 在有序子序列中寻找插入位置时,只要满足前面的元素要比当前需要插入元素要大时,就要将这个前面的元素后移,直到     * 当前判断的这个元素小于等于当前需要插入元素时,就可以将需要插入的这个元素插入到当前判断的元素的后面即可,当所     * 有需要插入的元素插入完毕后,整个表就将是有序表     *      * @param arr     *            待排序列     */    public static void insertSort(int[] arr) {        int i, ii, temp;        for (i = 1; i < arr.length; i++) {// 逐个将索引[1, arr.length - 1]处的元素插入到前面有序的子序列中            temp = arr[i]; // 将当前要插入的元素保存            for (ii = i - 1; ii >= 0 && arr[ii] > temp; ii--) {// ii >= 0保证在第一个元素要结束比较,此时插入到整个序列中的第一个位置                arr[ii + 1] = arr[ii];            }            arr[ii + 1] = temp; // 将本次要插入的元素插入到有序子序列中,找到的索引位置为ii+1(退出for时,ii--)        }    }}

折半插入排序

package ccnu.allSort;public class InsertSort2 {    /**     * 折半插入排序是首先将当前要插入的元素的位置在前面的有序序列中找到,然后将所有元素后移,最后用将待插入元素插入到找到的位置上     * 直到所有需要插入的元素插入完毕,整个表就是有序表(与直接插入排序(一边找一边后移)不同是,它是首先找到插入位置,一次性后移,然后将需要插入的元素插入到找到的位置上)     *      * @param arr     *            待排序列     */    public static void insertSort2(int[] arr) {        int i, ii, low, mid, high, temp;        for (i = 1; i < arr.length; i++) {            temp = arr[i];            // 当前有序子序列为[0, i - 1]            low = 0;            high = i - 1;            while (low <= high)// 当low==high时,还要继续判断是插入在low(high)的左边还是右边            {                mid = (low + high) / 2;                if (arr[mid] > arr[i]) {                    high = mid - 1;                } else if (arr[mid] == arr[i]) {// 当中间元素恰好等于待插入元素时,就将这个待插入元素直接插入到这个中间位置的右边第一个位置(mid+1),减少比较次数,但是这样却导致这个排序算法不稳定,如序列(1 1 1 1 1)                    high = mid;                    break;                } else {                    low = mid + 1;                }            }            for (ii = i - 1; ii >= high + 1; ii--) {// 将索引为[i-1, high+1]的所用元素后移,其中i-1为当前要插入元素索引的前一个索引,high+1为当前要插入元素的索引位置                arr[ii + 1] = arr[ii];            }            arr[high + 1] = temp;        }    }}

希尔排序

package ccnu.allSort;public class ShellSort {    /**     *      * 由于直接插入排序算法适用于基本有序的排序表(因为基本有序就会使得前后移动的次数减少),那么就可以将待排序表分成若干个子表,进行各自排序.     * 开始取一个步长d,按照d进行分组(必然会将整个待排序列分为d组),然后将各个组按照直接插入排序进行排序,当每一组均自身有序时,步长减少,再次分组排序,最后当步长     * 为1时(此时整个序列已然基本有序了),再次进行直接插入排序就会使得整个序列有序     *      * @param arr     *            待排序列     */    public static void shellSort(int[] arr) {        int d; // 将序列进行分割成多个子序列的步长        int i, ii, temp;        for (d = arr.length / 2; d >= 1; d /= 2) {// 将整个序列按照步长为d分为d组,当步长为1时,再次排序后,那么整个序列将会为有序            for (i = d; i < arr.length; i++) {// 从第一组第二个元素开始,当i+1时,表示第二组的第二个元素,当i为2*d-1时表示最后一组的第二个元素,那么当i为2*d时,此时前面的d组中的每一组自身均是有序                temp = arr[i];                for (ii = i - d; ii >= 0 && arr[ii] > temp; ii -= d) {// 对每一组(步长为d)的内部进行直接插入排序排序                    arr[ii + d] = arr[ii];                }                arr[ii + d] = temp;            }        }    }}

选择排序

简单选择排序

package ccnu.allSort;public class SelectSort {    /**     * 每次从无序的子序列(当然起初无序子序列就是原待排子序列本身)中找出一个最小的元素与当前这个无序子序列的第一个元素进行交换,直到无序子序列为空,也就是所有元素均排好序     *      * @param arr     *            待排序列     */    public static void selectSort(int[] arr) {        int i, ii, temp;        int min;// 当前这个无序子序列中的最小元素的索引        for (i = 0; i < arr.length; i++) {            min = i;// 首先将当前这个无序子序列[i, arr.length-1]的第一个元素看作最小            temp = arr[i];            for (ii = i + 1; ii < arr.length; ii++) {                if (arr[ii] < arr[min]) {// 如果后面又出现比当前元素更小的元素,就将当前这个更小的元素的索引赋值给min                    min = ii;                }            }            // 将当前无序子序列的第一个元素与最小元素进行交换            arr[i] = arr[min];            arr[min] = temp;        }    }}

堆排序

package ccnu.allSort;public class HeapSort {    /**     * 堆排序是将一个待排序表按照一棵完全二叉树的顺序存储结构进行存储,再利用完全二叉树中的双亲节点与其子节点的内在关系在当前的无序区选择最大(最小)元素     * 首先将这个无序表(完全二叉树的顺序存储结构)中的所有根节点依次进行大根堆调整,直到整个表满足大根堆性质,接着就可以取堆顶元素(当前无序表最大元素)与     * 当前无序表的最后一个元素进行交换,此时减少一个元素(与上一次相比)的无序表已破坏大根堆性质,就要继续调整这个无序表,调整好之后,再次交换,再次调整,直     * 到无序表只剩下一个元素为止     *      * @param arr     *            待排序列     */    public static void heapSort(int[] arr) {        buildMaxHeap(arr);// 将待排序表调整为一个大根堆,利用完全二叉树的顺序存储结构将其存储        for (int i = arr.length - 1; i > 0; i--) {// 当整个序列的初始大根堆建立完成后,就要将最大元素(整个序列堆顶,索引0处)与当前无序序列的最后(索引i处,当然第一次为索引arr.length-1处)进行交换,这样就会破坏大根堆的性质那么就需要继续以根节点arr[0]进行向下调整(每一次都是调整这个节点)            int temp = arr[i];            arr[i] = arr[0];            arr[0] = temp;            adjust(arr, 0, i);// 因为交换后,破坏大根堆性质的根节点是arr[0],所以每一次需要调整这个根节点,每一次调整序列的元素个数比上次少一个(因为每一次已经选出一个最大元素放在当前无序序列的最后)        }    }    private static void buildMaxHeap(int[] arr) {        // 我们知道将一个顺序表按照完全二叉树的顺序存储结构来存储,它的所有根节点的索引为[0,        // arr.length/2],任一个根节点i的孩子的索引为2*i与2*i+1(右孩子如果存在)        for (int i = arr.length / 2; i >= 0; i--) {// 对完全二叉树的每一个节点[arr.length/2, 0]进行满足大根堆的调整            adjust(arr, i, arr.length);        }    }    /**     *      * @param arr     *            待调整的序列     * @param k     *            当前需要调整的根节点的索引     * @param length     *            对于整个序列中待调整元素的个数(当然当第一次对整个序列建立大根堆的时候,每一次调整的元素个数应当是arr.length,当整个序列的初始大根堆建立完成     *            后,将最大元素(当前序列的堆顶)与当前无序序列的最后一个元素交换后,就会破坏大根堆性质,就需要重新调整,此时需要调整的元素个数必然要比上一次调整的个数少一个(堆     *            顶元素与当前无序序列的最后一个元素交换后,从这个索引位置到整个序列最后一个索引均有序))     */    private static void adjust(int[] arr, int k, int length) {        for (int i = 2 * k; i < length; i *= 2) {// 当前根节点arr[k]的左孩子arr[2*k]            if (i < (length - 1) && arr[i] < arr[i + 1]) {// i < (length-1)表示当arr[i]为最大根节点时,它有左孩子,但是有可能没有右孩子,就不需要比较两个孩子arr[i] < arr[i+1](另一方面索引出现越界)                i++;// 找出根节点arr[k]的两个孩子arr[2*k]与arr[2*k+1]的比较大的一个,注意当左孩子arr[2*k]较大时,说明以右孩子arr[2*k+1]为根的树仍然满足大根堆性质,不需处理,当右孩子较大时,同理            }            if (arr[k] >= arr[i]) {// 如果当前根节点不小于其左右孩子,说明以此根节点的二叉树满足大根堆性质,无需再进一步调整了                break;            } else {// 如果当前根节点小于其孩子节点中的一个,就要将这个较大的孩子移到当前堆顶处(statement1),然后将堆顶元素移到这个较大孩子节点处(statement2),接着以这个较大的孩子节点为根节点(如果存在)继续调整(也就是i*=2)                int temp = arr[k];                arr[k] = arr[i]; // statement1                arr[i] = temp; // statement2                k = i; // 下一个待处理的根节点(如果存在)就是当前处理根节点的两个孩子中的较大者            }        }    }}

交换排序

冒泡排序

package ccnu.allSort;import java.util.List;public class BubbleSort /* implements java.util.Comparator<String> */ {    /**     * 冒泡排序就是将大的元素向序列尾部移动,小的元素向上浮起     * 第一趟;执行过程为,第一个元素与第二个元素进行比较,大的元素在后面,接着将第二个元素与第三个元素比较,大的在后面,依次进行,当一趟比较完毕后,最大的元素就是当前     * 无序序列的最后一个元素,第二趟同样选出最大元素放入当前无序序列(就是不含前几趟已选出的元素)的最后一个,依次进行     *      * @param arr     *            待排序列     */    public static void bubbleSort(int[] arr) {        for (int i = 1; i < arr.length; i++) {// 比较趟数            boolean isSorted = true;            for (int ii = 0; ii < arr.length - i; ii++) {// 当前趟下比较次数                if (arr[ii] > arr[ii + 1]) {                    isSorted = false;                    int temp = arr[ii];                    arr[ii] = arr[ii + 1];                    arr[ii + 1] = temp;                }            }            if (isSorted) {// 当前趟比较完后,没有元素交换位置,表示已有序,不必进行下一趟                break;            }        }    }}

快速排序

package ccnu.allSort;public class QuickSort {    /**     * 首先取一个基准值(一般取待排序列的第一个元素),将比这个基准值不大的放在它的左边,比这个基准值不小的放在它的右边,这样就可以将这个基准值放在整个序列的最终位置上,接着     * 以这个基准值的索引将这个序列分割为两个子表,前后这两个子表再次进行同样的分割,依此分割下去,直到所有子表不能分割(前索引不小于后索引)     *     * @param arr     *            待排序列     * @param low     *            待排序列的最小索引     * @param high     *            待排序列的最大索引     */    public static void quickSort(int[] arr, int low, int high) {        int indexPivot;        if (low < high) {            indexPivot = partition(arr, low, high);// 返回[low,                                                    // high]序列以基准值为标准的指针(索引),以这个基准将这个序列分为左边不大于基准值的序列和右边不小于基准值的序列            quickSort(arr, low, indexPivot - 1);            quickSort(arr, indexPivot + 1, high);        }    }    private static int partition(int[] arr, int low, int high) {        int pivot = arr[low];// 每一次划分都是以[low, high]序列中的第一个元素作为基准        while (low < high) // low != high即可        {// 当当前序列含有至少两个元素,就可以划分            while (low < high && arr[high] >= pivot) {// 当右边元素(从high开始)大于或者等于基准时,high指针就会左移,其中条件low<high是为了不断左移到low处                high--;            }            arr[low] = arr[high];// 当出现右边元素小于基准时(或者low=high,就会直接退出循环),将这个元素赋给指针为low的元素            while (low < high && arr[low] <= pivot) {// 当左边元素(从low开始)小于或者等于基准时,low指针就会右移,其中条件low<high是为了不断右移到high处                low++;            }            arr[high] = arr[low];// 当出现左边元素大于基准时(或者low=high,就会直接退出循环),将这个元素赋给指针为high的元素        }        // 当low==high时,退出循环,就将基准值赋给low或high指针处的元素        arr[low] = pivot;        return low;    }}

快速排序优化建议

  • 基准的选取可以是从索引low,(low+high)/2,high三个中选取中间大小的,而不总是low处,这样就可以尽量避免递归树的不平衡
    int mid = (low + high) / 2;
    int[] tmp = new int[]{arr[low], arr[mid], arr[high]};
    Arrays.sort(tmp);
    int pivot = tmp[1];
  • 当某一次递归时,其处理数组中的元素个数(high-low+1)小于某个设定的值(我们假定这个阈值为MAX_LENGTH=7)就不需要再使用递归了,而是直接使用直接插入排序,这样效率会更高
    if((high - low + 1) > MAX_LENGTH){
    // 使用递归
    // ...
    }else{
    // 直接使用插入排序
    // ...
    }
  • 使用尾递归,而不是双递归
while(low < high){    indexPivot = partition(arr, low, high);    quickSort(arr, low, pivot - 1);    low = pivot + 1;    }
  • 当通过某个基准值进行将当前待排序列分为两个子部分后,应当先递归序列长度较短的子序列,然后再递归子序列长度较长的那一部分,这样可以降低递归栈的深度
if(indexPivot - low <= high - indexPivot){ // 左边子序列长度小于等于右边    quickSort(arr, low, indexPivot - 1);    quickSort(arr, indexPivot + 1, high);}else{    quickSort(arr, indexPivot + 1, high);    quickSort(arr, low, indexPivot - 1);}

归并排序

package ccnu.allSort;public class MergeSort {    /**     * 对于待排序中的arr.length个元素,可以首先将这个待排序表不断的分割,直到将整个表分割为arr.length个长度为1的子表,那么此时我们可以看作这arr.length个子表均是有     * 序的,接着就可以将这些有序子表两两归并为一个有序子表,如此进行下去,直到归并为一个长度为arr.length的表,此时就可以得到原表的有序表(对于一个无序表arr[low,high]来     * 说,可以将其分为两个部分,然后将前后两个部分归并为一个表(在将前后两个子表归并为一个子表前,要保证这两个子表必须自身已有序,所以就要将表不断的分割为长度为1的子表,此时自然有序了))     *      * @param arr     *            当前待排序表     * @param low     *            当前待排序中的第一个索引     * @param high     *            当前待排序表中的最后一个索引     */    public static void mergeSort(int[] arr, int low, int high) {// 对于一个无序表arr[low,high]来说,可以将其分为两个部分,然后将前后两个部分归并为一个表(在将前后两个子表归并为一个子表前,要保证这两个子表必须自身已有序,所以就要将表不断的分割为长度为1的子表,此时自然有序了)        if (low < high) {            int mid = (low + high) / 2;            mergeSort(arr, low, mid); // 当前表(arr[low,high])分割的前半部分            mergeSort(arr, mid + 1, high); // 当前表(arr[low,high])的后半部分            merge(arr, low, mid, high); // 如果当前表的长度为2时,再次进行前后分割时,均会出现分割失败,此时就可以将这两个表(有序)归并了        }    }    private static void merge(int[] arr, int low, int mid, int high) {        int[] temp = new int[arr.length]; // 将arr序列暂存在temp中,然后再将从temp序列中选出元素放入arr中        for (int i = 0; i < arr.length; i++) {            temp[i] = arr[i];        }        int i = low; // 归并序列中前一个序列的开始索引        int j = mid + 1; // 归并序列中后一个序列的开始索引        int k = i; // 将temp中选出的元素放入arr中的索引,当然k初始值是这个归并序列的首个索引,然后以此递增,直到这个归并序列的最后一个索引        while (i <= mid && j <= high) {// 当两个子表均还没有遍历完所有元素            if (temp[i] <= temp[j]) {// 当前一个子表中的元素不大于后面一个子表中时,自然地就要将前一个子表中的这个较大元素插入到arr[k]中(k指示当前需要插入到arr中的位置)                arr[k++] = temp[i++];            } else {// 当前一个子表中的元素大于后面一个子表中时,自然地就要将后一个子表中的这个较大元素插入到arr[k]中(k指示当前需要插入到arr中的位置)                arr[k++] = temp[j++];            }        } // while            // 下面这两个while只会执行某一个        while (i <= mid) {// 如果i<=mid说明后一个子表已经遍历完毕,那么就可以将前一个子表中剩余的所有元素直接复制到arr中            arr[k++] = temp[i++];        }        while (j <= high) {// 如果j<=high说明前一个子表已经遍历完毕,那么就可以将后一个子表中剩余的所有元素直接复制到arr中            arr[k++] = temp[j++];        }    }}
1 0
原创粉丝点击