您的位置:首页 > 理论基础 > 数据结构算法

数据结构笔记整理第7章:排序

2016-06-03 00:37 495 查看

第7章 排序

本章内容

本章主要介绍多种内部排序算法,包括它们的排序过程、排序时间复杂度以及实现等等,本章在考研中是重点内容。本章几个内部排序的代码实现都比较重要。

7.1 排序的分类



内部排序是指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列。[2]

外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。[3]

在这一章,我们主要讨论的是内部排序下面的多种排序算法。

7.2 稳定性

待排序的序列中有两个或两个以上相同的项。排序前和排序后,这些相同项的相对位置发生变化,则属于不稳定,否则是稳定的。

7.3 插入类排序

插入类排序包括:简单(直接)插入排序、折半插入排序和希尔排序。

7.3.1 简单(直接)插入排序

思路:将待排序记录插入已排好的记录中,不断扩大有序序列。即:将待排序记录插入有序序列,重复n-1次。

【例子1】:将52,49,80,36,14,58,61进行直接插入排序。



分析:



平均(n^2)/4,时间复杂度O(n^2),空间复杂度O(1),稳定的。

代码:

void InsertSort(int R[], int n) {
int i, j;

4000
int temp;
for (i = 2; i <= n; ++i) {
temp = R[i];
j = i-1;
while(j >= 1 && temp < R[j]) {
R[j+1] = R[j];
--j;
}
R[j+1] = temp;
}
}


7.3.2 折半插入排序

思路:在直接插入排序中,查找插入位置时采用折半查找的方法。其基本条件是序列已经有序。

【例子2】:现在的序列是13,38,49,65,76,97,需要在此基础上折半插入27和49。



(1) low=1,high=6,m=(1+6)/2=3->49,27<49,因此27位于49的低半区间;
(2) low=1,high=m-1=3-1=2,m=(1+2)/2=1->13,27>13,因此27位于13的高半区间;
(3) low=m+1=2,high=2,m=2->38,27<38,因此27位于38低半区间;
(4) low=2,high=m-1=1,low>high,结束
(5) 27在13与38之间


分析:时间复杂度最好是O(n),最坏是O(n^2),平均情况O(n^2),空间复杂度O(1),比直接插入减少了比较次数,稳定的。

代码:

void BinaryInsertSort(int R[], int n) {
int i, j, high, low, m, temp;
for (i = 1; i < n; i++) {
//折半查找插入位置
low = 0, high = i-1;
while(low <= high) {
m = (low + high)/2;
if (R[i] < R[m]) {
high = m - 1;
}
else {
low = m + 1;
}
}
//移动,在指定位置插入
temp = R[i];
for (j = i-1; j > high; j--) {
R[j+1] = R[j];
}
R[high+1] = temp;
}
}


7.3.3 希尔排序

思路:缩小增量排序,将待排序的序列按照某种规则分成几个序列,分别对这几个子序列直接插入排序,基本有序后再对整个序列进行直接插入排序。

步骤:

(1) 分成子序列(按照增量dk);

(2) 对子序列排序(直接插入排序);

(3) 缩小增量,重复以上步骤,直到增量dk=1。

【例子3】:将52,49,80,36,14,58,61进行希尔排序。



分析:时间复杂度O(n^1.5),空间复杂度O(1),不稳定的。

代码:

void ShellSort(int R[], int n) {
int dk = n/2;
int i, j, temp;
for (i = dk; i < n; ++i) {
temp = R[i];
for (j = i-dk; j >=0 && temp < R[j]; j -= dk) {
R[j+dk] = R[j];
}
R[j+dk] = temp;
}
dk /= 2;
}


7.4 交换类排序

交换类排序包括:冒泡排序和快速排序。

7.4.1 冒泡排序

思路:依次比较相邻元素,“逆序”则交换,重复n-1次。算法结束的条件是:在一趟排序中没有发生元素交换。

【例子4】:冒泡排序:52,49,80,36,14,58,61



分析:比较和交换总数发生在相邻元素之间,时间复杂度O(n^2),空间复杂度O(1),稳定的。

代码:

void BubbleSort(int R[], int n) {
int i, j, flag, temp;
for (i = n; i >= 2; --i) {
flag = 0;
for (j = 2; j<= i; ++j) {
if (R[j-1] > R[j]) {
temp = R[j];
R[j] = R[j-1];
R[j-1] = temp;
flag = 1;
}
}
if (flag == 0) {
return ;
}
}
}


7.4.2 快速排序

思路:一趟排序选择一个关键字,接着把序列分割成独立的左右两个部分:右边部分都比关键字要大,左边部分都比关键字要小。然后再对两个部分进行快排。

【例子5】:快速排序:49,38,65,97,76,13,27,49







分析:平均时间复杂度为O(nlogn),平均空间复杂度为O(logn)。记录为逆序时为最坏情况,时间复杂度为O(n^2),空间复杂度为O(n),不稳定的。

代码:

void QuickSort(int R[], int l, int r) {
int temp;
int i = l, j = r;
if (l < r) {
temp = R[l];
while(i != j) {
while(j > i && R[j] > temp) {
--j;
}
if (i < j) {
R[i] = R[j];
i++;
}
while(i < j && R[i] < temp) {
i++:
}
if (i < j) {
R[j] = R[i];
--j;
}
}
R[i] = temp;
QuickSort(R, l, i-1);
QuickSort(R, i+1, r);
}
}


