读书笔记 -- 算法导论(第二部分 排序和顺序统计学)

输入数据的结构

在实际中,待排序的数很少是孤立的值,它们通常是一个称为记录的数据集的一部分。每个记录有一个关键字key,它是待排序的值。记录的其他数据称为卫星数据,即它们通常以key为中心传送。在一个排序的算法中,当交换关键字时,卫星数据也必须交换。如果记录都很大,我们可以交换一组指向各个记录的指针而不是记录本身,以求将数据移动量减少到最小。

在一定意义上,正式这些实现细节才使得一个完整的程序不同于算法。不管我们要排序的是单个的数值还是包含数据的大型记录,就排序的方法来说它们都是一样的。因为,为了集中考虑排序问题,我们一般都假设输入仅由数值构成。将对数字的排序算法转换为对记录排序的程序是很直接的。当然,在具体的工程条件下,实际的程序设计可能还会遇到其他难以捉摸的挑战。

 

为什么要研究排序?

许多计算机科学家认为,排序算法是算法学习中最基本的问题。原因有以下几个:

  1. 有时候应用程序本身就需要对信息进行排序。e.g. 准本客户账目,银行的支票号码
  2. 许多算法通常把排序作为关键子程序。
  3. 现在已经有很多的排序算法,它们采用各种技术。事实上,在算法设计中使用的很多重要技术在已经发展很多年的排序算法中早已用到了。所以,排序是一个具有历史意义的问题。
  4. 在实现排序算法时很多工程问题即浮出水面。对于某个特定的应用场景来说,最快的排序算法可能与许多因素有关:譬如关键字值和卫星数据的先验知识,主机存储器层次结构(高速缓存和虚拟存储)以及软件环境。

排序算法

  1. 插入排序:最坏情况运行时间为(n^2),但其算法的内循环时紧密的,对小规模输入来说时一个快速的原地排序算法。
  2. 合并排序:有着较好的渐进运行时间(nlgn),但其中的Merge程序不在原地操作。
  3. 堆排序:可以在O(nlgn) 时间内对n个数进行原地排序。这个算法用到了一种重要的称为堆的数据结构,还要用它实现优先级队列。
  4. 快速排序:原地排序算法,但最坏情况运行时间为n^2,平均运行时间是(nlgn),在实际中常常优于堆排序算法。对于大输入数组的排序来说,这是一个很常用的算法。
    1. 1-4都是比较排序(comparison sort):为研究比较排序算法的性能极限,利用决策树模型,证明对任何n个输入比较排序算法的最坏情况运行时间的下界为nlgn,说明堆排序和合并排序都是渐近最优的比较排序算法

 

第六章 堆排序

 

Heap Sort将Merge Sort和Insertion Sort优点结合起来:运行时间为nlg(n)且In place。

 

6.1 堆

 

(二叉)堆数据结构是一种数组对象,可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层可能除外(最后一层从一个结点的左子树开始填)

表示堆的数组A是一个具有两个属性的对象:length[A] —— 数组中的元素个数,heap-size[A]——存放在A中的堆的元素个数

在树中,给定了某个结点的下标i,则有:

PARENT(i)

return [i/2]

LEFT(i)

return 2i

RIGHT(i)

return 2i+1

在大多数计算机上,

LEFT过程可以在一条指令内计算出2i,方法是将i的二进制表示左移1位。

RIGHT过程可以通过将i的二进制表示左移1位并在低位中加1,快速计算出2i+1。

PARENT过程则可以通过把i右移1位而得到i/2。

在一个好的堆排序的实现中,这三个过程通常是用“宏”过程或者是“内联”过程实现的。

二叉堆有两种:最大堆和最小堆(小根堆)。在这两种堆中,结点内的数值都要满足堆特性,其细节则视堆的种类而定。最大堆:堆中的最大元素就存放在根结点中。最小堆则反之。

