第8天排序

7.1    概 述

7.2  插入排序

7.3  快速排序

7.4  堆排序

7.5  归并排序

7.6  基数排序

7.7  各种排序方法的综合比较

7.1    概 述

一、排序的定义

二、内部排序和外部排序

三、内部排序方法的分类

内部排序和外部排序

若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;   

反之,若参加排序的记录数量很大,  整个序列的排序过程不可能在内存中 完成,则称此类排序问题为外部排序。

三、内部排序的方法

内部排序的过程是一个逐步扩大 记录的有序序列长度的过程。

基于不同的“扩大” 有序序列长度的方法,内部排序方法大致可分下列几种类型:

 插入类  交换类  选择类   归并类

//插入类:  (1)直接插入排序 (2)折半插入排序  (3)希尔排序

//交换类;  (1)冒泡排序     (2)一趟快速排序  (3)快速排序

//选择类:  (1)简单选择排序 (2) 堆排序

//归并类:  (1)归并排序

待排记录的数据类型定义如下:

#define MAXSIZE  1000 // 待排顺序表最大长度

typedef  int  KeyType;  // 关键字类型为整数类型

typedef  struct {

KeyType   key;             // 关键字项

InfoType  otherinfo;       // 其它数据项

} RcdType;                     // 记录类型

typedef  struct {

RcdType    r[MAXSIZE+1];   // r[0]闲置

int         length;        // 顺序表长度

} SqList;                      // 顺序表类型

1. 插入类

将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。

2. 交换类

通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。

3. 选择类

从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。

4. 归并类

通过归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。

7. 2     插 入 排 序

实现“一趟插入排序”可分三步进行:

1.在R[1..i-1]中查找R[i]的插入位置, R[1..j].key <= R[i].key < R[j+1..i-1].key;

2.将R[j+1..i-1]中的所有记录均后移一个位置;

3.将R[i] 插入(复制)到R[j+1]的位置上。

不同的具体实现方法 导致不同的算法描述

直接插入排序(基于顺序查找)

折半插入排序(基于折半查找)

希尔排序(基于逐趟缩小增量)

一、直接插入排序

利用 “顺序查找”实现 “在R[1..i-1]中查找R[i]的插入位置”

算法的实现要点:

从R[i-1]起向前进行顺序查找,监视哨设置在R[0];

R[0] = R[i];                                     // 设置“哨兵

for (j=i-1; R[0].key<R[j].key; --j);    // 从后往前找

循环结束表明R[i]的插入位置为 j +1

对于在查找过程中找到的那些关键字不小于R[i].key的记录,并在查找的同时实现记录向后移动;

for (j=i-1; R[0].key<R[j].key; --j);

R[j+1] = R[j]

上述循环结束后可以直接进行“插入

void InsertionSort ( SqList &L ) {

// 对顺序表 L 作直接插入排序。

for ( i=2; i<=L.length; ++i )

if (L.r[i].key < L.r[i-1].key)

{

L.r[0] = L.r[i];            // 复制为监视哨

for ( j=i-1; L.r[0].key < L.r[j].key;  -- j )

L.r[j+1] = L.r[j];        // 记录后移

L.r[j+1] = L.r[0];        // 插入到正确位

}

} // InsertSort

内部排序的时间分析:

实现内部排序的基本操作有两个:

1)“比较”序列中两个关键字的 大小;

2)“移动”记录。

二、折半插入排序

因为 R[1..i-1] 是一个按关键字有序的有序序列,则可以利用折半查找实现

“在R[1..i-1]中查找R[i]的插入位置”,如此实现的插入排序为折半插入排序。

void BiInsertionSort ( SqList &L ) {

for ( i=2; i<=L.length; ++i ) {

L.r[0] = L.r[i];      // 将 L.r[i] 暂存到 L.r[0]

// L.r[1..i-1]中折半查找插入位置

 for ( j=i-1;  j>=high+1;  --j )

L.r[j+1] = L.r[j];      // 记录后移

L.r[high+1] = L.r[0];  // 插入

} // for

} // BInsertSort

low = 1;   high = i-1;

while (low<=high) {

m = (low+high)/2;           // 折半

if (L.r[0].key < L.r[m].key)

high = m-1;   // 插入点在低半区

else  low = m+1; // 插入点在高半区

}

 三、希尔排序 (又称缩小增量排序)

