排序算法

排序算法

1.什么叫排序?

     排序前:3,1,6,9,8,9
     排序后:1,3,5,6,8,9

排序无处不在

汽车销量排行.png
游戏装备排行.png

十大排序算法:

十大排序算法.png

2 算法介绍

什么是排序算法的稳定性?

回答:如果相等的2个元素,在排序前后的相对位置保持不变,那么这是稳定的排序算法
举例:
  • 排序前:5,1,3(a), 4,7,3(b)

  • 稳定的排序: 1,3(a),3(b),4,5,7

  • 不稳定的排序: 1,3(b),3(a),4,5,7

在对自定义对象进行排序时,稳定性会影响最终的排序效果

什么是原地算法(In-place Algorithm)?

回答:不依赖额外的资源或者依赖少数的额外资源,仅依靠输出覆盖输入,空间复杂度为0(1)的可以认为是原地算法

ps.(代码实现用到的方法定义,本文统一以升序为例子)
cmp(index0,index1)
cmp方法传入二个数组索引比较两个索引所指元素的大小
如果index0索引的元素大于index1索引的元素则返回 一个大于0 的整数
反之则返回小于0的整数 ,相等返回0。

swp(index0,index1)
swp方法传入二个数组索引交换索引所指元素的位置

2.1冒泡排序

  • 冒泡排序也叫起泡排序

  • 执行流程

    ① 从头开始比较相邻每一对元素,如果第1个比第2个大,就交换它们的位置,执行完一轮后,最末尾那个元素就是最大的元素

    ② 忽略①中曾经找到的最大元素,重复执行步骤1,知道全部元素有序

代码实现:
for (int end = array.length - 1; end > 0 ; end --){
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
              }
        }
} 
冒泡排序优化①

在冒泡排序的过程中会出现这么一种情况在某一轮的排序过程中根本就没有调用swap方法说明此时的数组已经是完全有序了但是按照之前的做法我们的程序还是要执行下去直到所有轮次结束,这样后面的代码都是在做无用功大大的浪费了性能,所以我们可以在检测到数组已经提前有序的情况下及时的结束方法提高效率。

代码实现:
for (int end = array.length - 1; end > 0 ; end --){
       bool sorted = true;
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
                   sorted = false;
              }
        }
        if (sorted) break
} 
  • 从代码中可以看出 如果swap 在一轮排序中始终没有调用,那么sorted变量则为true
    发现sorted为true时,我们直接结束循环。

  • 在实际应用中很少会出现①中提前有序的情况,所以第一种优化的能用到的情况不会太大。

冒泡排序优化②

如果序列尾部已经局部有序,我们可以记录最后一次交换的位置,减少比较次数

什么意思?
举个例子:有无序列表 a = [2,1,8,9,7,10,11,12,13,14]

  • 通过观察可以发现a的尾部从10元素开始后面都是有序的,如果通过我们之前的代码排序在交换了9 和 7 的位置之后 我们还是会去判断10之后的元素的大小关系但是它们已经是有序的了。所以后面我们几轮的排序都会做一些无用的判断。这个时候我们可以记录下我们最后一次交换元素的位置 7 的位置 此时 a = [1,2,8,7,9,10,11,12,13,14]

  • 当我们下一轮排序时到7 的位置我们就直接结束排序因为后面的元素已经是有序的了。这样可以优化我们判断次数。

代码实现:
for (int end = array.length - 1; end > 0 ; end --){
       int sortedIndex = 1;
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
                   sortedIndex = begin;
              }
        }
        end = sortedIndex
} 
  • 设置 sortedIndex 的初始值为1可以完美的兼容整个序列提前有序的情况
  • sortedIndex 的值会不断被begin的值覆盖 单一轮排序完成时 sortedIndex的值必然是最后一次交换元素的位置

2.2 选择排序

  • 执行流程

    ① 遍历一边序列找出最大的那个元素,然后与末尾的元素交换位置

    ② 忽略①中找到的最大元素,重复执行步骤 ①

    代码实现:
     for (int end = array.length - 1; end > 0 ; end --){
         int maxIndex = 0;
         for (int begin = 1; begin <= end ; begin ++){
                if (cmp(max, begin ) <= 0) {
                     max = begin;
                }
          }
          swap(max,end);
     } 
    
  • 选择排序的交换次数远远少于冒泡排序,平均性能优于冒泡排序

  • 最好,最坏,平均时间复杂度:O(n2)

2.3 插入排序

  • 执行流程
    ① 在执行过程中,插入排序会将序列分为2部分,头部是已经排好序的,尾部是待排序的
    ② 从头开始扫描每一个元素,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部的数据依次保持有序
代码实现:
for (int begin = 1; begin < array.length; begin++) {
     int cur = begin;
    while (cur > 0 && amp(cur, cur -1) < 0) {
             swap(cur, cur - 1);
             cur--
    }
}
  • 插入排序的时间复杂度与逆序对的数量成正比关系

  • 逆序对的数量越多,插入排序的时间复杂度越高
    (ps.什么是逆序对? 举个例子:数组<2,3,8,6,1>的逆序对为: <2,1><3,1><8,1><8,6><6,1>,一共5个逆序对)

  • 最坏,平均时间复杂度::O(n2)

  • 最好时间复杂度: O(n)

  • 空间复杂度:O(1)

  • 属于稳定排序

