Java常用排序算法实现

目录

概述

这里总的给出了几种常用的排序算法,也是当作复习和巩固.

  • 插入排序
  • 希尔排序
  • 简单排序
  • 堆排序
  • 冒泡排序
  • 快速排序

以上几个都是根据排序的原理和原则自定义实现的.对于同一个排序实际的实现过程可能有一些不同,但思想和处理方式是相同的.


插入排序

  • 基本思想:在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
//插入排序
public static boolean insertSort(int[] src, boolean isAsc) {
    if (src != null) {
        return insertSort(src, 0, src.length - 1, isAsc);
    } else {
        return false;
    }
}

//指定数组的某一段进行排序
//startIndex/endIndex分别为数组中开始排序的位置
private static boolean insertSort(int[] src, int startIndex, int endIndex, boolean isAsc) {
    if (src == null || startIndex > endIndex) {
        return false;
    } else {
        int temp = 0;
        for (int i = startIndex; i <= endIndex; i++) {
            //取下一个位置的数据
            for (int k = 0; k < i; k++) {
                swap(src, k, i, isAsc);
            }
        }
        return true;
    }
}

希尔排序

  • 基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
//希尔排序是基于插入排序的,先将数组分为N份进行插入排序,完成后减小增量再进行排序
public static boolean shellSort(int[] src, boolean isAsc) {
    if (src == null) {
        return false;
    } else {
        //计算一个增量
        int increment = src.length / 4;
        if (increment <= 0) {
            //增量必须大于等于1
            increment = 1;
        }
        //增量递减进行排序
        for (int i = increment; i >= 1; i--) {
            int j = 0;
            //增量分组进行插入排序
            for (; j + i < src.length; j += i) {
                insertSort(src, j, j + i, isAsc);
            }
            //如果数组不能完全按增量切分,最后部分也可以正常排序
            insertSort(src, j, src.length - 1, isAsc);
        }
        return true;
    }
}

简单排序

  • 基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
//依次从数组中每次取出最值,按序排放直到排序结束
public static boolean simpleSort(int src[], boolean isAsc) {
    if (src == null) {
        return false;
    } else {
        for (int i = 0; i < src.length; i++) {
            //记录第N个要排序的位置
            int j = i;
            //记录当前最大/小值的位置
            int k = j;
            for (; j + 1 < src.length; j++) {
                if (isAsc) {
                    if (src[k] > src[j + 1]) {
                        //若当前值小于记录值,更新记录值的位置,否则记录值位置不变
                        k = j + 1;
                    }
                } else {
                    if (src[k] > src[j + 1]) {
                        k = j + 1;
                    }
                }
            }
            j = src[i];
            src[i] = src[k];
            src[k] = j;
        }
        return true;
    }
}

堆排序

  • 基本思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。
//堆排序
public static boolean heapSort(int[] src, boolean isAsc) {
    if (src == null) {
        return false;
    } else {
        //这里的处理方法是:
        //将数组进行堆排序,得到第一个值为最大/小值;
        //除去第一个值,对后面的数组再次堆排序,得到第二个值为二次数组的最大/小值,即该值为数组中的第二大/小值
        //重复以上的方法直到数组被完全排序
        for (int i = 0; i < src.length; i++) {
            //依次建堆,得到一个堆后就排除1项重新建堆
            buildHeap(src, i, src.length - 1, !isAsc);
        }
        return true;
    }
}
  • 堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
//建堆,将数组指定的索引范围内的数据进行建堆
private static void buildHeap(int[] src, int startIndex, int endIndex, boolean isBiggest) {
    if (src == null || startIndex > endIndex) {
        return;
    } else {
        //计算出当前排序的数组长度
        int length = endIndex - startIndex;
        //得到当前计算数组的中间位置
        int mid = (length - 1) / 2;
        for (int i = mid; i >= 0; i--) {
            //计算中间位置的左子树位置
            int left = i * 2 + 1 + startIndex;
            //计算中间位置的右子树位置
            int right = left + 1;
            //若左子树位置有效,则尝试比较左子树及其父节点并交换
            if (left <= endIndex) {
                swap(src, left, i + startIndex, isBiggest);
            }
            //若右子树位置有效,尝试比较右子树及其父节点并交换
            if (right <= endIndex) {
                swap(src, right, i + startIndex, isBiggest);
            }
        }
    }
}

