排序算法Java实现

来源:互联网 发布:网络摄像头安装图解 编辑:程序博客网 时间:2024/05/19 23:16

选择排序

直接选择排序

在直接选择排序中,共需要进行n-1次选择和交换,每次选择需要进行 n-i 次比较 (1<=i<=n-1),而每次交换最多需要3次移动,因此,总的比较次数C=(n*n - n)/2,总的移动次数 3(n-1)。由此可知,直接选择排序的时间复杂度为 O(n2) (n的平方),所以当记录占用字节数较多时,通常比直接插入排序的执行速度快些。
由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。

//为了使用Random类,需要加入如下这行:import java.util.*;/** * 直接选择排序算法的Java实现。<br/> *   1:从a[0]-a[N-1]中选出最小的数据,然后与a[0]交换位置<br/> *   2:从a[1]-a[N-1]中选出最小的数据,然后与a[1]交换位置(第1步结束后a[0]就是N个数的最小值)<br/> *   3:从a[2]-a[N-1]中选出最小的数据,然后与a[2]交换位置(第2步结束后a[1]就是N-1个数的最小值)<br/> *   以此类推,N-1次排序后,待排数据就已经按照从小到大的顺序排列了。<br/> * 此代码作为课件提供给学生参考,在学完数组、循环、判断后练习。<br/> * @author luo_wenqiang@126点com * @version 1.0.0 */public class 直接选择排序{    public static void main(String[] args)     {        //声明一个数组        int[] array = new int[10];        //为这个数组随机填入整型数字        Random random = new Random();        for (int i = 0; i < array.length ; i++)        {            array[i] = random.nextInt(500);        }        System.out.print("原始数组  :");        System.out.println(Arrays.toString(array));        /****************************************         下面开始正式的“直接选择排序”算法         直接选择排序的关键:            1:从a[0]-a[N-1]中选出最小的数据,然后与a[0]交换位置            2:从a[1]-a[N-1]中选出最小的数据,然后与a[1]交换位置(第1步结束后a[0]就是N个数的最小值)            3:从a[2]-a[N-1]中选出最小的数据,然后与a[2]交换位置(第2步结束后a[1]就是N-1个数的最小值)            以此类推,N-1次排序后,待排数据就已经按照从小到大的顺序排列了。         ****************************************/         //N个数组元素,就需要循环N轮         for(int i = 0; i < array.length; i++){             //最小数的索引,该索引每次都根据外层循环的计数器来觉得初始值。             int minIndex = i;             for (int j = i + 1; j < array.length; j++)             {                 //根据最小数的索引,判断当前这个数是否小于最小数。                 //如果小于,则把当前数的索引作为最小数的索引。                 //否则不处理。                 if(array[minIndex] > array[j]){                     minIndex = j;                 }                 //直到循环完成的时候,minIndex肯定就是当前这轮循环中,最小的那个。             }             //System.out.print(i + "轮,最小数" + array[minIndex] + ",");             //System.out.print("原索引" + minIndex + ",新索引" + i);             //得到最小数的索引后,把该索引对应的值放到最左边,并且把最左边的值放到索引所在的位置.             //最左边的值             int temp = array[i];             //把最小数索引对应的值放到最左边             array[i] = array[minIndex];             //把原来最左边对应的值放到最小数索引所在的位置             array[minIndex] = temp;             System.out.println(String.format("%2s",(i + 1)) + "轮排序后:" + Arrays.toString(array));         }    }}

堆排序

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)[2]
注意
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止
特点
堆排序(HeapSort)是一树形选择排序。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系(参见二叉树的顺序存储结构),在当前无序区中选择关键字最大(或最小)的记录

