排序算法

排序

概念

所谓排序,就是要整理文件中的记录,使之按关键字递增/递减次序排列起来。其确切定义如下:
输入:n个记录R1,R2,…Rn,其对应的关键字分别是K1,K2,…Kn。
输出:Ri1,Ri2,…Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。
⑴ 被排序对象——文件
被排序的对象——文件由一组记录组成。记录则由若干个数据项(或域)组成。其中有一项可用来表示一个记录,称为关键字项。该数据项的值称为关键字。
⑵ 排序运算的依据——关键字
用来做排序运算依据的关键字,可以是数字类型,也可以是字符类型。
关键字的选取应根据问题的要求而定。例如,在高考成绩统计中,将每个考生作为一个记录,每条记录包含准考证号、姓名、各科的分数和总分数等项内容。若要唯一的表示一个考生的记录,则必须用准考证号为关键字。若要按照考生的总分数排名次,则需用分数为关键字。

排序的稳定性
⒈ 当待排序记录的关键字均不相同时,排序结果是唯一的,否则排序结果不唯一。
在待排序的文件中,若存在多个关键字相同的记录,进过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序算法是不稳定的。
【注意】
排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。
⒉ 按是否涉及数据的内、外存交换分类
在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序,简称内排序。反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。
【注意】
⑴ 内排序适应于记录个数不是很多的小文件
⑵ 外排序适用于记录个数太多,不能一次将其全部记录放入内存的大文件

排序算法
**排序算法**

◆ 冒泡排序

  1. 算法思想
    将被排序的记录数组R[1…n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R,凡是扫描到违反本原则的轻气泡,就使其向上“漂浮”。如此反复进行,直到最后任意两个气泡都是轻者在上,重者在下为止。
    初始:R[1…n]为无序区
    ⑴ 第一趟扫描
    从无序区底部向上依次比较相邻的两个气泡的重量,若发现轻者在下,重者在上,则交换两者位置,即依次比较(R[n],R[n-1]),(R[n-1],R[n-2]),…,(R[2],R[1]);对于每队气泡(R[j+1],R[j]),若R[j+1].key<R[j].key,则交换两者位置。
    第一趟扫描完毕,“最轻”的气泡就漂浮到该区间的顶部,即关键字最小的记录放到了最高位置R[1]上。
    ⑵ 第二趟扫描
    扫描R[2…n]。扫描完毕时,“次轻”的气泡漂浮到R[2]的位置上…最后,进过n-1趟扫描,可得到有序区R[1…n]。
    第i趟扫描时,R[1…i-1]和R[i…n]分别为当前的有序区和无序区。扫描仍是从无序区底部向上直至该区顶部。扫描完毕时,该区中最轻气泡漂浮到顶部位置R[i]上,结果是R[1…i]变成新的有序区。
  2. 算法分析
    ⑴ 复杂度分析
    因为每趟排序度都使有序区间增加了一个气泡,在经过n-1趟排序后,有序区间就有n-1个气泡,而无序区中气泡的重量总是大于等于有序区间气泡的重量,所以整个冒泡排序过程至多需要进行n-1趟排序。
    若在某一趟排序中未发现气泡位置的交换,则说明待排序的无序区中所有气泡均满足轻者在上,重者在下的原则,因此,冒泡排序过程可在此趟排序后终止。
    ⑵ 冒泡排序的具体步骤和实现代码如下:
    ① 比较相邻的元素。如果第一个比第二个大,就交换它们两个;

  3. ② 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
    ③ 针对所有的元素重复以上的步骤,除了最后一个;
    ④ 重复步骤1~3,直到排序完成。
function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比
                var temp = arr[j+1];        // 元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

⑶ 冒泡排序的动图如下:

⑷ 算法稳定性
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

◆ 插入排序

  1. 算法思想
    假设待排序的记录存放在数组R[1…n]中。初始时,R[1]自成一个有序区,无序区为R[2…n]。从i=2起至i=n为止,依次将R[i]插入当前的有序R[1…i-1]中,生成含有n个记录的有序区。
    通常将一个记录R[i](i=2,3…,n-1)插入到当前的有序区,使得插入后仍保证该区间的记录是按关键字有序的操作,称第i-1趟直接插入排序。
    排序过程的某一中间时刻,R被划分成两个子区间R[1…i-1](已排好序的有序区)和R[i…n](当前未排好序的无序区)。
    插入排序的基本操作时将当前无序区的第i个记录R[i]插入到有序区R[1…i-1]中适当的位置上,使R[1…i]变成新的有序区。因为这种方法每次使有序区增加1个记录,通常称增量法。
    插入排序和打扑克牌整理手里的牌非常类似。摸来的第1张牌无需整理,此后每次从桌上的牌(无序区)中摸最上面的1张并插入左手的牌(有序区)中正确的位置上。为了找到这个正确的位置,须自左向右(或自右向左)将摸来的牌与左手中已有的牌逐一比较。
  2. 算法分析
    ⑴ 复杂度分析
    如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。
    ⑵ 插入排序的具体步骤和实现代码如下:
    ① 从第一个元素开始,该元素可以认为已经被排序;
    ② 取出下一个元素,在已经排序的元素序列中从后向前扫描;
    ③ 如果该元素(已排序)大于新元素,将该元素移到下一位置;
    ④ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
    ⑤ 将新元素插入到该位置后;
    ⑥ 重复步骤2~5。
function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while (preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex + 1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex + 1] = current;
    }
    return arr;
}

