归并排序思想很适用于_排序

fbc1f40292e23bf1dd0570e52955de41.png

排序的稳定性和复杂度

      不稳定:

      选择排序(selection sort)— O(n2)

      快速排序(quicksort)— O(nlogn) 平均时间, O(n2) 最坏情况; 对于大的、乱序串列一般认为是最快的已知排序

      堆排序 (heapsort)— O(nlogn)

      希尔排序 (shell sort)— O(nlogn)

      基数排序(radix sort)— O(n·k); 需要 O(n) 额外存储空间 (K为特征个数)

      稳定:

      插入排序(insertion sort)— O(n2)

      冒泡排序(bubble sort) — O(n2)

      归并排序 (merge sort)— O(n log n); 需要 O(n) 额外存储空间

      二叉树排序(Binary tree sort) — O(nlogn); 需要 O(n) 额外存储空间

      计数排序  (counting sort) — O(n+k); 需要 O(n+k) 额外存储空间,k为序列中Max-Min+1

      桶排序 (bucket sort)— O(n); 需要 O(k) 额外存储空间

二、算法各自的特点(具体实现见后面博客)

1.快排

(1)算法思想

选择一个基准元素,将比基准元素小的元素放在其前面,比基准元素大的元素放在其后面,然后在将小于基准值元素的子数列和大于基准元素的子数列按原来的方法排序,直到整个序列有序;

(2)优缺点

优点:极快数据移动少;

缺点:不稳定;

(3)效率分析

此排序算法的效率在序列越乱的时候,效率越高。在数据有序时,会退化成冒泡排序;

(4)对于基准的选择

a.三数取中

具体思想:对待排序序列中low、mid、high三个位置上数据进行排序,取他们中间的那个数据作为枢轴,并用0下标元素存储枢轴;

b.随机选取基准

引入原因:在待排序列是部分有序时,固定选取枢轴使快排效率低下;

具体思想:取在待排序列中任意一个元素作为基准;

5)优化方法

a.当待排序序列的长度分割到一定大小后,使用插入排序;

原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排;

b.在一次分割结束后,可以把与key相等的元素聚集在一起,继续下次分割时,不必再对于key相等元素分割;

(6)应用场景

 a.求数组中第k小的数

将数组中某一个元素m作为划分依据,即m=arr[0]。若m前面的元素个数大于k,则第k小的数一定在m前面的元素中,这时我们只需要继续在m前面的元素中找第k小的数;若m前面的元素小于k,则第k小的数一定在m后面的元素中,这时我们只需要在m后面的元素中找第k小的数;

2.冒泡排序

(1)基本原理

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

(2)优缺点

优点:稳定

缺点:慢,每次只能移动两个相邻的数据;

3. 插入排序—直接插入排序(Straight Insertion Sort)

(1)基本思想

将一个记录插入到已排序好的有序表中,从而得到一个新的,记录数增1的有序表。即先将序列的第一个记录看成是一个有序的子序列,然后从第二个记录逐个进行插入,直至整个序列有序为止。

(2)优缺点

优点:稳定,快

缺点:比较次数不一定,比较次数越少,插入点后的数据移动越多,特别是数据量庞大的时候

要点:设立哨兵,作为临时存储和判断数组边界之用。