这里的堆排序与定义上的排序是有一些不同的.定义上的堆排序是建成堆后,最后一个数据与堆顶交换;这里是建堆后取走第一个最值,其它的数据直接重新建成一个堆,重复直到堆中只有一个数据为止.(不存在堆顶数据与最后的数据进行交换)


冒泡排序

  • 基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
public static boolean bubbleSort(int[] src, boolean isAsc) {
    if (src == null) {
        return false;
    } else {
        boolean isFinish = false;
        while (!isFinish) {
            isFinish = true;
            for (int i = 0; i + 1 < src.length; i++) {
                if (swap(src, i, i + 1, isAsc)) {
                    //只要进行过交换,就说明还没有排序完成,直到不再交换为止
                    isFinish = false;
                }
            }
        }
        return true;
    }
}

快速排序

  • 基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
public static boolean quickSort(int[] src, boolean isAsc) {
    if (src == null) {
        return false;
    } else {
        //一趟扫描的递归操作
        onceRecursionSort(src, 0, src.length - 1, isAsc);
        return true;
    }
}

快速排序是基于一趟扫描的;一趟扫描是选择一个数值划分出数组的大小区域,每个区域再重复这个过程直到所有区域不再需要划分为止.
一趟快速排序的算法是:

  • 1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  • 2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  • 3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  • 4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  • 5)重复第3、4步,直到i>=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)
//一趟快速扫描
private static void onceRecursionSort(int[] src, int left, int right, boolean isAsc) {
    if (src == null || left > right) {
        return;
    } else {
        //保存初始值
        int stickLeft = left;
        int stickRight = right;
        //选择排序标准值
        int key = src[stickLeft];
        //进行一次大小值区分的排序
        while (left < right) {
            //从右边开始向左边扫描
            while (left < right) {
                //当前左右两个数值是否满足比较条件(此处为左<右)
                if (!isCompareCorrect(src[right], key, isAsc)) {
                    //右边数大于标准值时,检测索引左移
                    right--;
                    continue;
                }
                //否则交换数据,并且左边索引右移(因为左边已经与右边交换了,不再需要检测左边的值)
                swap(src, left, right);
                left++;
                break;
            }
            //从左边开始向右边扫描
            while (left < right) {
                if (!isCompareCorrect(key, src[left], isAsc)) {
                    left++;
                    continue;
                }
                swap(src, left, right);
                right--;
                break;
            }
        }
        //当中间值小于初始值时,说明没有完全排序完整,递归进行排序,重复每个区域的排序
        if (stickLeft < right) {
            onceRecursionSort(src, stickLeft, right, isAsc);
        }
        if (right + 1 < stickRight) {
            onceRecursionSort(src, right + 1, stickRight, isAsc);
        }
    }
}

源码

public class SortAlgorithm {

    /**
     * 插入排序<br/>
     * 基本思想:在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
     *
     * @param src
     */
    public static boolean insertSort(int[] src, boolean isAsc) {
        if (src != null) {
            return insertSort(src, 0, src.length - 1, isAsc);
        } else {
            return false;
        }
    }

    /**
     * 指定数组的某一段进行排序
     *
     * @param src
     * @param startIndex 开始排序的index
     * @param endIndex   结束排序的index
     * @param isAsc      是否升序
     */
    private static boolean insertSort(int[] src, int startIndex, int endIndex, boolean isAsc) {
        if (src == null || startIndex > endIndex) {
            return false;
        } else {
            int temp = 0;
            for (int i = startIndex; i <= endIndex; i++) {
                //取下一个位置的数据
                for (int k = 0; k < i; k++) {
                    swap(src, k, i, isAsc);
                }
            }
            return true;
        }
    }


