排序算法总结

外部排序

为什么需要外部排序
指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中需要对外存进行访问的排序过程。
定义
外部排序指的是大文件的排序,即待排序的记录存储在外部存储器上,在排序过程中需进行多次的内、外存之间的交换。
怎么实现
首先将打文件记录分成若干个子文件,然后读入内存中,并利用内部排序的方法进行排序;然后把排序好的有序子文件,重新写入外存,再对这些归并段进行逐个归并,直到整个有序文件为止。

内部排序

指的是待排序记录存放在计算机随机存储器(内存)中进行的排序过程;
下面总结我们常用的内部排序算法:

算法平均时间复杂度空间复杂度最坏情况最好情况稳定性算法
插入排序O( n2 n 2 )O(1)O(n)O( n2 n 2 )稳定性算法
选择排序O( n2 n 2 )O(1)O( n2 n 2 )O( n2 n 2 )稳定性算法
冒泡排序O( n2 n 2 )O(1)O( n2 n 2 )O(n)稳定性算法
基数排序O( d(n+rd) d ( n + r d ) )O( rd r d )O( d(n+rd) d ( n + r d ) )稳定性算法
归并排序O( nlogn n l o g n )O( n n ) O(nlogn)O( nlogn n l o g n )稳定性算法
快速排序O( nlogn n l o g n )O( logn l o g n )-O( n n ) O(n2)O( nlogn n l o g n )不稳定算法
希尔排序O( nlogn n l o g n )-O( n2 n 2 )O(1)O( n2 n 2 )O(n^1.3)不稳定算法
堆排序O( nlogn n l o g n )O(1)O( nlogn n l o g n )O( nlogn n l o g n )不稳定算法

稳定性的定义:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

插入排序

将一个待排序的记录,按照其关键字的大小将其插入到前边已经排好序的子序列的适当的位置,直到全部插入完毕.这就像我们在玩扑克牌时,将每一张拍插入到其他已经有序的牌中适当的位置.在计算及中,为了给要插入的元素腾出位置,需要将其余所有元素在插入位置之前都向右移动一位。
[] 5 7 9 3 1
[5] 7 9 3 1
[5 7] 9 3 1
[5 7 9] 3 1
[3 6 7 9] 1
[1 3 6 7 9]

void insertSort(int *data, int n){
    int i, j, k;
    for(i=1;i<n;i++){
        for(j=i-1;j>=0;j--){
            if(data[j] > data[i])
                break;
        }
        if (j!=i-1) {
            int temp = data[i];//比data[i]大的数据往后移动
            for(k=i-1; k>j; k--){
                data[k+1] = data[k];
            }//将data[i]放在待插入的位置
            data[k+1] = temp;
        }
    }
}

选择排序

刚开始数组为空,i从0开始,对于第i个位置,选择(i+1,n)之间最小值存入。

[] 5 7 9 3 1
[1] 5 7 9 3
[1 3] 5 7 9
[1 3 5] 7 9
[1 3 5 7] 9
[1 3 5 7 9]

void selectSort(int *data, int n){
    for(int i=0;i<n;i++){
        int min = data[i];
        int min_index = i;
        for(int j=i+1;j<n;j++){
            if(data[j]<min){
                min = data[j];
                min_index = j;
            }
        }
        if(min_index != i){
            swap(data, i, min_index);
        }
    }
}

冒泡排序

两两相邻进行比较,如果它们的排序与排序要求不符合,则交换两个相邻的值。
[5 7 9 3 1]
[5 7 9 3 1]
[5 7 3 9 1]
[5 3 7 9 1]
[3 5 7 9 1]
[3 5 7 1 9]
[3 5 1 7 9]
[1 3 5 7 9]

void bubbleSort(int *data, int n){
    for(int i=1;i<n;i++){
        for(int j=i-1;j>=0;j--){
            if(data[j] > data[j+1]){
                swap(data, j,j+1);
            }
        }
    }
}

希尔排序

