深入浅出交换类排序算法

原文:http://kb.cnblogs.com/page/185060/

 1) 冒泡排序

  冒泡排序在众多排序算法中算比较简单的一个, 基本思想是, 重复的进行整个数列的排序, 一次比较两个元素(两两排序),如果它们顺序不符合就交换,重复这样直到数列没有再需要交换的数为止(结束条件).就好像气泡一样, 轻的气泡会往上漂浮,在不断漂浮的过程中,发生了两两交换过程, 所以叫冒泡排序.

  其实也可以用生活中的例子理解, 就比如: 在军训排队时, 按个子高的和个子矮的的顺序进行排列, 个子高的和个子矮的会进行两两进行比较.

  我们来大致看下算法的流程:

  选一组序列 4, 3 , 5, 6, 2, 1 (极端情况)

  从头开始进行冒泡排序,1号和2号进行交换,  4 > 3, 所以需要进行交换:

  -> 3, 4, 5, 6, 2, 1

  2号和3号进行交换, 4<5, 不交换

  -> 3, 4, 5, 6, 2, 1

  3号和4号进行交换, 5<6, 不交换

  -> 3, 4, 5, 6, 2, 1

  4号和5号进行交换,6>2,交换

  -> 3, 4, 5, 2, 6, 1

  5号和6号进行交换,6>1,交换

  -> 3, 4, 5, 2, 1, 6

  第一轮冒泡排序结束, 把最大的数交换到最后一位, 如此循环, 直到没有需要交换的元素为止! 冒泡排序才结束.

  代码实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void  BubbletSort( int *a, int  len) {
     int  m;
     for  ( bool  bSwap= true ; bSwap; len--) {
         bSwap = false ;
         for  ( int  j=1;j<len;j++) {
             if  (a[j-1]>a[j]) {   // 交换值
                 m=a[j];
                 a[j]=a[j-1];
                 a[j-1]=m;
                 bSwap= true ;
             }
         }
     }
}

  6.使用i向后扫描遇到比49大的97停止 并交换到j的位置  其实冒泡排序整体看来是非常"傻"的, 有很多可以优化的余地, 比方说,每次比较如果发现较小的元素在后面,就交换两个相邻的元素,而如果我只扫描元素, 记下最小元素, 等一次扫描完后, 再交换两者为止, 这样最小元素就排到了前面, 每扫描一次,只需要一次真正的交换, 而刚才的冒泡可能需要交换多次, 刚才说的算法优化其实就是选择排序, 以后我会细说, 他属于选择排序的范畴.

  我们来考虑下冒泡算法的复杂度:
  在时间复杂度上, 若待排序的序列为完全逆序, 则每次都需要进行元素之间的交换, 所以时间复杂度为O(   ),若待排序为顺序, 也就是不需要交换元素, 但是需要扫描,所以还是需要O( )的时间复杂度, 平均情况下时间复杂度为O(   ) .

  在空间复杂度上, 需要辅助空间只有一个m(如上面代码), 所以空间复杂度为O(1).

  2) 快速排序

  如果大家还记得折半插入排序的过程:在一个有序序列中,插入关键字和折半序列的中间关键字进行比较, 若小则在关键字左边,若大则在关键字右边,而快速排序和折半插入排序有异曲同工之妙, 差别在于折半插入排序插入的序列自身是个有序序列, 选取中间关键字时 两边已经有序. 而快速排序在于它不一定是有序的,它的操作过程是:随便选取一个关键字(一般选取第一个), 让所有关键字和它进行比较一次,小的放在左边, 大的放在它右边.然后递归地对左边和右边进行排序。把该区间内的所有数依次与关键字比较,我们就可以在线性的时间里完成分割的操作。

  我们来看下算法的步骤:
  初始状态:                【49,38,65,97,76,13,27,49'】
  一次划分后:           【27,38,13】 49 【76,97,65,49'】
  分别进行快速排序:  【13】 27 【38】 【49',65】76【97】
  有序序列:                    【13,27,38,49,49',65,76,97】

  上面是算法的大致步骤,一般完成分割操作有很多有技巧性的实现方法,比如最常用的一种是定义两个指针,一个从前往后找找到比关键字大