在堆排序算法中,我们使用的是最大堆。最小堆通常在构造优先队列时使用。

  1. MAX-HEAPIFY

    1. - 运行时间为O(lgn),是保持最大堆性质的关键
  2. BUILD-MAX-HEAP
    1. - 线性时间运行,可以在无序的输入数组基础上构造出最大堆
  3. HEAPSORT
    1. - 运行时间为O(nlgn),对一个数组原地进行排序
  4. MAX-HEAP-INSERT,HEAP-EXTRACT-MAX, HEAP-INCREASE-KEY, HEAP-MAXIMUM 过程的运行时间为 O(lgn), 可以让堆结构作为优先队列使用

 

6.2 保持堆的性质

MAX-HEAPIFY: 输入为一个数组A和下标i,当MAX-HEAPIFY被调用时,我们假定以LEFT(i)和RIGHT(i)为根的两棵二叉树都是最大堆,但这时A[i]可能小于其子女,这样就违反了最大堆的性质。

MAX-HEAPIFY(A, i)

{

var l = LEFT(i);

var r = RIGHT(i);

var largest = i;

if l <= heap-size[A] and A[l] > A[i]

then largest = l;

if r <= heap-size[A] and A[r] > A[largest]

then largest = r;

if largest != i

then swap(A[i], A[largest])

MAX-HEAPIFY(A, largest)

}

运行时间为O(lgn),或者说,MAX-HEAPIFY作用于一个高度为h的结点所需的运行时间为O(h)

6.3 建堆

 

我们可以自底向上地用MAX-HEAPIFY来将一个数组A[1..n]变成一个最大堆。

BUILD-MAX-HEAP(A)

{

heap-size[A] = A.Length;

for(int i = A.Length / 2; i >= 1; i—)

{

MAX-HEAPIFY(A, i)

}

}

理想中,我们每次调用MAX-HEAPIFY的时间为O(lgn),共有O(n)次调用,故运行时间是O(nlgn),这个界尽管是对的,但从渐近意义上讲不够紧确。

实际上,我们可以得到一个更加紧确的界,这是因为,在树中不同高度的结点处运行MAX-HEAPIFY的时间不同,而且大部分结点的高度都较小。— O(n)

6.4 堆排序算法

开始时,堆排序算法先用BUILD-MAX-HEAP将输入数组构造成一个最大堆;因为数组中最大元素在根A[1],则可以通过把它与A[n]互换来达到最终正确的位置。现在,如果从堆中去掉结点n(通过减小heap-size[A]),可以很容易地将A[1..n-1]建成最大堆。

HEAPSORT(A)

{

BUILD-MAX-HEAP(A)

for(int i = A.Length; i > 2; i--)

{

swap(A[1], A[i]);

heap-size[A] = heap-size[A]-1;

MAX-HEAPIFY(A,1);

}

}

HEAPSORT过程的时间代价为O(nlgn)。

6.5 优先级队列

 

虽然堆排序算法是一个很漂亮的算法,但在实际中,快速排序的一个好的实现往往优于堆排序。

优先级队列是一种用来维护由一组元素构成的集合S的数据结构,这一组元素中的每一个都有一个关键字key。一个最大优先级队列支持以下操作:

  1. INSERT(S,x)
  2. MAXIMUM(S)
  3. EXTRACT-MAX(S)
  4. INCREASE-KEY(S,x,k)

最大优先级队列的一个应用是在一台分时计算机上进行作业调度。这种队列对要执行的各作业及它们之间的相对优先关系加以记录。当一个作业做完或被中断时,用EXTRACT-MAX操作从所有等待的作业中,选择出具有最高优先级的作业。在任何时候,一个新作业都可以用INSERT加入到队列中去。

优先级队列可以用堆来实现。在一个给定的,诸如作业调度或事件驱动的模拟应用中,优先级队列的元素对应着应用中的对象。通常,我们需要确定一个给定的队列中元素所对应的应用对象,反之亦然。当用堆来实现优先级队列时,需要在堆中的每个元素里存储对应的应用对象的Handle。

HEAP-MAXIMUM用O(1)时间实现了MAXIMUM操作:

HEAP-MAXIMUM(A)