选择增量,根据增量来划分数组,然后排序子数组,交换位置,然后再改变增量,再划分子数组,再排序,最后直至增量为1,全排序。
592 401 874 141 348 72 911 887 820 283
增量为5
72 401 874 141 283 592 911 887 820 348
增量为5/2=2
72 141 283 348 820 401 874 592 911 887
增量为1
72 141 283 348 401 592 820 874 887 911

归并排序

先分组,再排序
2个一组,4个一组,8个一组…
10,8,12,7,5
第一趟:(10,8),(12,7),5->(8,10),(7,12),5
第二趟:(8,10,7,12),5->(7,8,10,12),5
第三趟:5,7,8,10,12

void _mergeSort(vector<int>& nums, int start, int mid, int end){
    int index1 = start;
    int index2 = mid+1;
    int index = start;
    vector<int> tmp;
    while((index1<=mid)&&(index2<=end)){
        if(nums[index1]<nums[index2]){
            tmp.push_back(nums[index1++]);
        }
        else{
            tmp.push_back(nums[index2++]);
        }
    }
    while(index1<=mid){
        tmp.push_back(nums[index1++]);
    }
    while(index2<=end){
        tmp.push_back(nums[index2++]);
    }
    int count = 0;
    for(int i=0;i<tmp.size();i++){
        nums[start+i] = tmp[i];
    }
}
void mergeSort(vector<int>& nums, int start,  int end){
    if(start>=end){
        return;
    } 
    int mid = (start+end)>>1;
    mergeSort(nums, start, mid);
    mergeSort(nums, mid+1, end);
    _mergeSort(nums, start, mid, end);
}

快速排序

[5 7 9 3 1] key=5, first = 0, last=4
[1 7 0 3 1] data[first]=data[last]
[1 7 0 3 7] data[last]=data[first]
[1 3 0 3 7]
[1 3 0 5 7]
[1 3 0] 5 [7]
1. 先选控制值key,这里默认数组的第一个值
2. 然后设置头尾指针,
3. while(头指针<尾指针)
首先从尾指针开始移动,直至遇到比key小的值,就把该值移动到data[first]的位置
然后从头指针开始移动,直至遇到比key大的值,就把该值移动到data[last]的位置
4. 最后将a[first]设置为控制值key

void quickSort(int *data, int low, int high){
    if(low>=high)
        return;
    int first = low;
    int last = high;
    int key = data[first];
    while(first < last){
        while(first<last && data[last]>=key){
            last --;
        }
        data[first] = data[last];//将较小的值移到左端
        while(first<last && data[first]<=key){
            first++;
        }
        data[last] = data[first];//将较大的值移到右端
    }
    data[first] = key;
    quickSort(data, low, first-1);
    quickSort(data, first+1, high);
}

堆排序

步骤
1. 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
2. 将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

基数排序

先比较个位,再比较十位

92 35 67 76 84 51
第一趟:51→92→84→35→67→76
第二趟:35→51→67→76→84→92

大数据排序:

  • 位图法
    位图法的基本思想就是利用一位代表一个数字,例如3位上为1,则说明3在数据中出现过,若为0,则说明3在数据中没有出现过。所以当题目中出现每个数据最多重复一次这个条件时,我们可以考虑使用位图法来进行大数据排序。
    步骤
    一般来说是整数,第k位表示第k个数,每一位初始化为0。可以通过累加来计算这个数出现的次数。
  • 堆排序法
    使用场景:从1亿个整数里找出100个最大的数
    步骤
    1.读取前100个数字,建立最大值堆。(这里采用堆排序将空间复杂度讲得很低,要排序1亿个数,但一次性只需读取100个数字,或者设置其他基数,不需要1次性读完所有数据,降低对内存要求)
    2、依次读取余下的数,与最大值堆作比较,维持最大值堆。可以每次读取的数量为一个磁盘页面,将每个页面的数据依次进堆比较,这样节省IO时间。
    3.将堆进行排序,即可得到100个有序最大值。
    最大堆最小堆…
  • 分治策略
    很多情况下,分治策略的解法都不是最优解,但是其通用性很强。
    步骤
    1. 从大数据中抽取样本,将需要排序的数据切分为多个样本数大致相等的区间,例如:1-100,101-300…
    2. 将大数据文件切分为多个小数据文件,这里要考虑IO次数和硬件资源问题,例如可将小数据文件数设定为1G(要预留内存给执行时的程序使用)
    3. 使用最优的算法对小数据文件的数据进行排序,将排序结果按照步骤1划分的区间进行存储
    4. 对各个数据区间内的排序结果文件进行处理,最终每个区间得到一个排序结果的文件
    5. 将各个区间的排序结果合并.
      通过分治将大数据变成小数据进行处理,再合并。
  • 多台机器上处理
    考虑下分布式计算呗
