Java-排序算法

冒泡排序

原始版本

冒泡算法逐个比较相邻元素的大小,一趟下来将最大元素(或最小元素)归位到序列最高

public void bubbleSort(int[] array) {
        for ( int i = 1 ; i <= array.length - 1 ; i++ ) {
            for ( int j = 0 ; j <= array.length -i - 1 ; j++ ) {
                if ( array[j] > array[j + 1] ) {
                    swap(array,j,j+1);
                }
            }
        }
    }

冒泡排序改进版

假若现在原始序列除了最大元素处在序列前面某一位置(本应该在最高下标处),其余元素都是在正确的位置上。那么其实只要进行一趟冒泡排序就可以将最大元素归位到最高下标处了,即可终止算法。改进版冒泡排序在每一趟冒泡过程中用一个标志位flag判断是否有元素发生交换,若发生了交换,则冒泡算法程序仍然需要进行下去,若某趟冒泡的过程中没有发生交换操作,则证明该序列已经有序便无需在执行冒泡排序算法了。

public void swap( int[] array , int i, int j ){
        int temp = array[i];
        array[i] = array[j];
        array[j] =temp;
    }
    public void bubbleSort( int[] array ){
        boolean flag = true;
        for ( int i = 0 ; flag && i < array.length ; i++ ) {
            flag = false;
            for ( int j = 0 ; j < array.length - i - 1 ; j++ ) {
                if ( array[j] > array[j+1] ) {
                    swap( array , j ,j + 1);
                    flag = true;
                }
            }
        }
    }

 选择排序

原始版

选择排序从序列首部开始,首先考察array[0],让其后的所有元素(1~array.length-1)逐个与array[0]比较,比较时保留住最小元素的值与下标,这样一趟下来便筛选出了0~array.length-1序列中最小的元素下标,让它与seq[0]交换。下一趟则考察seq[1],让其后的所有元素(2~seq.length-1)逐个与seq[1]比较,同上,如此往复。咋一看其实选择排序和冒泡的大致原理差不多,都是每趟选出最大元素,但不同的是冒泡排序是相邻交换(既保证相同大小的元素在排完序后的顺序与排序前的顺序一致,即是稳定排序方式)而选择排序则是各种跳跃性的交换。

public void selectionSort(int[] array) {
        int index = 0;
        for ( int i = 0 ; i < array.length ; i++ ) {
            index = i;
            for ( int j = i + 1 ; j < array.length ; j++ ) {
                if ( array[j] < array[index] ) {
                    index = j;
                }
            }
            swap(array,i,index);
        }
    }

改进版选择排序

从朴素的选择排序过程中可以看出,其实既然每一趟可以选出最小值并将其归位于array[0]处,那么为何有不可在一趟过程中同步筛选出最大值并归位于array[array.length-1]呢,这样的话排seq.length / 2趟就可以了。但有一点需要注意,就是假如筛选出的最大元素刚好在子序列的首部,而首部恰好又是要被最小元素交换的,这时如果最小元素先被交换到了首部,那么最大元素就被带到中间去了,而接下来最大元素要交换到子序列尾部时,实质是把最小元素给移到尾部了,这样就错了。

public void improveSelectSort(int[] array) {
        int minIndex = 0;
        int maxIndex = 0;
        for ( int i = 0 ; i < array.length / 2 ; i++ ) {
            minIndex = i;
            maxIndex = i;
            for ( int j = i + 1; j <= array.length - 1 - i ; j++ ) {
                if ( array[j] < array[minIndex]) {
                    minIndex = j;
                } else if ( array[j] > array[maxIndex]) {
                    maxIndex = j;
                }
            }
            //将子序列最小的元素至于前端
            if ( i != minIndex ) {
                swap(array,i,minIndex);
                /*
                 * 原来的第一个元素array[i]已经与array[minIndex]的元素交换了位置
                 * 如果之前maxIndex指向的是i位置,那么需要将maxIndex重新指向array[miIndex]
                 * 因为现在array[minPoint]中存放的才是之前第一个元素中的数据
                 * */
                if ( maxIndex == i ) {
                    maxIndex = minIndex;
                }
            }
            if ( maxIndex != array.length - i - 1 ) {
                swap(array,array.length - i - 1,maxIndex);
            }
        }
    }

 插入排序

原始版