{

return A[1];

}

HEAP-EXTRACT-MAX用O(lgn)运行时间:(将最尾结点赋给首位,将数组长度减一,运行MAX-HEAPIFY)

HEAP-EXTRACT-MAX(A)

{

if heap-size[A] < 1

then error “heap underflow”;

max = A[1];

A[1] = A[heap-size[A]];

heap-size[A] = heap-size[A] - 1;

MAX-HEAPIFY(A, 1);

return max;

}

HEAP-INCREASE-KEY运行时间O(lgn):(从本结点往根结点移动的路径上,不断与其父母相比,若此元素关键字较大,则交换它们的关键字并且继续移动;若小于其父母,则此时最大堆性质成立)

HEAP-INCREASE-KEY(A,i,key)

{

if key < A[i]

then error “new key is smaller than current key”;

A[i] = key

while i > 1 and A[PARENT(i)] < A[i]

swap(A[i], A[PARENT(i)])

i = PARENT(i)

}

MAX-HEAP-INSERT要实现INSERT操作,可以加入一个关键值为负无穷的叶子结点来扩展最大堆,然后调用HEAP-INCREASE-KEY来设置新结点的关键字的正确值,并保持最大堆性质。运行时间为 O(lgn)

MAX-HEAP-INSERT(A,key)

{

heap-size[A] = heap-size[A] + 1;

A[heap-size[A]] = -9999999999999999;

HEAP-INCREASE-KEY(A, heap-size[A], key);

}

 

第7章 快速排序

虽然最坏运行时间为O(n^2),但快速排序通常是用于排序的最佳实用选择,这是因为其平均性能相当好:期望的运行时间为O(nlgn) , 另外它还能够进行就地排序,在需存环境中也能很好地工作。

QUICKSORT(A, p, r)

{

if p > r

then q = PARTITION(A, p, r);

QUICKSORT(A, p, q-1);

QUICKSORT(A, q+1, r);

}

PARTITION(A, p, r)

{

x = A[r];

i = p - 1;

for(var j = p; j < r-1; j++)

{

if(A[j] <= x)

{

i++;

swap(A[i], A[j]);

}

}

swap(A[i + 1], A[r]);

return i + 1;

}

 

第8章 线性时间排序

 

介绍了计数排序,基数排序和桶排序。这些算法都用非比较的一些操作来确定排序顺序。因此下界O(nlgn)对它们是不适用的。

原文地址:https://www.cnblogs.com/lizzzzzz/p/8642861.html

时间: 2024-08-28 17:59:41

读书笔记 -- 算法导论(第二部分 排序和顺序统计学)的相关文章

读书笔记 -- 算法导论 (序言+第一部分)

什么是基础呢? 就是要把我们大学所学的离散数学,算法与数据结构,操作系统,计算机体系结构,编译原理等课程学好.对计算机的体系,CPU本身,操作系统内核,系统平台,面向对象编程,程序的性能等要有深层次的掌握.要编写出优秀的代码同样要扎实的基础,如果数据结构和算法学的不好,怎么对程序的性能进行优化,怎样从类库中选择合适的数据结构.如果不了解操作系统,怎样能了解这些开发工具的原理,它们都是基于操作系统的.不了解汇编,编译原理,怎么知道程序运行时要多长时间要多少内存,就不能编出高效的代码.把面向对象,软

[读书笔记]算法(Sedgewick著)·第二章.初级排序算法