package com.itant.sort;public class HeapSort {    private static int[] sort = new int[] { 1, 0, 10, 20, 3, 5, 6, 4, 9, 8, 12, 17, 34, 11 };    public static void main(String[] args) {        buildMaxHeapify(sort);        heapSort(sort);        print(sort);    }    private static void buildMaxHeapify(int[] data) {        // 没有子节点的才需要创建最大堆,从最后一个的父节点开始        int startIndex = getParentIndex(data.length - 1);        // 从尾端开始创建最大堆,每次都是正确的堆        for (int i = startIndex; i >= 0; i--) {            maxHeapify(data, data.length, i);        }    }    /**     * 创建最大堆     *      * @paramdata     * @paramheapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了     * @paramindex当前需要创建最大堆的位置     */    private static void maxHeapify(int[] data, int heapSize, int index) {        // 当前点与左右子节点比较        int left = getChildLeftIndex(index);        int right = getChildRightIndex(index);        int largest = index;        if (left < heapSize && data[index] < data[left]) {            largest = left;        }        if (right < heapSize && data[largest] < data[right]) {            largest = right;        }        // 得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整        if (largest != index) {            int temp = data[index];            data[index] = data[largest];            data[largest] = temp;            maxHeapify(data, heapSize, largest);        }    }    /**     * 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的     *      * @paramdata     */    private static void heapSort(int[] data) {        // 末尾与头交换,交换后调整最大堆        for (int i = data.length - 1; i > 0; i--) {            int temp = data[0];            data[0] = data[i];            data[i] = temp;            maxHeapify(data, i, 0);        }    }    /**     * 父节点位置     *      * @paramcurrent     * @return     */    private static int getParentIndex(int current) {        return (current - 1) >> 1;    }    /**     * 左子节点position注意括号,加法优先级更高     *      * @paramcurrent     * @return     */    private static int getChildLeftIndex(int current) {        return (current << 1) + 1;    }    /**     * 右子节点position     *      * @paramcurrent     * @return     */    private static int getChildRightIndex(int current) {        return (current << 1) + 2;    }    private static void print(int[] data) {        int pre = -2;        for (int i = 0; i < data.length; i++) {            if (pre < (int) getLog(i + 1)) {                pre = (int) getLog(i + 1);                System.out.println();            }            System.out.print(data[i] + "|");        }    }    /**     * 以2为底的对数     *      * @paramparam     * @return     */    private static double getLog(double param) {        return Math.log(param) / Math.log(2);    }}

插入排序

直接插入排序

直接插入排序(straight insertion sort)的做法是:
每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。
直接插入排序属于稳定的排序,最坏时间复杂性为O(n^2),空间复杂度为O(1)。
直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。
值得注意的是,我们必需用一个存储空间来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 插入排序类似玩牌时整理手中纸牌的过程。插入排序的基本方法是:每步将一个待排序的记录按其关键字的大小插到前面已经排序的序列中的适当位置,直到全部记录插入完毕为止。
排序方法

1.简单方法
首先在当前有序区R[1..i-1]中查找R[i]的正确插入位置k(1≤k≤i-1);然后将R[k..i-1]中的记录均后移一个位置,腾出k位置上的空间插入R[i]。
注意:若R[i]的关键字大于等于R[1..i-1]中所有记录的关键字,则R[i]就是插入原位置。
2.改进的方法
一种查找比较操作和记录移动操作交替地进行的方法。具体做法:
将待插入记录R[i]的关键字从右向左依次与有序区中记录Rj的关键字进行比较:
① 若R[j]的关键字大于R[i]的关键字,则将R[j]后移一个位置;
②若R[j]的关键字小于或等于R[i]的关键字,则查找过程结束,j+1即为R[i]的插入位置。
关键字比R[i]的关键字大的记录均已后移,所以j+1的位置已经腾空,只要将R[i]直接插入此位置即可完成一趟直接插入排序。

public class InsertSort{    for(int i=1;i<array.length;i++)//第0位独自作为有序数列,从第1位开始向后遍历    {        if(array[i]<array[i-1])//0~i-1位为有序,若第i位大于i-1位,继续寻位并插入,否则认为0~i位也是有序的,忽略此次循环,相当于continue        {            int temp=array[i];//保存第i位的值            for(int j=i-1;j>=0 && temp<array[j];j--)//从第i-1位向前遍历并移位,直至找到小于第i位值停止            {                array[j+1]=array[j];            }            array[j+1]=temp;//插入第i位的值        }    }}

希尔排序

不需要大量的辅助空间,和归并排序一样容易实现。希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O( ),希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O( )复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。 原因是,当n值很大时数据项每一趟排序需要的个数很少,但数据项的距离很长。当n值减小时每一趟需要和动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。Shell算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。

public static void main(String [] args){    int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1};        System.out.println("排序之前:");        for(int i=0;i<a.length;i++)        {            System.out.print(a[i]+" ");        }        //希尔排序        int d=a.length;            while(true)            {                d=d/2;                for(int x=0;x<d;x++)                {                    for(int i=x+d;i<a.length;i=i+d)                    {                        int temp=a[i];                        int j;                        for(j=i-d;j>=0&&a[j]>temp;j=j-d)                        {                            a[j+d]=a[j];                        }                        a[j+d]=temp;                    }                }                if(d==1)                {                    break;                }            }            System.out.println();            System.out.println("排序之后:");                for(int i=0;i<a.length;i++)                {                    System.out.print(a[i]+" ");                }    }上面写的看的人头晕  我写个好理解的    while(true){            for(int i=0;i<d;i++){                for(int j=i;j+d<a.length;j+=d){                int temp;                if(a[j]>a[j+d]){                    temp=a[j];                    a[j]=a[j+d];                    a[j+d]=temp;                    }                }            }            if(d==1){break;}            d--;           }

交换排序

冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序算法的运作如下:

比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

public class BubbleSort {    public static void main(String[] args) {        int scores[] = {1, 5, 9, 7, 4};        // 最多做n-1趟排序        for (int i = 0; i < scores.length - 1; i++) {            // 对当前无序区间score[0......length-i-1]进行排序(j的范围很关键,这个范围是在逐步缩小的)            for (int j = 0; j < scores.length - i - 1; j++) {                if (scores[j] < scores[j + 1]) {                    // 将小的数往后移                    int temp = scores[j];                    scores[j] = scores[j + 1];                    scores[j + 1] = temp;                }            }        }        for (int a = 0; a < scores.length; a++) {            System.out.print(scores[a] + "\t");        }    }}

快速排序

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

class Quick{ public void sort(int arr[],int low,int high) { int l=low; int h=high; int povit=arr[low]; while(l<h) { while(l<h&&arr[h]>=povit) h--; if(l<h){ int temp=arr[h]; arr[h]=arr[l]; arr[l]=temp; l++; } while(l<h&&arr[l]<=povit) l++; if(l<h){ int temp=arr[h]; arr[h]=arr[l]; arr[l]=temp; h--; } } print(arr); System.out.print("l="+(l+1)+"h="+(h+1)+"povit="+povit+"\n"); if(l>low)sort(arr,low,h-1); if(h<high)sort(arr,l+1,high); }}/*//////////////////////////方式二////////////////////////////////*/更高效点的代码:public<TextendsComparable<?superT>>T[]quickSort(T[]targetArr,intstart,intend){inti=start+1,j=end;Tkey=targetArr[start];SortUtil<T>sUtil=newSortUtil<T>();if(start>=end)return(targetArr);/*从i++和j--两个方向搜索不满足条件的值并交换**条件为:i++方向小于key,j--方向大于key*/while(true){while(targetArr[j].compareTo(key)>0)j--;while(targetArr[i].compareTo(key)<0&&i<j)i++;if(i>=j)break;sUtil.swap(targetArr,i,j);if(targetArr[i]==key){j--;}else{i++;}}/*关键数据放到‘中间’*/sUtil.swap(targetArr,start,j);if(start<i-1){this.quickSort(targetArr,start,i-1);}if(j+1<end){this.quickSort(targetArr,j+1,end);}returntargetArr;}/*//////////////方式三:减少交换次数,提高效率/////////////////////*/private<TextendsComparable<?superT>>voidquickSort(T[]targetArr,intstart,intend){inti=start,j=end;Tkey=targetArr[start];while(i<j){/*按j--方向遍历目标数组,直到比key小的值为止*/while(j>i&&targetArr[j].compareTo(key)>=0){j--;}if(i<j){/*targetArr[i]已经保存在key中,可将后面的数填入*/targetArr[i]=targetArr[j];i++;}/*按i++方向遍历目标数组,直到比key大的值为止*/while(i<j&&targetArr[i].compareTo(key)<=0)/*此处一定要小于等于零,假设数组之内有一亿个1,0交替出现的话,而key的值又恰巧是1的话,那么这个小于等于的作用就会使下面的if语句少执行一亿次。*/{i++;}if(i<j){/*targetArr[j]已保存在targetArr[i]中,可将前面的值填入*/targetArr[j]=targetArr[i];j--;}}/*此时i==j*/targetArr[i]=key;/*递归调用,把key前面的完成排序*/this.quickSort(targetArr,start,i-1);/*递归调用,把key后面的完成排序*/this.quickSort(targetArr,j+1,end);}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

public class MergeSort {// private static long sum = 0;/** * <pre> * 二路归并 * 原理:将两个有序表合并和一个有序表 * </pre> *  * @param a * @param s * 第一个有序表的起始下标 * @param m * 第二个有序表的起始下标 * @param t * 第二个有序表的结束小标 *  */private static void merge(int[] a, int s, int m, int t) {int[] tmp = new int[t - s + 1];int i = s, j = m, k = 0;while (i < m && j <= t) {if (a[i] <= a[j]) {tmp[k] = a[i];k++;i++;} else {tmp[k] = a[j];j++;k++;}}while (i < m) {tmp[k] = a[i];i++;k++;}while (j <= t) {tmp[k] = a[j];j++;k++;}System.arraycopy(tmp, 0, a, s, tmp.length);}/** *  * @param a * @param s * @param len * 每次归并的有序集合的长度 */public static void mergeSort(int[] a, int s, int len) {int size = a.length;int mid = size / (len << 1);int c = size & ((len << 1) - 1);// -------归并到只剩一个有序集合的时候结束算法-------//if (mid == 0)return;// ------进行一趟归并排序-------//for (int i = 0; i < mid; ++i) {s = i * 2 * len;merge(a, s, s + len, (len << 1) + s - 1);}// -------将剩下的数和倒数一个有序集合归并-------//if (c != 0)merge(a, size - c - 2 * len, size - c, size - 1);// -------递归执行下一趟归并排序------//mergeSort(a, 0, 2 * len);}public static void main(String[] args) {int[] a = new int[] { 4, 3, 6, 1, 2, 5 };mergeSort(a, 0, 1);for (int i = 0; i < a.length; ++i) {System.out.print(a[i] + " ");}}}
0 0