    /**
     * 希尔排序<br/>
     * 基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
     *
     * @param src
     * @param isAsc 是否升序
     * @return
     */
    public static boolean shellSort(int[] src, boolean isAsc) {
        if (src == null) {
            return false;
        } else {
            //计算一个增量
            int increment = src.length / 4;
            if (increment <= 0) {
                //增量必须大于等于1
                increment = 1;
            }
            //增量递减进行排序
            for (int i = increment; i >= 1; i--) {
                int j = 0;
                //增量分组进行插入排序
                for (; j + i < src.length; j += i) {
                    insertSort(src, j, j + i, isAsc);
                }
                //如果数组不能完全按增量切分,最后部分也可以正常排序
                insertSort(src, j, src.length - 1, isAsc);
            }
            return true;
        }
    }

    /**
     * 简单排序<br/>
     * 基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
     *
     * @param src
     * @param isAsc 是否升序
     * @return
     */
    public static boolean simpleSort(int src[], boolean isAsc) {
        if (src == null) {
            return false;
        } else {
            for (int i = 0; i < src.length; i++) {
                //记录第N个要排序的位置
                int j = i;
                //记录当前最大/小值的位置
                int k = j;
                for (; j + 1 < src.length; j++) {
                    if (isAsc) {
                        if (src[k] > src[j + 1]) {
                            //若当前值小于记录值,更新记录值的位置,否则记录值位置不变
                            k = j + 1;
                        }
                    } else {
                        if (src[k] > src[j + 1]) {
                            k = j + 1;
                        }
                    }
                }
                j = src[i];
                src[i] = src[k];
                src[k] = j;
            }
            return true;
        }
    }

    /**
     * 堆排序<br/>
     * 基本思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。<br/>
     * 堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
     *
     * @param src
     * @param isAsc 是否升序
     * @return
     */
    public static boolean heapSort(int[] src, boolean isAsc) {
        if (src == null) {
            return false;
        } else {
            //这里的处理方法是:
            //将数组进行堆排序,得到第一个值为最大/小值;
            //除去第一个值,对后面的数组再次堆排序,得到第二个值为二次数组的最大/小值,即该值为数组中的第二大/小值
            //重复以上的方法直到数组被完全排序
            for (int i = 0; i < src.length; i++) {
                //依次建堆,得到一个堆后就排除1项重新建堆
                buildHeap(src, i, src.length - 1, !isAsc);
            }
            return true;
        }
    }

    /**
     * 冒泡排序<br/>
     * 基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
     *
     * @param src
     * @param isAsc
     * @return
     */
    public static boolean bubbleSort(int[] src, boolean isAsc) {
        if (src == null) {
            return false;
        } else {
            boolean isFinish = false;
            while (!isFinish) {
                isFinish = true;
                for (int i = 0; i + 1 < src.length; i++) {
                    if (swap(src, i, i + 1, isAsc)) {
                        //只要进行过交换,就说明还没有排序完成,直到不再交换为止
                        isFinish = false;
                    }
                }
            }
            return true;
        }
    }

    /**
     * 快速排序<br/>
     * 基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
     *
     * @param src
     * @param isAsc
     * @return
     */
    public static boolean quickSort(int[] src, boolean isAsc) {
        if (src == null) {
            return false;
        } else {
            //一趟扫描的递归操作
            onceRecursionSort(src, 0, src.length - 1, isAsc);
            return true;
        }
    }