的,一个从后往前找到比关键字小的,然后两个指针对应的元素交换位置并继续移动指针重复刚才的过程。这只是大致的方法,具体的实现还
  有很多细节问题。

  我们来看一下一轮快排序的细节步骤:

  原始序列(i和j分别指向序列的最低和最高位置):

  选取第一个数49作为比较排序关键字.
  1.使用j,从序列最右端开始扫描遇到比49小的则停止.

  2.将27交换到i的位置

  3.使用i,从序列最左端开始扫描遇到比49大的数则停止

  4.将65交换到j的位置

  5.再使用j向前扫描遇到比49小的13停止,并把13交换到i位置.

  7.继续使用j向前扫描遇到比49小的并停止,这时发现i和j相遇,代表扫描结束.

  8.最后把49放置在ij的位置:

  从上面的一轮快排可以看出, 49把整个序列划分为两个部分, 小于49的在它左边, 大于49的在它右边. 根据算法思想再分别把49两边的序列进行快排一次. 另外从整个排序过程来看, 先对整个序列进行一次快排, 然后再对其中子序列再进行快排, 如此反复直到有序为止. 整个过程是个递归的思想.所以代码比较好写. 我们来实现一下.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// head=>序列的开头
// tail=>序列的结尾
void  quickSort( int  array[], int  head, int  tail) {
     if  (head > tail) {
         return ;
     }
     // i,j指向头和尾巴
     int  i=head;
     int  j=tail;
     int  iPivot=array[i]; /**< 选取枢轴 */
     while  (i<j) {
         // 使用j,从序列最右端开始扫描,直到遇到比枢轴小的数
         while  ((i<j) && (iPivot <= array[j])) {
             j--;
         }
         // 交换位置
         if  (i<j) {
             array[i++]=array[j];
         }
         // 使用i,从序列最左端开始扫描,直到遇到比枢轴小的数枢轴大的数
         while  ( (i<j) && (array[i] <= iPivot) ) {
             i++;
         }
         // 交换位置
         if  (i<j) {
             array[j--]=array[i];
         }
     }
     // 最后填入枢轴位置
     array[j]=iPivot;
     // 这里就是对枢轴两边序列进行排序的递归调用
     quickSort(array, head, i-1);
     quickSort(array, i+1, tail);
}

   代码已经严格测试过,一般不会有问题. 下面我们来看下时间复杂度空间复杂度.

  快速排序有个特点,待排序列越接近无序,算法效率越高,也就是在基本有序的情况下时间复杂度为O(   ),最好情况下为O(   ),平均复杂度为O(   ),从所有内排序来看,快排是所有内排序中平均复杂度最好的. 另外空间复杂度也为O(   ).因为上面的算法实现是递归进行的,递归需要栈空间
