关于一些算法和时间/空间复杂度

排序的分类可以分为两种:内排序和外排序。

在排序过程中,全部记录放在内存,则称为内排序,如果排序过程中要使用外村,则称为外排序。这里讲的排序都是属于内排序。

内排序又可以分为以下几类:

(1)插入排序:直接插入排序,二分法插入排序和希尔排序

(2)选择排序:简单选择排序,堆排序

(3)交换排序:冒泡排序,快速排序

(4)归并排序

(5)基数排序

一、插入排序

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

关键问题:在前面已经排序好的序列中找到合适的插入位置。

方法:

  直接插入排序: 直接插入排序是稳定的排序。当文件初态为正态,则每个待插入的记录只需比较一次既能够找到合适的位置插入,故算法的时间复杂度为O(n),是最好的情况。如果初态为反序,则第i个待插入的记录需要比较i+1次才能找到合适位置,故时间复杂度为O(n2),这也是平均复杂度。


  二分插入排序:它的排序方法与直接插入思想一样,只是找合适位置的方式不同。用二分法可以减少比较的次数。

  希尔排序:一次插入排序是稳定的,但是在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,就会打乱其稳定性。希尔排序的时间性能优于直接排序。但是它是不稳定的排序。当n值较小时,n和n2的区别也小,所以直接插入排序的时间复杂度最好与最坏差不多。因此希尔排序经过分组,所以插入速度较快,后期虽然分组较少,但是经过之前的排序,使文件已经比较接近有序状态。所以也能提高速度。平均时间复杂度为O(nlogn)


实现:

  -直接插入排序  从后面向前找到合适位置后插入。直到序列中的每个元素都插入。

package com.sort;

public class DirectSort {

public static void main(String [] args){

             int[] a={58,46,59,26,45,57,87,62,12,1,64};

             int tmp;

                        //直接插入排序

                         for(int i=1;i<a.length;i++){

                              for(int j=i; j>0;j--){

                                if(a[j]<a[j-1]){

                                tmp=a[j-1]

                                a[j-1]=a[j];

                                a[j]=tmp;

                                                }

                                         }

                              }

                      for(int i:a){

                      System.out.print(i+" ");

                                     }

                }

}


-二分法插入排序 它的比较次数与带排序记录的初始状态无关,仅依赖于记录的个数。当n较大时,比直接插入排序的最大比较次数少的多。最坏的情况为n2/2,最好的情况为n,平均移动次数为n2

static public void halfSort(int a[]){

     for(int i =0;i<a.length;i++){

              int low = 0;

              int high = i;

              int mid = 0;

              int temp = a[i]; 

              while(low<=high){

               mid=(low+high)/2;

                if(a[mid]<temp){

                   low=mid+1;

                 }else{

                       high=mid-1;

                  }

           }

         //找到要插入的位置,然后将这个位置以后的元素向后移动

                 for(int j = i -1;j>high;j--){

                  a[j+1]=a[j];

        }

                 a[high+1]=temp;

    }

}


-希尔排序 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中,现在各组内进行直接插入排序;然后取第二个增量d2<d1重复上述的分组和排序。直至所取的增量dt为1.即所有记录放在同一组中进行直接插入排序位置。其实质是分组的直接插入排序。

public static void sort(int [] arrays){

            int incrementNum = arrays.length/2;

            while(incrementNum>=1){

         for(int i=0;i<arrays.length;i++){

               //进行插入排序

      for(int j=i;j<arrays.length-incrementNum;j+=incrementNum)             {

                       if(arrays[j]>arrays[j+incrementNum]){

                        int temp = arrays[j];

                        arrays[j] = arrays[j+incrementNum];

                        arrays[j+incrementNum] = temp;

                                }

                        }

              }

               //设置新的增量

                incrementNum/=2;

      }

}

二、选择排序

思想:每趟从待排序的序列里选择关键字最小的记录放置到已排序的最前位置。直到全部排完。

关键问题:在剩余的待排序记录序列中找到最小关键码记录。

方法:直接选择排序

