java排序算法整理(一)

来源:互联网 发布:瞻博网络最新新闻 编辑:程序博客网 时间:2024/06/05 04:11

排序是应用软件设计中经常遇到的问题之一,也是在面试过程中最常考察的算法。排序是对数据元素序列简历某种有序排列的过程。这里总结一下常用的排序算法,主要有插入排序、交换排序、选择排序、归并排序和基数排序等,并附上Java代码的实现(本文主要讨论非递减有序排序)。

1. 插入排序

插入排序的基本思想是:从初始有序的子集合开始,不断地把新的数据元素按其值的大小插入到已排序数据元素子集合的适当位置,使子集合中数据元素的个数不断增多,当子集合个数等于集合个数时,插入排序算法结束。常用的插入排序有直接插入排序希尔排序两种。

a. 直接插入排序

思想:顺序的把待排序的数据元素按照值的大小插入到已排序数据元素子集合的适当位置。
直接插入排序算法如下:

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

复杂度分析:
(1) 最好:原始数据已经全部排好序,这是算法内层的while循环次数每次均为0,外层for循环中每次数据元素的比较次数均为1,整个排序过程中的比较次数为n-1。所以直接插入排序算法最好情况下的时间复杂度为O(n)。
(2) 最坏:与最好相反,算法内层while循环的循环次数每次均为i。这样整个外层for循环中的比较次数和赋值语句执行次数中,比较次数为(n-1)(n+2)/2,移动次数为(n-1)(n+4)/2,因此直接插入排序算法最坏情况下的时间复杂度为O(n^2)。
(3) 平均情况下直接插入排序的时间复杂度为O(n^2)。
结论:原始数据元素越接近有序,直接插入排序算法的时间效率越高,其时间效率在O(n)到O(n^2)之间;直接插入排序算法的空间复杂度为O(1),是一种稳定的排序算法

b. 希尔排序

基本思想:先将整个待排序的元素序列分为若干个子序列(由相隔某个增量的元素组成)分别进行插入排序,然后缩减增量继续进行排序,待整个序列中的元素基本有序时,再对全体元素进行一次插入排序。因为直接插入排序在元素基本有序的情况下效率很高,因此希尔排序在时间效率上相较前者有较大提高。
希尔排序算法如下:

public static void shellSort(int[] arr){        int increment = arr.length/2;        while(increment >= 1){            for(int i = 0; i < arr.length; i++){                for(int j = i; j < arr.length - increment; j += increment){                    if (arr[j] > arr[j+increment]){                        int k = j;                        int tmp = arr[j+increment];                        while(k >= 0 && arr[k] > tmp){                            arr[k+increment] = arr[k];                            k -= increment;                        }                        arr[k+increment] = tmp;                    }                }            }            increment /= 2;        }    }

总结:希尔排序最坏时间复杂度为O(n^2)平均时间复杂度为O(n^1.3)最好时间复杂度为O(n),空间复杂度为O(1)。由于希尔排序算法是按照增量分组进行的排序,两个相同的数据元素有可能分在不同的组中,所以希尔排序是一种不稳定的排序算法

2. 选择排序

选择排序的基本思想是,每次从待排序的数据元素集合选取最小(或最大)的数据元素放到数据元素集合的最前(或最后),数据元素集合不断缩小,当数据元素集合为空时排序过程结束。

a. 直接选择排序

思想:从待排序的数据元素集合中选取最小的数据元素并将它与原始数据元素集合中第一个数据元素交换位置。

直接选择排序的算法:

public static void selectSort(int[] arr){        int len = arr.length;        for(int i = 0; i < len-1; i++){            int small = i;            for(int j = i+1; j < len; j++){                if (arr[j] < arr[small])                    small = j;                          }            if (small != i){                int tmp = arr[i];                arr[i] = arr[small];                arr[small] = tmp;            }        }    }

但是这是一种不稳定的排序算法,因为每次从无序记录区选出最小记录后,与无序去第一个记录交换而引起的,因为交换可能引起相同数据元素位置发生变化。交换可能引起相同的数据元素位置发生变化,若能够在选出最小记录后,将它前面无序记录依次后移,然后再将最小记录放在有序区的后面,这样就能保证排序算法的稳定性。
因此,稳定的直接选择排序算法如下:

public static void selectSort2(int[] arr){        int len = arr.length;        for(int i = 0; i < len-1; i++){            int minIndex = i;            for(int j = i+1; j < len; j++){                if (arr[j] < arr[minIndex])                    minIndex = j;            }            if (minIndex != i){                int tmp = arr[minIndex];                for(int k = minIndex; k > i; k--){                    arr[k] = arr[k-1];                }                arr[i] = tmp;            }        }    }

总结:直接选择排序的算法空间复杂度为O(1)最好最坏以及平均的时间复杂度都为O(n^2)。

b. 堆排序

在直接选择排序中,放在数组中的n个数据元素排成一个线性序列,要从n个数据元素的数组中选择出一个最小的数据元素需要比较n-1次。若能把待排序的数据元素集合构造成一个完全二叉树结构,则每次选择出一个最大(或最小)的数据元素只需比较完全二叉树的高度次(logn)次,而排序算法的时间复杂度就是O(nlogn)。

1. 堆的定义和性质
堆分为最大堆和最小堆两种。当父节点的键值总是大于或等于任何一个子节点的键值时称最大堆;当父节点的键值总是小于等于任何一个子节点的键值时为最小堆。
根据堆的定义可以推知,堆有如下两个性质:
(1) 最大堆的根节点是堆中值最大的数据元素,最小堆的根节点是堆中值最小的数据元素,我们称堆的根节点元素为堆顶元素。
(2) 对于最大堆,从根节点到每个叶结点的路径上,数据元素组成的序列都是递减有序的;对于最小堆,从根节点到每个叶结点的路径上,数据元素组成的序列都是递增有序的。

2. 创建堆
要进行堆排序,首先要创建堆。非递减序列排序时,要创建最大堆。设数组a中存放了n个数据元素,当把数组a中这n个数组元素看做是一颗完全二叉树的n个结点,则这棵有n个结点的完全二叉树采用了顺序存储结构,但是完全二叉树还不一定满足最大堆的定义,要让一颗完全二叉树满足最大堆的定义,需要调整数组元素,使它们满足最大堆的定义。
在一棵按顺序存储结构存储的完全二叉树中,所有叶结点都满足最大堆的定义。对于第1个非叶结点ai,由于其左孩子结点a[2i+1]和右孩子结点a[2i+2]都已是最大堆,所以只需找出a[2i+1]结点和a[2i+2]结点的较大者,然后比较这个较大者结点和a[i]结点,若a[i]大于或等于这个较大结点,则以a[i]结点为根节点的完全二叉树已满足最大堆的定义;否则,交换a[i]结点和这个较大结点的数据元素,交换后以a[i]结点为根节点的完全二叉树就满足最大堆的定义。这样一次调整非叶结点,直至最后调整根结点,当根节点调整玩之后,则这棵完全二叉树就是一个最大堆了。

把完全二叉树调整为最大堆的过程就是把数组中的元素按照最大堆的要求进行调整的过程,所以这样的过程也称为数组的最大堆化。
调整完全二叉树中某个非叶结点a[i],使之满足最大堆定义,前提条件是该结点的左孩子结点a[2i+1]和右孩子结点a[2i+2]都已是最大堆。
此功能的算法如下:

public static void createHeap(int[] arr, int n, int h){        int i, j, flag;        int tmp;        i = h;        j = 2*i+1;        tmp = arr[i];        flag = 0;        while(j < n && flag != 1){            if (j < n-1 && arr[j] < arr[j+1])                j++;            if (tmp > arr[j]){                flag = 1;            }            else{                arr[i] = arr[j];                i = j;                j = 2*i + 1;            }        }        arr[i] = tmp;    }
    public static void initCreateHeap(int[] arr){        int n = arr.length;        for(int i = (n-1)/2; i >= 0; i--){            createHeap(arr,n,i);        }    }

3. 堆排序算法
堆排序的基本思想是:(1)把堆顶a[0]元素(最大元素)和当前最大堆的最后一个元素交换;(2)最大堆元素个数减1;(3)调整根结点使其满足最大堆的定义。
堆排序算法如下:

public static void heapSort(int[] arr){        int tmp;        int len = arr.length;        initCreateHeap(arr);        for(int i = len-1; i > 0; i--){            tmp = arr[0];            arr[0] = arr[i];            arr[i] = tmp;            createHeap(arr,i,0);        }    }

总结:堆排序算法是基于完全二叉树的排序。把一个完全二叉树调整为堆,以及每次堆顶元素交换后进行调整的时间复杂度均为O(logn),所以堆排序算法的时间复杂度为O(nlogn)。堆排序算法的空间复杂度为O(1)。

1 0
原创粉丝点击