基本思想:对待排记录序列先作“宏观”调整,再作“微观”调整。

所谓“宏观”调整,指的是,“跳跃式”的插入排序。    具体做法为:

记录序列分成若干子序列, 分别对每个子序列进行插入排序。

例如:将 n 个记录分成 d 个子序列

{ R[1],R[1+d],R[1+2d],…,R[1+kd] }

{ R[2],R[2+d],R[2+2d],…,R[2+kd] }

{ R[d],R[2d],R[3d],…,R[kd],R[(k+1)d] }

其中,d 称为增量,它的值在排序过程中从大到小逐渐缩小,

直至最后一趟排序减为 1。

void ShellInsert ( SqList &L, int dk ) {

for ( i=dk+1; i<=n; ++i )

if ( L.r[i].key< L.r[i-dk].key) {

L.r[0] = L.r[i];            // 暂存在R[0]

for (j=i-dk;  j>0&&(L.r[0].key<L.r[j].key);j-=dk)

L.r[j+dk] = L.r[j];  // 记录后移,查找插入位置

L.r[j+dk] = L.r[0];                // 插入

} // if

} // ShellInsert

7.3     快 速 排 序

一、起泡排序      

二、一趟快速排序

三、快速排序

一、起泡排序 

void BubbleSort(Elem R[ ], int n) {

i = n;

while (i >1)

{

lastExchangeIndex = 1;

for (j = 1;  j < i;  j++)

if (R[j+1].key < R[j].key)

{

Swap(R[j], R[j+1]);

lastExchangeIndex = j;  //记下进行交换的记录位置

} //if

i = lastExchangeIndex; // 本趟进行过交换的

} // while    // 最后一个记录的位置

} // BubbleSort

二、一趟快速排序(一次划分)

目标:找一个记录,以它的关键字作为“枢轴”,凡其关键字小于枢轴的记录均移动至该记录之前,

反之,凡关键字大于枢轴的记录均移动至该记录之后。

致使一趟排序之后,记录的无序序列R[s..t]将分割成两部分:

R[s..i-1]和R[i+1..t],且 R[j].key≤ R[i].key ≤ R[j].key  (s≤j≤i-1)    枢轴     (i+1≤j≤t)。

int Partition (RedType R[], int low, int high) {

R[0] = R[low];

pivotkey = R[low].key;  // 枢轴

while (low<high)

{

while(low<high&& R[high].key>=pivotkey)

-- high;      // 从右向左搜索

R[low] = R[high];

while (low<high && R[low].key<=pivotkey)

++ low;      // 从左向右搜索

R[high] = R[low];

}

R[low] = R[0];

return low;

}// Partition

三、快速排序

先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行快速排序。

结论: 快速排序的时间复杂度为O(nlogn),若待排记录的初始状态为按关键字有序时,快速排序将蜕化为起泡排序,其时间复杂度为O(n2)。

void QSort (RedType & R[],  int s,  int  t ) {

// 对记录序列R[s..t]进行快速排序

if (s < t-1)          // 长度大于1

{

pivotloc = Partition(R, s, t);// 对 R[s..t] 进行一次划分

QSort(R, s, pivotloc-1);// 对低子序列递归排序,pivotloc是枢轴位置

QSort(R, pivotloc+1, t); // 对高子序列递归排序

}

}

假设一次划分所得枢轴位置 i=k,则对n 个记录进行快排所需时间:

快速排序的时间复杂度为O(nlogn),若待排记录的初始状态为按关键字有序时,快速排序将蜕化为起泡排序,其时间复杂度为O(n2)

10.4     堆 排 序

 

简 单 选 择 排 序

堆 排 序 

 

一、简单选择排序

void SelectSort (Elem R[], int n ) { // 对记录序列R[1..n]作简单选择排序。

        for (i=1; i<n; ++i)

     {

               // 选择第 i 小的记录,并交换到位

               j = SelectMinKey(R, i);      

              // 在 R[i..n] 中选择关键字最小的记录

             if (i!=j)  R[i]←→R[j];

              // 与第 i 个记录交换

            }

} // SelectSort

二:堆排序

 

{12, 36, 27, 65, 40, 34, 98, 81, 73, 55, 49} 是小顶堆