7.5 选择类排序

选择类排序包括:简单选择排序和堆排序。

7.5.1 简单选择排序

思路:从头至尾顺序扫描,找出一个最小的记录,和第一个记录交换接着从剩下的记录中继续这种选择和交换,最终使序列有序。即:在待排记录中选择最小的,交换到合适位置,重复n-1次。

【例子6】:简单选择排序:52,49,80,36,14,58,61



分析:平均时间复杂度为O(n^2),主要耗费在比较记录上,不稳定的。

代码:

void SelectSort(int R[], int n) {
int i, j, k;
int temp;
for (i = 1; i <= n; i++) {
k = i;
for (j = i + 1; j <= n ; j++) {
if (R[k] > R[j]) {
k = j;
}
}
temp = R[i];
R[i] = R[k];
R[k] = temp;
}
}


7.5.2 堆排序

堆是一种数据结构,可以看作一种二叉树:任何一个非叶子结点值都不大于(或不小于)其他左右孩子结点的值。

若父亲大孩子小,则为大顶堆(堆顶为最大元素);

若父亲小孩子大,则为小顶堆(堆顶为最小元素)。

思路:将序列调整为堆,整个排序的过程是不断调整使不符合堆定义的完全二叉树变为符合堆定义的完全二叉树的过程。

每次进行堆调整的时候,从最后一个结点开始,与其父结点比较,根据大(小)堆特点决定是否交换:

如果是大顶堆,那么交换的时候要选择两个子结点中值更大的子结点与父结点交换;

如果是小顶堆,那么交换的时候要选择两个子结点中值更小的子结点与父节点交换;

【例子7】:将24,85,47,53,30,91,12,36进行堆排序













分析:平均时间复杂度为O(nlogn),空间复杂度O(1),不稳定的。

代码:

以下代码不要求掌握,估计不会考这么难的,但是堆排序的过程和方法一定要理解。

//在R[low]到R[high]范围内对low上的结点调整
void Sift(int R[], int low, int high) {
int i = low, j = 2 * i;
int temp = R[i];
while(j <= high) {
if (j < high && R[j] < R[j+1]) {
++j;
}
if (temp < R[j]) {
R[i] = R[j];
i = j;
j = 2 * i;
}
else {
break;
}
}
R[i] = temp;
}

void HeapSort(int R[], int n) {
int i, temp;
for (i = n/2; i >= 1; --i) {
Sift(R, i, n);
}
for (i = n; i >= 2; --i) {
temp = R[1];
R[1] = R[i];
R[i] = temp;
Sift(R, i, i-1);
}
}


7.6 归并排序

思路:归并,两个或多个有序表合成一个有序表,这里,我们主要讨论二路归并排序。

【例子8】:对24,85,47,53,30,91归并排序



分析:平均时间复杂度为O(nlogn),空间复杂度O(n),稳定的。

7.7 基数排序

思路:关键字排序,分为最高位优先(MSD)和最低位优先(LSD),主要两种方式是“收集”和“分配”。排序的时候,以数字元素为例:每一个元素都是数,每一位的数字都是0~9组成,我们按照每一位的大小进行排序。

再比如我们考虑一副扑克牌,共有4种花色,每种花色有13张牌,我们排序的时候可以先将所有牌按照花色扔进4个桶中(♥、♦、♠以及♣),然后在每个桶中对13张牌进行排序。

**【例子9】:**278,109,063,930,589,184,505,269,008,083按照最低位优先排序。





分析:平均时间复杂度为O(d(n+rd)),空间复杂度O(rd),稳定的。n为序列中元素个数,d为位数,rd为取值范围。如上例:n=10,d=3,rd=0~9=10

7.8 外部排序

对于文件中的数据进行排序

归并排序法:分为两个阶段:

第一阶段:将文件中的数据分段输入到内存中,内部排序法对其排序,排序之后归并段写回外存中;

第二阶段:对这些初始归并段采用多遍归并方法。

败者树:考研中涉及很少,有兴趣的童鞋自觉去搜索一下吧,也许面试会随机问一问。

7.9 各种内部排序总结比较



记忆窍门以及规律:

1.时间复杂度:“快些以nlogn的速度归队。基数是O(d(n+rd)),其他是O(n^2)。”

快:快速排序;

些:希尔排序(O(n^1.5)有时也认为是O(nlogn));

归:归并排序;

队:堆排序;

2.空间复杂度:快排为O(logn),归并为O(n),基数为O(rd),其余为O(1)。

3.稳定性:心情不稳定,快些选一堆好友聊天吧。

快:快速排序;

些:希尔排序;

选:简单选择排序;

堆:堆排序;

4.规律:

保证一个元素经过一趟排序达到最终位置:交换类排序和选择类排序;

元素比较次数与原始序列无关:简单选择排序;

排序趟数和原始序列有关:交换类排序。

参考资料

1.严蔚敏《数据结构与算法分析》:清华大学出版社,2011

2.http://baike.baidu.com/link?url=aTrLAS7mtxHjPGA4rg0oLojzT4RcIEjrRTMqrrpSIPS5Nn8gSVQLxRuTxPihih422lS4oz7NBs0V9PmPCsG3aa

3.http://baike.baidu.com/link?url=49nG5vQjHCkkDxEdr2c87dQailUBJLLYTgsIbosZsRRofeOH-TLeagHQgCv31cWG7BSE5MbNgp8Wm6T8DXmb9K
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息