本章开始学习排序算法 1.初级排序算法 先从选择排序和插入排序这两个简单的算法开始学习排序算法.选择排序就是依次找到当前数组中最小的元素,将其和第一个元素交换位置,直到整个数组有序. 1 public static void sort(Comparable a[]){ 2 int N = a.length; 3 for(int i = 0; i < N; i ++){ 4 int min = i; //最小元素索引 5 for(int j = i + 1; j < N; j++){ 6 if(

[读书笔记]算法(Sedgewick著)·第一章(1)

到家放松之后就开始学习算法了,手里拿的是拿的是一本Robert Sedgewick的橙皮书<算法(第四版)>的.这本书与导论那本书的不同之处在于轻数学思想.重实现,也就是说这是一本很不错的基础编程书.拿来做书中的练习还是蛮不错的,封面说有50种算法哦.思维导图如下,就且学且更新吧. 1.基本编程模型 第一章开始讲述用程序实现算法的优点:程序是对算法精确.优雅和完全的描述:可以通过运行程序来学习算法的各种性质:可以在应用程序中直接使用这些算法.还有这种学习算法的缺点缺点:分离思想和实现细节的困难

算法导论 第二章

2014-12-02 20:21:40 http://www.cnblogs.com/sungoshawk/p/3617652.html 上面链接指向算法导论第二章的预习博客,很值得一看,很详细. 插入算法: 1 #include <iostream> 2 3 using namespace std; 4 void insert_sort(int *datas, int length); 5 int main() 6 { 7 int a[10]={1,2,4,35,6,1,4,7,9,7};

算法导论之所有排序算法的Python实现

最近一段时间学习了算法导论第二版书的第一部分和第二部分的内容,自己编写了其中排序相关的几乎全部算法,包括冒泡排序(bubble sort).选择排序( selection sort).插入排序(insertion sort).希尔排序(shell sort).归并排序(merge sort).快速排序(quick sort).计数排序(count sort).基数排序(radix sort).桶排序(bucket sort).期望线性时间的第k个顺序统计量选择.最坏情况线性时间的中位数选择,并给

算法导论专题一--排序算法

排序算法作为许多程序的中间步骤,是计算机科学中的一个基本操作. 一.问题描述 排序算法输入的是n个数的一个序列<a1,a2…..an>,输出为输入的一个排列<a1’,…..an’>,满足a1’<a2’<….<an’ 简言之就是输入一个序列,输出的是这个数组元素从小到大排列的另一序列. 二.方法思想综述 从算法导论这本书上根据算法的复杂度可以将排序算法分为三种,,.,这两种方法都需要数据间的比较,而不需要. 其中有三种为选择,冒泡,插入. 选择排序:最直观,简单但是

读书笔记:计算机网络第二章:物理层

这是我在Coursera上的学习笔记.课程名称为<Computer Networks>,出自University of Washington. 由于计算机网络才诞生不久,目前正在以高速在发展,所以有些旧的教材可能都已经跟不上时代了.这门课程在2013年左右录制,知识相对还是比较新的.覆盖了计算机网络中的各个协议层,从物理层到应用层都讲得非常仔细.学完这门课程之后对计算机网络会有比较深刻的了解. 本章详细讲解了物理层,讲解了比特流如何通过各种介质进行传播. 基本概述 物理层的功能 专注比特信号是

算法导论专题一--排序算法(2)

上节分析了O(n^2)的算法,这节就分析O(nlgn)的算法-归并,快速和堆排序. 一:综述 O(nlgn) 的算法可以分为两大类,两者所用的技术差别较大.归并和快速排序采用的是分治策略,这两者相当于一个对称的过程,一个是自顶向上合并子问题,另一个则自上向下分解子问题.而堆排序利用堆这一数据结构元素间的特殊关系来排序一个序列,另外采用二叉树的方式组织数据使其效率大大提高. 二:分治策略排序算法 1.为什么使用分治? 在上节算法的分析中,不管是冒泡.选择还是插入都不适用于大规模的数据,因为数据一大

读书笔记: 博弈论导论 - 06 - 混合的策略

读书笔记: 博弈论导论 - 06 - 混合的策略 混合的策略 本文是Game Theory An Introduction (by Steven Tadelis) 的学习笔记. 策略,信念和期望收益 混合策略 玩家i的有限纯策略集合\(S_i = {s_{i1}, s_{i2}, \cdots, s_{im}}\). 将\(\Delta S_i\)定义为\(S_i\)的单纯形,是在\(S_i\)上所有概率分布的集合. 玩家i的一个混合策略(mixed strategy)是\(\sigma_i \