十大经典排序算法(一) java

目录

交换排序

冒泡排序

快速排序

插入排序

直接插入排序()

希尔排序(Shell Sort)

归并排序(Merge Sort)

选择排序


交换排序

冒泡排序(Bubble Sort)

原理:

这种排序方法是通过相邻的两个元素两两比较,根据大小来交换位置,最值元素就像气泡一样从左侧向右侧移动,故名冒泡排序。 冒泡排序是一种计算机科学领域的较简单基础的排序算法。 其基本思路是,对于一组要排序的元素列,依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面,如此继续,直到比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成。

1. fori循环选取arr中第i个元素要操作的元素item

2. forj循环中用item向右依次与每个元素比较,

        更大就两者交换顺序,更小则继续向后继续寻找,直到遍历结束

3. 重复1、2步直到arr.length

程序:

    static void BubbleSort(int [] arr) {
        for (int i = 0;i < arr.length-1;i++){
            for (int j = 0;j < arr.length-1-i;j++){
                int temp;
                if (arr[j] > arr[j+1]) {
                    swap(arr, j, j+1);
                }
            }
        }
    }

快速排序(Quick Sort)

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

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

递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

    static void QuickSort(int[]arr) {
        QuickSort(arr, 0, arr.length-1);
    }
    static void QuickSort(int[] arr, int low, int high ) {
        if (low >= high) return;
        int pivotKey = partition(arr, low, high);
        QuickSort(arr, low, pivotKey-1);
        QuickSort(arr, pivotKey+1, high);
    }

    static int partition(int[] arr, int low, int high) {
        int pivot = arr[low];

        while (low < high) {
            while (low < high && arr[high] > pivot) {//如果high元素已经大于pivot,左移high
                high--;
            }//此时high元素<=pivot
            arr[low] = arr[high];//比pivot小的记录移到low端
            while (low < high && arr[low] <= pivot) {//如果low元素已经小于pivot,右移low
                low++;
            }//此时low元素>pivot
            arr[high] = arr[low];//比pivot大的记录移到high端
            arr[low] = pivot;
        }
        return low;
    }

    static void swap(int[] arr, int a, int b) {
        int temp = arr[b];
        arr[b] = arr[a];
        arr[a] = temp;
    }

插入排序

直接插入排序(Direct Insertion Sort)

插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序数组中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常为in-place排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  1. 把待排序数组的左侧视作为一个已经排列好的有序数组
  2. 选取有序数组右侧的第一个元素item
  3. 将item从右到左的与有序数组中的元素elem依次比较。如果item更大,则elem往左的所有元素都比item小,item找到位置,在该位置插入;如果item更小,则与elem交换位置,继续向左比较
  4. 重复步骤2、3,直到整个数组有序
    /**
     * Common Insertion Sort
     * 1.pick an item
     * 2.iterating to exchange with left element
     *      till meet a target element that less than item.
     * 3.insert item to index of target.
     */
    public static void InsertionSort(int [] arr) {
        int sorted;
        for (int i = 1; i < arr.length; i++) {// pick out element to insert
            sorted = arr[i];
            // Direct Insertion Sort
            int j;
            // iter to leftmost or meet a target element that less than item
            for (j = i; j > 0 && sorted < arr[j - 1]; j--) arr[j] = arr[j-1];
            arr[j] = sorted;
        }
    }

希尔排序(Shell Sort)

希尔排序可以认为是直接插入排序的优化形式,它的原理是通过一个gap increment(步长)来把待排序数组分割成许多个大小=gap increment的子数组(序列末尾分割出的子序列长度可能小于gap increment),再对这些子列进行直接插入排序。每一组子列排成有序后,整体就变有序了。

在希尔排序中,gap increment起到十分重要的作用,不同gap increment的选取直接关系到了该算法的性能表现,这也是这个算法之所以优于直接插入排序的地方。

这里给出不同步长对排序性能的影响图

    /**
     * ShellSort:
     * @methodology gap increment sequence -> direct insertion sort
     * Using gap increment sequence to divide the array in many groups,
     * then use direct insertion sort to those smaller groups.
     */
    public static void ShellSort(int[] arr) {
        int sorted;
        for (int gap = arr.length / 2; gap > 0;
             gap = gap == 2 ? 1 : (int)(gap / 2.2)) {//gap increment sequence
            //use gap increment sequence to separate arr by groups
            for (int i = gap; i < arr.length; i++) {
                sorted = arr[i];
                int j = i;

                //and then direct insertion sort
                for (; j >= gap && sorted < arr[j - gap]; j -= gap) {
                    arr[j] = arr[j - gap];
                }
                arr[j] = sorted;
            }
        }
    }