插入排序优化1
  • 思路将【交换】转为【挪移】
    ① 先将待插入的元素备份
    ②头部有序数据中比待插入元素大的,都朝尾部方向挪动一个位置
    ③将待插入元素放入最终合适的位置
代码实现:
for (int begin = 1 ; begin < array.length; begin++){
    int cur = begin;
    T value = array[cur];//备份元素
    while (cur > 0 && cmp(v, array[cur - 1]) < 0) {
              array[cur] = array[cur - 1];
              cur --;
    }
    array[cur] = v;
}
插入排序优化2
  • 思路在优化1的基础上 优化确定位置的过程优化1是一个一个比较来确定,所以可以通过二分法直接求出要插入的位置。从而减少比较次数
代码实现:
for (int i = 1; i < array.length; i ++ ){
      //找出插入位置
      int index = -1;
      int begin = 0;
      int end = I;
      while (begin < end) {
           int mid = (begin + end) >> 1;
           if (cmp(i ,mid) < 0 ){
               end = mid; 
           }else {
               begin = mid + 1;
           }
      }
      index = begin;
      //备份元素
      T value = array[I];
       for (int j = i; j > index; j --) {
             array[j] = array[j - 1];
      }
      array[index] = value;
}

2.4 归并排序

  • 执行流程

    ① 不断地将当前序列平均分割成2个子序列,直到不能分割(序列中只剩一个元素)

    ② 不断地将2个子序列合并成一个有序序列直到最终只剩下1个有序序列

代码实现

// 准备一段临时的数组空间,在合并操作中使用
leftArray = (T[ ]) new Object[array.lengtn >> 1];
sort(0,array.length);

private void sort(int begin, int end) {
     // 至少需要有2个元素
    if (end - begin < 2) return;
    int mid = (begin + end) >> 1;
    sort(begin,mid);
    sort(mid,end);
    merge(begin,mid,end);
}

private void merge(int begin, int mid ,int end) {
   int li = 0;
   int le = mid - begin; // 左边数组(基于leftArray)
   int ri = mid;
   int re = end;//右边数组(基于array)
   int ai = begin;//array 的索引
   for (int I = li; i < le; i ++){//拷贝左边数组到leftArray
         leftArray[I] = array[begin + I];
    }
    while (li < le) {
           if (ri < re && amp(array[ri]),leftArray[li] < 0) {
                array[ai ++] = array[ri ++];// 拷贝右边数组到array
           } else {
                array[ai ++] = leftArray[li ++];//拷贝左边数组到array
           }
    }//cmp位置改为 <= 会失去稳定性
}
归并排序 - 复杂度分析
  • 归并排序花费的时间

T(n) = 2 * T(n/2) + 0(n)
T(1) = O(1)
T(n)/n = T(n/2)/(n/2) + O(1)

令S(n) = T(n)/n
S(1) = O(1)
S(n) = S(n/2) + O(1) = S(n/4) + 0(2) = S(n/8) + 0(3) = S(n/2k) + O(k) = S(1) + O(logn) = O(logn)
T(n) = n* S(n) = 0(nlogn)

  • 由于归并排序总是平均分割子序列,所以最好,最坏,平均时间复杂度都是O(nlogn)
常见的递推式与复杂度
常见的递推式与复杂度

2.5 快速排序

  • 执行流程

① 从序列中选择一个轴点元素,一般每次选择0位置的元素为轴点元素

② 利用轴点将序列分割成2个子序列,将小于轴点元素的元素放在轴点前面(左侧)
将大于轴点元素的元素放在轴点的后面(右侧)

③ 对子序列进行①②操作知道不能再分割(子序列中只剩下1个元素)

代码实现

private void sort(int begin, int end) {
     //至少要用2个元素
     if (end - begin < 2) return;
    int middle = privotIndex(begin,end);
    sort(begin,middle);
    sort(middle + 1, end);
}

private int privotIndex(int begin,  int end) {
      
    T privot = array[begin];
    end --;//end指向最后一个元素
    while (begin < end) {
        
          while (begin < end) {
               if (cmp(privot, array[end]) < 0) {
                       end --;
                } else {
                      array[begin ++] = array[end];
                      break;
                 }
          }
           while (begin < end) {
               if (cmp(privot, array[begin]) > 0) {
                      begin ++;
                } else {
                      array[end --] = array[begin];
                      break;
                 }
          }
    } 

    array[begin] = pivot;
    return begin;
}

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

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,178评论 0 52
  • 概述 因为健忘,加上对各种排序算法理解不深刻,过段时间面对排序就蒙了。所以决定对我们常见的这几种排序算法进行统一总...
    清风之心阅读 694评论 0 1
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    zwb_jianshu阅读 1,130评论 0 0
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    printf200阅读 766评论 0 0
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    闲云清烟阅读 757评论 0 6