排序算法

作者: 奉先 | 来源:发表于2021-09-18 16:53 被阅读0次

    从学习数据结构开始,就接触了很多排序算法,但是并没有很好的做出总结,训练自己的熟悉写排序算法代码的能力。这篇文章是一个总结,也是一个对排序理解的深入。

    1. 选择排序 :

    选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
    选择排序需要比对N-1轮,每轮在所有元素选择最小元素放在最左端,下边是动画演示图 :

    选择排序
    选择排序java实现 :
    public class SelectionSort {
        /**
         * 调换数组a中,c和d位置的元素
         */
        public void swap(int[] a, int c , int d ){
            int temp = a[c];
            a[c] = a[d] ;
            a[d] = temp;
        }
        /**
         * 完成对数组a的选择排序
         * @param a
         */
        public void selectionSort(int [] a ){
            if(a.length < 2) return ;
            for (int i =0 ; i <= a.length -1 ; i++){
                int minNumIndex = i ;
                for (int j =i ; j <= a.length -1 ;j ++){
                    minNumIndex = a[j] < a[minNumIndex] ? j : minNumIndex ;
                }
                swap(a ,i ,minNumIndex);
            }
        }
        public static void main(String[] args) {
            int [] demo = new int[]{7,2,5,4,9,1} ;
            new SelectionSort().selectionSort(demo);
            System.out.println(Arrays.toString(demo));
        }
    }
    

    2.冒泡排序 :

    冒泡排序(Bubble Sort),是一种较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
    下图演示了冒泡排序的过程 :


    冒泡排序

    不难看出,一轮的冒泡排序会将最大元素放到数组的最后边,所以需要N-1轮迭代,每次迭代确定了最后一个数的位置。下面是冒泡排序的java实现:

    public class BubbleSort {
        /**
         * 调换数组a中,c和d位置的元素
         */
        public void swap(int[] a, int c , int d ){
            int temp = a[c];
            a[c] = a[d] ;
            a[d] = temp;
        }
    
        public void bubbleSort(int[] a){
            if (a.length < 2) return;
            for(int e=a.length -1 ; e>0 ; e--){
                for(int i=0 ;i < e ; i++){
                    if(a[i] > a[i+1]){
                        swap(a , i, i+1);
                    }
                }
            }
        }
        public static void main(String[] args) {
            int [] demo = new int[]{7,2,5,4,9,1} ;
            new BubbleSort().bubbleSort(demo);
            System.out.println(Arrays.toString(demo));
        }
    }
    

    3. 插入排序

    前边介绍了冒泡和选择排序,这两种排序方式,不受数据集的影响(是否部分有序), 插入排序相比这两种方式有了改进,在最好的情况只需要比较N次(待排序数组已经有序),在最坏的情况下,也需要比较O(N^2)次。
    插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。插入排序在最好情况只需要比较N次,在最坏条件需要比较N^2次
    下面动图演示了插入排序的过程:

    插入排序
    下面简单解释一下,插入排序迭代N轮(当然是N-1,第1个数不用排)。 第1轮让 a[0]有序,第2轮让<a[0],a[1]>有序,第三轮让 <a[0],a[1],a[2]>有序,依次类推。
    算法的主要思想就是每轮将第i个元素,插入到前边已经有序的 i-1个元素中,让这i个元素都有序即可。
    下面用java实现插入排序。
    public class InsertionSort {
        public void insertionSort(int[] a){
            if (a ==null || a.length < 2 ) return;
            for(int i= 0 ; i< a.length ; i++ ){
                for( int j = i-1 ; j>=0 && a[j] > a[j+1] ; j--){
                    ArrayUtils.intArraySwap(a, j, j+1);
                }
            }
        }
    
        public static void main(String[] args) {
            int [] demo = new int[]{7,2,5,4,9,1} ;
            new InsertionSort().insertionSort(demo);
            System.out.println(Arrays.toString(demo));
        }
    }
    

    在表示算法的时间复杂度时,O(logN) 不写底数就代表以2为底N的对数,如果底数是其他(例如3)需要写上。这是一个默认规定。

    4.归并排序

    归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
    归并排序采取递归的方式解决,而且有二分法思想。 开始时,将数组在中间位置划分为左右两个数组,左右分别递归到只剩下一个元素的时候,完成递归条件的终止,同时,合并2个有序的数组为一个有序数组。
    下图展示一次归并排序的向下执行过程 :


    递归执行过程

    下动图展示了归并排序的过程 :


    归并排序
    下面是归并排序的java代码实现 :
    public class MergeSort {
    
        /**
         * 实现归并排序
         * @param a
         * @param L
         * @param R
         */
        public void mergeSort(int[] a,int L,int R){
    //        if(a == null || a.length < 1) return;
            if (L == R) return;
            //注意,这里不能是 (R-L)/2 而是 L + (R-L)/2
            int midIndex = L + (R - L)/2 ;
            mergeSort(a,L,midIndex);
            mergeSort(a,midIndex+1,R);
            process(a,L,midIndex,R) ;
        }
        //合并2个有序数组
        public void process(int[] a, int L, int midIndex, int R) {
            int i = L;
            int j =midIndex +1 ;
            //temp和k用来保存合并排序后的数据
            int[] temp = new int[R-L+1] ;
            int k = 0 ;
            while(i<=midIndex && j<=R){
                temp[k++] = a[i] > a[j] ? a[j++] : a[i++] ;
            }
            while (i<=midIndex){
                temp[k++] = a[i++] ;
            }
            while (j<=R){
                temp[k ++] = a[j++] ;
            }
            for(int p=0 ;p<temp.length ;p ++){
                a[L+p] = temp[p] ;
            }
        }
    
        public static void main(String[] args) {
            int []arr = {9,8,7,6,5,4,3,2,1};
            new MergeSort().mergeSort(arr,0,arr.length-1);
            System.out.println(Arrays.toString(arr));
        }
    
    }
    

    5.快速排序:

    快速排序是O(nlogn)时间复杂度的排序算法。 快速排序的算法基础是“荷兰国旗问题” 。

    荷兰国旗问题介绍 :给定一个数组arr和一个数num,请把小于num是数放在数组的左边,等于num的数放在数组的中间,大于num是数放在数组的右边,要求额外空间为o(1),时间为o(n)。

    这里边就涉及到了选择一个边界值的问题,根据选择边界值的方法衍生除了快速排序的多个版本,较低级版本,每次选择数组的最后一个值作为边界值来进行partition 。改进后的快速排序,每次从数组中随机选一个数字作为边界值来进行partition,下面看下partition的主要算法思想 :

    荷兰国旗问题算法
    这里边涉及到了3个变量 : 小于区域边界 、 大于区域边界 、中间的变量指针i。 下图是快速排序的演示 :
    快速排序演示
    下面是快速排序的java代码实现 :
    public class QuickSort {
        public void quickSort(int[] a){
            if (a == null || a.length <2) return;
            quickSort(a, 0 ,a.length -1);
        }
        public void quickSort(int[] a,int L, int R){
            if(L < R){
                //在数组的L到R之间随机选取一个数字,并与最后一个数字交换(会使用最后一个数字来partition),作为待partition的分界值
                //Math.random()生成一个[0,1]之间的随机数字
                ArrayUtils.intArraySwap(a ,L + (int)Math.random() * (R-L+1) , R) ;
                //荷兰国旗问题,按照数组最后一个元素来partition ,将数组分为3个区域,区域1<最后一个元素; 区域2=最后一个元素;区域3大于最后一个元素
                //p数组有2个元素,p[0]是小于和等于区域的边界点 p[1]是等于和大于区域的边界点
                int[] p = partition(a,L,R) ;
                quickSort(a,L,p[0]-1);
                quickSort(a,p[0]+1,R);
            }
        }
        private int[] partition(int[] a, int L, int R) {
            //定义左右边界变量,图中的括号
            int less = L - 1 ;
            int more = R ;
            //L是移动游标,图中的i ,滑动比价的终止条件是 变量i右移与右边界左移相遇
            while (L < more){
                if(a[L] < a[R]){
                    //如果a[i]<num,交换a[i]和小于区域边界的下一个,小于区域边界右移
                    ArrayUtils.intArraySwap(a,++less,L++);
                }
                else if(a[L] > a[R]){
                    //如果a[i]>num,交换a[i]和大于区域的前一个元素,大于区域向左移动一位
                    ArrayUtils.intArraySwap(a,--more,L);
                }
                else{
                    //如果a[i]==num的话,直接向后移动指针
                    L ++ ;
                }
            }
            ArrayUtils.intArraySwap(a,more,R);
            return new int[]{less+1,more};
        }
    
        public static void main(String[] args) {
            int []arr = {9,8,7,6,5,4,3,2,1};
            new QuickSort().quickSort(arr,0,arr.length-1);
            System.out.println(Arrays.toString(arr));
        }
    }
    

    6.堆排序:

    堆是一种很重要的数据结构,堆通常是一个可以被看做一棵完全二叉树的数组对象。
    堆中包含2个非常常用的操作,heapInsert 和 heapify 。

    下面是堆排序的java代码实现 :

    public class HeapInsertion {
        
        //在index位置插入一个数字,并保证数据结构是一个堆,不使用额外空间,自身来做调换。
        public void heapInsert (int[] a ,int index){
            while(a[index] > a[(index-1)/2]){
                ArrayUtils.intArraySwap(a , index , (index-1)/2);
                index = (index - 1)/2 ;
            }
        }
    
        //堆化一个数组 ,index表示从哪个节点开始做堆化操作; heapsize是堆的大小,控制循环次数(heapsize >= left)
        public void heapify(int[] a, int index, int heapsize){
            int left = index*2+1;  //得到左孩子的位置
            while (left < heapsize){
                //获取2个孩子更大的那一个的节点的下标
                int larger = left + 1 < heapsize && a[left+1] > a[left]? left +1 : left ;
                //比较父节点和两个孩子中更大的孩子,更大的数给larger
                larger = a[larger] > a[index] ? larger : index ;
                if (larger == index){
                    break;
                }
                ArrayUtils.intArraySwap(a,larger,index);
                index = larger ;
                left = index*2+1;
            }
        }
    
        /**
         * 快速排序的实现
         * @param a
         */
        public void heapSort(int[] a){
            if(a == null || a.length < 2) return;
            for(int i = 0; i< a.length ; i++){
                heapInsert(a, i);
            }
            int heapSize = a.length ;
            ArrayUtils.intArraySwap(a,0, --heapSize);
            while(heapSize > 0){
                heapify(a,0,heapSize);
                ArrayUtils.intArraySwap(a,0,--heapSize);
            }
        }
    
        public static void main(String[] args) {
            int []arr = {9,8,7,6,5,4,3,2,1};
            new HeapInsertion().heapSort(arr);
            System.out.println(Arrays.toString(arr));
        }
    }
    

    相关文章

      网友评论

        本文标题:排序算法

        本文链接:https://www.haomeiwen.com/subject/xskygltx.html