排序算法说明
1.排序的定义:对一序列对象根据某个关键字排序
2.评价算法优劣的术语说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序所有排序操作都在内存中完成; 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。 空间复杂度: 运行完一个程序所需内存的大小。
3.排序算法总结(来源于网络)
一.冒泡排序优化版
冒泡排序即:第一个数与第二个数进行比较,如果满足条件位置不变,再把第二个数与第三个数进行比较.不满足条件则替换位置,再把第二个数与第三个数进行比较,以此类推,执行完为一个趟,趟数等于比较的个数减一.
优化版:每一次减少一次循环
public class BubbleSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arry={9,8,7,6,5};
int len=arry.length;
for(int i=0;i<len-1;i++){
for(int j=0;j<len-1-i;j++){
if(arry[j]>arry[j+1]){
int temp=arry[j];
arry[j]=arry[j+1];
arry[j+1]=temp;
}
}
}
System.out.println(Arrays.toString(arry));
}
}
二.选择排序
选择排序就是每次从待排序元素中找到最小元素放在序列的起始位置,在剩余的元素中在找到最小的元素,放到排好序的序列后面,依次类推,直到所有元素都排序完。
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
public static void main(String[] args)
{
int[] ins = {2,3,5,1,23,6,78,34};
int[] ins2 = sort(ins);
System.out.println(Arrays.toString(ins2));
}
public static int[] sort(int[] ins){
int n = ins.length-1;//经过n-1次提取最小最大值
for(int i=0; i<n; i++){//控制选择的次数
int min_index = i;
for(int j=i+1; j<n; j++){
if(ins[j]<ins[i]){
min_index = j;
}
}
if(min_index != i){
int temp = ins[i];
ins[i] = ins[min_index];
ins[min_index] = temp;
}
}
return ins;
}
三.插入排序
插入排序的工作原理就是通过构建有序序列,对未排序序列,在已排好序的序列中从后往前遍历,找到合适的位置将其插入。在实现上就是在从后往前遍历过程中,不断把已排好序的元素逐步后移,为待插入元素提供插入空间。
public static void main(String[] args)
{
int[] ins = {2,3,5,1,23,6,78,34};
int[] ins2 = sort(ins);
System.out.println(Arrays.toString(ins2));
}
public static int[] sort(int[] ins){
for(int i=1;i<ins.length;i++){
int temp=ins[i];
int j;
for( j=i;j>0&&ins[j-1]>temp;j--){
ins[j]=ins[j-1];
}
ins[j]=temp;
}
return ins;
}
四.希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序过程
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
然后我们对每列进行排序:
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
排序之后变为:
最后以1步长进行排序(此时就是简单的插入排序了)
public static void main(String[] args)
{
int[] ins = {2,3,5,1,23,6,78,34,23,4,5,78,34,65,32,65,76,32,76,1,9};
int[] ins2 = sort(ins);
System.out.println(Arrays.toString(ins2));
}
public static int[] sort(int[] ins){
int len=ins.length;
int gap=len/2;
int i,j;
while(gap>0){
for(j=gap;j<len;j++){
int temp=ins[j];
for(i=j;i>=gap&&ins[i-gap]>ins[i];i-=gap){
ins[i]=ins[i-gap];
}
ins[i]=temp;
}
gap/=2;
}
return ins;
}
五.归并排序
原理:
归并排序利用的是分治的思想实现的,对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的子序列,之后对子序列排序,最后再用递归方法将排好序的子序列合并成为有序序列。合并两个子序列时,需要申请两个子序列加起来长度的内存,临时存储新的生成序列,再将新生成的序列赋值到原数组相应的位置。
`public static void main(String[] args)
{
int[] ins = {2,3,5,1,23,6,78,34,23,4,5,78,34,65,32,65,76,32,76,1,9};
int[] ins2 = sort(ins,0,ins.length-1);
System.out.println(Arrays.toString(ins2));
}
public static int[] sort(int[] ins,int left,int right){
if(left<right){
int mid=(left+right)/2;
sort(ins,left,mid);
sort(ins,mid+1,right);
merge(ins,left,mid,right);
}
return ins;
}
private static void merge(int[] ins,int left,int mid,int right){
int[] arry=new int[right-left+1];
int k=0;
int i=left,j=mid+1;
while(i<=mid&&j<=right){
if(ins[i]<ins[j]){
arry[k++]=ins[i++];
}else{
arry[k++]=ins[j++];
}
}
while(i<=mid){
arry[k++]=ins[i];
i++;
}
while(j<=right){
arry[k++]=ins[j];
j++;
}
for(int p=0;p<arry.length;p++){
ins[p+left]=arry[p];
}
}
`
六.快速排序
原理:
快速排序是我们之前学习的冒泡排序的升级,他们都属于交换类排序,都是采用不断的比较和移动来实现排序的。快速排序是一种非常高效的排序算法,它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动次数。同时采用“分而治之”的思想,把大的拆分为小的,小的拆分为更小的,其原理如下:对于给定的一组记录,选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分,直到序列中的所有记录均有序为止。
public class test {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
七.堆排序
原理
基本思想:把待排序的元素按照一定大小顺序在二叉在这里插入代码片
树上排好序,这个过程就就是建堆,如果父节点大于它的两个子节点,就是最大值堆,反之,是最小值堆。在建好堆的基础上,每次把根节点和最后一个叶结点交换,再堆化子树得到的元素顺序就是升序。
基本步骤
操作的核心就是建堆操作,这个过程就是将父节点和他的左右子节点进行比较,如果父节点不满足大于任意两个子节点,那么就将较大的子节点和父节点交换,这个过程要是逆序的,即从二叉树的倒数第二排最后一个元素开始,因为下面最后一层的叶结点满足条件,这样不断比较交换到了根节点,再从根节点返回比较,才能完成建堆。具体可看下图:(转载图片)
代码实现
//建堆函数
public static void initHeap(int[] arry,int i,int len){
int child,temp;
while(2*i+1<len){
child=2*i+1;
if(child+1<len&&arry[child]<arry[child+1]) child++;
if(arry[i]<arry[child]){
temp=arry[i];
arry[i]=arry[child];
arry[child]=temp;
}
i=child;
}
}
//堆排序
public static void HeapSort(int[]arry ,int len){
if(len<1) return ;
for(int i=len/2-1;i>=0;i--){
initHeap(arry, i, len);//先初始化堆,使最大元素在根节点
}
int temp;
for(int i=0;i<len;i++){
temp=arry[0];
arry[0]=arry[len-1-i];
arry[len-i-1]=temp;
initHeap(arry, 0, len-1-i);//交换位置后,再对剩余子树进行建堆,这样每次才能建一个真的大根堆
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int [] arry={2, 1, 4, 0, 12, 520, 2, 9, 5, 3, 13, 14};
HeapSort(arry,arry.length);
System.out.println(Arrays.toString(arry));
}
时间复杂度分析
堆排序的时间复杂度,主要在初始化堆过程和每次选取最大数后重新建堆的过程;
初始化建堆过程时间:O(n)
更改堆元素后重建堆时间:O(nlogn)
推算过程:
1、循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn ;
综上所述:堆排序的时间复杂度为:O(nlogn)