    /**
     * 递归的一趟扫描<br/>
     * 一趟快速排序的算法是:<br/>
     * 1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;<br/>
     * 2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];<br/>
     * 3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;<br/>
     * 4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;<br/>
     * 5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。<br/>
     *
     * @param src
     * @param left
     * @param right
     * @param isAsc
     */
    private static void onceRecursionSort(int[] src, int left, int right, boolean isAsc) {
        if (src == null || left > right) {
            return;
        } else {
            //保存初始值
            int stickLeft = left;
            int stickRight = right;
            //选择排序标准值
            int key = src[stickLeft];
            while (left < right) {
                //从右边开始向左边扫描
                while (left < right) {
                    //当前左右两个数值是否满足比较条件(此处为左<右)
                    if (!isCompareCorrect(src[right], key, isAsc)) {
                        //右边数大于标准值时,检测索引左移
                        right--;
                        continue;
                    }
                    //否则交换数据,并且左边索引右移(因为左边已经与右边交换了,不再需要检测左边的值)
                    swap(src, left, right);
                    left++;
                    break;
                }
                //从左边开始向右边扫描
                while (left < right) {
                    if (!isCompareCorrect(key, src[left], isAsc)) {
                        left++;
                        continue;
                    }
                    swap(src, left, right);
                    right--;
                    break;
                }
            }
            //当中间值小于初始值时,说明没有完全排序完整,递归进行排序
            if (stickLeft < right) {
                onceRecursionSort(src, stickLeft, right, isAsc);
            }
            if (right + 1 < stickRight) {
                onceRecursionSort(src, right + 1, stickRight, isAsc);
            }
        }
    }

    /**
     * 数组中从指定位置startIndex到endIndex建堆进行排序
     * 结过排序后src[startIndex]位置的值为堆中最大/小值(其它位置的值并不确定)
     *
     * @param src
     * @param startIndex 开始位置索引
     * @param endIndex   结束位置索引
     * @param isBiggest  是否大顶堆
     */

    private static void buildHeap(int[] src, int startIndex, int endIndex, boolean isBiggest) {
        if (src == null || startIndex > endIndex) {
            return;
        } else {
            //计算出当前排序的数组长度
            int length = endIndex - startIndex;
            //得到当前计算数组的中间位置
            int mid = (length - 1) / 2;
            for (int i = mid; i >= 0; i--) {
                //计算中间位置的左子树位置
                int left = i * 2 + 1 + startIndex;
                //计算中间位置的右子树位置
                int right = left + 1;
                //若左子树位置有效,则尝试比较左子树及其父节点并交换
                if (left <= endIndex) {
                    swap(src, left, i + startIndex, isBiggest);
                }
                //若右子树位置有效,尝试比较右子树及其父节点并交换
                if (right <= endIndex) {
                    swap(src, right, i + startIndex, isBiggest);
                }
            }
        }
    }

    /**
     * 在满足要求时交换指定位置的两个索引的值
     *
     * @param src
     * @param firstIndex
     * @param secondIndex
     * @param isFirstBiggerThanSecond 第一个数是否大于第二个数,当此参数为true时,若scr[firstIndex]>src[secondIndex],交换值,否则不操作返回false
     * @return
     */
    private static boolean swap(int[] src, int firstIndex, int secondIndex, boolean isFirstBiggerThanSecond) {
        int temp = 0;
        boolean isChanged = false;
        if (isFirstBiggerThanSecond && src[firstIndex] > src[secondIndex]) {
            isChanged = true;
        } else if (!isFirstBiggerThanSecond && src[firstIndex] < src[secondIndex]) {
            isChanged = true;
        }

        if (isChanged) {
            temp = src[firstIndex];
            src[firstIndex] = src[secondIndex];
            src[secondIndex] = temp;
        }
        return isChanged;
    }

    /**
     * 交换指定位置的两个数值
     *
     * @param src
     * @param firstIndex
     * @param secondIndex
     */
    public static void swap(int[] src, int firstIndex, int secondIndex) {
        int temp = 0;
        temp = src[firstIndex];
        src[firstIndex] = src[secondIndex];
        src[secondIndex] = temp;w
    }

    /**
     * 判断当前两个值的比较是否按要求正常
     *
     * @param firstInt
     * @param secondInt
     * @param isAsc     是否按升序排序,当此参数为true时,若第一个数&lt;第二个数,返回true,否则返回false
     * @return
     */
    private static boolean isCompareCorrect(int firstInt, int secondInt, boolean isAsc) {
        if (isAsc) {
            return firstInt < secondInt;
        } else {
            return firstInt > secondInt;
        }
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值