{12, 36, 27, 65, 40, 14, 98, 81, 73, 55, 49} 不是小顶堆

 

堆排序的时间复杂度为O(nlogn)。

void HeapSort ( HeapType &H ) {

// 对顺序表 H 进行堆排序

for ( i=H.length/2;   i>0;   --i )

HeapAdjust ( H.r, i, H.length );    // 建大顶堆

for ( i=H.length; i>1; --i )

{

H.r[1]←→H.r[i];

// 将堆顶记录和当前未经排序子序列

//  H.r[1..i]中最后一个记录相互交换

HeapAdjust(H.r, 1, i-1);  // 对 H.r[1] 进行筛选

}

} // HeapSort

void HeapAdjust (RcdType &R[], int s, int m)

{   // 已知 R[s..m]中记录的关键字除 R[s] 之外均

// 满足堆的特征,本函数自上而下调整 R[s] 的

// 关键字,使 R[s..m] 也成为一个大顶堆

rc = R[s];    // 暂存 R[s]

for ( j=2*s; j<=m; j*=2 )

{ // j 初值指向左孩子自上而下的筛选过程;

if ( j<m && R[j].key<R[j+1].key )  ++j;

// 左/右“子树根”之间先进行相互比较

// 令 j 指示关键字较大记录的位置

if ( rc.key >= R[j].key )  break;

// 再作“根”和“子树根”之间的比较,

// 若“>=”成立,则说明已找到 rc 的插

// 入位置 s ,不需要继续往下调整

R[s] = R[j];   s = j;

// 否则记录上移,尚需继续往下调整

}

R[s] = rc;  // 将调整前的堆顶记录插入到 s 位置

} // HeapAdjust

7.5   归 并 排 序

归并排序的过程基于下列基本思想进行:     将两个或两个以上的有序子序列 “归并” 为一个有序序列。 

在内部排序中,通常采用的是2-路归并排序。即:将两个位置相邻的记录有序子序列 归并为一个记录的有序序列。

 

对 n 个记录进行归并排序的时间复杂度为Ο(nlogn)

 

void Merge (RcdType SR[], RcdType &TR[], int i, int m, int n) {

// 将有序的记录序列 SR[i..m] 和 SR[m+1..n]

// 归并为有序的记录序列 TR[i..n]

for (j=m+1, k=i;  i<=m && j<=n;  ++k)    // 将SR中记录由小到大地并入TR

{

if (SR[i].key<=SR[j].key)

TR[k] = SR[i++];

else TR[k] = SR[j++];

}

if (i<=m)

TR[k..n] = SR[i..m];

// 将剩余的 SR[i..m] 复制到 TR

if (j<=n)

TR[k..n] = SR[j..n];

// 将剩余的 SR[j..n] 复制到 TR

} // Merge

7.7  各种排序方法的综合比较

 

 

1.  平均的时间性能

 

时间复杂度为 O(nlogn):快速排序、堆排序和归并排序

 

时间复杂度为 O(n2):直接插入排序、起泡排序和 简单选择排序

 

时间复杂度为 O(n): 基数排序

2. 当待排记录序列按关键字顺序有序时


直接插入排序和起泡排序能达到O(n)的时间复杂度,        

 

快速排序的时间性能从O(nlogn),蜕化为O(n2) 。

3. 简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。

 

4.  快速排序、堆排序和希尔排序是不稳定的排序方法。

//插入类:  (1)直接插入排序 (2)折半插入排序  (3)希尔排序

//交换类;  (1)冒泡排序     (2)一趟快速排序  (3)快速排序

//选择类:  (1)简单选择排序 (2) 堆排序

//归并类:  (1)归并排序

 

特别关注:快速排序

快速排序 :先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行快速排序。

结论: 快速排序的时间复杂度为O(nlogn),若待排记录的初始状态为按关键字有序时,快速排序将蜕化为起泡排序,其时间复杂度为O(n2)。

掌握各种排序方法的时间复杂度的分析方法。能从“关键字间的比较次数”分析排序算法的平均情况和最坏情况的时间性能。

按平均时间复杂度划分,内部排序可分为三类:

O(n2)的简单排序方法,

O(nlogn)的高效排序方法  

O(dn)的基数排序方法。

时间: 2024-07-29 03:31:47

