三种常用的排序算法

三种常用的排序算法

三种常用的排序算法

引言

笔者 是一名在校大学生,在学校常用的算法有很多,毕竟算法是程序的灵魂 ,我就整理一点算法笔记放在博客上以供参考。PS: Java版本

冒泡排序

  • 平均时间复杂度:O(n^2)

  • 稳定性:稳定[1]

  • 排序算法的稳定性(腾讯校招2016笔试题曾考过)

    冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依此比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。

    代码如下

    import java.util.Arrays;
    
    public class BubbleSort {
        public static void bubbleSort(int[] arr){
            //定义临时变量
            int p;
            for(int i =0; i<arr.length-1; i++){ //对n个数排序只要交换n-1次即可
                //外层循环,每次把最大的元素像气泡一样“浮”到数组的最后
                for(int j =0; j<arr.length-i-1; j++){
                    //内层循环
                    if(arr[j]>arr[j+1]){ //依此比较相邻的两个元素,使较大的那个向后移
                        //三变量交换法
                          p = arr[j];
                        arr[j] = arr[j+1];
                        arr[j+1] = p;
                    }
                }
            }
        }
        
        public static void main(String[] args){
            int[] arr = new int[] {4,7,6,7,5,3,28,1,8};
            bubbleSort(arr);
            System.out.println(Arrays.toString(arr));
            
        }
    
    }
    

    运行结果如下

    [1, 3, 4, 5, 6, 7, 7, 8, 28]

选择排序

  • 平均时间复杂度:O(n^2)

  • 稳定性:不稳定

    选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

      注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。

    代码如下:

    import java.util.Arrays;
    
    public class SelectionSort {
        public static void selectionSort(int[] arr){
            for (int i = 0; i < arr.length-1; i++) {
                //i 为已排序序列的末尾 
                 int k = i;						
                for (int j= i+1;j<arr.length;j++) {	//未排序序列
                    if (arr[k]>arr[j]) {	//找出未排序序列中的最小值
                        k = j;
                    }
                }
                if (i!=k) {		//放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法
                    int temp = arr[k];
                    arr[k] = arr[i];
                    arr[i] = temp;
                }
                
            }
        }
    
        public static void main(String[] args) {
            int[ ] arr = new int[] {4,11,6,7,5,3,28,1,8};
            selectionSort(arr);
            System.out.println(Arrays.toString(arr));
    
        }
    
    }
    

    运行结果如下:

    [1, 3, 4, 5, 6, 7, 8, 11, 28]

快速排序

  • 平均时间复杂度:O(nlogn)

  • 稳定性:不稳定

  • 快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。

    ​ 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlog n)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlog n) 算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

      快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:

    1. 从序列中挑出一个元素,作为"基准"(pivot).
    2. 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
    3. 对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。

快速排序的代码如下:

import java.util.Arrays;

public class QuickSort {
    //快速排序的递归版本
    public static void quickSort(int[] arr, int startIndex,int endIndex) {
    // 递归结束条件:startIndex大等于endIndex的时候
    if (startIndex >= endIndex) {
        return;
    }
    
    // 得到基准元素位置
    int pivotIndex = partition(arr, startIndex, endIndex);

    // 根据基准元素,分成两部分递归排序
    quickSort(arr, startIndex, pivotIndex - 1);
    quickSort(arr, pivotIndex + 1, endIndex);
}
    private static int partition(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素
        int pivot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;

        while (left != right) {
            //控制right指针比较并左移
            while (left<right && arr[right] > pivot) {
                right--;
            }
            //控制left指针比较并右移
            while (left<right && arr[left] <= pivot) {
                left++;
            }
            //交换left和right指向的元素
            if (left<right) {
                int p = arr[left];
                arr[left]  = arr[right];
                arr[right] = p;
            }
        }
        //pivot和指针重合点交换
        int p = arr[left];
        arr[left]  = arr[startIndex];
        arr[startIndex] = p;

        return left;
    }

public static void main(String[] args) {
    int[] arr = new int[] {4,7,6,7,5,3,28,1,8};
    quickSort(arr,0,arr.length-1);
    System.out.println(Arrays.toString(arr));
}

}

运行结果如下:

[1, 3, 4, 5, 6, 7, 7, 8, 28]

  • 快速排序的非递归实现:

    绝大多数用递归来实现的问题,都可以用【栈】的方式来代替。

    因为我们代码中一层一层的方法调用,本身就是一个函数栈。每次进入一个新方法,就相当于入栈;每次有方法返回,就相当于出栈。

    所以,我们可以把原本的递归实现转化成一个栈的实现,在栈当中存储每一次方法调用的参数。

    代码如下:

    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Stack;
    
    public class QuickSortWithStack{
        public static void quickSort(int[] arr, int startIndex, int endIndex){
            //  用一个集合栈来代替递归的函数栈
            Stack<Map<String, Integer>> quickSortStack = new Stack<Map<String, Integer>>();
            //  整个数列的起止下标,以哈希的形式入栈
            Map rootParam = new HashMap();
            rootParam.put("startIndex", startIndex);
            rootParam.put("endIndex", endIndex);
            quickSortStack.push(rootParam);
    
            //循环结束条件: 栈为空时结束
            while(!quickSortStack.isEmpty()){
                //栈顶元素出栈,得到起止下标
                Map<String, Integer> param = quickSortStack.pop();
                //得到基准元素位置
                int pivotIndex  = partition(arr, param.get("startIndex"), param.get("endIndex"));
                //根据基准元素分成两部分,把每一部分的起止下标入栈
                if (param.get("startIndex")< pivotIndex -1) {
                    Map<String, Integer> leftParam = new HashMap<String, Integer>();
                    leftParam.put("startIndex", param.get("startIndex"));
                    leftParam.put("endIndex", pivotIndex -1);
                    quickSortStack.push(leftParam);
                }
    
                if (pivotIndex + 1 < param.get("endIndex")) {
                    Map<String, Integer> rightParam = new HashMap<String, Integer>();
                    rightParam.put("startIndex", pivotIndex + 1);
                    rightParam.put("endIndex", param.get("endIndex"));
                    quickSortStack.push(rightParam);
                }
    
            }
    
    }
    
    private static int partition(int[] arr, int startIndex, int endIndex) {
            // 取第一个位置的元素作为基准元素
            int pivot = arr[startIndex];
            int left = startIndex;
            int right = endIndex;
    
            while (left != right) {
                //控制right指针比较并左移
                while (left<right && arr[right] > pivot) {
                    right--;
                }
                //控制left指针比较并右移
                while (left<right && arr[left] <= pivot) {
                    left++;
                }
                //交换left和right指向的元素
                if (left<right) {
                    int p = arr[left];
                    arr[left]  = arr[right];
                    arr[right] = p;
                }
            }
            //pivot和指针重合点交换
            int p = arr[left];
            arr[left]  = arr[startIndex];
            arr[startIndex] = p;
    
            return left;
        }
    
        public static void main(String[] args) {
        int[] arr = new int[] {4,7,6,5,3,2,8,1};
        quickSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    
    
    }
    

    运行结果如下:

    [1, 2, 3, 4, 5, 6, 7, 8]


    结语:

    emmm,常用的排序算法就介绍到这里,如有错漏之处,或者有疑问可联系笔者,愿一起进步。

    Email: 15270013718@163.com


  1. 如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。 ↩︎

posted @ 2018-11-01 11:00  Mikejiawei  阅读(250)  评论(0编辑  收藏  举报