简单选择排序是不稳定的排序。时间复杂度:T(n)=O(n2)

           堆排序:也是一种不稳定的排序方法。堆排序可通过树形结构保存部分比较结果,可减少比较次数。堆排序的最坏时间复杂度为O(nlogn).因为建初始堆所需的比较次数较多、所以堆排序不适宜于记录数比较少的文件。

              是一种就地排序算法(不需要额外的存储空间)

实现:

-直接选择排序它的在要排序的一组数中,选出最小的一个数与第一个位置的数交换,然后在剩下的数中再找最小的与第二个位置交换。直到最后一个数和倒数第二个数比较为止。这个是按固定的位置放置,插入排序是挨个比较。

//是不稳定的

public class simpleSelectSort{

           public static void main(String [] args){

        int[] a={49,38,65,97,76,13,27,49,78,34,12,64,1,8};

           for(int i = 0;i<a.length;i++){

             int min = a[i];

             int n = i;//最小索引

            for(int j = i+1;j<a.length;j++){

             if(a[j]<min){

            //找出最小的数

              min=a[j];

              n=j;

           }

       }

         a[n] = a[i];//如果更换了最小值,相应的交换了位置

         a[i] = min;

}

}

}

-堆排序它是一种属性选择排序,是对直接选择排序的一种改进。

由堆的定义可以看出,堆顶元素必为最大项。完全二叉树可以很直观的表示堆的结构。堆顶为根,其它为左子树、右子树。初始排序时,将待排序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,使之成为一个堆,堆的根节点的数最大。然后将根节点与堆的最后一个节点交换,然后前面剩下的n-1个元素再去构成一个堆。以此类推,直到只剩两个节点的堆,将它们作交换,得到一个n个节点的有序排列。从算法来看,堆排序需要经过两个过程,一、建立堆,二、是堆顶与最后一个元素交换位置。所以堆排序由两个函数组成。一个是建立堆的渗透函数,一个是反复调用渗透函数实现排序的函数。

在这里首先学习一下,大顶堆的实现。堆的建立在堆排序里是至关重要的。除了最后一层,其它层都是填满的,也正是因为这样,树里面每个节点的子女和双亲节点的序号都可以根据当前节点的序号直接求出。

parent(i)=i/2

Left(i)=2*i

right(i)=2*i+1

最大堆的性质就是,根节点值大于所有子节点,我们构建最大堆的操作,对于每个节点i,我们观察它的子节点的大小,如果它小于某个子节点, 那么则进行位置调换。然后在相应的子节点上同样进行这样的操作。直到比所有的子节点都大为止。


三、交换排序

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

冒泡最好复杂度是O(n),最差是O(n2),这也是平均时间复杂度。


public class Maopao{

public static void main(String [] args){

              int[] a= {49,38,65,97,76,13,27,49,78,34,12,64,1,8};

              for(int i = 0;i<a.length){

                             for(int j = 0;j<a.length-i-1;j++){

  //这里-i主要是每遍历一次都把最大的i个数沉到最底下去了,     没有必要再替换了

                               if(a[j]>a[j+1]){

                               //一次次遍历后,最大的数就到最后去了

                             int temp = a[j+1];

                              a[j+1]=a[j];

                              a[j]=temp;

}

}

}

}

}

-快速排序 选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分都比基准元素大或等于,一部分都比基准元素小。这时基准元素就处在了正确的位置。然后用同样的方法递归的排序划分的两部分。快速排序也是不稳定的。


public static void sort(int a[],int low,int high){

int index=a[low];

while(low<high){

index = a[low];//用子表的第一个记录做基准

while(low<high&&a[high]>=index){

           high--;

}

a[low]=array[high];

}

while(a[low]<=index&&high>low){

         lo++;

}

a[high]=a[low];

}

array[high]=key;

return hi;

}

public static void sort(int[] array,int low,int high){

 int inidex=partition(array,low,high);

sort(array,low,index-1);

sort(array,index+1,hi);

}


四、归并排序

-归并排序 归并排序法是把两个或两个以上的有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列都是有序的。然后把有序子序列合并为整体有序序列。归并排序是稳定的排序方法,归并序列的复杂度为O(nlogn)。速度仅次于快速排序,为稳定排序算法,一般用于总体无序,但是各子项相对有序的数列。

