概念
算法稳定性:相同元素在排序后相对位置没有变化,则称算法是稳定的。 内部排序:数据全放入内存中再进行排序。 外部排序:数据太多,无法全部放入内存中进行排序。 稳定算法:插入排序、冒泡排序、并归排序、(桶、计数)基数排序
插入排序
空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 时间复杂度:
最好
:
O
(
n
)
最坏
:
O
(
n
2
)
平均时间复杂度
:
O
(
n
2
)
最好:O(n)~~~~最坏:O(n^2)~~~~平均时间复杂度:O(n^2)
最好 : O ( n ) 最坏 : O ( n 2 ) 平均时间复杂度 : O ( n 2 ) 稳定性:稳定 优化:采用二分法
int a[ 10 ] = { 2 , 42 , 42 , 1 , 64 , 35 , 6 , 32 , 4354 , 78 } ;
void InsertSort ( int * suzu) {
for ( int i= 1 ; i< 10 ; i++ ) {
int a= suzu[ i] , j= i;
if ( suzu[ i] < suzu[ i- 1 ] ) {
for ( ; j>= 0 && j- 1 >= 0 ; j-- ) {
if ( a>= suzu[ j- 1 ] )
break ;
if ( a< suzu[ j- 1 ] )
suzu[ j] = suzu[ j- 1 ] ;
}
suzu[ j] = a;
}
}
}
希尔排序
步骤:将表划分为若干子表,每个子表执行插入排序。建议第一次选择
⌊
总元素数
/
2
⌋
\lfloor总元素数/2\rfloor
⌊ 总元素数 /2 ⌋ 来作为增量,以后每一次都/2作为增量。(可以缩短增量 d) 空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 时间复杂度:
最坏情况:
O
(
n
2
)
(
直接退化成插入排序
)
当
n
在某个范围内
:
O
(
n
1.3
)
O(n^2)~~_{(直接退化成插入排序)}~~~当n在某个范围内:O(n^{1.3})
O ( n 2 ) ( 直接退化成插入排序 ) 当 n 在某个范围内 : O ( n 1.3 ) 最优情况:
O
(
N
log
2
N
)
O(N\log_2N)
O ( N log 2 N ) 稳定性:不稳定 适用性:适用于顺序表,不适用于链表
void ShellSort ( int * suzu) {
for ( int k= 10 / 2 ; k>= 1 ; k/= 2 ) {
for ( int i= k; i< 10 ; i++ ) {
int a= suzu[ i] , j= i;
if ( suzu[ i] < suzu[ i- k] ) {
for ( ; j>= 0 && j- k>= 0 ; j-= k) {
if ( a>= suzu[ j- k] )
break ;
if ( a< suzu[ j- k] )
suzu[ j] = suzu[ j- k] ;
}
suzu[ j] = a;
}
}
}
}
冒泡排序
空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 时间复杂度:
最好
:
O
(
n
)
最坏
:
O
(
n
2
)
平均
:
O
(
n
2
)
最好:O(n)~~~最坏:O(n^2)~~~~平均:O(n^2)
最好 : O ( n ) 最坏 : O ( n 2 ) 平均 : O ( n 2 ) 稳定性:稳定 适用:顺序表、链表
void BubbleSort ( int * suzu) {
for ( int i= 0 ; i< 10 - 1 ; i++ ) {
int sign= false;
for ( int j= 0 ; j< 10 - 1 - i; j++ ) {
if ( suzu[ j] > suzu[ j+ 1 ] ) {
int a= suzu[ j] ;
suzu[ j] = suzu[ j+ 1 ] ;
suzu[ j+ 1 ] = a;
sign= true;
}
}
if ( sign== false)
return ;
}
}
void Swap ( int * a, int * b) {
int c= * a;
* a= * b;
* b= c;
}
void Logic ( int * array) {
int len= 8 ;
int front= 0 , rear= len- 1 ;
_Bool tag= false;
while ( front< rear && tag== false) {
tag= true;
for ( int i = front; i < rear; i++ ) {
if ( array[ i] > array[ i + 1 ] ) {
Swap ( & array[ i] , & array[ i + 1 ] ) ;
tag = false;
}
}
rear-- ;
for ( int i = rear; i > front; i-- ) {
if ( array[ i] < array[ i - 1 ] ) {
Swap ( & array[ i] , & array[ i - 1 ] ) ;
tag = false;
}
}
front++ ;
}
}
快速排序
算法步骤:( “画成” 二叉排序树)
从数列中挑出一个元素,称为 “基准”(pivot); 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作; 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。 空间复杂度:
O
(
递归深度
)
O(递归深度)
O ( 递归深度 ) 最好:
O
(
log
2
h
)
O(\log_2h)
O ( log 2 h ) 最坏:
O
(
h
)
(
树最高
)
O(h)~~~_{(树最高)}
O ( h ) ( 树最高 ) 时间复杂度:
O
(
n
∗
递归深度
)
=
>
最好、平均
:
O
(
n
log
2
n
)
最坏
:
O
(
n
2
)
O(n*递归深度)=>~~最好、平均:O(n\log_2n)~~最坏:O(n^2)
O ( n ∗ 递归深度 ) => 最好、平均 : O ( n log 2 n ) 最坏 : O ( n 2 ) 有序序列使用快速排序性能最差,因为树最高,递归深度最大。 稳定性:不稳定
int QSort ( int * array, int head, int tail) {
int mid= array[ head] ;
while ( head< tail) {
while ( array[ tail] >= mid && head< tail) {
tail-- ;
}
array[ head] = array[ tail] ;
while ( array[ head] <= mid && head< tail) {
head++ ;
}
array[ tail] = array[ head] ;
}
array[ head] = mid;
return head;
}
void QS2 ( int * array, int head, int tail) {
if ( head< tail) {
int a= QSort ( array, head, tail) ;
QS2 ( array, head, a- 1 ) ;
QS2 ( array, a+ 1 , tail) ;
}
}
void QuickSort ( int * array) {
int head= 0 , tail= 9 ;
QS2 ( array, head, tail) ;
}
直接选择排序
时间复杂度:
O
(
n
2
)
O(n^2)
O ( n 2 ) 空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 稳定性:不稳定 适用:顺序表、链表
void SimpleSelect ( int * array) {
for ( int i= 0 ; i< 10 - 1 ; i++ ) {
for ( int j= i+ 1 ; j< 10 ; j++ ) {
if ( array[ i] > array[ j] ) {
int a= array[ i] ;
array[ i] = array[ j] ;
array[ j] = a;
}
}
}
}
简单选择排序
时间复杂度:
O
(
n
2
)
O(n^2)
O ( n 2 ) 空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 稳定性:不稳定 适用:顺序表、链表
void swap ( int * a, int * b) {
int temp = * a;
* a = * b;
* b = temp;
}
void selection_sort ( int arr[ ] , int len) {
int i, j;
for ( i = 0 ; i < len - 1 ; i++ ) {
int min = i;
for ( j = i + 1 ; j < len; j++ ) {
if ( arr[ j] < arr[ min] )
min = j;
swap ( & arr[ min] , & arr[ i] ) ;
}
}
}
堆排序(选择排序 Pro)
堆:(利用完全二叉树)
大顶堆:所有节点元素都 大于 它的左右子树节点的值。 小顶堆:所有节点元素都 小于 它的左右子树节点的值。 详情:https://www.runoob.com/w3cnote/heap-sort.html 建堆过程:关键字对比次数不超过
4
n
4n
4 n ,建堆时间复杂度:
O
(
n
)
O(n)
O ( n ) 时间复杂度:
O
(
n
log
2
n
)
O(n\log_2n)
O ( n log 2 n ) 空间复杂度:
O
(
1
)
O(1)
O ( 1 ) 稳定性:不稳定 堆插入元素:将新插入元素放到堆尾,然后调整成大(小)顶堆。 堆删除元素:用堆低元素替代被删除元素,然后调整成大(小)顶堆。 每上升一次对比一次关键字,每下降一次可能对比一次、可能对比两次关键字。
void BigHeapSort ( int * array) {
for ( int k= ( 10 - 1 ) ; k>= 0 ; k-- ) {
for ( int i = k / 2 ; i >= 0 ; i-- ) {
int max = array[ i] ;
for ( int j= i* 2 ; j<= k; j*= 2 ) {
if ( array[ j] < array[ j + 1 ] && j < k) {
j++ ;
}
if ( array[ i] < array[ j] ) {
array[ i] = array[ j] ;
i = j;
} else
break ;
}
array[ i] = max;
}
int a= array[ 0 ] ;
array[ 0 ] = array[ k] ;
array[ k] = a;
}
}
归并排序
将多个有序数组进行合并。 归并树:归并排序形态上是倒立的 k 叉树。二叉树第 h 层最多有
k
h
−
1
k^{h-1}
k h − 1 个节点,若树高为 h ,则
n
≤
k
h
−
1
n\le k^{h-1}
n ≤ k h − 1 ,归并总次数是
⌊
log
k
n
⌋
+
1
\lfloor\log_kn\rfloor+1
⌊ log k n ⌋ + 1 (k为归并路数,n为元素个数)。 每次归并时间复杂度:
O
(
n
)
O(n)
O ( n ) 时间复杂度:
O
(
n
log
k
n
)
(
每趟归并时间为
n
)
O(n\log_kn)~~~_{(每趟归并时间为n)}
O ( n log k n ) ( 每趟归并时间为 n ) 空间复杂度:
O
(
n
)
O(n)
O ( n ) 稳定性:稳定
void TwoMergeSort ( int * array1, int * array2) {
BubbleSort ( array1) ;
BubbleSort ( array2) ;
int len1= 5 , len2= 5 ;
int target[ 10 ] ;
int tar_len= 0 , point2= 0 ;
for ( int i= 0 ; i< len1; i++ ) {
for ( int j= point2; j< len2; j++ ) {
if ( array1[ i] <= array2[ j] ) {
target[ tar_len++ ] = array1[ i] ;
break ;
}
if ( array1[ i] > array2[ j] ) {
target[ tar_len++ ] = array2[ j] ;
point2++ ;
}
}
if ( point2== len2)
target[ tar_len++ ] = array1[ i] ;
}
while ( point2< len2)
target[ tar_len++ ] = array2[ point2++ ] ;
}
(桶)基数排序
桶类型排序区别:
基数排序:根据键值的每位数字来分配桶; 计数排序:每个桶只存储单一键值; 桶排序:每个桶存储一定范围的数值。 步骤:
初始化队列个数(r):有多少种类型的数字就初始化多少个队列,一般初始化10个; 每一轮依次按照“个十百…”位的大小将元素加入相应队列的队尾;(一趟耗时 n) 加入完成后依次从每个队头取出所有元素组成新的序列;(一趟耗时 r) 重复 b。 分配次数(排序次数):依据整个序列中最大位的元素。 空间复杂度:
O
(
r
)
O(r)
O ( r ) 时间复杂度:
O
(
d
∗
(
n
+
r
)
)
(
d
为排序次数,
n
为元素个数,
r
为队列个数
)
O(d*(n+r))~~~~~~_{(d为排序次数,n为元素个数,r为队列个数)}
O ( d ∗ ( n + r )) ( d 为排序次数, n 为元素个数, r 为队列个数 ) 稳定性:稳定 适用于:基数关键字可以很方便的拆分成多个小关键字(d较小);每组关键字取值范围不是很多(r较小);需要排序的数据基数很大(n很大)。
外部排序
进行 k 路并归排序,需要在内存中开辟 k 个输入缓冲区和 1 个输出缓冲区。(缓冲区大小等于磁盘block大小)(采用多路归并可以减少并归趟数,进而减少磁盘IO次数) 步骤:(总归并次数:
⌊
log
k
n
⌋
+
1
\lfloor\log_kn\rfloor+1
⌊ log k n ⌋ + 1 )
先将每个片段进行内部排序,生成 r 个初始并归片段; 进行 k 路并归排序。 优化:
增加并归路数 k ;(代价:需要增加相应缓冲区;增加了多路并归排序的关键字对比次数) 减少并归片段 r 数量。(代价:增加了内部归并时间) 外部排序时间开销:读写外存时间(主要) + 内部排序时间 + 内部并归时间 什么是 k 路平衡归并:
最多只能有 k 段归并为一个; 每趟归并,若有 m 个段参与归并,则经过一趟归并后得到
⌈
m
+
k
⌉
\lceil m+k\rceil
⌈ m + k ⌉ 个新的归并段。 败者树:(可以减少多路归并中关键字对比次数)(第一次构造败者树需要进行 n-1 次对比)
对于 n 路并归,最多需要对比关键词次数:
⌈
log
2
n
⌉
\lceil\log_2n\rceil
⌈ log 2 n ⌉
置换-选择排序
步骤:
从文件记录中输入 n 个记录到工作区;(工作区长度为 n) 找出工作区中最小关键词(MIX)输入到输出缓冲区,并递增式记录当前最小值MIX;
若当前元素中全部元素都小于MIX,则重新开辟新的并归段,执行 b。 继续加入文件记录中的待排序元素,执行 b,直到工作区为空。
最佳归并树(哈夫曼树)
归并中磁盘
I
/
O
=
k
路归并树
W
P
L
∗
2
(
W
P
L
为归并树树的带权路径长度之和
)
归并中磁盘I/O=k路归并树WPL * 2~~~~_{(WPL为归并树树的带权路径长度之和)}
归并中磁盘 I / O = k 路归并树 W P L ∗ 2 ( W P L 为归并树树的带权路径长度之和 ) 归并排序: 优化:
减少磁盘IO:构造哈夫曼树以获取最优WPL。(若无法严格构成 k 叉归并树,则需补充 0 补全(虚段,长度为 0,占位用)) k 叉最佳并归树一定是一个严格的 k 叉并归树,设度为 k 的节点有 a 个,度为 0 的节点有 b 个,总的节点数为 c,则:(添加虚段的数量)