插入排序,具体过程就是让原始局部有序的序列,随着一个个新元素的插入,逐步演变成全部有序的过程,即最初先固定住array[0]让其成为一个局部有序的子序列,然后考察array[1]及它前面那个有序序列,变挪动元素,变根据元素的大小寻找插入点,找到后就插入,一趟便结束了。下一趟又考察seq[2]及它前面那个有序序列,同理操作。循环往复,直到考察完seq[seq.length – 1]。需要指出的是,插入排序在序列基本有序的时候完全不费吹灰之力,顶多挪动两三趟,然后每次待插入元素都不用挪动比较了,正是它应该待的位置,所以可以让时间复杂度降为O(N)。

public void insertSort(int[] array) {
        for ( int i = 1 ; i < array.length ; i++ ) {
            int temp = array[i];
            int j = i - 1;
            while ( j >= 0 && temp < array[j]) {
                array[j+1] = array[j];
                j--;
            }
            array[j + 1] = temp;
        }
    }

插入排序改进版-二分插入

原始的插入排序是边比较插入点,边挪动元素。对于二分插入来说元素的挪动次数是没有那么容易减少的了,然而比较查找的次数很容易减少,只要采用二分查找,找到应该插入的位置后在逐个挪动元素腾出空间,最后插入即可。改进之处其实就是把查找和挪动操作分离开了,而一旦分离,便可以在查找里做文章,利用二分查找减少比较次数(因为二分查找有跳跃性,它和挪动操作没法同步进行)。

public void binaryInsertSort(int[] array) {
        for ( int i = 1 ; i < array.length ; i++ ) {
            int temp = array[i];
            int left = 0;
            int right = i - 1;
            int mid = 0;
            //二分查找应该插入的位置
            while ( left <= right ) {
                mid = (right - left) / 2 + left; //可以有效防溢出,不要写成 mid = (right + left) / 2;
                if ( temp >= array[mid] ) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            
            int j = i - 1;
            while ( j > right ) {
                array[j + 1] = array[j];
                j--;
            }
            array[right + 1] = temp;
        }
    }

希尔排序

希尔排序又称缩小增量排序,是插入排序的一种叠加与改进。朴素的插入排序中的有序子序列都是以1位增量的,中间不能跃过任一元素,而希尔排序则可以,它第一轮先设定一个增量gap,然后锁定一个起始位置start,让start+i+gap,start + i + gap,start + i + 2*gap ,start + i + 3*gap,.......(0<=i<gap)构成序列i,最终得到gap个序列,然后分别对齐使用插入排序,第二轮让gap = gap/2(每次除以2),后面原理同上。如此往复,知道最后gap=1,则演变为一般的插入排序,而此时不必担心,因为序列已经基本有序了,所以基本不需要排多少次。

public void shellSort(int[] array) {
        //除2增量的希尔排序
        for ( int gap = array.length / 2 ; gap > 0 ; gap /= 2 ) {
            for ( int start = 0 ; start < gap ; start++ ) {
                insertSortWithGap(array,start,gap);
            }
        }
    }
    
    public void insertSortWithGap( int[] array , int start ,int gap ) {
        for ( int i = start + gap ; i < array.length ; i += gap ) {
            int temp = array[i];
            int j = i - gap;
            while ( j >= 0 && array[j] > temp ) {
                array[j + gap] = array[j];
                j -= gap;
            }
            array[j + gap] = temp;
        }
    }

快速排序

 快速排序(QuickSort)是对冒泡排序的一种改进,快速排序在1962年提出,它的基本思想是:通过一趟排序将要排序的数据分割成独立的两个部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到这个数据变成有序序列。

//快速排序
    public void quickSort( int[] array ){
        quickSort(array , 0 , array.length - 1 );
    }
    
    public void quickSort( int[] array , int left ,int right ){
        if ( left >= right ) {
            return;
        }
        int j = partition(array, left, right);
        quickSort( array , left , j - 1 );
        quickSort(array , j + 1 , right );
    }
    
    public void exchange( int[] array , int left ,int right ){
        int temp = array[left];
        array[left] = array[right];
        array[right] = temp;
    }
    
    public int partition( int[] array , int left ,int right ){
        int i = left , j = right + 1;
        while( true ){
            while ( array[++i] <= array[left] ) {
                if ( i == right ){
                    break;
                }
            }
            
            while ( array[--j] >= array[left] ) {
                if ( j == left ) {
                    break;
                }
            }
            
            if ( i >= j ) {
                break;
            }
            exchange(array, i, j);
        }
        exchange(array, left, j);
        return j;
    }