以下笔记为jjyaoao本人制作,欢迎借鉴学习和提出相关建议,转载需要标明出处www.jjyaoao.space

之前我们已经学过了……..

image-20220306202930870

快速排序

概述

分而治之

image-20220306203053938

确定轴点

image-20220306203214992

image-20220306203340647

构造轴点

image-20220306203415344

image-20220306203428408

单调性和不变性

image-20220306203524669

算法实现

quickSort()

image-20220306203255260

partition()

image-20220306203602837

实例

通过实例,可以清楚看出,5a和5b的位置已经彼此颠倒,他们是不稳定的

image-20220306203729188

性能分析

不稳定+就地

不稳定:同样数字位置会变动

就地:只需要常数的辅助空间

image-20220306203950419

最好、最坏情况

image-20220306204018463

平均情况

image-20220306204338563

变种

L U G —–> L G U (L:小的 G: 大的 U:轴点)

不变性

image-20220306204446460

单调性

image-20220306204556336

实现

image-20220306204630240

实例

从实现可以看出,稳定性仍然无法保证…..

image-20220306204646890

选取

选取+中位数

image-20220306205421228

从中位数到众数

image-20220306205502602

遍历一趟整个数据集,统计出目标元素的数目,根据定义即可判断是否是众数

image-20220306205557392

从频繁数到众数

​ 在高效的中位数算法未知之前,也许我们应该寻找其他的出路,比如,频繁数

image-20220306205722939

根据定义,众数不仅最多,而且多于其他元素的总和

减而治之

不断减小范围,不断统计m,直到占据半壁江山

image-20220306210048562

算法实现

image-20220306210122044

1
2
3
4
5
6
7
8
9
10
template <typename T> T majEleCandidate ( Vector<T> A ) { //选出具备必要条件的众数候选者
0002 T maj; //众数候选者
0003 // 线性扫描:借助计数器c,记录maj与其它元素的数量差额
0004 for ( int c = 0, i = 0; i < A.size(); i++ )
0005 if ( 0 == c ) { //每当c归零,都意味着此时的前缀P可以剪除
0006 maj = A[i]; c = 1; //众数候选者改为新的当前元素
0007 } else //否则
0008 maj == A[i] ? c++ : c--; //相应地更新差额计数器
0009 return maj; //至此,原向量的众数若存在,则只能是maj —— 尽管反之不然
0010 }

选取:通用算法

排序计算自身需要高复杂度,如果我们只需要选取众数(选出特定的数)

则可以舍弃排序的思路

尝试

蛮力

image-20220306210344618

堆A

image-20220306210424822

image-20220306210457600

堆B

image-20220306210559557

堆C

image-20220306210629991

quickSelect

image-20220306210753382

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T> void quickSelect ( Vector<T> & A, Rank k ) { //基于快速划分的k选取算法
0002 for ( Rank lo = 0, hi = A.size() - 1; lo < hi; ) {
0003 Rank i = lo, j = hi; T pivot = A[lo]; //大胆猜测
0004 while ( i < j ) { //小心求证:O(hi - lo + 1) = O(n)
0005 while ( ( i < j ) && ( pivot <= A[j] ) ) j--; A[i] = A[j];
0006 while ( ( i < j ) && ( A[i] <= pivot ) ) i++; A[j] = A[i];
0007 } //assert: quit with i == j
0008 A[i] = pivot; // A[0,i) <= A[i] <= A(i, n)
0009 if ( k <= i ) hi = i - 1;
0010 if ( i <= k ) lo = i + 1;
0011 } //A[k] is now a pivot
0012 }

linearSelect

基本上没听了TAT这个

算法

image-20220306211117371

性能分析

复杂度

image-20220306211235862

image-20220306211400347

image-20220306211429827

我的实现

quickSort.h

1
2
3
4
5
6
7
8
9
10
#pragma once
#include "partition.h"
using Rank = int;
template <typename T>
void quickSort(T* _elem, Rank lo, Rank hi) {
if (hi - lo < 2) return;
T mi = partition(_elem, lo, hi - 1);
quickSort(_elem, lo, mi);
quickSort(_elem, mi + 1, hi);
}