public class MergeSort{

public static void main (String [] args){

int [] a={49,38,65,97,76,13,27,49,78,34,12,64,1,8};

//归并排序

mergeSort(a,0,a.length-1);

for(int i =0;i<a.length;i++){

System.out.print(a[i]+" ");

}

}

private static void mergeSort(int[] a,int left,int right){

if(left<right){

int middle = (left+right)/2;

//对左边递归

mergeSort(a,left,middle);

//对右边递归

mergeSort(a,middle+1,right);

//合并

merge(a,left,middle,right);

}

}


private static void merge(int[] a,int left,int middle, int right){

int[] tmpArr = new int[a.length];

int mid= middle+1;//右边的起始位置

int tmp = left;

int third = left;

while(left<=middle&&mid<=right){

//从两个数组中选取较小的数放入中间数组

if(a[left]<=a[mid]){

tmpArr[third++] = a[left++];

}else{

tmpArr[third++]=a[mid++];

}

}

//将剩余的部分放入中间数组

while(left<=middle){

tmpArr[third++] = a[left++];

}

while(mid<=right){

tmpArr[third++]=a[mid++];

}

//将中间数组复制回原数组

while(tmp<=right){

a[tmp] = tmpArr[tmp++];

}

}

}

五、基数排序

-基数排序 将所有待比较数值(正整数)统一为同样的数位长度,数位较短者的前面补零。然后从最低位开始,依次进行一次排序。这样从最低位到最高位排序完成后,数列就变成了一个有序数列。基数排序是稳定的排序算法,基数排序的时间复杂度为O(d(n+r)),d为位数,r为基数。

public class baseSort{

public static void main(String[] args){

int[] a={49,38,65,97,176,213,227,49,78,34,12,164,11,18,1};

sort(a);

}

private static void sort(int[] array){

//找到最大数,确定要排序几趟

int max = 0;

for(int i = 0;i<array.length;i++){

if(max<array[i]){

max=array[i];

}

}

//判断位数

int times = 0;

while(max>0){

max = max/10;

times++;

}

//建立十个队列

List<ArrayList> queue = new ArrayList<ArrayList>();

for(int i = 0;i<10;i++){

ArrayList queue1 = new ArrayList();

queue.add(queue1);

}

//进行times次分配和收集

for(int i=0;i<times;i++){

//分配

for(int j=0;j<array.length;j++){

int x = array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i);

ArrayList queue2 = queue.get(x);

queue2.add(array[j]);

queue.set(x,queue2);

}

//收集

intcount = 0;

for(intj = 0; j < 10; j++) {

while(queue.get(j).size()>0){

ArrayList queue3 =queue.get(j);

array[count] = queue3.get(0);

queue3.remove(0);

count++;

}

}

}

}

}

总结:

一、稳定性

稳定:冒泡排序,基数排序,归并排序,插入排序

不稳定:希尔排序,选择排序,快速排序,堆排序

二、平均时间复杂度

O(n^2):直接插入排序,简单选择排序,冒泡拍戏

在数据规模较小时(9w内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。这种算法基本是稳定

O(nlogn):快速排序,归并排序,希尔排序,堆排序

其中快速排序是最好的,其次是归并和希尔,堆排序在数据量很大时,效果明显。

三、排序算法的选择

1.数据规模较小时

(1)待排序列基本序的情况下,可以选择直接插入排序

(2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡。

2.数据规模很大时

(1)对稳定性有要求,可考虑归并排序

(2)对稳定性没有要求,宜用堆排序

3.数据规模适中

(1)完全可以用内存空间,序列杂乱,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。

(2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序。

4.序列初始基本有序(正序),宜用直接插入或冒泡。



更新巩固

1.几乎有序的时候,快排的时间复杂度退化到O(n*n),无序的时候快速排序才是比较快。这个时候采用堆排序是最省时间的。

2.归并排序在任何时候的时间复杂度均为O(N*logN),空间复杂度为O(N);但是需要建立一个栈来维护,快排在最好的情况下时间复杂度为O(N*logN)。空间复杂度为O(logN)

3.希尔排序是缩小增量排序,所以对于初始序列有序还是无序没有直接关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容

  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,318评论 0 1
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,155评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,726评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,233评论 0 2
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,253评论 0 35