⑶ 插入排序的动图如下:

⑷ 算法稳定性
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

◆ 快速排序

  1. 算法思想
    快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。它采用了一种分治的策略,通常也将其称为分治法。
    ⑴ 分治法的基本思想
    分治法的基本思想是将原问题分解为若干个规模更小的但结构和原问题相似的子问题。用递归的方法解决这些子问题,然后将这些子问题的解组合成原问题的解。
    ⑵ 快速排序的基本思想
    在R[low…high]中任选一个记录作为基准,以此基准将当前无序区划分为左右两个较小的子区间R[low…pivotpos-1]和R[pivotpos+1…high],并使左边子区间中所有记录的关键字均小于等于基准记录(记为pivot)的关键字pivot.key,右边子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置上,无需参与后续的排序。
  2. 算法分析
    ⑴ 快速排序的具体步骤和实现代码如下:
    ① 从数列中挑出一个元素,称为 “基准”(pivot);
    ② 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    ③ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;
 
    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}
 
function partition(arr, left ,right) {     // 分区操作
    var pivot = left,                      // 设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }       
    }
    swap(arr, pivot, index - 1);
    return index-1;
}
 
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

⑵ 快速排序的动图如下:

⑶ 算法稳定性
快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。

各种排序算法的比较

◇ 排序算法分类
按平均时间将排序分为以下四类:
⑴ 平方阶(0(n2))排序。一般称为简单排序,如插入排序、选择排序和冒泡排序。
⑵ 线性对数阶(0(nlgn))排序,如快速、堆和归并排序。
⑶ 0(n1+£)阶排序,£是介于0和1之间的常数,如希尔排序。
⑷ 线性阶(0(n))排序,如桶、箱和基数排序。
◇ 排序算法比较
简单排序中插入排序最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。 因为不同的排序方法适用不同的应用环境和要求,所以选择合适的排序方法应考虑下列因素:
① 待排序的记录数目m
② 记录的大小(规模)
③ 关键字的结构及其初始状态
④ 对稳定性的要求
⑤ 语言工具的条件
⑥ 存储结构
⑦ 时间和辅助空间复杂度等
◇ 排序算法选择
排序方法选择的主要依据如下:
⑴ 若n较小,如n≤50,可采用插入或选择排序。
⑵ 若文件初始状态基本有序(指正序),则应选用插入、冒泡或快速排序为宜。
⑶ 若n较大,则应采用时间复杂度为0(nlgn)的排序方法:快速排序、堆排序或归并排序。 快速排序是目前基于比较内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。但这两种排序都是不稳定的,若要求排序稳定,则可选用归并排序。

PS:若想学习更多的排序算法,推荐一篇排序算法博客:十大经典排序算法(动图演示),该博客讲的很赞,值得一看。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值