排序算法(Java)

来源:互联网 发布:淘宝直播端口 编辑:程序博客网 时间:2024/05/01 17:15



一、插入排序

每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止。

public static int[] insert_sort(int[] a){   int N = a.length;for(int i = 1; i < N; i++){for(int j = i; j>0; j--){if(a[j] < a[j-1]){int temp = a[j];a[j] = a[j-1];a[j-1] = temp;}}}return a;}

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,也就是第一个元素(默认它有序)。

比较是从有序序列的末尾开始,也就是把待插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面。

否则一直往前找直到找到它该插入的位置。如果遇见一个与插入元素相等的,那么把待插入的元素放在相等元素的后面。

所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序仍是排好序后的顺序,所以插入排序是稳定的。


二、希尔排序

基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

/** * 希尔排序 * 首先产生一个h数组[1, 4, 13, .....] * 当 h = 1 时 ,就是插入排序 */public class Shell_sort {public static void sort(int[] a){//将a[] 升序排序int N = a.length;int h = 1;//生成一个h数组while(h < N/3){h = 3*h + 1;  // 1 4 13 40 ...}while(h >= 1){for(int i = h; i < N; i++){for(int j = i; j >= h; j -= h){if(a[j] < a[j-h]){int temp = a[j-h];a[j-h] = a[j];a[j] = temp;}}}h = h / 3;}}public static void main(String[] args){int[] a = {10, 23, 1, 53, 654, 54, 16, 646, 65, 3155, 546, 31};sort(a);System.out.println(Arrays.toString(a));}}

一次插入排序是稳定的,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。

希尔排序的时间性能优于直接插入排序,原因如下:

(1) 当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。

(2) 当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。

(3) 在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。

因此,希尔排序在效率上较直接插人排序有较大的改进。希尔排序的平均时间复杂度为O(nlogn)。


三、选择排序

 基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

public static void select_sort(int[] a){   int N = a.length;for(int i = 0; i < N; i++){int min = i;for(int j = i+1; j < N; j++){if(a[j] < a[min]){min = j;}}int temp = a[min];a[min] = a[i];a[i] = temp;}}

举个例子:序列5 8 5 29我们知道第一趟选择第1个元素5会与2进行交换,那么原序列中两个5的相对先后顺序也就被破坏了。所以选择排序不是一个稳定的排序算法。 


四、堆排序

  1、基本思想:

  堆排序是一种树形选择排序,是对直接选择排序的有效改进。

  堆的定义下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=h2i+1)或(hi<=h2i,hi<=h2i+1)(i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。

  思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。堆排序也是一种不稳定的排序算法

public class Heap_sort {public static void main(String[] agrs){int[] a={49,38,65,97,76,13,27,49,78,34,12,64,1};heap_sort(a);System.out.println(Arrays.toString(a));}public static void heap_sort(int[] a){int N = a.length;for(int k = N/2; k >= 1; k--){ //构建堆sink(a, k, N);}while(N > 1){exch(a, 1, N); // 交换堆顶与堆最后一个元素N--;        //缩小数组长度sink(a, 1, N); // 构建堆}}public static void sink(int[] a, int k, int N){while(2*k <= N){int j = 2*k; // 2kif(j < N && less(a, j, j+1)){ //比较找出2k 2k+1最大值j++;}if(!less(a, k, j)){  //若父节点大于两个子节点中最大的子节点,无需交换break;}exch(a, k, j);k = j;}}private static boolean less(int[] a, int i, int j){return a[i-1] < a[j-1];}private static void exch(int[] a, int i, int j){int temp = a[i-1];a[i-1] = a[j-1];a[j-1] = temp;}}


五、冒泡排序

基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

public static void bubble_sort(int[] a){     for (int i = 0; i < a.length; i++) {            for(int j = 0; j<a.length-i-1; j++){                //这里-i主要是每遍历一次都把最大的i个数沉到最底下去了,没有必要再替换了                if(a[j]>a[j+1]){                    int temp = a[j];                    a[j] = a[j+1];                    a[j+1] = temp;                }            }        }}

冒泡排序就是把小的元素往前调(或者把大的元素往后调)。注意是相邻的两个元素进行比较,而且是否需要交换也发生在这两个元素之间。

如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个元素相邻起来,最终也不会交换它俩的位置,所以相同元素经过排序后顺序并没有改变。冒泡排序是一种稳定排序算法

 

六、快速排序

  基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

public static void quick_sort(int[] a){sort(a, 0, a.length-1);}private static void sort(int[] a, int lo, int hi){if(hi <= lo){return;}int j = partition(a, lo, hi);sort(a, lo, j-1);  // 将左半部分a[lo...j-1]排序sort(a, j+1, hi);  // 将右半部分a[j+1...hi]排序}private static int partition(int[] a, int lo, int hi){//将数组切分成 a[lo..i-1], a[i], a[i+1...hi]int i = lo, j = hi+1;    // 左右扫描指针int v = a[lo];    //切分元素while(true){//扫描左右数组,检查扫描是否结束,并交换元素while(a[++i] < v){if(i == hi)break;}while(v < a[--j]){if(j == lo)break;}if (i >= j)break;//交换a[i]  a[j]int temp = a[j];a[j] = a[i];a[i] = temp;}// 将v = a[j] 放入正确的位置int temp = a[lo];a[lo] = a[j];a[j] = temp;return j;         // a[lo..j-1] <= a[j] <= a[j+1...hi] 达成}

快速排序有两个方向,左边的i下标一直往右走(当条件a[i]<= a[center_index]时),其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走(当a[j] > a[center_index]时)。如果i和j都走不动了,i <=j, 交换a[i]和a[j],重复上面的过程,直到i>j。交换a[j]和a[center_index],完成一趟快速排序。

在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱。

所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。


七、归并排序

  基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

/** * 归并排序 * 归并排序主要缺点:辅助数组所使用的额外空间和N的大小成正比 */private static int[] aux; //辅助数组public static void merge_sort(int[] a){aux = new int[a.length];sort(a, 0, a.length-1);}public static void sort(int[] a, int lo, int hi){// 将数组 a[lo,....hi] 排序if(hi <= lo)return;int mid = lo + (hi - lo)/2;sort(a, lo, mid);    //左半边排序sort(a, mid+1, hi);  //右半边排序merge(a, lo, mid, hi);}public static void merge(int a[], int lo, int mid, int hi){// 将 a[lo...mid] 和 a[mid+1...hi] 归并int i = lo, j = mid+1;// 将a[lo....hi] 复制到aux[lo.....hi]中for(int k = lo; k <= hi; k++){aux[k] = a[k];}for(int k = lo; k <= hi; k++){if(i > mid)a[k] = aux[j++];else if(j > hi)a[k] = aux[i++];else if(aux[j] < aux[i])a[k] = aux[j++];elsea[k] = aux[i++];}}

归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的短序列合并成一个有序的长序列,不断合并直到原序列全部排好序。

可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?

没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。












1 0
原创粉丝点击