1、相关概念
时间复杂度:
反映当数据量变化时,操作次数的多少;时间复杂度在评估时,要只保留最高项,并且不要最高项的系数。(下面用logN表示 log以2为底,N的对数)
空间复杂度:
是指算法在计算机内执行时,所需额外开辟的空间。
指标:
同时间复杂度。
常数项:
与N的大小无关的操作。
稳定性:
(1)稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
(2)不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
2、算法分类
十大经典排序算法可以分为两大类:
0、非线性时间排序:通过比较来决定元素间的相对次序。时间复杂度最快为O(logN)
1、线性时间排序:通过创建有序的空间,将元素按照一定的规则放入有序空间,再依次取出。以空间来换取时间,可以突破O(logN)

3、各算法的时间复杂度

4、排序算法的实现
0、通用函数及其他
(1)、求数组长度(需要传数组,不要传数组指针)
/*这里的注意点:在计算数组大小的时候,
一定要注意传入的数组是否为数组指针,
如果传入的是数组指针,sizeof后出来的值为8(64位下),读者应注意。*/
template
int len(T& arr){
int length=(int)sizeof(arr)/sizeof(arr[0]);
return length;
}
(2)、交换数组两个数
void exchangee(int arr[],int a, int b){
/* ^符号 即”异或“运算符,特点是与0异或,保持原值;与本身异或,结果为0。这里可以使用位运算,交换时不用开辟额外空间。但是如果传入的'位置相同'的两个数,就不能在此函数中进行交换。因为,自己跟自己异或后结果一定为0,就没有什么意义了。
// arr[a] = arr[a] ^ arr[b];
// arr[b] = arr[a] ^ arr[b];
// arr[a] = arr[a] ^ arr[b];
*/
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
(3)、算法中的表达
* A代表平均时间复杂度
* B代表最坏时间复杂度
* E代表最好时间复杂度
* 省略了O()
(3)、大数据样本下四钟最快算法的比较
/*
数据是随机整数,时间单位是秒
数据规模|快速排序 归并排序 希尔排序 堆排序
1000万 | 0.75 1.22 1.77 3.57
5000万 | 3.78 6.29 9.48 26.54
1亿 | 7.65 13.06 18.79 61.31
*/
(4)、时间复杂度的大小比较
/*
N!> x^N >...>3^N >2^N > N^x>...>N^3 >N^2>NlogN>N>logN>1
*/
(5)数组和数组大小的结构体
struct arrAndSize{
int *array;
int size;
};
(6)初始化数组
//一维 每个元素都没有初始化
int *p = new int[10];
//一维 每个元素初始化为0
int *p = new int[10](0);
//二维 每个元素都没有初始化
int (*bucket)[10] = new int[10][10];
//二维 每个一维中含N个数,N为确定的数值。
vector> bucket(N);
//通过动态创建的数组,要进行内存释放,否则内存将泄漏
//(本文中,未进行内存释放)
delete []p;
(7)综合排序总结
1.思考一个排序时候,考虑时间复杂度中的指标和常数项,空间复杂度,稳定性.
2.代码规模,一定程度上说明了常数项的大小。(最终常数项的大小是看发生常数操作的次数)
3.系统的sort 方法,发现传进来的值为数值型,会使用快排,如果发现传的还有比较器,会使用归并排序
4.归并和快排哪种更快?
快排比归并排序的常数项要低,所以要快。
5.为什么会有归并和快排两种呢?
在比较的时候,使用比较器的时候,要追求一个稳定性,使用 归并排序 可以达稳定性的效果;使用快排不能够实现稳定性的效果。
6.面对大规模的时候,当排序量是小于等于60的时候,sort方法 会在内部使用插入排序的方法(不一定是60,是一定的规模)当数据量很低的时候,插入排序的常数项低。
7.在c语言中有一版,把归并排序,改成非递归,是基于工程其他考虑。
*/
(8)对比两个数组是否相同

(9)复制数组

(10)产生随机数组

(11)疯狂递归 -递归master公式

(12)比较器

(13)vector容器

1、冒泡排序
算法描述:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
实现逻辑:
比较相邻的元素。如果第一个比第二个大,就交换它们两个;
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
针对所有的元素重复以上的步骤,除了最后一个;
重复步骤1~3,直到排序完成。
1.0、简单冒泡排序
(交换排序;时间A:N^2 ,B:N^2 ,E:N^2 ;空间1;稳定)
for (int i = 1 ; i
for (int j = 0 ; j
if(arr[j]>arr[j+1]){
exchangee(arr, j, j+1);
}
}
}
1.1、外层循环优化冒泡排序
(交换排序; 时间A:N^2 , B:N^2 , E:N; 空间1; 稳定)
/*如果用一个flag来判断一下,当前数组是否已经有序,有序就退出循环,可以提高冒泡排序的性能。
*/
for (int i = 1; i
bool flag = true;
for (int j = 0; j < length -i; j++) {
if (arr[j]>arr[j+1]) {
exchangee(arr, j, j+1);
flag =false;
}
}
if (flag) {
break;
}
}
1.2、内层循环优化冒泡排序
(交换排序; 时间A:N^2 , B:N^2 , E:N ; 空间1 ; 稳定)
/*
(1)完美冒泡
(2)再用last标记一下最后一个发生交换的数,下次可以减少循环次数。其中第一次内部循环的控制条件,单独拿出来。
*/
2、快速排序
算法描述:
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
实现逻辑:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法流程如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

2.1、小和问题
问题描述
求小和问题:在随机元素,随机数组大小的数组中,找出左边比右边元素小的所有元素之和。
例如:数组[4,2,5,1,7,3,6] 第一个元素4比2大,不算小和,5比4和2都大,那就是4+2=6;1比4和2和5都小,不算小和;7比前面的都大,那就是上次小和6+4+2+5+1=18;然后3前面比2和1大,那就是18+2+1=21;最后6比4、2、5、1、3都大,结果就是21+4+2+5+1+3=36。那么最后的结果就是36。

3.0、插入排序
算法描述:
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
2、快速排序
算法描述:
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
实现逻辑:
一般来说,插入排序都采用in-place在数组上实现。具体算法流程如下:
从第一个元素开始,该元素可以认为已经被排序;
取出下一个元素,在已经排序的元素序列中从后向前扫描;
如果该元素(已排序)大于新元素,将该元素移到下一位置;
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
将新元素插入到该位置后;
重复步骤2~5。

4、希尔排序
算法描述:
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
实现逻辑:
>
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法流程:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

5、选择排序
算法描述:
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
实现逻辑:
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法流程如下:
初始状态:无序区为R[1..n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。

6、堆排序
算法描述:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
实现逻辑:
>
将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
/*
堆的概念:对于大根堆,其子树下的所有节点,包括它自己在内的最大值为头结点。时间复杂度为0+log1+log2+……数学上可以证明这个值收敛于O(N)*/
//向上走
void heapInsert(int arr[],int index){
while (arr[index] > arr[(index-1)/2]) {
exchangee(arr,index, (index-1)/2);
index = (index -1)/2;
}
}
//向下走
//size为最右的边界,size是取不到的.
void heapify(int arr[],int index ,int size){
int leftChild = index*2 + 1;
while (leftChild < size) {
int maxChild = leftChild + 1 < size && arr[leftChild+1] >arr[leftChild] ? leftChild+1 : leftChild;
int maxAll = arr[maxChild] > arr[index] ? maxChild: index;
if (maxAll == index) {
break;
}
exchangee(arr, maxAll, index);
index = maxAll;
leftChild = index*2 +1;
}
}
int main(){
for(int i = 0;i
heapInsert(arr, i);
}
int size = length;
exchangee(arr, 0, --size);
while (size > 0){
//heapify时间复杂度为O(logN)
heapify(arr, 0, size);
exchangee(arr, 0, --size);
}
return 0;
}
7、归并排序
算法描述:
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
实现逻辑:
把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。

8、计数排序
算法描述:
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
动画演示:
实现逻辑:>
找出待排序的数组中最大和最小的元素;
统计数组中每个值为i的元素出现的次数,存入数组C的第i项;对所有的计数累加(从桶中的第0个元素开始,每一项和前一项相加);
反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1,是为了保证算法的稳定性。

9、桶排序
算法描述:
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
实现逻辑:
设置一个定量的数组当作空桶;
遍历输入数据,并且把数据一个一个放到对应的桶里去;对每个不是空的桶进行排序;从不是空的桶里把排好序的数据拼接起来。

9.1、桶排序思想
问题描述
数组最大值问题。
给定一个无序数组,求如果排序之后,相邻两数的最大差值,
要求时间复杂度O(N),且要求不能用基于比较的排序。以此题发现桶排序妙趣思想.
/*
映射函数getGroupCount是得到在第几个桶,其能保证第一个桶有整个数组的最小值和最后一个桶有整个数组的最大值。
*/
int getGroupCount(long num,long size,long min ,long max ){
int count = (int)((size-1)*(nummin)/(max - min));
return count;
}
int maxGroup(int arr[],int length){
if (length < 2){
return 0;
}
int len = length;
//拿到系统的最大值和系统的最小值
int min = INT_MAX;
int max = INT_MIN;
for (int i= 0; i
min = arr[i]
max = arr[i]>max ? arr[i]:max;
}
if (min == max){
return 0;
}
int bid = 0;
int* maxValue =new int[len+1];
int* minValue =new int[len+1];
bool* flag = new bool[len+1];
for (int j = 0; j
bid = getGroupCount(arr[j], len, min, max);
minValue[bid] = minValue[bid]? (minValue[bid]
maxValue[bid] = maxValue[bid]? (maxValue[bid]>arr[j]?maxValue[bid]:arr[j]):arr[j];
flag[bid] = true;
}
int res = 0;
int lastMax = 0;
for (int k= 1 ; k
if (flag[k]) {
res = res > (minValue[k] - maxValue[lastMax]) ? res :(minValue[k] - maxValue[lastMax]);
lastMax =k;
}
}
return res;
}
10、基数排序
算法描述:
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
实现逻辑:
取得数组中的最大数,并取得位数;
arr为原始数组,从最低位开始取每个位组成radix数组;对radix进行计数排序(利用计数排序适用于小范围数的特点);

