void insertion_sort (int a[], int n) {    int i,j,v;    for (i=1; i      //如果第i个元素小于第j个,则第j个向后移动        for (v=a[i], j=i-1; j>=0&&v            a[j+1]=a[j];        a[j+1]=v;    }}

4.堆排序

4.1、二叉堆定义:

二叉堆是完全二叉树或近似完全二叉树。二叉堆满足两个特性:

(1)父结点的键值总是大于或者等于(小于或者等于)任何一个子节点的键值;

(2)每个结点的左子树和右子树都是一个二叉堆;

当父结点的键值总是大于或者等于任何一个子节点的键值时为大根堆。当父结点的键值总是小于或等于任何一个子节点的键值时为小根堆;

4.2、堆的存储:

一般都用数组来表示堆,i结点的父结点下标就为(i-1)/2.它的左右子节点的下标分别为2*i+1和2*i+2.

bcc3c6de4702e4d3bb39546c0948c6ea.png

4.3、堆的插入:

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,然后将这个新数据插入到这个有序数据中

(1)用大根堆排序的基本思想

先将初始数组建成一个大根堆,此对为初始的无序区;

再将最大的元素和无序区的最后一个记录交换,由此得到新的无序区和有序区,且满足<=的值;

由于交换后新的根可能违反堆性质,故将当前无序区调整为堆。然后再次将其中最大的元素和该区间的最后一个记录交换,由此得到新的无序区和有序区,且仍满足关系的值<=的值,同样要将其调整为堆;

..........

直到无序区只有一个元素为止;

4.4:应用

寻找M个数中的前K个最小的数并保持有序;

时间复杂度:O(K)[创建K个元素最大堆的时间复杂度] +(M-K)*log(K)[对剩余M-K个数据进行比较并每次对最大堆进行从新最大堆化]

5.希尔排序

(1)基本思想

先将整个待排序元素序列分割成若干子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序(因为直接插入排序在元素基本有序的情况下,效率很高);

(2)适用场景

比较在希尔排序中是最主要的操作,而不是交换。用已知最好的步长序列的希尔排序比直接插入排序要快,甚至在小数组中比快速排序和堆排序还快,但在涉及大量数据时希尔排序还是不如快排;

void shell_sort(int a[], int n){    int d, i, j, temp; //d为增量    for(d = n/2;d >= 1;d = d/2) //增量递减到1使完成排序    {        for(i = d; i < n;i++)   //插入排序的一轮        {            temp = a[i];            for(j = i - d;(j >= 0) && (a[j] > temp);j = j-d)            {                a[j + d] = a[j];            }        a[j + d] = temp;        }    }}

6.归并排序

(1)基本思想

首先将初始序列的n个记录看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2的有序子序列,在此基础上,再对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列,以此类推,直到得到一个长度为n的有序序列为止;

(2)适用场景

若n较大,并且要求排序稳定,则可以选择归并排序;

归并排序是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。这需要将待排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行.logn.次,因此,总的时间复杂度为O(nlogn)。

归并排序在归并过程中需 要与原始记录序列同样数量的存储空间存放归并结果,因此空间复杂度为O(n)。

归并算法需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。 

void mergearray(int a[], int first, int mid, int last, int temp[]){    int i = first, j = mid + 1;    int m = mid,   n = last;    int k = 0;    while (i <= m && j <= n)    {        if (a[i] <= a[j])            temp[k++] = a[i++];        else            temp[k++] = a[j++];    }    while (i <= m)        temp[k++] = a[i++];    while (j <= n)        temp[k++] = a[j++];    for (i = 0; i < k; i++)        a[first + i] = temp[i];}void merge_sort(int a[], int first, int last, int temp[]){    if (first < last)    {        int mid = (first + last) / 2;        merge_sort(a, first, mid, temp);    //左边有序        merge_sort(a, mid + 1, last, temp); //右边有序        mergearray(a, first, mid, last, temp); //再将二个有序数列合并    }}

7.简单选择排序

(1)基本思想

第一趟:从第一个记录开始,将后面n-1个记录进行比较,找到其中最小的记录和第一个记录进行交换;

第二趟:从第二个记录开始,将后面n-2个记录进行比较,找到其中最小的记录和第2个记录进行交换;

...........

第i趟:从第i个记录开始,将后面n-i个记录进行比较,找到其中最小的记录和第i个记录进行交换;

以此类推,经过n-1趟比较,将n-1个记录排到位,剩下一个最大记录直接排在最后;

void selection_sort (int a[], int n) {    int i,j,pos,tmp;    for (i=0; i1; i++) {      //寻找最小值的下标        for (pos=i, j=i+1; j            if (a[pos]>a[j])                pos=j;        if (pos != i) {            tmp=a[i];            a[i]=a[pos];            a[pos]=tmp;        }    }}

2.时间复杂性比较

 插入排序、冒泡排序、选择排序的时间复杂性为O(n2)

 其它非线形排序的时间复杂性为O(nlog2n)

 线形排序的时间复杂性为O(n);

3.辅助空间的比较

 线形排序、二路归并排序的辅助空间为O(n),其它排序的辅助空间为O(1);

4.其它比较

插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。

反而在这种情况下,快速排序反而慢了。

当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。

当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下,宜用归并排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值