partition.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
#include "quickSort.h"
#include<iostream>
using Rank = int;
template <typename T>//partitionLRU版本,我的最爱
Rank partition(T* _elem, Rank lo, Rank hi) {
std::swap(_elem[lo], _elem[lo + rand() % (hi - lo + 1)]);
T pivot = _elem[lo];
int mi = lo;

for (int k = lo + 1; k <= hi; k++)//k <= hi 而不是k < hi,第一次写写错了
if (_elem[k] < pivot) //因为传进来的时候已经hi-1了
std::swap(_elem[k], _elem[++mi]);

std::swap(_elem[lo], _elem[mi]);
return mi;

}

quickSelect.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
using Rank = int;
#include <iostream>;
template <typename T>
void quickSelect(T* A,int capacity ,Rank k) {
//std::cout << sizeof(A) / sizeof(A[0]) - 1 << std::endl;
for (Rank lo = 0, hi = capacity-1 ; lo < hi;) {
Rank i = lo, j = hi; T pivot = A[lo];
while (i < j) {
while (i < j && pivot <= A[j]) j--;
A[i] = A[j];
while (i < j && A[i] <= pivot) i++;
A[j] = A[i];
}//assert i == j; 快排中partition的LUR版本
A[i] = pivot;
if (k <= i)hi = i - 1;
if (i <= k)lo = i + 1;
}//A[k] is now a pivot
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include "quickSort.h"
#include "qucikSelect.h"
using namespace std;
int main() {
int *test =new int[20];
for (int i = 0; i < 20; i++) {
test[i] = rand() % 88 + 1;//随机生成0-87的随机数,再加上1也就是
if (i < 19) //随机生成1-88的随机数
std::cout << test[i] << " ";
else
std::cout << test[i] << std::endl;
}
//1 4 13 18 20 35 39 42 50 52 55 61 63 69 74 74 74 76 76 87
//quickSort(test, 0, 20);//默认规范,左闭右开
quickSelect(test,20 ,5);//1 4 18 13 20 35 39 42 55 61 74 74 50 74 87 52 76 63 76 69
//至少秩为5时的35是在正确的位置
for (int i = 0; i < 20; i++) {
std::cout << test[i] << " ";
}
}

希尔排序

只是一个框架,可以把它想象成播放机,你往里面放入什么CD,他就会播放什么样的音乐,所以,我们与其说他是一个算法,不如说他是一类算法

策略

image-20220306211811754

​ 不过,既然最终都要做一次one Sorting,那么之前这么多次有什么意义呢?

现在说这个还为时尚早,我们通过一个具体的实例来感受一下实现过程….

实例

W5 = 8

image-20220306212020531

W4 = 5

image-20220306212107984

W3 = 3

image-20220306212243085

W2 = 2

image-20220306212315425

W1 = 1

image-20220306212350044

框架

循秩访问

image-20220306212614210

底层:插入排序

随着算法的不断推进,在序列有序性逐渐改变的同时,若我们采用输入敏感的算法,则成本会逐渐地抵减,从而保证总体成本足够低廉

image-20220306212805101

image-20220306212823898

Shell序列

尽管该序列出自Shell本人,并且十分优美,可是我们能看出,他确实存在很多问题

当2-sorting结束时,二者泾渭分明,没有任何的元素互换,为此我们应该反观shell序列,他除了第一项,其他都是偶数,偶数?也就意味着,在二维矩阵中都来自于同属一列的整数,或者来自A,或者来自B,自然不会发生A,B元素互换了

我们在1-sorting那就必定会付出高昂的代价

image-20220306213141942

image-20220306213342893

image-20220306213426769

逆序对最后仍然剩下的十分之多….恰好构成算术级数

​ 算法的效率已经退化于冒泡排序相当了,

我们当然不满足于仅仅指出错误,反观问题,我们可以发现,相邻项需要尽可能互素,不然,每一轮排序都有大量的精力浪费于对前一轮排序的重复之上

实用问题

邮资问题

image-20220306213748323

image-20220306213757979

image-20220306213843252

h-sorting

将整个排序,在逻辑上转化为宽度为h的矩阵,并且逐列进行排序

h-ordered

因此我们可以得出,任何序列,在经过h-sorting之后必然是h-ordered的

image-20220306214133673

定理K

整个序列达到h有序的同时,此前以g为间隔的有序是否能有一定的延续呢?

image-20220306214305624

因此,有序性在希尔排序,会不断保持,并且积累

逆序对

根据邮资问题的结论,顺序性会得以延续

image-20220306214527781

从逆序对的原理分析

image-20220306214739815

我们不难得出,这就是与插入序列有异曲同工之妙,这也不难想出,为何希尔排序的底层我们采用插入排序了!