第8天排序的相关文章

经典排序算法 - 冒泡排序Bubble sort

 原文出自于 http://www.cnblogs.com/kkun/archive/2011/11/23/bubble_sort.html 经典排序算法 - 冒泡排序Bubble sort 原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换,直到倒数第二位时结束,其余类似看例子 例子为从小到大排序, 原始待排序数组| 6 | 2 | 4 | 1 | 5 | 9 | 第一趟排序(外循环) 第

算法 希尔排序

希尔排序 Shell Sort 介绍: 希尔排序(Shell Sort)也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本. 该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序.因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率比直接插入排序有较大提高. 执行流程: 首先

算法 排序NB二人组 堆排序 归并排序

参考博客:基于python的七种经典排序算法     常用排序算法总结(一) 序前传 - 树与二叉树 树是一种很常见的非线性的数据结构,称为树形结构,简称树.所谓数据结构就是一组数据的集合连同它们的储存关系和对它们的操作方法.树形结构就像自然界的一颗树的构造一样,有一个根和若干个树枝和树叶.根或主干是第一层的,从主干长出的分枝是第二层的,一层一层直到最后,末端的没有分支的结点叫做叶子,所以树形结构是一个层次结构.在<数据结构>中,则用人类的血统关系来命名,一个结点的分枝叫做该结点的"

数字在排序数组中出现的次数

题目:统计一个数字在排序数组中出现的次数.例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4. 程序实现: import java.util.Scanner; public class Test38 { public static void main(String[] args) { /**  * 初始化数组,测试数据可以多种途径初始化  */   Scanner in = new Scanner(System.in); /*int[] a = 

算法 排序lowB三人组 冒泡排序 选择排序 插入排序

参考博客:基于python的七种经典排序算法   [经典排序算法][集锦]     经典排序算法及python实现 首先明确,算法的实质 是 列表排序.具体就是操作的列表,将无序列表变成有序列表! 一.排序的基本概念和分类 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法. 排序的稳定性: 经过某种排序后,如果两个记录序号同等,且两者在原无序记录中的先后秩序依然保持不变,则称所使用的排序方法是稳定的,反之是不稳定

C/C++算法竞赛入门经典Page11 例题1-5 三整数排序

题目:输入3个整数,从小到大排序后输出 样例输入: 20 7 33 样例输出: 7 20 33 首先,先声明三个整数a,b,c和一个临时变量t: int a,b,c,t;//1,b,c为三个整数,t为临时变量 输入三个整数: scanf("%d%d%d",&a,&b,&c); 进行3次if判断: 1.如果b>a,则a,b对调 2.如果c>a,则a,c对调 3.如果c>b,则b,c对调 代码: if(b>=a){ t=b; b=a; a=t

排序与查找

1.冒泡法排序 2.选择排序 3.二分法查找(有序数组) 4.无序数组(基本查找)

使用插件bootstrap-table实现表格记录的查询、分页、排序等处理

在业务系统开发中,对表格记录的查询.分页.排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这个bootstrap-table是一款非常有名的开源表格插件,在很多项目中广泛的应用.Bootstrap-table插件提供了非常丰富的属性设置,可以实现查询.分页.排序.复选框.设置显示列.Card view视图.主从表显示.合并列.国际化处理等处理功能,而且该插件同时也提供了一些不错的扩展功能,如移动行.移动列位置等一些特殊的功能,插件可

AC日记——双栈排序 洛谷 P1155

双栈排序 思路: 二分图染+模拟: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 1005 #define maxm 2000005 int n,head[maxn],E[maxm],V[maxm],cnt,col[maxn]; int minn[maxn],ai[maxn],sta1[maxn],sta2[maxn],top1,top2; bool if_[maxn][maxn]; inline void in(

输入password登录到主界面,录入学生编号,排序后输出

n 题目:输入password登录到主界面,录入学生编号,排序后输出 n 1.  语言和环境 A.实现语言 C语言 B.环境要求 VC++ 6.0 n 2.  要求 请编写一个C语言程序.将若干学生编号按字母顺序(由小到大)输出. 程序的功能要求例如以下: 1)  输入password"admin",正确则进入主界面,错误则直接推出(exit(0)): 2)从键盘输入5个学生编号"BJS1001","BJS2001"."BJS1011&