.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第一章 Linux底层分段分页机制 5 1.1 基于x86的Linux分段机制 5 1.2 基于x86的Linux分页机制 7 1.2.1 页全局目录和页表 8 1.2.2 线性地址到物理地址 10 1.2.3 线性地址字段处理 13 1.2.4 页表处理 15 1.3 扩展分页与联想存储器 20 1.4 Linux内存布局 21 1.5 内核空间和用户空间 23 1.5.1 初始化临时内核页表 24 1.5.2 永久内核页表的初始化 32 1.5.3 第一次进入用户空间 41 1.5.4 内核映射机制实例 44 1.6 固定映射的线性地址 48 1.7 高端内存内核映射 50 1.8.1 永久内存映射 50 1.8.2 临时内核映射 55 第二章 内核级内存管理系统 58 2.1 Linux页面管理 58 2.1.1 NUMA架构 61 2.1.2 内存管理区 62 2.2 伙伴系统算法 65 2.2.1 数据结构 66 2.2.2 块分配 67 2.2.3 块释放 69 2.3 Linux页面级内存管理 72 2.3.1 分配一组页面 73 2.3.2 释放一组页面 80 2.4 每CPU页面高速缓存 81 2.4.1 数据结构 81 2.4.2 通过每CPU 页高速缓存分配页面 82 2.4.3 释放页面到每CPU 页面高速缓存 83 2.5 slab分配器 85 2.5.1 数据结构 86 2.5.2 分配/释放slab页面 92 2.5.3 增加slab数据结构 93 2.5.4 高速缓存内存布局 94 2.5.5 slab着色 95 2.5.6 分配slab对象 96 2.5.7 释放Slab对象 100 2.5.8 通用对象 102 2.5.9 内存池 103 2.6 非连续内存区 104 2.6.1 高端内存区回顾 105 2.6.2 非连续内存区的描述符 106 2.6.3 分配非连续内存区 109 2.6.4 释放非连续内存区 113 第三章 进程的地址空间 117 3.1 用户态内存分配 117 3.1.1 mm_struct数据结构 118 3.1.2 内核线程的内存描述符 122 3.2 线性区的数据结构 123 3.2.1 线性区数据结构 123 3.2.2 红-黑树算法 126 3.2.3 线性区访问权限 128 3.3 线性区的底层处理 130 3.3.1 查找给定地址的最邻近区 131 3.3.2 查找一个与给定的地址区间相重叠的线性区 135 3.3.3 查找一个空闲的地址区间 135 3.3.4 向内存描述符链表中插入一个线性区 137 3.4 分配线性地址区间 141 3.5 释放线性地址区间 151 3.5.1 do_munmap()函数 151 3.5.2 split_vma()函数 153 3.5.3 unmap_region()函数 155 3.6 创建和删除进程的地址空间 156 3.6.1 创建进程的地址空间 156 3.6.2 删除进程的地址空间 175 3.6.3 内核线程1号的地址空间 176 3.7 堆的管理 178 第四章 磁盘文件内存映射 182 4.1 内存映射的数据结构 182 4.2 内存映射的创建 184 4.3 内存映射的请调页 194 4.4 刷新内存映射的脏页 203 4.5 非线性内存映射 210 第五章 页面的回收 215 5.1 页框回收概念 215 5.1.1 选择目标页 216 5.1.2 PFRA设计 217 5.2 反向映射技术 218 5.2.1 匿名页的反向映射 220 5.2.2 优先搜索树 226 5.2.3 映射页的反向映射 231 5.3 PFRA实现 235 5.3.1 最近最少使用(LRU)链表 236 5.3.2 内存紧缺回收 242 5.3.3 回收磁盘高速缓存的页 267 5.3.4 周期回收 273 5.3.5 内存不足删除程序 283 第六章 交换机制 289 6.1 交换区数据结构 289 6.1.1 创建交换区 290 6.1.2 交换区描述符 291 6.1.3 换出页标识符 293 6.2 激活和禁用交换区 295 6.2.1 sys_swapon()系统调用 296 6.2.2 sys_swapoff()系统调用 304 6.2.3 try_to_unuse()函数 308 6.3 分配和释放页槽 313 6.3.1 scan_swap_map()函数 313 6.3.2 get_swap_page()函数 316 6.3.3 swap_free()函数 318 6.4 页面的换入换出 320 6.4.1 交换高速缓存 320 6.4.2 换出页 323 6.4.3 换入页 329 第七章 缺页异常处理程序 335 7.1 总体流程 335 7.2 vma以外的错误地址 341 7.3 vma内的错误地址 346 7.3.1 handle_mm_fault()函数 348 7.3.2 请调页 352 7.3.3 写时复制 358 7.4 处理非连续内存区访问 364
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值