问题一:若有1T的数据,需要实现由大到小排序,你用什么办法,说说你的思路和想法?
简单思路
如果单纯用内部排序的话,很不切合实际。首先要考虑内存,不可能一次性载入这么多数据。
思路:分治法
1. 先将所有的数据分成多份,然后对于每一份进行排序
2. 两两进行归并,进行内部排序
3. 将排序好的归并段重新写入外存,在进行两两归并,直到得到一个有序文件为止。
问题二:有10个G的数据,如果两条数据一样,则表示该两条数据重复了,现在给你512的内存,把这10G中重复次数最高的10条数据取出来。
思路一:
1. 先排序, 10G数据分成40份,每份256M,排序,合并相同数据并加上计数器,写到临时文件chunk01~chunk20。
2. 对每一chunk, 读入内存,对每一条数据,再依次读入其后续个chunk, 合并相同数据的计数,后写入一个文件count。为了避免重复计数,在计数累加后需要将原来chunk的计数清零并回写文件。
以chunk01为例。假设chunk01中有数据A-8(数据A, 8次),chunk02中有A-2,那么合并chunk02后
chunk01的内存中为A-10, chunk02中为A-0,这时把chunk02写回文件,然后读入chunk03继续处理处理。最后把chunk01中计数不为0的数据(chunk01里不会有计数为0的,但是后面的chunk会有)写入文件count.
3. 对count文件进行按重复次数排序。(分组,排序,然后每组选前10,再排序
思路2:
计数排序
其次是这10G数据时什么,是10G个BYTE,还是10G个字符串
因为BYTE的范围0-255,也就是说这个问题就变成了,找0-255范围内,出现次数最多的10个数,
用int64[255]来计数,遍历一次,每个数值对应下标里面记录其出现的次数就可以了,用int64是因为DWORD表示不了10G。
如果是字符串,或者是其他2进制数据块,就比较复杂,需要多次遍历
2进制数据块有多少个字节,就需要准备多少个int64[255]计数数组
假定每条记录,就是每个2进制数据块长10个字节,对于不足10字节的记录,不足的部分以0计算
需要的计数数组为int64[10][255]
对于每条记录的第一个字节相同的,算为相同记录的情况下,得出表:
A********* 1000次
B********* 900次
C********* 900次
D********* 890次
…统计结果计入int64[0][255]
然后对于100次的A*********,统计
AE******** 50次
AA******** 50次
AD******** 49次
AC******** 47次
…统计结果计入int64[1][255]
依此类推
AEDBBCADE* 10次
AEDBBCADB* 9次
AEDBBCADC* 9次
AEDBBCADA* 8次
…统计结果计入int64[8][255]
最终
AEDBBCADEA 3次
AEDBBCADEF 3次
AEDBBCADEC 2次
AEDBBCADEB 2次
…统计结果计入int64[9][255]
将这个结果放入另一个int64[255] res,这个就是当前的最终结果
然后逐个向前递归
对于int64[8][255]中排行第二的“AEDBBCADB* 9次”
统计出前10,得到一个新的int64[9][255],将其与res中的结果比较,得出这20个中的前10,更新res

参考:
https://bbs.csdn.net/topics/390360278?page=1
http://blog.csdn.net/guyuealian/article/details/51151674

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值