归并排序(Merge Sort)

归并排序(Merge sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

算法思路
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。

在实际实现上,为了确保排序算法的性能表现,我们会通过in-place排序,也就是通过分解操作待排序数组的索引来实现将序列分解的操作。

分解

  1. 将待排序的数组通过分割索引指针不断分成两个子数组,直到子数组只有1-2个元素。

合并

  1. 创建tmp数组用来存储比较结果

  2. 定义三个指针指向i,j,k指向tmp数组与两待合并数组A,B的左侧下标。

  3. 比较 A[i] 和 B[j] 的大小,将小的元素放入 tmp[k] 中,并将对应指针向后移动一位。

  4. 重复步骤 2,直到其中一个数组的元素全部放入 tmp 中。

  5. 将另一个数组中剩余的元素放入 tmp中。

归并排序的原理图:

    public static void MergeSort(int[] L) {
        if (L != null)
            MergeSort(L, 0, L.length-1);
    }

    private static void MergeSort(int[] L, int l, int r) {
        if ((r - l) <= 0 ) {
            return;
        }
        int m = (l+r)/2;
        MergeSort(L, l, m);
        MergeSort(L, m+1, r);
        Merge(L, l, r, m);
    }

    private static void Merge(int[] L, int l, int r, int m) {
        //creat a tmp arr to store comparison result
        int[] tmp = new int[r - l + 1];
        int i = l, j = m + 1, k = 0;
        //compare data between l-m and m+1 r,the smaller place into tmp
        while (i <= m && j <= r) {
            if(L[i] >= L[j]) {
                tmp[k++] = L[j++];
            } else {
                tmp[k++] = L[i++];
            }
        }
        //copy the rest
        while (i <= m) {
            tmp[k++] = L[i++];
        }
        while (j <= r) {
            tmp[k++] = L[j++];
        }
        //copy back
        System.arraycopy(tmp, 0, L, l, tmp.length);
    }

选择排序(Selection Sort)

选择排序(Selection Sort)是一种简单的排序算法,其基本原理是在未排序的元素中找到最小(或最大)的元素,然后将其放在已排序的序列的末尾。重复这个过程,直到所有元素都被排序完毕。

  1. 遍历整个待排序的数组,从第一个元素开始。
  2. 在未排序的部分中,找到最小(或最大)的元素,并将其与第一个元素交换位置。
  3. 接着从第二个元素开始,重复步骤2,直到所有元素都被排序
    static void SelectionSort(int[] arr) {
        int small;//{1, 2, 1, 5, 15, 1, 156, 8, 123}
        for (int i = 0; i < arr.length; i++){
            small = i;
            //遍历数组选择最小值索引
            for (int j = i; j < arr.length; j++) {
                if (arr[j] < arr[small]) small = j;
            }
            //依次交换到前面,形成有序列
            swap(arr, i, small);
        }
    }

    static void swap(int[] arr, int a, int b) {
        int temp = arr[b];
        arr[b] = arr[a];
        arr[a] = temp;
    }

算法性能

选择排序O(N^2)O(N^2)O(N^2)不稳定O(1)
插入排序O(N)O(N^2)O(N^2)稳定O(1)
冒泡排序O(N)O(N^2)O(N^2)稳定O(1)
希尔排序O(N)O(N^(3/2))O(N^S)(1<S<2)不稳定O(1)
快速排序O(NlogN)O(NlogN)O(N^2) (1<S<2)不稳定O(logN)
归并排序O(NlogN)O(NlogN)O(NlogN)稳定O(N)

要注意的是,条条大路通罗马,以上排序算法的代码通常只是作为实现算法思路的一种形式,不同的排序算法或者代码之间也往往都存在着一些局限性与可优化空间,大家可以自己动手去试着优化一下。

        除此之外,经典算法中还有相对来说更高级的堆排序、计数排序、桶排序和基数排序。这些排序往往都会使用一些更为复杂的数据结构来实现。我们会在以后提及。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值